UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

392 lines (332 loc) 12.5 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2004-2009 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Martin Wittemann (martinwittemann) ************************************************************************ */ /** * <h2>Form Controller</h2> * * *General idea* * * The form controller is responsible for connecting a form with a model. If no * model is given, a model can be created. This created model will fit exactly * to the given form and can be used for serialization. All the connections * between the form items and the model are handled by an internal * {@link qx.data.controller.Object}. * * *Features* * * * Connect a form to a model (bidirectional) * * Create a model for a given form * * *Usage* * * The controller only works if both a controller and a model are set. * Creating a model will automatically set the created model. * * *Cross reference* * * * If you want to bind single values, use {@link qx.data.controller.Object} * * If you want to bind a list like widget, use {@link qx.data.controller.List} * * If you want to bind a tree widget, use {@link qx.data.controller.Tree} */ qx.Class.define("qx.data.controller.Form", { extend : qx.core.Object, implement: [ qx.core.IDisposable ], /** * @param model {qx.core.Object | null} The model to bind the target to. The * given object will be set as {@link #model} property. * @param target {qx.ui.form.Form | null} The form which contains the form * items. The given form will be set as {@link #target} property. * @param selfUpdate {Boolean?false} If set to true, you need to call the * {@link #updateModel} method to get the data in the form to the model. * Otherwise, the data will be synced automatically on every change of * the form. */ construct : function(model, target, selfUpdate) { this.base(arguments); this._selfUpdate = !!selfUpdate; this.__bindingOptions = {}; if (model != null) { this.setModel(model); } if (target != null) { this.setTarget(target); } }, properties : { /** Data object containing the data which should be shown in the target. */ model : { check: "qx.core.Object", apply: "_applyModel", event: "changeModel", nullable: true, dereference: true }, /** The target widget which should show the data. */ target : { check: "qx.ui.form.Form", apply: "_applyTarget", event: "changeTarget", nullable: true, init: null, dereference: true } }, members : { __objectController : null, __bindingOptions : null, /** * The form controller uses for setting up the bindings the fundamental * binding layer, the {@link qx.data.SingleValueBinding}. To achieve a * binding in both directions, two bindings are needed. With this method, * you have the opportunity to set the options used for the bindings. * * @param name {String} The name of the form item for which the options * should be used. * @param model2target {Map} Options map used for the binding from model * to target. The possible options can be found in the * {@link qx.data.SingleValueBinding} class. * @param target2model {Map} Options map used for the binding from target * to model. The possible options can be found in the * {@link qx.data.SingleValueBinding} class. */ addBindingOptions : function(name, model2target, target2model) { this.__bindingOptions[name] = [model2target, target2model]; // return if not both, model and target are given if (this.getModel() == null || this.getTarget() == null) { return; } // renew the affected binding var item = this.getTarget().getItems()[name]; var targetProperty = this.__isModelSelectable(item) ? "modelSelection[0]" : "value"; // remove the binding this.__objectController.removeTarget(item, targetProperty, name); // set up the new binding with the options this.__objectController.addTarget( item, targetProperty, name, !this._selfUpdate, model2target, target2model ); }, /** * Creates and sets a model using the {@link qx.data.marshal.Json} object. * Remember that this method can only work if the form is set. The created * model will fit exactly that form. Changing the form or adding an item to * the form will need a new model creation. * * @param includeBubbleEvents {Boolean} Whether the model should support * the bubbling of change events or not. * @return {qx.core.Object} The created model. */ createModel : function(includeBubbleEvents) { var target = this.getTarget(); // throw an error if no target is set if (target == null) { throw new Error("No target is set."); } var items = target.getItems(); var data = {}; for (var name in items) { var names = name.split("."); var currentData = data; for (var i = 0; i < names.length; i++) { // if its the last item if (i + 1 == names.length) { // check if the target is a selection var clazz = items[name].constructor; var itemValue = null; if (qx.Class.hasInterface(clazz, qx.ui.core.ISingleSelection)) { // use the first element of the selection because passed to the // marshaler (and its single selection anyway) [BUG #3541] itemValue = items[name].getModelSelection().getItem(0) || null; } else { itemValue = items[name].getValue(); } // call the converter if available [BUG #4382] if (this.__bindingOptions[name] && this.__bindingOptions[name][1]) { itemValue = this.__bindingOptions[name][1].converter(itemValue); } currentData[names[i]] = itemValue; } else { // if its not the last element, check if the object exists if (!currentData[names[i]]) { currentData[names[i]] = {}; } currentData = currentData[names[i]]; } } } var model = qx.data.marshal.Json.createModel(data, includeBubbleEvents); this.setModel(model); return model; }, /** * Responsible for syncing the data from entered in the form to the model. * Please keep in mind that this method only works if you create the form * with <code>selfUpdate</code> set to true. Otherwise, this method will * do nothing because updates will be synced automatically on every * change. */ updateModel: function(){ // only do stuff if self update is enabled and a model or target is set if (!this._selfUpdate || !this.getModel() || !this.getTarget()) { return; } var items = this.getTarget().getItems(); for (var name in items) { var item = items[name]; var sourceProperty = this.__isModelSelectable(item) ? "modelSelection[0]" : "value"; var options = this.__bindingOptions[name]; options = options && this.__bindingOptions[name][1]; qx.data.SingleValueBinding.updateTarget( item, sourceProperty, this.getModel(), name, options ); } }, // apply method _applyTarget : function(value, old) { // if an old target is given, remove the binding if (old != null) { this.__tearDownBinding(old); } // do nothing if no target is set if (this.getModel() == null) { return; } // target and model are available if (value != null) { this.__setUpBinding(); } }, // apply method _applyModel : function(value, old) { // set the model to null to reset all items before removing them if (this.__objectController != null && value == null) { this.__objectController.setModel(null); } // first, get rid off all bindings (avoids wrong data population) if (this.__objectController != null && this.getTarget() != null) { var items = this.getTarget().getItems(); for (var name in items) { var item = items[name]; var targetProperty = this.__isModelSelectable(item) ? "modelSelection[0]" : "value"; this.__objectController.removeTarget(item, targetProperty, name); } } // set the model of the object controller if available if (this.__objectController != null) { this.__objectController.setModel(value); } // do nothing is no target is set if (this.getTarget() == null) { return; } else { // if form was validated with errors and model changes // the errors should be cleared see #8977 this.getTarget().getValidationManager().reset(); } // model and target are available if (value != null) { this.__setUpBinding(); } }, /** * Internal helper for setting up the bindings using * {@link qx.data.controller.Object#addTarget}. All bindings are set * up bidirectional. */ __setUpBinding : function() { // create the object controller if (this.__objectController == null) { this.__objectController = new qx.data.controller.Object(this.getModel()); } // get the form items var items = this.getTarget().getItems(); // connect all items for (var name in items) { var item = items[name]; var targetProperty = this.__isModelSelectable(item) ? "modelSelection[0]" : "value"; var options = this.__bindingOptions[name]; // try to bind all given items in the form try { if (options == null) { this.__objectController.addTarget(item, targetProperty, name, !this._selfUpdate); } else { this.__objectController.addTarget( item, targetProperty, name, !this._selfUpdate, options[0], options[1] ); } // ignore not working items } catch (ex) { if (qx.core.Environment.get("qx.debug")) { this.warn("Could not bind property " + name + " of " + this.getModel() + ":\n" + ex.stack); } } } // make sure the initial values of the model are taken for resetting [BUG #5874] this.getTarget().redefineResetter(); }, /** * Internal helper for removing all set up bindings using * {@link qx.data.controller.Object#removeTarget}. * * @param oldTarget {qx.ui.form.Form} The form which has been removed. */ __tearDownBinding : function(oldTarget) { // do nothing if the object controller has not been created if (this.__objectController == null) { return; } // get the items var items = oldTarget.getItems(); // disconnect all items for (var name in items) { var item = items[name]; var targetProperty = this.__isModelSelectable(item) ? "modelSelection[0]" : "value"; this.__objectController.removeTarget(item, targetProperty, name); } }, /** * Returns whether the given item implements * {@link qx.ui.core.ISingleSelection} and * {@link qx.ui.form.IModelSelection}. * * @param item {qx.ui.form.IForm} The form item to check. * * @return {Boolean} true, if given item fits. */ __isModelSelectable : function(item) { return qx.Class.hasInterface(item.constructor, qx.ui.core.ISingleSelection) && qx.Class.hasInterface(item.constructor, qx.ui.form.IModelSelection); } }, /* ***************************************************************************** DESTRUCTOR ***************************************************************************** */ destruct : function() { // dispose the object controller because the bindings need to be removed if (this.__objectController) { this.__objectController.dispose(); } } });