define(function(require, exports, module) { main.consumes = ["ui", "Plugin", "settings"]; main.provides = ["Form"]; return main; function main(options, imports, register) { var Plugin = imports.Plugin; var settings = imports.settings; var ui = imports.ui; /***** Initialization *****/ function Form(options) { var plugin = new Plugin("Ajax.org", main.consumes); var emit = plugin.getEmitter(); var model = options.model; var edge = options.edge || "10 10 10 10"; var rowheight = options.rowheight || 40; var width = options.colwidth || 300; var maxwidth = options.colmaxwidth || 300; var widths = options.widths || {}; var skins = options.skins || {}; var container, meta = {}; var debug = location.href.indexOf('menus=1') > -1; var headings = {}; if (!Form.proxy) { Form.proxy = new apf.Class().$init(); apf.nameserver.register("all", "Form", Form); } var loaded; function load(){ if (loaded) return; loaded = true; if (options.html) draw(options.html); if (options.form) add(options.form); } var drawn = false; function draw(htmlNode) { if (drawn) return; drawn = true; // Create UI elements container = new ui.bar({ htmlNode: htmlNode || document.body, "class" : options.className, style: options.style }); plugin.addElement(container); if (model) container.setAttribute("model", model); emit("draw"); } /***** Method *****/ function add(state, foreign) { if (!drawn) { plugin.on("draw", function wait(){ add(state, foreign); plugin.off("draw", wait); }); return; } // An array of items if (state instanceof Array) { state.forEach(function(item) { createItem({ container: container }, item.title, item, foreign); }); return; } // A simple object as items for (var caption in state) { if (caption == "position") continue; var first = state[caption]; var heading = createHeading(caption, // basegroup + first.position, Object.keys(first).length < 1, foreign); if (first instanceof Array) { first.forEach(function(item) { createItem(heading, item.title, item, foreign); }); } else { for (var title in first) { if (title == "position") { continue; } var second = first[title]; createItem(heading, title, second, foreign); } } } } function createHeading(name, position, hack, foreign) { if (!foreign) foreign = plugin; var heading = headings[name]; if (!heading) { if (!hack) { var aml = container.appendChild(new apf.bar()); aml.$int.innerHTML = '
' + apf.escapeXML((debug ? "\[" + (position || "") + "\] " : "") + name) + '
'; } heading = headings[name] = { container: aml }; foreign.addOther(function(){ heading.container.destroy(true, true); // heading.nav.parentNode.removeChild(ns.nav); delete headings[name]; }); } if (position && position != heading.container.$position) { !hack && ui.insertByIndex(container, heading.container, position, foreign); } return heading; } function getName(path) { var parts = path.split("/"); var name = ""; parts.forEach(function(part) { name += part.uCaseFirst().replace(/[-\.]/g, "_"); }); if (~name.indexOf("@")) name = name.replace(/@(.*)/, "$1Attribute"); return name; } function createBind(path) { var name = getName(path); Form.proxy.setProperty(name, settings.get(path)); Form.proxy.on("prop." + name, function(e) { settings.set(path, e.value); }); settings.on(path, function(value) { if (Form.proxy[name] != value) Form.proxy.setProperty(name, value); }, plugin); return "{Form.proxy." + name + "}"; } function createItem(heading, name, options, foreign) { if (!foreign) foreign = plugin; var position = options.position; var node, childNodes; if (options.setting && !options.path) options.path = options.setting; if (debug) name = "\\[" + (position || "") + "\\] " + name; switch (options.type) { case "checkbox": childNodes = [ new ui.label({ width : width, maxwidth: maxwidth, caption: name + ":" }), new ui.checkbox({ value: options.path ? createBind(options.path) : (options.defaultValue || ""), values: options.values, skin: "cboffline" // width: "55" }) ]; break; case "dropdown": var model = options.model || new ui.model(); var data = options.items && options.items.map(function(item) { return ""; }).join(""); if (data) model.load("" + data + ""); childNodes = [ new ui.label({ width : width, maxwidth: maxwidth, caption: name + ":" }), new ui.dropdown({ model: model, width: options.width || widths.dropdown, skin: "black_dropdown", margin: "-1 0 0 0", zindex: 100, onafterchange: options.onchange && function(e) { options.onchange({ value: e.value || e.args[2] }); }, value: options.path ? createBind(options.path) //{settings.model}:: : (options.defaultValue || ""), each: options.each || "[item]", caption: options.caption || "[text()]", eachvalue: options.eachvalue || "[@value]", "empty-message" : options["empty-message"] }) ]; break; case "spinner": childNodes = [ new ui.label({ width : width, maxwidth: maxwidth, caption: name + ":" }), new ui.spinner({ width: options.width || widths.spinner, value: options.path ? createBind(options.path) : (options.defaultValue || ""), min: options.min, max: options.max, realtime: typeof options.realtime !== "undefined" ? options.realtime : 1 }) ]; break; case "checked-spinner": childNodes = [ new ui.checkbox({ value: options.checkboxPath ? createBind(options.checkboxPath) : (options.defaultCheckboxValue || ""), width: width, maxwidth: maxwidth, label: name + ":", skin: "checkbox_black" }), new ui.spinner({ width: options.width || widths["checked-spinner"], value: options.path ? createBind(options.path) : (options.defaultValue || ""), min: options.min, max: options.max, realtime: typeof options.realtime !== "undefined" ? options.realtime : 1 }) ]; break; case "checkbox-single": childNodes = [ new ui.checkbox({ value: options.path ? createBind(options.path) : (options.defaultValue || ""), width: options.width || widths["checked-single"], label: name, skin: "checkbox_black" }) ]; break; case "textbox": childNodes = [ new ui.label({ width : width, maxwidth: maxwidth, caption: name + ":" }), new ui.textbox({ skin: skins.textbox || "searchbox", margin: "-3 0 0 0", "initial-message": options.message || "", width: options.width || widths.textbox, value: options.path ? createBind(options.path) : (options.defaultValue || ""), realtime: typeof options.realtime !== "undefined" ? options.realtime : 1 }) ]; break; case "password": childNodes = [ new ui.label({ width : width, maxwidth: maxwidth, caption: name + ":" }), new ui.password({ //skin : "codebox", width: options.width || widths.password, value: options.path ? createBind(options.path) : (options.defaultValue || ""), realtime: typeof options.realtime !== "undefined" ? options.realtime : 1 }) ]; break; case "colorbox": childNodes = [ new ui.label({ width : width, maxwidth: maxwidth, caption: name + ":" }), new ui.colorbox({ width: options.width || widths.colorbox, value: options.path ? createBind(options.path) : (options.defaultValue || ""), realtime: typeof options.realtime !== "undefined" ? options.realtime : 1 }) ]; break; case "button": childNodes = [ new ui.label({ width : width, maxwidth: maxwidth, caption: name + ":" }), new ui.button({ skin: "blackbutton", height: 24, margin: "-2 0 -2 0", style: "line-height:22px", caption: options.caption, width: options.width || widths.button, onclick: options.onclick }) ]; break; case "submit": node = new ui.button({ skin: "btn-default-css3", margin: options.margin, caption: options.caption || "", submenu: options.submenu && options.submenu.aml || options.submenu || "", width: options.width || "", onclick: options.onclick, "default" : options["default"] ? "1" : "" }); break; case "label": node = new ui.label({ caption: options.caption, style: options.style, width: options.width || "" }); break; case "image": node = new ui.img({ skin: "simpleimg", src: options.src, margin: options.margin, width: options.width, height: options.height }); break; case "divider": node = new ui.divider({ skin: "c9-divider-hor", margin: "5 0 5 0" }); break; case "textarea": childNodes = [ new ui.label({ width : width, maxwidth: maxwidth, caption: name + ":" }), new ui.textarea({ width: options.width || widths.textarea, height: options.height || 200, value: options.path ? createBind(options.path) : (options.defaultValue || ""), realtime: typeof options.realtime !== "undefined" ? options.realtime : 1 }) ]; break; case "custom": node = options.node; break; default: throw new Error("Unknown form element type: " + options.type); } if (!node) { node = new ui.hsplitbox({ options: options, height: options.rowheight || rowheight, edge: options.edge || edge, type: options.type, childNodes: childNodes }); } if (options.name) node.setAttribute("id", options.name); ui.insertByIndex(heading.container, node, position, foreign); foreign.addElement(node); return node; } function update(items) { items.forEach(function(item) { var el = plugin.getElement(item.id); switch(el.type) { case "dropdown": var dropdown = el.lastChild; var data = item.items.map(function(item) { return ""; }).join(""); if (data) dropdown.$model.load("" + data + ""); if (item.value) dropdown.setAttribute("value", item.value); break; default: el.lastChild.setAttribute('value', item.value); break; } }) } function attachTo(htmlNode, beforeNode) { draw(); if (htmlNode.$amlLoaded) { container.parentNode = htmlNode; htmlNode = htmlNode.$int; } htmlNode.insertBefore(container.$ext, beforeNode || null); emit("show"); } function detach(){ container.$ext.parentNode.removeChild(container.$ext); emit("hide"); } function toJson(amlNode, json) { if (!json) json = {}; (amlNode || container).childNodes.forEach(function(row) { if (row.localName == 'bar') return toJson(row, json); var name = row.name; if (!name) return; var value = row.childNodes.filter(function(node) { return node.localName != "label"; }).map(function(node) { return node.value; }); if (value.length == 1) value = value[0]; json[name] = value; }); var result = emit("serialize", { json: json }); return result || json; } function reset(){ container.childNodes.forEach(function(row) { var options = row.options; if (!row.childNodes.length) return; if (row.firstChild.localName == "checkbox") row.firstChild.setValue(options.defaultCheckboxValue || ""); row.lastChild.setValue(options.defaultValue || ""); }); } function validate(){ return true; } function getRect(){ return container.$ext.getBoundingClientRect(); } function show(){ container.show(); emit("show"); } function hide(){ container.hide(); emit("hide"); } /***** Lifecycle *****/ plugin.on("load", function(){ load(); }); plugin.on("enable", function(){ }); plugin.on("disable", function(){ }); plugin.on("unload", function(){ loaded = false; drawn = false; }); /***** Register and define API *****/ /** * Form Builder for Cloud9. Use this class to create forms * that requests user input. * * This is what the {@link preferences.Preferences Preferences} plugin uses to draw the * settings and what the deploy plugins use to create forms that * allow the user to choose the deploy instances. * * Example: * * var authform = new Form({ * rowheight : 27, * colwidth : 70, * form : [ * { * type : "image", * src : "/logo.png", * width : 180, * height : 58 * }, * { * title : "Username", * name : "username", * type : "textbox", * }, * { * title : "Password", * name : "password", * type : "password", * }, * { * name : "loginfail", * type : "label", * caption : "Could not login. Please try again.", * style : "color:rgb(255, 143, 0);margin-left:5px;" * }, * { * type : "submit", * caption : "Login", * "default" : true, * margin : "10 20 5 20", * onclick : function(){ * if (authform.validate()) * login(authform.toJson(), function(){}); * } * } * ] * }); */ /** * @constructor * Creates a new Form instance based on an abstract definition of * the form elements. * * The form elements are described by simple objects that determine * the properties of each element. The following properties are the * same for all form element definitions: * * * * * * * * * * *
Property Possible Values
title The text of the label in the first column
name This value is used when creating a json object using {@link #toJson}
width A different width for the element in the 2nd column
rowheight A different height for this row
edge The edges on the top, right, bottom, left side of the form element in pixels, space separated (e.g. "5 5 10 5").
position Integer specifying the position in the form.
type The type of form element to create. The following values are supported: * * * * * * * * * * * * * * * * * *
Type Description
checkbox A simple two-state checkbox
dropdown A dropdown containing items that a user can select
spinner A widget that lets a users select integers
checked-spinner A combination of a checkbox and a spinner
textbox A simple textbox
password A textbox that doesn't show the content while the user types into it
colorbox A widget that allows a user to select a color
button A simple button
submit A button without a label
label A label that spans both columns
image An image that spans both columns
divider A divider that spans both columns
textarea A multi line input widget
custom A custom APF element. This can be anything
*
* * Each element type also has properties that are unique for it's * type. * * ## checkbox ## * * * * *
Property Possible Value
defaultValue The default value of the checkbox. This is the value that is set initially and after calling {@link #reset}
values Array defining the values that correspond with the checked and unchecked state. (i.e. ["on", "off"])
* ## dropdown ## * * * * * *
Property Possible Value
items Array of objects with a value and caption property.
defaultValue The value of the default selected item
empty-message The message displayed when no items have been selected
* ## spinner ## * * * * * * *
Property Possible Value
defaultValue The default value of the spinner. This is the value that is set initially and after calling {@link #reset}
min The smallest value that a number can have in the spinner
max The largest value that a number can have in the spinner
realtime Whether the values are updated while typing, or only when the form element has lost focus
* ## checked-spinner ## * * * * * * * *
Property Possible Value
defaultCheckboxValue The default value of the checkbox. This is the value that is set initially and after calling {@link #reset}
defaultValue The default value of the spinner. This is the value that is set initially and after calling {@link #reset}
min The smallest value that a number can have in the spinner
max The largest value that a number can have in the spinner
realtime Whether the values are updated while typing, or only when the form element has lost focus
* ## textbox ## * * * * * *
Property Possible Value
message The message displayed while the textbox has no value
defaultValue The default value of the textbox. This is the value that is set initially and after calling {@link #reset}
realtime Whether the values are updated while typing, or only when the form element has lost focus
* ## password ## * * * * *
Property Possible Value
defaultValue The default value of the password element. This is the value that is set initially and after calling {@link #reset}
realtime Whether the values are updated while typing, or only when the form element has lost focus
* ## textarea ## * * * * * *
Property Possible Value
height The height of the textarea in pixels.
defaultValue The default value of the password element. This is the value that is set initially and after calling {@link #reset}
realtime Whether the values are updated while typing, or only when the form element has lost focus
* ## colorbox ## * * * * *
Property Possible Value
defaultValue The default value of the colorbox. This is the value that is set initially and after calling {@link #reset}
realtime Whether the values are updated while typing, or only when the form element has lost focus
* ## button ## * * * * *
Property Possible Value
caption The text displayed on the button
onclick The function that is executed when a user clicks on the button
* ## submit ## * * * * * * * *
Property Possible Value
margin The edges on the top, right, bottom, left side of the submit element in pixels, space separated (e.g. "5 5 10 5").
caption The text displayed on the button
submenu A reference to a menu that is shown when the button is clicked
onclick The function that is executed when a user clicks on the button
default Whether this is the action that is executed when the user presses Enter in one of the form elements
* ## label ## * * * * *
Property Possible Value
caption The text displayed on the button
style A css string that is applied to the label
* ## image ## * * * * * *
Property Possible Value
src The URL to load the image from
margin The edges on the top, right, bottom, left side of the submit element in pixels, space separated (e.g. "5 5 10 5").
height The height of the image in pixels
* ## divider ## * *None* * * @param {Object} [options] The definition of the form. * @param {String} [options.edge] The edges on the top, right, bottom, left side of the form in pixels, space separated (e.g. "5 5 10 5"). * @param {Number} [options.rowheight] The default height of each row in pixels. * @param {Number} [options.colwidth] The width of the left column in pixels. * @param {String} [options.className] This className is applied to the form container. * @param {Object} [options.widths] A hash of the default widths of the form elements. The key of the hash is the type name of the form element. * @param {HTMLElement} [options.html] The html element to attacht the form to. You can also attach the form later using {@link #attachTo}. * @param {Object[]} [options.form] A list of form element definitions as described above. */ plugin.freezePublicAPI({ /** * The HTML Element that contains the form. * @property {HTMLElement} container * @readonly */ get container(){ return container.$ext; }, /** * The APF element that is the parent to all form elements. * @property {AMLElement} aml * @private * @readonly */ get aml(){ return container; }, /** * A hash of all the headings in this form, indexed by name. * @property {Object} headings * @private * @readonly */ get headings(){ return headings; }, /** * A meta data object that allows you to store whatever you want * in relation to this form. * @property {Object} meta */ get meta(){ return meta; }, set meta(v){ meta = v; }, _events: [ /** * Fires when the form is drawn. * @event draw */ "draw", /** * Fires when the form becomes visible. This happens when * it's attached to an HTML element using the {@link #attachTo} * method, or by calling the {@link #method-show} method. * @event show */ "show", /** * Fires when the form becomes hidden. This happens when * it's detached from an HTML element using the {@link #detach} * method, or by calling the {@link #method-hide} method. * @event hide */ "hide", /** * Fires when toJson is called. The event object contains * a JSON object containing key value pairs. Each * key is the value of the name property of each form * element and the value is the current value of the form * element at the time of calling serialize() * * This event allows plugins to alter the data, prior to * it being returned to the caller of the {@link #toJson} * method. * * @event serialize * @param {Object} e * @param {Object} e.json The JSON object representing the * serialized state of the form. */ "serialize" ], /** * Retrieves the geometrical properties of the form * @return {Object} * @return {Number} return.left The left position of the form, relative to the browser window. * @return {Number} return.top The top position of the form, relative to the browser window. * @return {Number} return.width The width of the form. * @return {Number} return.height The height of the form. */ getRect: getRect, /** * Returns a JSON object containing key value pairs. Each * key is the value of the name property of each form * element and the value is the current value of the form * element at the time of calling serialize() * @return {Object} * @fires serialize */ toJson: toJson, /** * Attaches this form to an HTML element. * @param {HTMLElement} htmlNode The HTML element to attach the form to. * @param {HTMLElement} beforeNode The HTML element before * which the form will be insert. This element should * always be a child of the `htmlNode`. * @fires show */ attachTo: attachTo, /** * Detaches this form from the HTML element it is attached to. * @fires hide */ detach: detach, /** * Renders all the form elements, defined during the creation of * the form instance. * @fires draw */ draw: draw, /** * Clear all values from the form elements or return values to * their default values, if defined. */ reset: reset, /** * Add additional form elements to the form. * @param {Object} state The form definition. See {@link Form}. * @param {Plugin} foreign The plugin responsible for adding these elements (needed for cleanup). */ add: add, /** * Updates form elements with new values. This method currently * only supports updating the items of a dropdown element. * @param {Array} items */ update: update, /** * Show the form. This requires the form to be * {@link #attachTo attached} to an HTML element. * @fires show */ show: show, /** * Hide the form. * @fires hide */ hide: hide, /** * Not implemented yet. Validate the form elements. */ validate: validate }); plugin.load(null, "form"); return plugin; } register(null, { Form: Form }); } });