var setup = function()
{
//Alpaca.logLevel = Alpaca.DEBUG;
var MODAL_VIEW = "bootstrap-edit-horizontal";
//var MODAL_VIEW = "bootstrap-edit";
var MODAL_TEMPLATE = ' \
\
\
\
\
\
\
\
\
\
\
\
\
\
';
var schema = {
"type": "object",
"properties": {
"email": {
"type": "string",
"required": false
},
"password": {
"type": "string",
"required": false,
"pattern": {}
},
"file": {
"type": "string",
"required": false
},
"check": {
"type": "boolean",
"required": false,
"default": true
}
}
};
var options = {
"fields": {
"email": {
"type": "text",
"label": "Email Address"
},
"password": {
"type": "password",
"label": "Password"
},
"file": {
"type": "file",
"label": "File Upload"
},
"check": {
"type": "checkbox",
"rightLabel": "Sign me up for the News Letter!",
"label": "Newsletter"
}
}
};
var data = {
"email": "Joe Smith",
"password": "MyPassword"
};
var setupEditor = function(id, json)
{
var text = "";
if (json)
{
text = JSON.stringify(json, null, " ");
}
var editor = ace.edit(id);
editor.setTheme("ace/theme/textmate");
if (json)
{
editor.getSession().setMode("ace/mode/json");
}
else
{
editor.getSession().setMode("ace/mode/javascript");
}
editor.renderer.setHScrollBarAlwaysVisible(false);
editor.setShowPrintMargin(false);
editor.setValue(text);
setTimeout(function() {
editor.clearSelection();
editor.gotoLine(0,0);
}, 100);
return editor;
};
var editor1 = setupEditor("schema", schema);
var editor2 = setupEditor("options", options);
var editor3 = setupEditor("data", data);
var editor4 = setupEditor("codeDiv");
var mainViewField = null;
var mainPreviewField = null;
var mainDesignerField = null;
var doRefresh = function(el, buildInteractionLayers, disableErrorHandling, cb)
{
try
{
schema = JSON.parse(editor1.getValue());
}
catch (e)
{
}
try
{
options = JSON.parse(editor2.getValue());
}
catch (e)
{
}
try
{
data = JSON.parse(editor3.getValue());
}
catch (e)
{
}
if (schema)
{
var config = {
"schema": schema
};
if (options)
{
config.options = options;
}
if (data)
{
config.data = data;
}
if (!config.options) {
config.options = {};
}
config.options.focus = false;
config.postRender = function(form) {
if (buildInteractionLayers)
{
// cover every control with an interaction layer
form.getFieldEl().find(".alpaca-container-item").each(function(iCount) {
var $el = $(this);
var alpacaFieldId = $el.children().first().attr("data-alpaca-field-id");
//iCount++;
$el.attr("icount", iCount);
var width = $el.outerWidth() - 22;
var height = $el.outerHeight() + 25;
// cover div
var cover = $("");
$(cover).addClass("cover");
$(cover).attr("alpaca-ref-id", alpacaFieldId);
$(cover).css({
"position": "absolute",
"margin-top": "-" + height + "px",
"margin-left": "-10px",
"width": width,
"height": height + 10,
"opacity": 0,
"background-color": "white",
"z-index": 900
});
$(cover).attr("icount-ref", iCount);
$el.append(cover);
// interaction div
var interaction = $("");
var buttonGroup = $("");
//var schemaButton = $("");
var schemaButton = $('');
buttonGroup.append(schemaButton);
//var optionsButton = $("");
var optionsButton = $('');
buttonGroup.append(optionsButton);
//var removeButton = $("");
var removeButton = $('');
buttonGroup.append(removeButton);
interaction.append(buttonGroup);
interaction.append("");
$(interaction).addClass("interaction");
$(interaction).attr("alpaca-ref-id", alpacaFieldId);
$(interaction).css({
"position": "absolute",
"margin-top": "-" + height + "px",
"margin-left": "-10px",
"width": width,
"height": height + 10,
"opacity": 1,
"z-index": 901
});
$(interaction).attr("icount-ref", iCount);
$el.append(interaction);
$(buttonGroup).css({
"margin-top": 5 + (($(interaction).height() / 2) - ($(buttonGroup).height() / 2)),
"margin-right": "16px"
});
$(schemaButton).off().click(function(e) {
e.preventDefault();
e.stopPropagation();
var alpacaId = $(this).attr("alpaca-ref-id");
editSchema(alpacaId);
});
$(optionsButton).off().click(function(e) {
e.preventDefault();
e.stopPropagation();
var alpacaId = $(this).attr("alpaca-ref-id");
editOptions(alpacaId);
});
$(removeButton).off().click(function(e) {
e.preventDefault();
e.stopPropagation();
var alpacaId = $(this).attr("alpaca-ref-id");
removeField(alpacaId);
});
// when hover, highlight
$(interaction).hover(function(e) {
var iCount = $(interaction).attr("icount-ref");
$(".cover[icount-ref='" + iCount + "']").addClass("ui-hover-state");
}, function(e) {
var iCount = $(interaction).attr("icount-ref");
$(".cover[icount-ref='" + iCount + "']").removeClass("ui-hover-state");
});
});
// add dashed
form.getFieldEl().find(".alpaca-container").addClass("dashed");
form.getFieldEl().find(".alpaca-container-item").addClass("dashed");
// for every container, add a "first" drop zone element
// this covers empty containers as well as 0th index insertions
form.getFieldEl().find(".alpaca-container").each(function() {
var containerEl = this;
// first insertion point
$(this).prepend("");
// all others
$(containerEl).children(".alpaca-container-item").each(function() {
$(this).after("");
});
});
form.getFieldEl().find(".dropzone").droppable({
"tolerance": "touch",
"drop": function( event, ui ) {
var draggable = $(ui.draggable);
if (draggable.hasClass("form-element"))
{
var dataType = draggable.attr("data-type");
var fieldType = draggable.attr("data-field-type");
// based on where the drop occurred, figure out the previous and next Alpaca fields surrounding
// the drop target
// previous
var previousField = null;
var previousFieldKey = null;
var previousItemContainer = $(event.target).prev();
if (previousItemContainer)
{
var previousAlpacaId = $(previousItemContainer).children().first().attr("data-alpaca-field-id");
previousField = Alpaca.fieldInstances[previousAlpacaId];
previousFieldKey = $(previousItemContainer).attr("data-alpaca-container-item-name");
}
// next
var nextField = null;
var nextFieldKey = null;
var nextItemContainer = $(event.target).next();
if (nextItemContainer)
{
var nextAlpacaId = $(nextItemContainer).children().first().attr("data-alpaca-field-id");
nextField = Alpaca.fieldInstances[nextAlpacaId];
nextFieldKey = $(nextItemContainer).attr("data-alpaca-container-item-name");
}
// parent field
var parentFieldAlpacaId = $(event.target).parent().parent().attr("data-alpaca-field-id");
var parentField = Alpaca.fieldInstances[parentFieldAlpacaId];
// now do the insertion
insertField(schema, options, data, dataType, fieldType, parentField, previousField, previousFieldKey, nextField, nextFieldKey);
}
else if (draggable.hasClass("interaction"))
{
var draggedIndex = $(draggable).attr("icount-ref");
// next
var nextItemContainer = $(event.target).next();
var nextItemContainerIndex = $(nextItemContainer).attr("data-alpaca-container-item-index");
var nextItemAlpacaId = $(nextItemContainer).children().first().attr("data-alpaca-field-id");
var nextField = Alpaca.fieldInstances[nextItemAlpacaId];
form.moveItem(draggedIndex, nextItemContainerIndex, false, function() {
var top = findTop(nextField);
regenerate(top);
});
}
},
"over": function (event, ui ) {
$(event.target).addClass("dropzone-hover");
},
"out": function (event, ui) {
$(event.target).removeClass("dropzone-hover");
}
});
// init any in-place draggables
form.getFieldEl().find(".interaction").draggable({
"appendTo": "body",
"helper": function() {
var iCount = $(this).attr("icount-ref");
var clone = $(".alpaca-container-item[icount='" + iCount + "']").clone();
return clone;
},
"cursorAt": {
"top": 100
},
"zIndex": 300,
"refreshPositions": true,
"start": function(event, ui) {
$(".dropzone").addClass("dropzone-highlight");
},
"stop": function(event, ui) {
$(".dropzone").removeClass("dropzone-highlight");
}
});
}
cb(null, form);
};
config.error = function(err)
{
Alpaca.defaultErrorCallback(err);
cb(err);
};
if (disableErrorHandling)
{
Alpaca.defaultErrorCallback = function(error) {
console.log("Alpaca encountered an error while previewing form -> " + error.message);
};
}
else
{
Alpaca.defaultErrorCallback = Alpaca.DEFAULT_ERROR_CALLBACK;
}
$(el).alpaca(config);
}
};
var removeFunctionFields = function(schema, options)
{
if (schema)
{
if (schema.properties)
{
var badKeys = [];
for (var k in schema.properties)
{
if (schema.properties[k].type === "function")
{
badKeys.push(k);
}
else
{
removeFunctionFields(schema.properties[k], (options && options.fields ? options.fields[k] : null));
}
}
for (var i = 0; i < badKeys.length; i++)
{
delete schema.properties[badKeys[i]];
if (options && options.fields) {
delete options.fields[badKeys[i]];
}
}
}
}
};
var editSchema = function(alpacaFieldId, callback)
{
var field = Alpaca.fieldInstances[alpacaFieldId];
var fieldSchemaSchema = field.getSchemaOfSchema();
var fieldSchemaOptions = field.getOptionsForSchema();
removeFunctionFields(fieldSchemaSchema, fieldSchemaOptions);
var fieldData = field.schema;
delete fieldSchemaSchema.title;
delete fieldSchemaSchema.description;
if (fieldSchemaSchema.properties)
{
delete fieldSchemaSchema.properties.title;
delete fieldSchemaSchema.properties.description;
delete fieldSchemaSchema.properties.dependencies;
}
var fieldConfig = {
schema: fieldSchemaSchema
};
if (fieldSchemaOptions)
{
fieldConfig.options = fieldSchemaOptions;
}
if (fieldData)
{
fieldConfig.data = fieldData;
}
fieldConfig.view = {
"parent": MODAL_VIEW,
"displayReadonly": false
};
fieldConfig.postRender = function(control)
{
var modal = $(MODAL_TEMPLATE.trim());
modal.find(".modal-title").append(field.getTitle());
modal.find(".modal-body").append(control.getFieldEl());
modal.find('.modal-footer').append("");
modal.find('.modal-footer').append("");
$(modal).modal({
"keyboard": true
});
$(modal).find(".okay").click(function() {
field.schema = control.getValue();
var top = findTop(field);
regenerate(top);
if (callback)
{
callback();
}
});
control.getFieldEl().find("p.help-block").css({
"display": "none"
});
};
var x = $("
");
$(x).find(".fieldForm").alpaca(fieldConfig);
};
var editOptions = function(alpacaFieldId, callback)
{
var field = Alpaca.fieldInstances[alpacaFieldId];
var fieldOptionsSchema = field.getSchemaOfOptions();
var fieldOptionsOptions = field.getOptionsForOptions();
removeFunctionFields(fieldOptionsSchema, fieldOptionsOptions);
var fieldOptionsData = field.options;
delete fieldOptionsSchema.title;
delete fieldOptionsSchema.description;
if (fieldOptionsSchema.properties)
{
delete fieldOptionsSchema.properties.title;
delete fieldOptionsSchema.properties.description;
delete fieldOptionsSchema.properties.dependencies;
delete fieldOptionsSchema.properties.readonly;
}
if (fieldOptionsOptions.fields)
{
delete fieldOptionsOptions.fields.title;
delete fieldOptionsOptions.fields.description;
delete fieldOptionsOptions.fields.dependencies;
delete fieldOptionsOptions.fields.readonly;
}
var fieldConfig = {
schema: fieldOptionsSchema
};
if (fieldOptionsOptions)
{
fieldConfig.options = fieldOptionsOptions;
}
if (fieldOptionsData)
{
fieldConfig.data = fieldOptionsData;
}
fieldConfig.view = {
"parent": MODAL_VIEW,
"displayReadonly": false
};
fieldConfig.postRender = function(control)
{
var modal = $(MODAL_TEMPLATE.trim());
modal.find(".modal-title").append(field.getTitle());
modal.find(".modal-body").append(control.getFieldEl());
modal.find('.modal-footer').append("");
modal.find('.modal-footer').append("");
$(modal).modal({
"keyboard": true
});
$(modal).find(".okay").click(function() {
field.options = control.getValue();
var top = findTop(field);
regenerate(top);
if (callback)
{
callback();
}
});
control.getFieldEl().find("p.help-block").css({
"display": "none"
});
};
var x = $("
");
$(x).find(".fieldForm").alpaca(fieldConfig);
};
var refreshView = function(callback)
{
if (mainViewField)
{
mainViewField.getFieldEl().replaceWith("");
mainViewField.destroy();
mainViewField = null;
}
doRefresh($("#viewDiv"), false, false, function(err, form) {
if (!err)
{
mainViewField = form;
}
if (callback)
{
callback();
}
});
};
var refreshPreview = function(callback)
{
if (mainPreviewField)
{
mainPreviewField.getFieldEl().replaceWith("");
mainPreviewField.destroy();
mainPreviewField = null;
}
doRefresh($("#previewDiv"), false, false, function(err, form) {
if (!err)
{
mainPreviewField = form;
}
if (callback)
{
callback();
}
});
};
var refreshDesigner = function(callback)
{
$(".dropzone").remove();
$(".interaction").remove();
$(".cover").remove();
if (mainDesignerField)
{
mainDesignerField.getFieldEl().replaceWith("");
mainDesignerField.destroy();
mainDesignerField = null;
}
doRefresh($("#designerDiv"), true, false, function(err, form) {
if (!err)
{
mainDesignerField = form;
}
if (callback)
{
callback();
}
});
};
var refreshCode = function(callback)
{
var json = {
"schema": schema
};
if (options) {
json.options = options;
}
if (data) {
json.data = data;
}
var code = "$('#div').alpaca(" + JSON.stringify(json, null, " ") + ");";
editor4.setValue(code);
editor4.clearSelection();
editor4.gotoLine(0,0);
if (callback)
{
callback();
}
};
var refresh = function(callback)
{
var current = $("UL.nav.nav-tabs LI.active A.tab-item");
$(current).click();
};
var rtChange = false;
editor1.on("change", function() {
rtChange = true;
});
editor2.on("change", function() {
rtChange = true;
});
editor3.on("change", function() {
rtChange = true;
});
// background "thread" to detect changes and update the preview div
var rtProcessing = false;
var rtFunction = function() {
if (rtChange && !rtProcessing)
{
rtProcessing = true;
if (mainPreviewField)
{
mainPreviewField.getFieldEl().replaceWith("");
mainPreviewField.destroy();
mainPreviewField = null;
}
doRefresh($("#previewDiv"), false, true, function(err, form) {
if (!err)
{
mainPreviewField = form;
}
rtChange = false;
rtProcessing = false;
});
}
setTimeout(rtFunction, 1000);
};
rtFunction();
var isCoreField = function(type)
{
var cores = ["any", "array", "checkbox", "file", "hidden", "number", "object", "radio", "select", "text", "textarea"];
var isCore = false;
for (var i = 0; i < cores.length; i++)
{
if (cores[i] == type)
{
isCore = true;
}
}
return isCore;
};
// types
var types = [{
"type": "string",
"title": "String",
"description": "A textual property"
}, {
"type": "number",
"title": "Number",
"description": "A numerical property"
}, {
"type": "boolean",
"title": "Boolean",
"description": "A true/false property"
}, {
"type": "object",
"title": "Object",
"description": "A collection of keyed sub-properties"
}, {
"type": "array",
"title": "Array",
"description": "An array of sub-properties"
}];
for (var i = 0; i < types.length; i++)
{
var title = types[i].title;
var type = types[i].type;
var description = types[i].description;
var div = $("");
$(div).append("
" + title + " (" + type + ")
");
$(div).append("
" + description + "
");
$("#types").append(div);
}
var afterAlpacaInit = function()
{
// show all fields
for (var type in Alpaca.fieldClassRegistry)
{
var instance = new Alpaca.fieldClassRegistry[type]();
var schemaSchema = instance.getSchemaOfSchema();
var schemaOptions = instance.getOptionsForSchema();
var optionsSchema = instance.getSchemaOfOptions();
var optionsOptions = instance.getOptionsForOptions();
var title = instance.getTitle();
var description = instance.getDescription();
var type = instance.getType();
var fieldType = instance.getFieldType();
var div = $("");
$(div).append("
" + title + " (" + fieldType + ")
");
$(div).append("
" + description + "
");
var isCore = isCoreField(fieldType);
if (isCore)
{
$("#basic").append(div);
}
else
{
$("#advanced").append(div);
}
// init all of the draggable form elements
$(".form-element").draggable({
"appendTo": "body",
"helper": "clone",
"zIndex": 300,
"refreshPositions": true,
"start": function(event, ui) {
$(".dropzone").addClass("dropzone-highlight");
},
"stop": function(event, ui) {
$(".dropzone").removeClass("dropzone-highlight");
}
});
}
};
// lil hack to force compile
$("").alpaca({
"data": "test",
"postRender": function(control)
{
afterAlpacaInit();
}
});
$(".tab-item-source").click(function() {
// we have to monkey around a bit with ACE Editor to get it to refresh
editor1.setValue(editor1.getValue());
editor1.clearSelection();
editor2.setValue(editor2.getValue());
editor2.clearSelection();
editor3.setValue(editor3.getValue());
editor3.clearSelection();
setTimeout(function() {
refreshPreview();
}, 50);
});
$(".tab-item-view").click(function() {
setTimeout(function() {
refreshView();
}, 50);
});
$(".tab-item-designer").click(function() {
setTimeout(function() {
refreshDesigner();
}, 50);
});
$(".tab-item-code").click(function() {
setTimeout(function() {
refreshCode();
}, 50);
});
$(".tab-source-schema").click(function() {
// we have to monkey around a bit with ACE Editor to get it to refresh
editor1.setValue(editor1.getValue());
editor1.clearSelection();
});
$(".tab-source-options").click(function() {
// we have to monkey around a bit with ACE Editor to get it to refresh
editor2.setValue(editor2.getValue());
editor2.clearSelection();
});
$(".tab-source-data").click(function() {
// we have to monkey around a bit with ACE Editor to get it to refresh
editor3.setValue(editor3.getValue());
editor3.clearSelection();
});
var insertField = function(schema, options, data, dataType, fieldType, parentField, previousField, previousFieldKey, nextField, nextFieldKey)
{
var itemSchema = {
"type": dataType
};
var itemOptions = {};
if (fieldType)
{
itemOptions.type = fieldType;
}
itemOptions.label = "New ";
if (fieldType)
{
itemOptions.label += fieldType;
}
else if (dataType)
{
itemOptions.label += dataType;
}
var itemData = null;
var itemKey = null;
if (parentField.getType() === "array")
{
itemKey = 0;
if (previousFieldKey)
{
itemKey = previousFieldKey + 1;
}
}
else if (parentField.getType() === "object")
{
itemKey = "new" + new Date().getTime();
}
var insertAfterId = null;
if (previousField)
{
insertAfterId = previousField.id;
}
parentField.addItem(itemKey, itemSchema, itemOptions, itemData, insertAfterId, function() {
var top = findTop(parentField);
regenerate(top);
});
};
var assembleSchema = function(field, schema)
{
// copy any properties from this field's schema into our schema object
for (var k in field.schema)
{
if (field.schema.hasOwnProperty(k) && typeof(field.schema[k]) !== "function")
{
schema[k] = field.schema[k];
}
}
// a few that we handle by hand
schema.type = field.getType();
// reset properties, we handle that one at a time
delete schema.properties;
schema.properties = {};
if (field.children)
{
for (var i = 0; i < field.children.length; i++)
{
var childField = field.children[i];
var propertyId = childField.propertyId;
schema.properties[propertyId] = {};
assembleSchema(childField, schema.properties[propertyId]);
}
}
};
var assembleOptions = function(field, options)
{
// copy any properties from this field's options into our options object
for (var k in field.options)
{
if (field.options.hasOwnProperty(k) && typeof(field.options[k]) !== "function")
{
options[k] = field.options[k];
}
}
// a few that we handle by hand
options.type = field.getFieldType();
// reset fields, we handle that one at a time
delete options.fields;
options.fields = {};
if (field.children)
{
for (var i = 0; i < field.children.length; i++)
{
var childField = field.children[i];
var propertyId = childField.propertyId;
options.fields[propertyId] = {};
assembleOptions(childField, options.fields[propertyId]);
}
}
};
var findTop = function(field)
{
// now get the top control
var top = field;
while (top.parent)
{
top = top.parent;
}
return top;
};
var regenerate = function(top)
{
// walk the control tree and re-assemble the schema, options + data
var _schema = {};
assembleSchema(top, _schema);
var _options = {};
assembleOptions(top, _options);
// data is easy
var _data = top.getValue();
if (!_data) {
_data = {};
}
editor1.setValue(JSON.stringify(_schema, null, " "));
editor2.setValue(JSON.stringify(_options, null, " "));
editor3.setValue(JSON.stringify(_data, null, " "));
setTimeout(function() {
refresh();
}, 100);
};
var removeField = function(alpacaId)
{
var field = Alpaca.fieldInstances[alpacaId];
var parentField = field.parent;
parentField.removeItem(field.propertyId, function() {
var top = findTop(field);
regenerate(top);
});
};
$(".tab-item-source").click();
// load button
$(".load-button").off().click(function() {
if (!localStorage)
{
alert("Your browser must support HTML5 local storage in order to use this feature");
return;
}
var configString = localStorage.getItem("alpacaDesignerConfig");
if (!configString)
{
return;
}
try
{
var config = JSON.parse(configString);
if (!config.schema) {
config.schema = {};
}
if (!config.options) {
config.options = {};
}
if (!config.data) {
config.data = {};
}
editor1.setValue(JSON.stringify(config.schema, null, " "));
editor2.setValue(JSON.stringify(config.options, null, " "));
editor3.setValue(JSON.stringify(config.data, null, " "));
//alert("Your form was loaded from HTML5 local storage");
}
catch (e)
{
// bad value
}
});
// save button
$(".save-button").off().click(function() {
if (!localStorage)
{
alert("Your browser must support HTML5 local storage in order to use this feature");
return;
}
var config = {};
if (schema)
{
config.schema = schema;
}
if (options)
{
config.options = options;
}
if (data)
{
config.data = data;
}
var configString = JSON.stringify(config);
localStorage.setItem("alpacaDesignerConfig", configString);
//alert("Your form was saved in HTML5 local storage");
});
};
$(document).ready(function() {
// wait a bit to allow ACE to load
setTimeout(function() {
setup();
}, 200);
});