Sebastian Werner schrieb:
Hi Christian,
please explain what are your exact use cases. Do you just add properties
e.g. IDs etc. or do you even modify them? To you attach modifiers? Do
you do this on instance level or on prototype level?
Cheers,
Sebastian
Hi Sebastian, have a look at the attached file. It is documented to be
self-explaining. As you will see, the whole idea depends on the
add-properties-at-runtime concept. I add behaviour to all widgets by
adding to the QxTarget and QxWidget prototypes, and then overriding
those behaviours on a per-widget basis also to their prototypes.
Thanks,
Christian
/**
* Qoodoo Data Manager Extensions
*
* @author Christian Boulanger
* @version 0.1
* @copyright Christian Boulanger
* @license GNU Lesser General Public License, see
http://www.opensource.org/licenses/lgpl-license.php
*
* With these extensions, all Objects inheriting from QxTarget get data binding
* and, if they have properties that implement "values," "states," or
"options",
* these properties can be bound to data sources on the server
*
* The data transmitted to or from the server is always encoded in a simple
JSON
* object with this structure
* {
* data: (mixed data),
* options: (mixed options),
* (any kind of property): (property value),
* ...
* }
* and sent by the chosen transport mechanism and method.
*
* In most widgets, the options property can be a string ( list values
separated by
* comma), an array with string values, or a hash with "text" and "value"
properties for
* the labels and the values of widgets which support this difference (for
example,
* QxListElements).
*
* The difference between "data" and "options" can be a bit confusing:
* - In a QxList, the "options" are the QxListViewItems and the "data" are the
values of
* the items selected (obviously, for this to work, valus must be unique)
*
* - In a QxListView, the "options" are the actual Grid data, whereas the
* "data" is a list of id numbers of the selected rows.
*
* You can also dynamically change the datasource on the server by setting a new
* reiceveUrl or sendUrl property, or set whatever property you like with the
JSON
* object.
*
* The following properties are added to QxTarget and its descendents:
*
* Data binding
* ------------
* dataManager associates data element and the
data manager. Object
* name name of the data
element, must be unique
* method determines whether the
data should be sent received or sent
* as "get" or
"post" (default). string.
* sendUrl url where to fetch the
data remotely. %foo is replaced by the
* value of
element with name foo. string.
* receiveUrl url with which to update data
remotely. %foo is replaced by the
* value of
element with name foo. string.
*
* Data events
* -----------
* dispatchOnDataSent event to dispatch when server has
acknowledged the receipt of
* the data sent.
string.
* dispatchOnDataReceived event to dispatch globally when server
has sent data. string.
* updateRemoteOnChange whether to automatically update
remotely when local data changes. boolean,
* default false.
* updateRemoteOnEvent event name which triggers remote
update. string.
* updateLocalOnEvent event name which triggers local update.
string.
* dispatchOnChange event to dispatch when data has
changed on the client. event name
* will be
dispatched as a data event containing the changed data
*
* UI events
* ---------
* enableOnEvent global event to listen for for
enabling or diabling the element
* displayOnEvent global event to listen for for
displaying or hiding the element
*
* The data elements are bound together by a QxDataManager object (see below),
which
* provides the transport mechanism. It also has these extended properties and
can update
* all bound data elements centrally.
*
* Drag & Drop support
* -------------------
*
* The Data Manager Extensions also adds a simple drag & drop default behaviour
that can be
* turned on with the following properties:
*
* dragDataType (string) makes a widget
draggable and specifies what kind of
* data type it contains.
* childrenDragDataType (string) same as dragDataType, but not
the widget itself is
* made draggable, but its children. this is
helpful, for example,
* in a QxList when you
don't want to manually assign the dragDataType
* to the QxListItems
* dropDataType (string) specifies which data
types are allowed to be dropped
* on the target.
Can be a list of values separated by comma.
* dropMarker (object) a widget that shoud
appear on top of the widget that
* the drag cursor
currently hovers over, marking the position
* where the
dragged element will be placed at upon drop.
* sortAfterDrop (boolean) Only applies to
QxList: should the list be sorted after
* dropping?
*/
//--------------------------------------------------------------------------
// QxTarget Property Extensions
//--------------------------------------------------------------------------
// associates data element and the data container
QxTarget.addProperty({ name : "dataManager", type : QxConst.TYPEOF_OBJECT,
defaultValue : null});
// name of data element, must be unique
QxTarget.addProperty({ name : "name", type : QxConst.TYPEOF_STRING,
defaultValue : "" });
// determines whether the data should be sent received or sent as GET or POST
QxTarget.addProperty({ name : "method", type : QxConst.TYPEOF_STRING,
defaultValue : "post" });
// url where to fetch the data remotely
QxTarget.addProperty({ name : "sendUrl", type : QxConst.TYPEOF_STRING,
defaultValue : "" });
// url with which to update data remotely
QxTarget.addProperty({ name : "receiveUrl", type : QxConst.TYPEOF_STRING,
defaultValue : "" });
// event to dispatch when server has acknowledged the receipt of the data sent
QxTarget.addProperty({ name : "dispatchOnDataSent", type :
QxConst.TYPEOF_STRING, defaultValue : "" });
// event to dispatch globally when server has sent data
QxTarget.addProperty({ name : "dispatchOnDataReceived", type :
QxConst.TYPEOF_STRING, defaultValue : "" });
// whether to update remotely when local data changes
QxTarget.addProperty({ name : "updateRemoteOnChange", type :
QxConst.TYPEOF_BOOLEAN, defaultValue : false });
// event name which triggers remote update
QxTarget.addProperty({ name : "updateRemoteOnEvent", type :
QxConst.TYPEOF_STRING, defaultValue : "" });
// event name which triggers local update
QxTarget.addProperty({ name : "updateLocalOnEvent", type :
QxConst.TYPEOF_STRING, defaultValue : "" });
// event to dispatch when data has changed on the client
QxTarget.addProperty({ name : "dispatchOnChange", type : QxConst.TYPEOF_STRING,
defaultValue : ""});
//--------------------------------------------------------------------------
// QxWidget Property Extensions
//--------------------------------------------------------------------------
// global event to listen for for enabling or diabling the element
QxWidget.addProperty({ name : "enableOnEvent", type : QxConst.TYPEOF_STRING,
defaultValue : ""});
// global event to listen for for displaying or hiding the element
QxWidget.addProperty({ name : "displayOnEvent", type : QxConst.TYPEOF_STRING,
defaultValue : ""});
// drag and drop property: drop data type
QxWidget.addProperty({ name : "dragDataType", type : QxConst.TYPEOF_STRING,
defaultValue : ""});
// drag and drop property: drop data type for all children
QxWidget.addProperty({ name : "childrenDragDataType", type :
QxConst.TYPEOF_STRING, defaultValue : ""});
// drag and drop property: drop data action
QxWidget.addProperty({ name : "dragDataAction", type : QxConst.TYPEOF_STRING,
defaultValue : ""});
// drag and drop property: drop data action for all children
QxWidget.addProperty({ name : "childrenDragDataAction", type :
QxConst.TYPEOF_STRING, defaultValue : ""});
// drag and drop property: sort parent after child element has been dragged /
dropped?
QxWidget.addProperty({ name : "sortAfterDrop", type : QxConst.TYPEOF_BOOLEAN,
defaultValue : false });
// drag and drop property: marker object
QxWidget.addProperty({ name : "dropMarker", type : QxConst.TYPEOF_OBJECT,
defaultValue : null});
//--------------------------------------------------------------------------
// Modifiers
//--------------------------------------------------------------------------
/**
* associates data element with data manager
*/
QxTarget.prototype._modifyDataManager = function( propValue, propOldValue,
propData )
{
if ( propValue )
{
propValue.add ( this );
}
else if ( propOldValue )
{
propOldValue.remove ( this );
};
return true;
};
/**
* whether to send data when local data changes
*/
QxTarget.prototype._modifyUpdateRemoteOnChange = function(propValue,
propOldValue, propData)
{
var changeEvent = "change" + this.getValuePropName();
if ( propValue )
{
this.addEventListener ( changeEvent, this.updateRemote ,this );
}
else if ( propOldValue )
{
this.removeEventListener ( changeEvent, this.updateRemote ,this
);
};
return true;
};
/**
* listens for an event to trigger remote update
*/
QxTarget.prototype._modifyUpdateRemoteOnEvent = function(propValue,
propOldValue, propData)
{
if ( propValue )
{
window.application.addEventListener (propValue, function(e) {
this.updateRemote();
},this);
};
return true;
};
/**
* listens for an event to trigger local update
*/
QxTarget.prototype._modifyUpdateLocalOnEvent = function(propValue,
propOldValue, propData)
{
if ( propValue )
{
window.application.addEventListener (propValue, function(e) {
this.updateLocal();
},this);
};
return true;
};
/**
* listen for change event event and dispatch passed event name
*/
QxTarget.prototype._modifyDispatchOnChange = function(propValue, propOldValue,
propData)
{
if ( propValue )
{
var changeProp = this.getValuePropName();
this.addEventListener ( "change" + changeProp, function() {
this.getDataManager().dispatch ( propValue,
this.getLocalData() );
},this);
};
return true;
};
/**
* listen for global event to enable or disable widget
*/
QxTarget.prototype._modifyEnableOnEvent = function(propValue, propOldValue,
propData)
{
if ( propValue )
{
window.application.addEventListener ( propValue, function(e) {
this.setEnabled( e.getData() !== false );
},this);
};
return true;
};
/**
* listen for global event to show or hide widget
*/
QxTarget.prototype._modifyDisplayOnEvent = function(propValue, propOldValue,
propData)
{
if ( propValue )
{
window.application.addEventListener ( propValue, function(e) {
this.setDisplay( e.getData() !== false );
},this);
};
return true;
};
//--------------------------------------------------------------------------
// Helper functions
//--------------------------------------------------------------------------
// generic methods suitable for widgets which have
// getValue and setValue alike methods
// need to be overridden with widget-specific methods
/**
* gets the name of the value property
**/
QxTarget.prototype.getValuePropName = function ()
{
switch( this.classname )
{
case "QxTextField":
case "QxPasswordField":
case "QxTextArea":
return "Value";
case "QxLabel":
return "Html";
case "QxText":
return "Text";
case "QxAtom":
return "Label";
default:
throw new Error ("getValuePropName: No value property
defined for " + this.classname + ".");
};
};
/**
* gets the setter function
*/
QxTarget.prototype.getSetter = function ()
{
var setter = "set" + this.getValuePropName();
return setter;
};
/**
* gets the getter function
*/
QxTarget.prototype.getGetter = function ()
{
var getter = "get" + this.getValuePropName();
return getter;
};
/**
* gets the url for sending or receiving
* dynamically modifies placeholders with data from other fields of the form
* %foo is replaced by the value of the data element named "foo". This way, you
* can populate form elements depending on the data of other form elements.
* @return string url
*/
QxTarget.prototype._prepareUrl = function( action )
{
var getter = 'get' + action.substr(0,1).toUpperCase() +
action.substr(1) + 'Url',
url = typeof this[getter] == "function" ? this[getter]():
null,
manager = this.getDataManager();
if ( url === null ){
this.error ("No getter " + getter );
}
if ( ! url || ! manager ) return false; // do not throw an error since
this could be intended
// dynamcially modify placeholders
url = url.replace(/%([a-zA-Z0-9_\-]+)/g,
function (str, p1) {
var data = manager.getFieldData(p1);
if ( typeof data == "undefined" )
{
return "*** " + p1 + " is not defined ***";
}
else
{
return data;
}
}
).replace(/%/g,'').replace(/\/\//g,"&");
return url;
}
//--------------------------------------------------------------------------
// Drag & Drop behaviour, optimized for QxList / QxListItems, override for
// other widgets if necessary
//--------------------------------------------------------------------------
/**
* dropDataType property modifier
* enable dragdrop for specific drop data types
* support for string values in dropDataTypes property, multiple values
* are separated by comma
*/
QxWidget.prototype._modifyDropDataTypes = function(propValue, propOldValue,
propData)
{
if ( propValue )
{
if ( typeof propValue == "string" )
{
this.setDropDataTypes( propValue.split(",") );
}
else
{
this.addEventListener("dragdrop", this.handleDragDrop,
this );
this.addEventListener("dragmove", this.handleDragMove,
this );
this.addEventListener("dragend", this.handleDragEnd,
this );
};
}
else if ( propOldValue )
{
this.removeEventListener ("dragdrop", this.handleDragDrop, this
);
this.removeEventListener("dragmove", this.handleDragMove, this
);
this.removeEventListener("dragend", this.handleDragEnd, this );
};
return true;
};
/**
* dragDataType property modifier:
* enable dragstart and provide data type
*/
QxWidget.prototype._modifyDragDataType = function(propValue, propOldValue,
propData)
{
if ( propValue )
{
this.addEventListener("dragstart", this.handleDragStart, this );
}
else if ( propOldValue )
{
this.removeEventListener ("dragstart", this.handleDragStart,
this );
};
return true;
};
/**
* childrenDragDataType property modifier:
* enable dragstart and provide data type for widget children
* needs to wait until item get rendered
*/
QxWidget.prototype._modifyChildrenDragDataType = function ( propValue,
propOldValue, propData )
{
var self = this;
window.setTimeout( function()
{
var i, children = self.getChildren();
for ( var i=0; i < children.length; i++ )
{
if ( propValue )
{
children[i].addEventListener("dragstart",
self.handleDragStart, children[i] );
}
else if ( propOldValue )
{
children[i].removeEventListener ("dragstart",
self.handleDragStart, children[i] );
};
};
}, 1000);
return true;
};
/**
* default drag start behaviour
*/
QxWidget.prototype.handleDragStart = function ( e )
{
var vTarget = e.getCurrentTarget(),
vType = vTarget.getDragDataType() ||
vTarget.getParent().getChildrenDragDataType(),
vAction = vTarget.getDragDataAction() ||
vTarget.getParent().getChildrenDragDataAction();
// pevent multiple drag starts
if ( vTarget._isBeingDragged ) return;
vTarget._isBeingDragged = true;
// prevent multiple selection during drag
if( vTarget.getParent().getManager ) {
vTarget.getParent().getManager().setDragSelection(false);
};
if ( vType ){
e.addData( vType, vTarget );
e.addAction( vAction || "move" );
e.startDrag();
};
};
// internal pointer to currently hovered node
QxWidget.prototype._insertBefore = null;
/**
* default drag drop behaviour: place item before targeted
* item if item allows it
*/
QxWidget.prototype.handleDragDrop = function (e)
{
e.stopPropagation();
var vTarget = e.getTarget(),
vType = e.getDropDataTypes()[0],
vSource = e.getData(vType),
vInsertBefore = vTarget._insertBefore
vMarker = vTarget.getDropMarker();
// remove marker line
if ( vMarker )
{
vMarker.setVisibility(false);
};
// move items
if ( vSource.getParent() && vSource.getParent().getManager )
{
// move multiple items
var vItems =
vSource.getParent().getManager().getSelectedItems();
vSource.getParent().getManager().deselectAll();
for ( var i=0; i <vItems.length; i++ )
{
if ( vInsertBefore &&
vTarget.getParent().contains(vInsertBefore) )
{
vTarget.addBefore ( vItems[i], vInsertBefore );
}
else
{
vTarget.add ( vItems[i] );
};
};
}
else
{
// move sigle item
if ( vInsertBefore &&
vTarget.getParent().contains(vInsertBefore) )
{
vTarget.addBefore ( vSource, vInsertBefore );
}
else
{
vTarget.add ( vSource );
};
};
// deselect
if ( vTarget.getManager )
{
vTarget.getManager().deselectAll();
};
// sort
if ( vTarget.getSortAfterDrop() && vTarget.sortItemsByString )
{
vTarget.sortItemsByString();
};
// update remote
if ( vTarget.getUpdateRemoteOnChange() )
{
vTarget.updateRemote();
};
// if source is a child widget, get parent
if ( vSource.getChildren().length == 0 )
{
vSource = vSource.getParent();
};
if ( vSource !== vTarget )
{
// sort
if ( vSource.getSortAfterDrop() && vSource.sortItemsByString )
{
vSource.sortItemsByString();
};
// update remote
if ( vSource.getUpdateRemoteOnChange() )
{
vSource.updateRemote();
};
};
QxWidget.flushGlobalQueues();
};
/**
* default drag move behaviour: display a marker line on top of the
* element hovered over. for more sophisticated behaviour, override
* this method
* todo: the marker should adapt itself to the orientation of the
* layout
*/
QxWidget.prototype.handleDragMove = function (e)
{
var vTarget = e.getCurrentTarget(),
vChildren = vTarget.getChildren(),
vEventTop = e.getPageY()
vInsertBefore = vTarget._insertBefore,
vMarker = vTarget.getDropMarker();
// no marker if target gets sorted after the drop
if ( vTarget.getSortAfterDrop() ) return;
for ( var i=0; i < vChildren.length; i++ )
{
var vChild = vChildren[i],
vElem = vChild.getElement(),
vTop = QxDom.getComputedPageBoxTop( vElem ),
vLeft = QxDom.getComputedPageBoxLeft( vElem ),
vWidth = QxDom.getComputedBoxWidth( vElem );
// is dragged object between two elements
if ( ( vTop - vEventTop ).betweenRange(-4, 4) )
{
// save next element
vTarget._insertBefore = vChild;
// show marker
if ( vMarker )
{
vMarker.set({visibility:true, zIndex:
vChild.getZIndex() +1,
top: vTop, left:
vLeft, width: vWidth } );
QxWidget.flushGlobalQueues();
};
};
};
};
/**
* handles the end of the drag event
*/
QxWidget.prototype.handleDragEnd = function (e)
{
var vTarget = e.getTarget(),
vMarker = vTarget.getDropMarker();
if ( vMarker )
{
vMarker.setVisibility(false);
};
vTarget._insertBefore = null;
vTarget._isBeingDragged = false;
};
//--------------------------------------------------------------------------
// Public methods
//--------------------------------------------------------------------------
/**
* update local data from server and dispatch event if set
**/
QxTarget.prototype.updateLocal = function ( callbackFunc )
{
var self = this,
name = this.getName(),
manager = this.getDataManager();
manager.transportReceive(
{
url: self._prepareUrl("receive"),
params: null,
handler: function ( data )
{
manager._preventRemoteUpdate = true;
// update properties
var property, setter;
for ( property in data)
{
setter = 'set' +
property.substr(0,1).toUpperCase() + property.substr(1);
if ( setter=="setOptions" || setter=="setData"
) continue;
if ( typeof self[setter]=="function" ){
self[setter]( data[property] );
};
};
// update options
if ( typeof data.options != "undefined" && typeof
self.setOptions == "function" )
{
self.setOptions ( data.options );
};
// update data
if ( typeof data.data != "undefined" )
{
self.setLocalData ( data.data );
};
manager._preventRemoteUpdate = false;
QxWidget.flushGlobalQueues();
if ( self.getDispatchOnDataReceived() )
{
manager.dispatch (
self.getDispatchOnDataReceived(), data );
};
// callback
if ( typeof callbackFunc == "function" )
{
callbackFunc( data );
};
}
});
};
/**
* update options only
*/
QxTarget.prototype.updateOptions = function ( callbackFunc )
{
var self = this,
name = this.getName(),
manager = this.getDataManager() || new QxDataManager;
manager.transportReceive(
{
url: self._prepareUrl("receive"),
params: null,
handler: function ( data )
{
manager._preventRemoteUpdate = true;
// update options
if ( typeof data.options != "undefined" && typeof
self.setOptions == "function" )
{
self.setOptions ( data.options );
};
manager._preventRemoteUpdate = false;
QxWidget.flushGlobalQueues();
// callback
if ( typeof callbackFunc == "function" )
{
callbackFunc( data );
};
}
});
};
/**
* update server with local data and dispatch event if set
**/
QxTarget.prototype.updateRemote = function ( callbackFunc )
{
var self = this,
name = this.getName(),
manager = this.getDataManager() || new QxDataManager,
data = {};
if ( manager._preventRemoteUpdate ) return;
data.data = this.getLocalData();
if ( typeof this.getOptions == "function" )
{
data.options = this.getOptions();
};
manager.transportSend (
{
url: self._prepareUrl("send"),
params: data,
handler: function ( data )
{
// event dispatcher
if ( self.getDispatchOnDataSent() )
{
manager.dispatch (
self.getDispatchOnDataSent(), data );
}
// callback
if ( typeof callbackFunc == "function" )
{
callbackFunc( data );
};
}
});
};
/**
* gets element data
**/
QxTarget.prototype.getLocalData = function() {
return this[this.getGetter()]();
};
/**
* sets element data
**/
QxTarget.prototype.setLocalData = function( value ) {
this._previousData = this.getLocalData();
this[this.getSetter()]( value || '' );
};
/**
* resets to previous data
*/
QxTarget.prototype.reset = function ()
{
this.setLocalData( this._previousData );
}
/**
* empties data
*/
QxTarget.prototype.empty = function ()
{
this.setLocalData( '' );
};
//--------------------------------------------------------------------------
// QxList
//--------------------------------------------------------------------------
/**
* QxList-specific interface for getting options
* @return array of all list item values
**/
QxList.prototype.getOptions = function ()
{
var options=[], i,
vItems=this.getChildren(),
vLength=vItems.length;
for ( i=0; i<vLength; i++) {
var value = vItems[i].getValue();
if ( value !== false && value !== null ) {
options.push(value);
}
};
return options;
};
/**
* QxList-specific interface for setting options
* @param array data array of list items
**/
QxList.prototype.setOptions = function ( options )
{
if ( typeof options == "string ")
{
options = options.split(",");
}
this.removeAll();
for ( var i=0; i < options.length; i++ )
{
if ( typeof options[i] == "object" ) {
this.add ( new QxListItem ( options[i].label, null,
options[i].value ) );
} else {
this.add ( new QxListItem ( options[i], null,
options[i] ) );
};
};
};
/**
* QxList-specific interface for getting data, i.e. selection
* @return array of selected values
**/
QxList.prototype.getLocalData = function ()
{
var i, values=[],
vItems=this.getSelectedItems(),
vLength=vItems.length;
for ( i=0; i<vLength; i++) {
values.push( vItems[i].getValue() );
};
return values;
};
/**
* set list data, i.e. selecte list items which have
* values present in the data
**/
QxList.prototype.setLocalData = function ( value )
{
var i, items=[],
data =( typeof value == "string" ) ? value.split(",") : value;
if ( data.length == 1 )
{
this.getManager().setSelectedItem ( this.findValueExact ( value
) );
}
else
{
for ( i=0; i<data.length; i++)
{
items.push ( this.findValueExact( data[i] ) );
};
this.getManager().setSelectedItems ( items );
};
QxWidget.flushGlobalQueues();
};
//--------------------------------------------------------------------------
// QxComboBox
//
// Note that this extension changes the behaviour of setValue() and getValue()
// Use "element.getLocalData()" to retrieve the real content of the combobox
//--------------------------------------------------------------------------
// autocomplete behaviour property
QxComboBox.addProperty({ name : "autocomplete", type : QxConst.TYPEOF_BOOLEAN,
defaultValue : false });
// where to get autocomplete data from
QxComboBox.addProperty({ name : "autocompleteUrl", type :
QxConst.TYPEOF_STRING, defaultValue : '' });
// a separator marks the beginning of a new autocomplete section
QxComboBox.addProperty({ name : "separator", type : QxConst.TYPEOF_STRING,
defaultValue : '' });
/**
* whether to send data when local data changes
*/
QxComboBox.prototype._modifyUpdateRemoteOnChange = function(propValue,
propOldValue, propData)
{
if ( propValue )
{
this.getField().addEventListener ( "changeValue",
this.updateRemote ,this );
}
else if ( propOldValue )
{
this.getField().removeEventListener ( "changeValue",
this.updateRemote ,this );
};
return true;
};
/**
* turns autocomplet behaviour on and off
*/
QxComboBox.prototype._modifyAutocomplete = function(propValue, propOldValue,
propData)
{
if ( propValue )
{
this.setEditable(true);
}
else if ( propOldValue )
{
//
};
return true;
};
/**
* overrides default _oninput event handler
* handles an textfield input by repopulating the combobox list and proposing
* a match
* receiveUrl: /keywords/autocomplete with get/post param data={word to be
matched}
* response: { options: (array of suggestions), data:"(text to be
autocompleted)" }
*/
QxComboBox.prototype._oninput = function ( e )
{
// flag to prevent matching
if ( this._preventMatch ){
this._preventMatch = false;
return true;
};
var self = this,
vUrl = this._prepareUrl("autocomplete"),
vManager = this.getDataManager(),
vValue = e.getData(),
vSeparator = this.getSeparator(),
vStartMatchAt = vSeparator ? ( vValue.lastIndexOf (
vSeparator ) + 1 ) : 0,
vBeforeMatch = vStartMatchAt ? vValue.substr ( 0,
vStartMatchAt ) : "",
vMatch = vValue.substr( vStartMatchAt );
this._fromInput = true;
this.setValue( vValue );
this._fromInput = false;
if ( ! dojo.string.trim( vMatch ).length ) {
this._popup.hide();
this._list.removeAll();
return true;
};
// execute with a timeout to prevent requests during rapid typing
window.setTimeout ( function()
{
if ( vValue != self._field.getComputedValue() ) {
// user has typed ahead in the meantime, abort
return;
}
vManager.transportReceive({
url: vUrl,
params: { data: vMatch },
handler: function ( data ){
if ( vValue != self._field.getComputedValue() )
{
// user has typed ahead in the
meantime, abort
return;
};
// update options
if ( typeof data.options != "undefined" &&
typeof self.setOptions == "function" )
{
self.setOptions ( data.options );
self._openPopup();
};
// update data
if ( typeof data.data != "undefined" )
{
self.setValue ( data.data );
};
QxWidget.flushGlobalQueues();
}
});
},200 );
};
/**
* overrides QxCombobox selected modifier
* necessary because default behaviour does not support partial replacement of
input
* @author Sebastian Werner
* @author Andreas Ecker
* modfied by CB;
* - popup doesn't open on "enter", but on typing and the arrow keys
*/
QxComboBox.prototype._modifySelected = function(propValue, propOldValue,
propData)
{
this._fromSelected = true;
// only do this if we called setSelected seperatly
// and not from the property "value".
if ( ! this._fromValue ) {
this.setValue( propValue ? propValue.getLabel() : QxConst.CORE_EMPTY);
};
// be sure that the item is in view
if ( this.getPopup().isSeeable() && propValue ) {
propValue.scrollIntoView();
};
// reset manager cache
this._manager.setLeadItem(propValue);
this._manager.setAnchorItem(propValue);
// sync to manager
this._manager.setSelectedItem(propValue);
// return focus to textfield
this._field.setFocused ( true );
// prevent matching, since this is a value from the popup list
this._preventMatch = true;
// reset hint
delete this._fromSelected;
return true;
};
/**
* overrides QxCombobox value modifier
* @author Sebastian Werner
* @author Andreas Ecker
* modfied by CB;
*/
QxComboBox.prototype._modifyValue = function(propValue, propOldValue, propData)
{
// no further action if this is called from an input event
if ( this._fromInput ){
this._originalValue = propValue;
return true;
};
this._fromValue = true;
// only do this if we called setValue seperatly
// and not from the property "selected".
if ( ! this._fromSelected )
{
// inform selected property
var vSelItem = this._list.findStringExact(propValue);
// ignore disabled items
if (vSelItem != null && !vSelItem.getEnabled()) {
vSelItem = null;
};
this.setSelected(vSelItem);
// be sure that the manager get informed
// if 'selected' was already 'null'
if (vSelItem == null) {
this._manager.deselectAll();
};
}
else
{
this._field.setValue ( propValue );
}
// CB: added autocomplete behaviour
if ( this.getAutocomplete() )
{
var vSeparator = this.getSeparator(),
vOldValue = this._originalValue ||
propOldValue || "",
vStartMatchAt = vSeparator ? ( vOldValue.lastIndexOf
( vSeparator ) + 1 ) : 0,
vBeforeMatch = vStartMatchAt ? vOldValue.substr ( 0,
vStartMatchAt ) : "",
vMatch = vOldValue.substr(
vStartMatchAt ),
vNewValue = vBeforeMatch + propValue,
vOldLength = vOldValue.length,
vNewLength = vNewValue.length;
// set value without updating the server
this._preventRemoteUpdate = true;
this._field.getElement().value = vNewValue;
this._preventRemoteUpdate = false;
// set textfield selection
this._field.setFocused ( true );
this._field.setSelectionStart ( vOldLength );
this._field.setSelectionLength ( vNewLength - vOldLength );
this._preventMatch = true;
};
// reset hint
delete this._fromValue;
return true;
};
/**
* overrides onkeydown event handler
* @author Sebastian Werner
* @author Andreas Ecker
* modified by CB:
* - disabled QxList key events so that "home" and "line end" keys do not
disturb input
*/
QxComboBox.prototype._onkeydown = function(e)
{
var vManager = this._manager;
var vKeyCode = e.getKeyCode();
var vKeys = QxKeyEvent.keys;
var vVisible = this._popup.isSeeable();
switch(vKeyCode)
{
// Handle <ENTER>
case vKeys.enter:
if (vVisible)
{
this._closePopup();
this.setSelected(this._manager.getSelectedItem());
};
return;
// Handle <ESC>
case vKeys.esc:
if (vVisible)
{
vManager.setLeadItem(this._oldSelected);
vManager.setAnchorItem(this._oldSelected);
vManager.setSelectedItem(this._oldSelected);
this.setValue(this._oldSelected ? this._oldSelected.getLabel() :
QxConst.CORE_EMPTY);
this._closePopup();
};
return;
// Handle <PAGEUP>
case vKeys.pageup:
if (!vVisible)
{
var vPrevious;
var vTemp = this.getSelected();
if (vTemp)
{
var vInterval = this.getPagingInterval();
do {
vPrevious = vTemp;
} while(--vInterval && (vTemp = vManager.getPrevious(vPrevious)));
}
else
{
vPrevious = vManager.getLast();
};
this.setSelected(vPrevious);
return;
};
break;
// Handle <PAGEDOWN>
case vKeys.pagedown:
if (!vVisible)
{
var vNext;
var vTemp = this.getSelected();
if (vTemp)
{
var vInterval = this.getPagingInterval();
do {
vNext = vTemp;
} while(--vInterval && (vTemp = vManager.getNext(vNext)));
}
else
{
vNext = vManager.getFirst();
};
this.setSelected(vNext);
return;
};
break;
};
// key handling
switch(vKeyCode)
{
case vKeys.pageup:
case vKeys.pagedown:
if (!this._popup.isCreated()) {
return;
};
// no break here
case vKeys.up:
case vKeys.down:
this._list._onkeydown(e);
// no break here
default:
this.setSelected(this._manager.getSelectedItem());
break;
};
};
/**
* get combobox options, disable so that options don't get sent back to the
server
* during update
**/
QxComboBox.prototype.getOptions = null;
/**
* set combobox options
**/
QxComboBox.prototype.setOptions = function ( options )
{
return this.getList().setOptions ( options );
};
/**
* get combobox data
* use this function instead of getValue()
**/
QxComboBox.prototype.getLocalData = function ()
{
if ( this.getEditable() )
{
return this._field.getElement().value;
}
else
{
var item = this.getList().findStringExact( this.getValue() );
return item ? item.getValue() : "";
};
};
/**
* set combobox data; needs to keep the "value" seperately from the "label"
displayed
**/
QxComboBox.prototype.setLocalData = function ( value )
{
if ( this.getEditable() )
{
this._field.getElement().value = ( value || "" );
}
else
{
var item = this.getList().findValueExact( value );
this.getList().getManager().setSelectedItem ( item );
this._field.getElement().value = ( item ? item.getLabel() :
value );
};
return true;
};
//--------------------------------------------------------------------------
// QxRadioManager
//--------------------------------------------------------------------------
/**
* get radio manager value
**/
QxRadioManager.prototype.getLocalData = function ()
{
return this.getSelected().getValue();
};
/**
* set radio manager value
**/
QxRadioManager.prototype.setLocalData = function ( value )
{
var i, vItems = this._items, vLength=vItems.length;
for ( i=0 ; i<vLength; i++) {
if ( vItems[i].getValue() == value ) {
this.setSelected( vItems[i] );
break;
};
};
};
//--------------------------------------------------------------------------
// QxCheckBox
//--------------------------------------------------------------------------
/**
* QxCheckBox-specific interface for getting content
**/
QxCheckBox.prototype.getLocalData = function ( value )
{
this.getChecked ();
};
/**
* QxCheckBox-specific interface for setting content
**/
QxCheckBox.prototype.setLocalData = function(value)
{
this.setChecked ( value );
};
//--------------------------------------------------------------------------
// QxLabel
//--------------------------------------------------------------------------
/**
* QxLabel-specific interface for setting content
**/
QxLabel.prototype.setLocalData = function( value )
{
this.setHtml ( "<span>" + (value||'') + "</span>" );
};
//--------------------------------------------------------------------------
// QxListView
//--------------------------------------------------------------------------
// multiselection
QxListView.addProperty({ name : "multiSelection", type :
QxConst.TYPEOF_BOOLEAN, defaultValue : false });
/**
* enable or disable multiselection
*/
QxListView.prototype._modifyMultiSelection = function(propValue, propOldValue,
propData)
{
this.getPane().getManager().setMultiSelection( propValue );
return true;
};
/**
* listen for change event event and dispatch passed event name
*/
QxListView.prototype._modifyDispatchOnChange = function(propValue,
propOldValue, propData)
{
if ( propValue )
{
this.getPane().getManager().addEventListener (
"changeSelection", function() {
this.getPane().getManager().setDragSelection(false);
this.getDataManager().dispatch ( propValue,
this.getLocalData() );
},this);
};
return true;
};
// row id lookup index
QxListView.prototype._rowById = [];
/**
* setting QxListView options, i.e, setting the rows
**/
QxListView.prototype.setOptions = function ( data )
{
// empty grid
this._rowById = [];
this._data.splice(0,this._data.length);
this.update();
// build new grid
if ( ! data.length ) return;
for ( var i=0; i < data.length; i++ ){
// set default row id if missing
if ( typeof data[i].id == "undefined" || typeof data[i].id.text ==
"undefined" ){
data[i].id = {text:i};
};
// set row data
this._data.push( data[i] );
this._rowById[ data[i].id.text ] = i;
};
this.updateSort();
this.update();
QxWidget.flushGlobalQueues();
};
// don't provide a getOptions methods, otherwise all listview rows
// will be sent back to the server
/**
* getting QxListView data, i.e. the selected row ids which
* need to be in the grid row data as { id: { text: ID }, ...}
* @return array
**/
QxListView.prototype.getLocalData = function ()
{
var i, vRows = [], vRowNo,
vItems = this.getPane().getSelectedItems();
for ( i=0; i < vItems.length; i++ ){
vRows.push ( vItems[i].id ? ( vItems[i].id.text ) : i );
};
return vRows;
};
/**
* setting QxListView data, i.e. the select the rows according to the
* array with ids list
* todo: there is a bug here! selection is not correct
* @return array
**/
QxListView.prototype.setLocalData = function ( ids )
{
var i, j, id, vFirst, rowNo,
vManager = this.getPane().getManager(),
vData = this.getData();
vManager.deselectAll();
for (j=0; j <ids.length; j++ ){
rowNo = this._rowById[ ids[j] ];
vManager.setItemSelected( vData[rowNo], true );
if ( ! vFirst )
{
vFirst = vData[rowNo];
};
};
if ( vFirst ) {
this.getPane().scrollItemIntoViewY ( vFirst, 0 );
};
};
//--------------------------------------------------------------------------
// QxDataManager
//--------------------------------------------------------------------------
/**
* QxDataManager
*
* provides a manager for enhanced form elements or other widgets
* which automagically get their data from the server and update the
* server when changed.
*
* A major conceptual difference to HTML forms is that the information
* about which data on the server is bound to the fields can, but does not
* need to be stored at the "form" level. You can also bind each element
* to a different datasource on the server. A "form" or dialogue can thus be
* populated from and update its value at many different sources.
* The data manager then serves as a reference point to connect the
* widgets which otherwise wouldn't know about each other.
*
* This concept also allows to integrate non-traditional, read-only form
* elements like listviews, trees, displayboxes, etc.
*
* You can use whatever transport mechanism you like by overriding
* the tranportSend and tranportReceive methods of this object. The default
* method relies on a homegrown transport mechanism based on dojo.
*
* Data binding
* ------------
* method determines whether the
data should be sent received or sent
* as "get" or
"post" (default). string.
* sendUrl url where to fetch the
data remotely. %foo is replaced by the
* value of
element with name foo. string.
* receiveUrl url with which to update data
remotely. %foo is replaced by the
* value of
element with name foo. string.
*
* Data events
* -----------
* dispatchOnDataSent event to dispatch when server has
acknowledged the receipt of
* the data sent.
string.
* dispatchOnDataReceived event to dispatch when server has sent
data. string.
* updateRemoteOnEvent event name which triggers remote
update. string.
* updateLocalOnEvent event name which triggers local update.
string.
*
*/
function QxDataManager ( )
{
if ( ! window.zophe || ! window.dojo )
{
throw new Error ("QxDataManager: zophe/dojo is not present. " +
"You need to override the
transportSend, transportReceive " +
"and dispach methods with your
own implementation.");
}
QxTarget.call(this);
// internal data
this._dataWidgets = [];
this._hiddenFields = {};
};
QxDataManager.extend(QxTarget, "QxDataManager");
//--------------------------------------------------------------------------
// Remove QxTarget Properties
//--------------------------------------------------------------------------
QxDataManager.removeProperty({ name : "dataManager"});
QxDataManager.removeProperty({ name : "updateRemoteOnChange" });
QxDataManager.removeProperty({ name : "dispatchOnChange" });
//--------------------------------------------------------------------------
// Methods that need to be adapted to environment
//--------------------------------------------------------------------------
/**
* hooks to insert your favorite transport mechanism. the kwArgs
* parameter should have the following keys defined:
* - method
* - url
* - params hash of key-value pairs to be sent
* - handler function which is called after request has been completed
* in tranportReceive the data received is passed to the handler
*/
proto.transportSend = function ( kwArgs )
{
// we don't need kwArgs.method since everything is sent via post
zophe.io.call ( kwArgs.url, kwArgs.params, kwArgs.handler );
};
proto.transportReceive = function ( kwArgs )
{
// we don't need kwArgs.method since everything is sent via post
zophe.io.request ( kwArgs.url, kwArgs.params, kwArgs.handler );
};
/**
* dispatch event method, override with your own solution
* I want to dispatch events globally, but you might want to
* restrict the scope of the event to the data manager.
*/
proto.dispatch = function (name, value)
{
var event = {}; event[name] = value; zophe.event.dispatch ( event );
//window.application.createDispatchDataEvent ( name, value );
//this.createDispatchDataEvent ( name, value );
};
//--------------------------------------------------------------------------
// Public methods
//--------------------------------------------------------------------------
/**
* overrides QxTarget function
*
*/
proto.getDataManager = function()
{
return this;
};
/**
* adds a form widget to the form
* @param object widget
**/
proto.add = function( vWidget )
{
var vName = vWidget.getName();
this._dataWidgets.push (vWidget);
vWidget.setDataManager( this );
};
/**
* removes a form widget from the form
* @param string vWdiget
**/
proto.remove = function( vWidget )
{
var vName = vWidget.getName();
vWidget.setDataManager( null );
for (var i=0; i<this._dataWidgets.length; i++)
{
if ( this._dataWidgets[i].getName()== vName )
{
delete this._dataWidgets[ i ];
};
};
};
/**
* gets all values from the bound field widgets
* and the hidden fields
*/
proto.getLocalData = function()
{
var i, vName, data = {};
// data widgets
for ( i=0; i<this._dataWidgets.length; i++ )
{
vName = this._dataWidgets[i].getName();
// only take first widget data if there are several with the
same name
if ( typeof data[vName] == "undefined" )
{
data[vName] = this._dataWidgets[i].getLocalData();
};
};
// hidden fields
for ( vName in this._hiddenFields )
{
data[vName] = this._hiddenFields[vName];
};
return data;
};
/**
* populates all associated elements with data
* overrides QxTarget.setLocalData
* @param object hash of key-value pairs, keys must correspond to the form
element names
*/
proto.setLocalData = function ( data )
{
var i, vName, vFields, vProp, vSetter;
for ( var vName in data )
{
vFields = this.getChildElements( vName );
vData = data[vName];
if ( vFields.length > 0 )
{
// populate all fields with the same name
// and set their properties
for ( i=0; i<vFields.length; i++)
{
for ( vProp in vData)
{
vSetter = 'set' +
vProp.substr(0,1).toUpperCase() + vProp.substr(1);
if ( vSetter=="setOptions" ||
vSetter=="setData" ) continue;
if ( typeof
vFields[i][vSetter]=="function" ){
vFields[i][vSetter](
vData[vProp] );
};
};
// update options
if ( typeof vData.options != "undefined" &&
typeof vFields[i].setOptions == "function" )
{
vFields[i].setOptions ( vData.options );
};
// update data
if ( typeof vData.data != "undefined" )
{
vFields[i].setLocalData ( vData.data );
};
};
}
else
{
// save data in hidden field
this.setHiddenField (vName, vData );
};
};
return true;
};
/**
* get the first form element with th given name
*/
proto.getChildElement = function ( vName )
{
for ( var i=0; i<this._dataWidgets.length; i++ )
{
if ( vName == this._dataWidgets[i].getName() )
{
return this._dataWidgets[i];
};
};
return false;
};
/**
* get all elements with a given name
*/
proto.getChildElements = function ( vName )
{
var vList = [];
for ( var i=0; i<this._dataWidgets.length; i++ )
{
if ( vName == this._dataWidgets[i].getName() )
{
vList.push (this._dataWidgets[i]);
};
};
return vList;
};
/**
* get all child elements
*/
proto.getChildren = function()
{
return this._dataWidgets;
}
/**
* gets the data from a form element or hidden field
*/
proto.getFieldData = function ( vName )
{
var child = this.getChildElement( vName );
if ( typeof child == "object" )
{
return child.getLocalData();
}
else
{
return this.getHiddenField ( vName );
};
};
/**
* sets the data from a form element or hidden field
*/
proto.setFieldData = function ( vName, vData )
{
var child = this.getChildElement( vName );
if ( typeof child == "object" )
{
child.setLocalData ( vData );
}
else
{
this.setHiddenField ( vName, vData );
};
return true;
};
/**
* clears a form
*/
proto.empty = function( )
{
for ( var i=0; i< this._dataWidgets.length; i++ ) {
this._dataWidgets[i].empty();
};
};
/**
* clears a form
*/
proto.reset = function( )
{
for ( var i=0; i< this._dataWidgets.length; i++ ) {
this._dataWidgets[i].reset();
};
};
/**
* set hidden field
* @param string field name
* @param mixed value
*/
proto.setHiddenField = function ( vName, vValue )
{
this._hiddenFields[vName] = vValue;
return true;
}
/**
* get hidden field
* @param string field name
*/
proto.getHiddenField = function ( vName )
{
return this._hiddenFields[vName];
}
/**
* flag to prevent remote update when updating locally
*/
proto._preventRemoteUpdate = false;
/**
* sets prevent-remote-update flag
*/
proto.setPreventRemoteUpdate = function ( vValue )
{
this._preventRemoteUpdate = vValue;
};
/**
* dispose form
*/
proto.dispose = function()
{
if (this.getDisposed()) {
return true;
};
this._dataWidgets = null;
return QxTarget.prototype.dispose.call(this);
};
-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys -- and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
qooxdoo-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/qooxdoo-devel