UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

361 lines (318 loc) 11.4 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>Object Controller</h2> * * *General idea* * * The idea of the object controller is to make the binding of one model object * containing one or more properties as easy as possible. Therefore the * controller can take a model as property. Every property in that model can be * bound to one or more target properties. The binding will be for * atomic types only like Numbers, Strings, ... * * *Features* * * * Manages the bindings between the model properties and the different targets * * No need for the user to take care of the binding ids * * Can create an bidirectional binding (read- / write-binding) * * Handles the change of the model which means adding the old targets * * *Usage* * * The controller only can work if a model is set. If the model property is * null, the controller is not working. But it can be null on any time. * * *Cross reference* * * * 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} * * If you want to bind a form widget, use {@link qx.data.controller.Form} */ qx.Class.define("qx.data.controller.Object", { extend : qx.core.Object, /* ***************************************************************************** CONSTRUCTOR ***************************************************************************** */ /** * @param model {qx.core.Object?null} The model for the model property. */ construct : function(model) { this.base(arguments); // create a map for all created binding ids this.__bindings = {}; // create an array to store all current targets this.__targets = []; if (model != null) { this.setModel(model); } }, /* ***************************************************************************** PROPERTIES ***************************************************************************** */ properties : { /** The model object which does have the properties for the binding. */ model : { check: "qx.core.Object", event: "changeModel", apply: "_applyModel", nullable: true, dereference: true } }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members : { // private members __targets : null, __bindings : null, /** * Apply-method which will be called if a new model has been set. * All bindings will be moved to the new model. * * @param value {qx.core.Object|null} The new model. * @param old {qx.core.Object|null} The old model. */ _applyModel: function(value, old) { // for every target for (var i = 0; i < this.__targets.length; i++) { // get the properties var targetObject = this.__targets[i][0]; var targetProperty = this.__targets[i][1]; var sourceProperty = this.__targets[i][2]; var bidirectional = this.__targets[i][3]; var options = this.__targets[i][4]; var reverseOptions = this.__targets[i][5]; // remove it from the old if possible if (old != undefined && !old.isDisposed()) { this.__removeTargetFrom(targetObject, targetProperty, sourceProperty, old); } // add it to the new if available if (value != undefined) { this.__addTarget( targetObject, targetProperty, sourceProperty, bidirectional, options, reverseOptions ); } else { // in shutdown situations, it may be that something is already // disposed [BUG #4343] if (targetObject.isDisposed() || qx.core.ObjectRegistry.inShutDown) { continue; } // if the model is null, reset the current target if (targetProperty.indexOf("[") == -1) { targetObject["reset" + qx.lang.String.firstUp(targetProperty)](); } else { var open = targetProperty.indexOf("["); var index = parseInt( targetProperty.substring(open + 1, targetProperty.length - 1), 10 ); targetProperty = targetProperty.substring(0, open); var targetArray = targetObject["get" + qx.lang.String.firstUp(targetProperty)](); if (index == "last") { index = targetArray.length; } if (targetArray) { targetArray.setItem(index, null); } } } } }, /** * Adds a new target to the controller. After adding the target, the given * property of the model will be bound to the targets property. * * @param targetObject {qx.core.Object} The object on which the property * should be bound. * * @param targetProperty {String} The property to which the binding should * go. * * @param sourceProperty {String} The name of the property in the model. * * @param bidirectional {Boolean?false} Signals if the binding should also work * in the reverse direction, from the target to source. * * @param options {Map?null} The options Map used by the binding from source * to target. The possible options can be found in the * {@link qx.data.SingleValueBinding} class. * * @param reverseOptions {Map?null} The options used by the binding in the * reverse direction. The possible options can be found in the * {@link qx.data.SingleValueBinding} class. */ addTarget: function( targetObject, targetProperty, sourceProperty, bidirectional, options, reverseOptions ) { // store the added target this.__targets.push([ targetObject, targetProperty, sourceProperty, bidirectional, options, reverseOptions ]); // delegate the adding this.__addTarget( targetObject, targetProperty, sourceProperty, bidirectional, options, reverseOptions ); }, /** * Does the work for {@link #addTarget} but without saving the target * to the internal target registry. * * @param targetObject {qx.core.Object} The object on which the property * should be bound. * * @param targetProperty {String} The property to which the binding should * go. * * @param sourceProperty {String} The name of the property in the model. * * @param bidirectional {Boolean?false} Signals if the binding should also work * in the reverse direction, from the target to source. * * @param options {Map?null} The options Map used by the binding from source * to target. The possible options can be found in the * {@link qx.data.SingleValueBinding} class. * * @param reverseOptions {Map?null} The options used by the binding in the * reverse direction. The possible options can be found in the * {@link qx.data.SingleValueBinding} class. */ __addTarget: function( targetObject, targetProperty, sourceProperty, bidirectional, options, reverseOptions ) { // do nothing if no model is set if (this.getModel() == null) { return; } // create the binding var id = this.getModel().bind( sourceProperty, targetObject, targetProperty, options ); // create the reverse binding if necessary var idReverse = null; if (bidirectional) { idReverse = targetObject.bind( targetProperty, this.getModel(), sourceProperty, reverseOptions ); } // save the binding var targetHash = targetObject.toHashCode(); if (this.__bindings[targetHash] == undefined) { this.__bindings[targetHash] = []; } this.__bindings[targetHash].push( [id, idReverse, targetProperty, sourceProperty, options, reverseOptions] ); }, /** * Removes the target identified by the three properties. * * @param targetObject {qx.core.Object} The target object on which the * binding exist. * * @param targetProperty {String} The targets property name used by the * adding of the target. * * @param sourceProperty {String} The name of the property of the model. */ removeTarget: function(targetObject, targetProperty, sourceProperty) { this.__removeTargetFrom( targetObject, targetProperty, sourceProperty, this.getModel() ); // delete the target in the targets reference for (var i = 0; i < this.__targets.length; i++) { if ( this.__targets[i][0] == targetObject && this.__targets[i][1] == targetProperty && this.__targets[i][2] == sourceProperty ) { this.__targets.splice(i, 1); } } }, /** * Does the work for {@link #removeTarget} but without removing the target * from the internal registry. * * @param targetObject {qx.core.Object} The target object on which the * binding exist. * * @param targetProperty {String} The targets property name used by the * adding of the target. * * @param sourceProperty {String} The name of the property of the model. * * @param sourceObject {String} The source object from which the binding * comes. */ __removeTargetFrom: function( targetObject, targetProperty, sourceProperty, sourceObject ) { // check for not fitting targetObjects if (!(targetObject instanceof qx.core.Object)) { // just do nothing return; } var currentListing = this.__bindings[targetObject.toHashCode()]; // if no binding is stored if (currentListing == undefined || currentListing.length == 0) { return; } // go threw all listings for the object for (var i = 0; i < currentListing.length; i++) { // if it is the listing if ( currentListing[i][2] == targetProperty && currentListing[i][3] == sourceProperty ) { // remove the binding var id = currentListing[i][0]; sourceObject.removeBinding(id); // check for the reverse binding if (currentListing[i][1] != null) { targetObject.removeBinding(currentListing[i][1]); } // delete the entry and return currentListing.splice(i, 1); return; } } } }, /* ***************************************************************************** DESTRUCT ***************************************************************************** */ destruct : function() { // set the model to null to get the bindings removed if (this.getModel() != null && !this.getModel().isDisposed()) { this.setModel(null); } } });