@qooxdoo/framework
Version:
The JS Framework for Coders
361 lines (318 loc) • 11.4 kB
JavaScript
/* ************************************************************************
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);
}
}
});