* <script type="text/javascript">
*/
-/**
- * @class Roo.View
- * @extends Roo.util.Observable
- * Create a "View" for an element based on a data model or UpdateManager and the supplied DomHelper template.
- * This class also supports single and multi selection modes. <br>
- * Create a data model bound view:
- <pre><code>
- var store = new Roo.data.Store(...);
-
- var view = new Roo.View({
- el : "my-element",
- tpl : '<div id="{0}">{2} - {1}</div>', // auto create template
-
- singleSelect: true,
- selectedClass: "ydataview-selected",
- store: store
- });
-
- // listen for node click?
- view.on("click", function(vw, index, node, e){
- alert('Node "' + node.id + '" at index: ' + index + " was clicked.");
- });
- // load XML data
- dataModel.load("foobar.xml");
- </code></pre>
- For an example of creating a JSON/UpdateManager view, see {@link Roo.JsonView}.
- * <br><br>
- * <b>Note: The root of your template must be a single node. Table/row implementations may work but are not supported due to
- * IE"s limited insertion support with tables and Opera"s faulty event bubbling.</b>
- *
- * Note: old style constructor is still suported (container, template, config)
- *
- * @constructor
- * Create a new View
- * @param {Object} config The config object
- *
+/**
+ * @class Roo.data.SortTypes
+ * @singleton
+ * Defines the default sorting (casting?) comparison functions used when sorting data.
*/
-Roo.View = function(config, depreciated_tpl, depreciated_config){
-
- if (typeof(depreciated_tpl) == 'undefined') {
- // new way.. - universal constructor.
- Roo.apply(this, config);
- this.el = Roo.get(this.el);
- } else {
- // old format..
- this.el = Roo.get(config);
- this.tpl = depreciated_tpl;
- Roo.apply(this, depreciated_config);
- }
- this.wrapEl = this.el.wrap().wrap();
- ///this.el = this.wrapEla.appendChild(document.createElement("div"));
-
-
- if(typeof(this.tpl) == "string"){
- this.tpl = new Roo.Template(this.tpl);
- } else {
- // support xtype ctors..
- this.tpl = new Roo.factory(this.tpl, Roo);
- }
-
-
- this.tpl.compile();
-
-
-
-
- /** @private */
- this.addEvents({
- /**
- * @event beforeclick
- * Fires before a click is processed. Returns false to cancel the default action.
- * @param {Roo.View} this
- * @param {Number} index The index of the target node
- * @param {HTMLElement} node The target node
- * @param {Roo.EventObject} e The raw event object
- */
- "beforeclick" : true,
- /**
- * @event click
- * Fires when a template node is clicked.
- * @param {Roo.View} this
- * @param {Number} index The index of the target node
- * @param {HTMLElement} node The target node
- * @param {Roo.EventObject} e The raw event object
- */
- "click" : true,
- /**
- * @event dblclick
- * Fires when a template node is double clicked.
- * @param {Roo.View} this
- * @param {Number} index The index of the target node
- * @param {HTMLElement} node The target node
- * @param {Roo.EventObject} e The raw event object
- */
- "dblclick" : true,
- /**
- * @event contextmenu
- * Fires when a template node is right clicked.
- * @param {Roo.View} this
- * @param {Number} index The index of the target node
- * @param {HTMLElement} node The target node
- * @param {Roo.EventObject} e The raw event object
- */
- "contextmenu" : true,
- /**
- * @event selectionchange
- * Fires when the selected nodes change.
- * @param {Roo.View} this
- * @param {Array} selections Array of the selected nodes
- */
- "selectionchange" : true,
-
- /**
- * @event beforeselect
- * Fires before a selection is made. If any handlers return false, the selection is cancelled.
- * @param {Roo.View} this
- * @param {HTMLElement} node The node to be selected
- * @param {Array} selections Array of currently selected nodes
- */
- "beforeselect" : true,
- /**
- * @event preparedata
- * Fires on every row to render, to allow you to change the data.
- * @param {Roo.View} this
- * @param {Object} data to be rendered (change this)
- */
- "preparedata" : true
-
-
- });
-
-
-
- this.el.on({
- "click": this.onClick,
- "dblclick": this.onDblClick,
- "contextmenu": this.onContextMenu,
- scope:this
- });
-
- this.selections = [];
- this.nodes = [];
- this.cmp = new Roo.CompositeElementLite([]);
- if(this.store){
- this.store = Roo.factory(this.store, Roo.data);
- this.setStore(this.store, true);
- }
-
- if ( this.footer && this.footer.xtype) {
-
- var fctr = this.wrapEl.appendChild(document.createElement("div"));
-
- this.footer.dataSource = this.store
- this.footer.container = fctr;
- this.footer = Roo.factory(this.footer, Roo);
- fctr.insertFirst(this.el);
-
- // this is a bit insane - as the paging toolbar seems to detach the el..
-// dom.parentNode.parentNode.parentNode
- // they get detached?
- }
-
-
- Roo.View.superclass.constructor.call(this);
-
-
-};
-
-Roo.extend(Roo.View, Roo.util.Observable, {
-
- /**
- * @cfg {Roo.data.Store} store Data store to load data from.
+Roo.data.SortTypes = {
+ /**
+ * Default sort that does nothing
+ * @param {Mixed} s The value being converted
+ * @return {Mixed} The comparison value
*/
- store : false,
+ none : function(s){
+ return s;
+ },
/**
- * @cfg {String|Roo.Element} el The container element.
+ * The regular expression used to strip tags
+ * @type {RegExp}
+ * @property
*/
- el : '',
+ stripTagsRE : /<\/?[^>]+>/gi,
/**
- * @cfg {String|Roo.Template} tpl The template used by this View
+ * Strips all HTML tags to sort on text only
+ * @param {Mixed} s The value being converted
+ * @return {String} The comparison value
*/
- tpl : false,
+ asText : function(s){
+ return String(s).replace(this.stripTagsRE, "");
+ },
+
/**
- * @cfg {String} dataName the named area of the template to use as the data area
- * Works with domtemplates roo-name="name"
+ * Strips all HTML tags to sort on text only - Case insensitive
+ * @param {Mixed} s The value being converted
+ * @return {String} The comparison value
*/
- dataName: false,
+ asUCText : function(s){
+ return String(s).toUpperCase().replace(this.stripTagsRE, "");
+ },
+
/**
- * @cfg {String} selectedClass The css class to add to selected nodes
- */
- selectedClass : "x-view-selected",
- /**
- * @cfg {String} emptyText The empty text to show when nothing is loaded.
+ * Case insensitive string
+ * @param {Mixed} s The value being converted
+ * @return {String} The comparison value
*/
- emptyText : "",
+ asUCString : function(s) {
+ return String(s).toUpperCase();
+ },
/**
- * @cfg {String} text to display on mask (default Loading)
+ * Date sorting
+ * @param {Mixed} s The value being converted
+ * @return {Number} The comparison value
*/
- mask : false,
+ asDate : function(s) {
+ if(!s){
+ return 0;
+ }
+ if(s instanceof Date){
+ return s.getTime();
+ }
+ return Date.parse(String(s));
+ },
+
/**
- * @cfg {Boolean} multiSelect Allow multiple selection
+ * Float sorting
+ * @param {Mixed} s The value being converted
+ * @return {Float} The comparison value
*/
- multiSelect : false,
+ asFloat : function(s) {
+ var val = parseFloat(String(s).replace(/,/g, ""));
+ if(isNaN(val)) val = 0;
+ return val;
+ },
+
/**
- * @cfg {Boolean} singleSelect Allow single selection
+ * Integer sorting
+ * @param {Mixed} s The value being converted
+ * @return {Number} The comparison value
*/
- singleSelect: false,
-
+ asInt : function(s) {
+ var val = parseInt(String(s).replace(/,/g, ""));
+ if(isNaN(val)) val = 0;
+ return val;
+ }
+};/*
+ * Based on:
+ * Ext JS Library 1.1.1
+ * Copyright(c) 2006-2007, Ext JS, LLC.
+ *
+ * Originally Released Under LGPL - original licence link has changed is not relivant.
+ *
+ * Fork - LGPL
+ * <script type="text/javascript">
+ */
+
+/**
+* @class Roo.data.Record
+ * Instances of this class encapsulate both record <em>definition</em> information, and record
+ * <em>value</em> information for use in {@link Roo.data.Store} objects, or any code which needs
+ * to access Records cached in an {@link Roo.data.Store} object.<br>
+ * <p>
+ * Constructors for this class are generated by passing an Array of field definition objects to {@link #create}.
+ * Instances are usually only created by {@link Roo.data.Reader} implementations when processing unformatted data
+ * objects.<br>
+ * <p>
+ * Record objects generated by this constructor inherit all the methods of Roo.data.Record listed below.
+ * @constructor
+ * This constructor should not be used to create Record objects. Instead, use the constructor generated by
+ * {@link #create}. The parameters are the same.
+ * @param {Array} data An associative Array of data values keyed by the field name.
+ * @param {Object} id (Optional) The id of the record. This id should be unique, and is used by the
+ * {@link Roo.data.Store} object which owns the Record to index its collection of Records. If
+ * not specified an integer id is generated.
+ */
+Roo.data.Record = function(data, id){
+ this.id = (id || id === 0) ? id : ++Roo.data.Record.AUTO_ID;
+ this.data = data;
+};
+
+/**
+ * Generate a constructor for a specific record layout.
+ * @param {Array} o An Array of field definition objects which specify field names, and optionally,
+ * data types, and a mapping for an {@link Roo.data.Reader} to extract the field's value from a data object.
+ * Each field definition object may contain the following properties: <ul>
+ * <li><b>name</b> : String<p style="margin-left:1em">The name by which the field is referenced within the Record. This is referenced by,
+ * for example the <em>dataIndex</em> property in column definition objects passed to {@link Roo.grid.ColumnModel}</p></li>
+ * <li><b>mapping</b> : String<p style="margin-left:1em">(Optional) A path specification for use by the {@link Roo.data.Reader} implementation
+ * that is creating the Record to access the data value from the data object. If an {@link Roo.data.JsonReader}
+ * is being used, then this is a string containing the javascript expression to reference the data relative to
+ * the record item's root. If an {@link Roo.data.XmlReader} is being used, this is an {@link Roo.DomQuery} path
+ * to the data item relative to the record element. If the mapping expression is the same as the field name,
+ * this may be omitted.</p></li>
+ * <li><b>type</b> : String<p style="margin-left:1em">(Optional) The data type for conversion to displayable value. Possible values are
+ * <ul><li>auto (Default, implies no conversion)</li>
+ * <li>string</li>
+ * <li>int</li>
+ * <li>float</li>
+ * <li>boolean</li>
+ * <li>date</li></ul></p></li>
+ * <li><b>sortType</b> : Mixed<p style="margin-left:1em">(Optional) A member of {@link Roo.data.SortTypes}.</p></li>
+ * <li><b>sortDir</b> : String<p style="margin-left:1em">(Optional) Initial direction to sort. "ASC" or "DESC"</p></li>
+ * <li><b>convert</b> : Function<p style="margin-left:1em">(Optional) A function which converts the value provided
+ * by the Reader into an object that will be stored in the Record. It is passed the
+ * following parameters:<ul>
+ * <li><b>v</b> : Mixed<p style="margin-left:1em">The data value as read by the Reader.</p></li>
+ * </ul></p></li>
+ * <li><b>dateFormat</b> : String<p style="margin-left:1em">(Optional) A format String for the Date.parseDate function.</p></li>
+ * </ul>
+ * <br>usage:<br><pre><code>
+var TopicRecord = Roo.data.Record.create(
+ {name: 'title', mapping: 'topic_title'},
+ {name: 'author', mapping: 'username'},
+ {name: 'totalPosts', mapping: 'topic_replies', type: 'int'},
+ {name: 'lastPost', mapping: 'post_time', type: 'date'},
+ {name: 'lastPoster', mapping: 'user2'},
+ {name: 'excerpt', mapping: 'post_text'}
+);
+
+var myNewRecord = new TopicRecord({
+ title: 'Do my job please',
+ author: 'noobie',
+ totalPosts: 1,
+ lastPost: new Date(),
+ lastPoster: 'Animal',
+ excerpt: 'No way dude!'
+});
+myStore.add(myNewRecord);
+</code></pre>
+ * @method create
+ * @static
+ */
+Roo.data.Record.create = function(o){
+ var f = function(){
+ f.superclass.constructor.apply(this, arguments);
+ };
+ Roo.extend(f, Roo.data.Record);
+ var p = f.prototype;
+ p.fields = new Roo.util.MixedCollection(false, function(field){
+ return field.name;
+ });
+ for(var i = 0, len = o.length; i < len; i++){
+ p.fields.add(new Roo.data.Field(o[i]));
+ }
+ f.getField = function(name){
+ return p.fields.get(name);
+ };
+ return f;
+};
+
+Roo.data.Record.AUTO_ID = 1000;
+Roo.data.Record.EDIT = 'edit';
+Roo.data.Record.REJECT = 'reject';
+Roo.data.Record.COMMIT = 'commit';
+
+Roo.data.Record.prototype = {
/**
- * @cfg {Boolean} toggleSelect - selecting
+ * Readonly flag - true if this record has been modified.
+ * @type Boolean
*/
- toggleSelect : false,
-
+ dirty : false,
+ editing : false,
+ error: null,
+ modified: null,
+
+ // private
+ join : function(store){
+ this.store = store;
+ },
+
/**
- * Returns the element this view is bound to.
- * @return {Roo.Element}
+ * Set the named field to the specified value.
+ * @param {String} name The name of the field to set.
+ * @param {Object} value The value to set the field to.
*/
- getEl : function(){
- return this.wrapEl;
+ set : function(name, value){
+ if(this.data[name] == value){
+ return;
+ }
+ this.dirty = true;
+ if(!this.modified){
+ this.modified = {};
+ }
+ if(typeof this.modified[name] == 'undefined'){
+ this.modified[name] = this.data[name];
+ }
+ this.data[name] = value;
+ if(!this.editing && this.store){
+ this.store.afterEdit(this);
+ }
},
-
-
/**
- * Refreshes the view. - called by datachanged on the store. - do not call directly.
+ * Get the value of the named field.
+ * @param {String} name The name of the field to get the value of.
+ * @return {Object} The value of the field.
*/
- refresh : function(){
- var t = this.tpl;
-
- // if we are using something like 'domtemplate', then
- // the what gets used is:
- // t.applySubtemplate(NAME, data, wrapping data..)
- // the outer template then get' applied with
- // the store 'extra data'
- // and the body get's added to the
- // roo-name="data" node?
- // <span class='roo-tpl-{name}'></span> ?????
-
-
-
- this.clearSelections();
- this.el.update("");
- var html = [];
- var records = this.store.getRange();
- if(records.length < 1) {
-
- // is this valid?? = should it render a template??
-
- this.el.update(this.emptyText);
- return;
+ get : function(name){
+ return this.data[name];
+ },
+
+ // private
+ beginEdit : function(){
+ this.editing = true;
+ this.modified = {};
+ },
+
+ // private
+ cancelEdit : function(){
+ this.editing = false;
+ delete this.modified;
+ },
+
+ // private
+ endEdit : function(){
+ this.editing = false;
+ if(this.dirty && this.store){
+ this.store.afterEdit(this);
}
- var el = this.el;
- if (this.dataName) {
- this.el.update(t.apply(this.store.meta)); //????
- el = this.el.child('.roo-tpl-' + this.dataName);
+ },
+
+ /**
+ * Usually called by the {@link Roo.data.Store} which owns the Record.
+ * Rejects all changes made to the Record since either creation, or the last commit operation.
+ * Modified fields are reverted to their original values.
+ * <p>
+ * Developers should subscribe to the {@link Roo.data.Store#update} event to have their code notified
+ * of reject operations.
+ */
+ reject : function(){
+ var m = this.modified;
+ for(var n in m){
+ if(typeof m[n] != "function"){
+ this.data[n] = m[n];
+ }
}
-
- for(var i = 0, len = records.length; i < len; i++){
- var data = this.prepareData(records[i].data, i, records[i]);
- this.fireEvent("preparedata", this, data, i, records[i]);
- html[html.length] = Roo.util.Format.trim(
- this.dataName ?
- t.applySubtemplate(this.dataName, data, this.store.meta) :
- t.apply(data)
- );
+ this.dirty = false;
+ delete this.modified;
+ this.editing = false;
+ if(this.store){
+ this.store.afterReject(this);
}
-
-
-
- el.update(html.join(""));
- this.nodes = el.dom.childNodes;
- this.updateIndexes(0);
},
/**
- * Function to override to reformat the data that is sent to
- * the template for each node.
- * DEPRICATED - use the preparedata event handler.
- * @param {Array/Object} data The raw data (array of colData for a data model bound view or
- * a JSON object for an UpdateManager bound view).
+ * Usually called by the {@link Roo.data.Store} which owns the Record.
+ * Commits all changes made to the Record since either creation, or the last commit operation.
+ * <p>
+ * Developers should subscribe to the {@link Roo.data.Store#update} event to have their code notified
+ * of commit operations.
*/
- prepareData : function(data, index, record)
- {
- this.fireEvent("preparedata", this, data, index, record);
- return data;
+ commit : function(){
+ this.dirty = false;
+ delete this.modified;
+ this.editing = false;
+ if(this.store){
+ this.store.afterCommit(this);
+ }
},
- onUpdate : function(ds, record){
- this.clearSelections();
- var index = this.store.indexOf(record);
- var n = this.nodes[index];
- this.tpl.insertBefore(n, this.prepareData(record.data, index, record));
- n.parentNode.removeChild(n);
- this.updateIndexes(index, index);
+ // private
+ hasError : function(){
+ return this.error != null;
},
+ // private
+ clearError : function(){
+ this.error = null;
+ },
+
+ /**
+ * Creates a copy of this record.
+ * @param {String} id (optional) A new record id if you don't want to use this record's id
+ * @return {Record}
+ */
+ copy : function(newId) {
+ return new this.constructor(Roo.apply({}, this.data), newId || this.id);
+ }
+};/*
+ * Based on:
+ * Ext JS Library 1.1.1
+ * Copyright(c) 2006-2007, Ext JS, LLC.
+ *
+ * Originally Released Under LGPL - original licence link has changed is not relivant.
+ *
+ * Fork - LGPL
+ * <script type="text/javascript">
+ */
+
+
+
+/**
+ * @class Roo.data.Store
+ * @extends Roo.util.Observable
+ * The Store class encapsulates a client side cache of {@link Roo.data.Record} objects which provide input data
+ * for widgets such as the Roo.grid.Grid, or the Roo.form.ComboBox.<br>
+ * <p>
+ * A Store object uses an implementation of {@link Roo.data.DataProxy} to access a data object unless you call loadData() directly and pass in your data. The Store object
+ * has no knowledge of the format of the data returned by the Proxy.<br>
+ * <p>
+ * A Store object uses its configured implementation of {@link Roo.data.DataReader} to create {@link Roo.data.Record}
+ * instances from the data object. These records are cached and made available through accessor functions.
+ * @constructor
+ * Creates a new Store.
+ * @param {Object} config A config object containing the objects needed for the Store to access data,
+ * and read the data into Records.
+ */
+Roo.data.Store = function(config){
+ this.data = new Roo.util.MixedCollection(false);
+ this.data.getKey = function(o){
+ return o.id;
+ };
+ this.baseParams = {};
+ // private
+ this.paramNames = {
+ "start" : "start",
+ "limit" : "limit",
+ "sort" : "sort",
+ "dir" : "dir",
+ "multisort" : "_multisort"
+ };
+
+ if(config && config.data){
+ this.inlineData = config.data;
+ delete config.data;
+ }
+
+ Roo.apply(this, config);
-
-// --------- FIXME
- onAdd : function(ds, records, index)
- {
- this.clearSelections();
- if(this.nodes.length == 0){
- this.refresh();
- return;
+ if(this.reader){ // reader passed
+ this.reader = Roo.factory(this.reader, Roo.data);
+ this.reader.xmodule = this.xmodule || false;
+ if(!this.recordType){
+ this.recordType = this.reader.recordType;
}
- var n = this.nodes[index];
+ if(this.reader.onMetaChange){
+ this.reader.onMetaChange = this.onMetaChange.createDelegate(this);
+ }
+ }
+
+ if(this.recordType){
+ this.fields = this.recordType.prototype.fields;
+ }
+ this.modified = [];
+
+ this.addEvents({
+ /**
+ * @event datachanged
+ * Fires when the data cache has changed, and a widget which is using this Store
+ * as a Record cache should refresh its view.
+ * @param {Store} this
+ */
+ datachanged : true,
+ /**
+ * @event metachange
+ * Fires when this store's reader provides new metadata (fields). This is currently only support for JsonReaders.
+ * @param {Store} this
+ * @param {Object} meta The JSON metadata
+ */
+ metachange : true,
+ /**
+ * @event add
+ * Fires when Records have been added to the Store
+ * @param {Store} this
+ * @param {Roo.data.Record[]} records The array of Records added
+ * @param {Number} index The index at which the record(s) were added
+ */
+ add : true,
+ /**
+ * @event remove
+ * Fires when a Record has been removed from the Store
+ * @param {Store} this
+ * @param {Roo.data.Record} record The Record that was removed
+ * @param {Number} index The index at which the record was removed
+ */
+ remove : true,
+ /**
+ * @event update
+ * Fires when a Record has been updated
+ * @param {Store} this
+ * @param {Roo.data.Record} record The Record that was updated
+ * @param {String} operation The update operation being performed. Value may be one of:
+ * <pre><code>
+ Roo.data.Record.EDIT
+ Roo.data.Record.REJECT
+ Roo.data.Record.COMMIT
+ * </code></pre>
+ */
+ update : true,
+ /**
+ * @event clear
+ * Fires when the data cache has been cleared.
+ * @param {Store} this
+ */
+ clear : true,
+ /**
+ * @event beforeload
+ * Fires before a request is made for a new data object. If the beforeload handler returns false
+ * the load action will be canceled.
+ * @param {Store} this
+ * @param {Object} options The loading options that were specified (see {@link #load} for details)
+ */
+ beforeload : true,
+ /**
+ * @event beforeloadadd
+ * Fires after a new set of Records has been loaded.
+ * @param {Store} this
+ * @param {Roo.data.Record[]} records The Records that were loaded
+ * @param {Object} options The loading options that were specified (see {@link #load} for details)
+ */
+ beforeloadadd : true,
+ /**
+ * @event load
+ * Fires after a new set of Records has been loaded, before they are added to the store.
+ * @param {Store} this
+ * @param {Roo.data.Record[]} records The Records that were loaded
+ * @param {Object} options The loading options that were specified (see {@link #load} for details)
+ * @params {Object} return from reader
+ */
+ load : true,
+ /**
+ * @event loadexception
+ * Fires if an exception occurs in the Proxy during loading.
+ * Called with the signature of the Proxy's "loadexception" event.
+ * If you return Json { data: [] , success: false, .... } then this will be thrown with the following args
+ *
+ * @param {Proxy}
+ * @param {Object} return from JsonData.reader() - success, totalRecords, records
+ * @param {Object} load options
+ * @param {Object} jsonData from your request (normally this contains the Exception)
+ */
+ loadexception : true
+ });
+
+ if(this.proxy){
+ this.proxy = Roo.factory(this.proxy, Roo.data);
+ this.proxy.xmodule = this.xmodule || false;
+ this.relayEvents(this.proxy, ["loadexception"]);
+ }
+ this.sortToggle = {};
+ this.sortOrder = []; // array of order of sorting - updated by grid if multisort is enabled.
+
+ Roo.data.Store.superclass.constructor.call(this);
+
+ if(this.inlineData){
+ this.loadData(this.inlineData);
+ delete this.inlineData;
+ }
+};
+
+Roo.extend(Roo.data.Store, Roo.util.Observable, {
+ /**
+ * @cfg {boolean} isLocal flag if data is locally available (and can be always looked up
+ * without a remote query - used by combo/forms at present.
+ */
+
+ /**
+ * @cfg {Roo.data.DataProxy} proxy The Proxy object which provides access to a data object.
+ */
+ /**
+ * @cfg {Array} data Inline data to be loaded when the store is initialized.
+ */
+ /**
+ * @cfg {Roo.data.Reader} reader The Reader object which processes the data object and returns
+ * an Array of Roo.data.record objects which are cached keyed by their <em>id</em> property.
+ */
+ /**
+ * @cfg {Object} baseParams An object containing properties which are to be sent as parameters
+ * on any HTTP request
+ */
+ /**
+ * @cfg {Object} sortInfo A config object in the format: {field: "fieldName", direction: "ASC|DESC"}
+ */
+ /**
+ * @cfg {Boolean} multiSort enable multi column sorting (sort is based on the order of columns, remote only at present)
+ */
+ multiSort: false,
+ /**
+ * @cfg {boolean} remoteSort True if sorting is to be handled by requesting the Proxy to provide a refreshed
+ * version of the data object in sorted order, as opposed to sorting the Record cache in place (defaults to false).
+ */
+ remoteSort : false,
+
+ /**
+ * @cfg {boolean} pruneModifiedRecords True to clear all modified record information each time the store is
+ * loaded or when a record is removed. (defaults to false).
+ */
+ pruneModifiedRecords : false,
+
+ // private
+ lastOptions : null,
+
+ /**
+ * Add Records to the Store and fires the add event.
+ * @param {Roo.data.Record[]} records An Array of Roo.data.Record objects to add to the cache.
+ */
+ add : function(records){
+ records = [].concat(records);
for(var i = 0, len = records.length; i < len; i++){
- var d = this.prepareData(records[i].data, i, records[i]);
- if(n){
- this.tpl.insertBefore(n, d);
- }else{
-
- this.tpl.append(this.el, d);
- }
+ records[i].join(this);
}
- this.updateIndexes(index);
+ var index = this.data.length;
+ this.data.addAll(records);
+ this.fireEvent("add", this, records, index);
},
- onRemove : function(ds, record, index){
- this.clearSelections();
- var el = this.dataName ?
- this.el.child('.roo-tpl-' + this.dataName) :
- this.el;
- el.dom.removeChild(this.nodes[index]);
- this.updateIndexes(index);
+ /**
+ * Remove a Record from the Store and fires the remove event.
+ * @param {Ext.data.Record} record The Roo.data.Record object to remove from the cache.
+ */
+ remove : function(record){
+ var index = this.data.indexOf(record);
+ this.data.removeAt(index);
+ if(this.pruneModifiedRecords){
+ this.modified.remove(record);
+ }
+ this.fireEvent("remove", this, record, index);
},
/**
- * Refresh an individual node.
- * @param {Number} index
+ * Remove all Records from the Store and fires the clear event.
*/
- refreshNode : function(index){
- this.onUpdate(this.store, this.store.getAt(index));
+ removeAll : function(){
+ this.data.clear();
+ if(this.pruneModifiedRecords){
+ this.modified = [];
+ }
+ this.fireEvent("clear", this);
},
- updateIndexes : function(startIndex, endIndex){
- var ns = this.nodes;
- startIndex = startIndex || 0;
- endIndex = endIndex || ns.length - 1;
- for(var i = startIndex; i <= endIndex; i++){
- ns[i].nodeIndex = i;
+ /**
+ * Inserts Records to the Store at the given index and fires the add event.
+ * @param {Number} index The start index at which to insert the passed Records.
+ * @param {Roo.data.Record[]} records An Array of Roo.data.Record objects to add to the cache.
+ */
+ insert : function(index, records){
+ records = [].concat(records);
+ for(var i = 0, len = records.length; i < len; i++){
+ this.data.insert(index, records[i]);
+ records[i].join(this);
}
+ this.fireEvent("add", this, records, index);
},
/**
- * Changes the data store this view uses and refresh the view.
- * @param {Store} store
+ * Get the index within the cache of the passed Record.
+ * @param {Roo.data.Record} record The Roo.data.Record object to to find.
+ * @return {Number} The index of the passed Record. Returns -1 if not found.
*/
- setStore : function(store, initial){
- if(!initial && this.store){
- this.store.un("datachanged", this.refresh);
- this.store.un("add", this.onAdd);
- this.store.un("remove", this.onRemove);
- this.store.un("update", this.onUpdate);
- this.store.un("clear", this.refresh);
- this.store.un("beforeload", this.onBeforeLoad);
- this.store.un("load", this.onLoad);
- this.store.un("loadexception", this.onLoad);
- }
- if(store){
-
- store.on("datachanged", this.refresh, this);
- store.on("add", this.onAdd, this);
- store.on("remove", this.onRemove, this);
- store.on("update", this.onUpdate, this);
- store.on("clear", this.refresh, this);
- store.on("beforeload", this.onBeforeLoad, this);
- store.on("load", this.onLoad, this);
- store.on("loadexception", this.onLoad, this);
- }
-
- if(store){
- this.refresh();
- }
+ indexOf : function(record){
+ return this.data.indexOf(record);
},
+
/**
- * onbeforeLoad - masks the loading area.
- *
+ * Get the index within the cache of the Record with the passed id.
+ * @param {String} id The id of the Record to find.
+ * @return {Number} The index of the Record. Returns -1 if not found.
*/
- onBeforeLoad : function()
- {
- this.el.update("");
- this.el.mask(this.mask ? this.mask : "Loading" );
+ indexOfId : function(id){
+ return this.data.indexOfKey(id);
},
- onLoad : function ()
- {
- this.el.unmask();
+
+ /**
+ * Get the Record with the specified id.
+ * @param {String} id The id of the Record to find.
+ * @return {Roo.data.Record} The Record with the passed id. Returns undefined if not found.
+ */
+ getById : function(id){
+ return this.data.key(id);
},
-
/**
- * Returns the template node the passed child belongs to or null if it doesn't belong to one.
- * @param {HTMLElement} node
- * @return {HTMLElement} The template node
+ * Get the Record at the specified index.
+ * @param {Number} index The index of the Record to find.
+ * @return {Roo.data.Record} The Record at the passed index. Returns undefined if not found.
*/
- findItemFromChild : function(node){
- var el = this.dataName ?
- this.el.child('.roo-tpl-' + this.dataName,true) :
- this.el.dom;
-
- if(!node || node.parentNode == el){
- return node;
- }
- var p = node.parentNode;
- while(p && p != el){
- if(p.parentNode == el){
- return p;
- }
- p = p.parentNode;
- }
- return null;
+ getAt : function(index){
+ return this.data.itemAt(index);
},
- /** @ignore */
- onClick : function(e){
- var item = this.findItemFromChild(e.getTarget());
- if(item){
- var index = this.indexOf(item);
- if(this.onItemClick(item, index, e) !== false){
- this.fireEvent("click", this, index, item, e);
- }
- }else{
- this.clearSelections();
- }
+ /**
+ * Returns a range of Records between specified indices.
+ * @param {Number} startIndex (optional) The starting index (defaults to 0)
+ * @param {Number} endIndex (optional) The ending index (defaults to the last Record in the Store)
+ * @return {Roo.data.Record[]} An array of Records
+ */
+ getRange : function(start, end){
+ return this.data.getRange(start, end);
},
- /** @ignore */
- onContextMenu : function(e){
- var item = this.findItemFromChild(e.getTarget());
- if(item){
- this.fireEvent("contextmenu", this, this.indexOf(item), item, e);
- }
+ // private
+ storeOptions : function(o){
+ o = Roo.apply({}, o);
+ delete o.callback;
+ delete o.scope;
+ this.lastOptions = o;
},
- /** @ignore */
- onDblClick : function(e){
- var item = this.findItemFromChild(e.getTarget());
- if(item){
- this.fireEvent("dblclick", this, this.indexOf(item), item, e);
+ /**
+ * Loads the Record cache from the configured Proxy using the configured Reader.
+ * <p>
+ * If using remote paging, then the first load call must specify the <em>start</em>
+ * and <em>limit</em> properties in the options.params property to establish the initial
+ * position within the dataset, and the number of Records to cache on each read from the Proxy.
+ * <p>
+ * <strong>It is important to note that for remote data sources, loading is asynchronous,
+ * and this call will return before the new data has been loaded. Perform any post-processing
+ * in a callback function, or in a "load" event handler.</strong>
+ * <p>
+ * @param {Object} options An object containing properties which control loading options:<ul>
+ * <li>params {Object} An object containing properties to pass as HTTP parameters to a remote data source.</li>
+ * <li>callback {Function} A function to be called after the Records have been loaded. The callback is
+ * passed the following arguments:<ul>
+ * <li>r : Roo.data.Record[]</li>
+ * <li>options: Options object from the load call</li>
+ * <li>success: Boolean success indicator</li></ul></li>
+ * <li>scope {Object} Scope with which to call the callback (defaults to the Store object)</li>
+ * <li>add {Boolean} indicator to append loaded records rather than replace the current cache.</li>
+ * </ul>
+ */
+ load : function(options){
+ options = options || {};
+ if(this.fireEvent("beforeload", this, options) !== false){
+ this.storeOptions(options);
+ var p = Roo.apply(options.params || {}, this.baseParams);
+ // if meta was not loaded from remote source.. try requesting it.
+ if (!this.reader.metaFromRemote) {
+ p._requestMeta = 1;
+ }
+ if(this.sortInfo && this.remoteSort){
+ var pn = this.paramNames;
+ p[pn["sort"]] = this.sortInfo.field;
+ p[pn["dir"]] = this.sortInfo.direction;
+ }
+ if (this.multiSort) {
+ var pn = this.paramNames;
+ p[pn["multisort"]] = Roo.encode( { sort : this.sortToggle, order: this.sortOrder });
+ }
+
+ this.proxy.load(p, this.reader, this.loadRecords, this, options);
}
},
- onItemClick : function(item, index, e)
- {
- if(this.fireEvent("beforeclick", this, index, item, e) === false){
- return false;
+ /**
+ * Reloads the Record cache from the configured Proxy using the configured Reader and
+ * the options from the last load operation performed.
+ * @param {Object} options (optional) An object containing properties which may override the options
+ * used in the last load operation. See {@link #load} for details (defaults to null, in which case
+ * the most recently used options are reused).
+ */
+ reload : function(options){
+ this.load(Roo.applyIf(options||{}, this.lastOptions));
+ },
+
+ // private
+ // Called as a callback by the Reader during a load operation.
+ loadRecords : function(o, options, success){
+ if(!o || success === false){
+ if(success !== false){
+ this.fireEvent("load", this, [], options, o);
+ }
+ if(options.callback){
+ options.callback.call(options.scope || this, [], options, false);
+ }
+ return;
}
- if (this.toggleSelect) {
- var m = this.isSelected(item) ? 'unselect' : 'select';
- Roo.log(m);
- var _t = this;
- _t[m](item, true, false);
- return true;
+ // if data returned failure - throw an exception.
+ if (o.success === false) {
+ // show a message if no listener is registered.
+ if (!this.hasListener('loadexception') && typeof(o.raw.errorMsg) != 'undefined') {
+ Roo.MessageBox.alert("Error loading",o.raw.errorMsg);
+ }
+ // loadmask wil be hooked into this..
+ this.fireEvent("loadexception", this, o, options, o.raw.errorMsg);
+ return;
}
- if(this.multiSelect || this.singleSelect){
- if(this.multiSelect && e.shiftKey && this.lastSelection){
- this.select(this.getNodes(this.indexOf(this.lastSelection), index), false);
- }else{
- this.select(item, this.multiSelect && e.ctrlKey);
- this.lastSelection = item;
+ var r = o.records, t = o.totalRecords || r.length;
+
+ this.fireEvent("beforeloadadd", this, r, options, o);
+
+ if(!options || options.add !== true){
+ if(this.pruneModifiedRecords){
+ this.modified = [];
}
- e.preventDefault();
+ for(var i = 0, len = r.length; i < len; i++){
+ r[i].join(this);
+ }
+ if(this.snapshot){
+ this.data = this.snapshot;
+ delete this.snapshot;
+ }
+ this.data.clear();
+ this.data.addAll(r);
+ this.totalLength = t;
+ this.applySort();
+ this.fireEvent("datachanged", this);
+ }else{
+ this.totalLength = Math.max(t, this.data.length+r.length);
+ this.add(r);
+ }
+ this.fireEvent("load", this, r, options, o);
+ if(options.callback){
+ options.callback.call(options.scope || this, r, options, true);
}
- return true;
},
+
/**
- * Get the number of selected nodes.
- * @return {Number}
+ * Loads data from a passed data block. A Reader which understands the format of the data
+ * must have been configured in the constructor.
+ * @param {Object} data The data block from which to read the Records. The format of the data expected
+ * is dependent on the type of Reader that is configured and should correspond to that Reader's readRecords parameter.
+ * @param {Boolean} append (Optional) True to append the new Records rather than replace the existing cache.
*/
- getSelectionCount : function(){
- return this.selections.length;
+ loadData : function(o, append){
+ var r = this.reader.readRecords(o);
+ this.loadRecords(r, {add: append}, true);
},
/**
- * Get the currently selected nodes.
- * @return {Array} An array of HTMLElements
+ * Gets the number of cached records.
+ * <p>
+ * <em>If using paging, this may not be the total size of the dataset. If the data object
+ * used by the Reader contains the dataset size, then the getTotalCount() function returns
+ * the data set size</em>
*/
- getSelectedNodes : function(){
- return this.selections;
+ getCount : function(){
+ return this.data.length || 0;
},
/**
- * Get the indexes of the selected nodes.
- * @return {Array}
+ * Gets the total number of records in the dataset as returned by the server.
+ * <p>
+ * <em>If using paging, for this to be accurate, the data object used by the Reader must contain
+ * the dataset size</em>
*/
- getSelectedIndexes : function(){
- var indexes = [], s = this.selections;
- for(var i = 0, len = s.length; i < len; i++){
- indexes.push(s[i].nodeIndex);
- }
- return indexes;
+ getTotalCount : function(){
+ return this.totalLength || 0;
},
/**
- * Clear all selections
- * @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange event
+ * Returns the sort state of the Store as an object with two properties:
+ * <pre><code>
+ field {String} The name of the field by which the Records are sorted
+ direction {String} The sort order, "ASC" or "DESC"
+ * </code></pre>
*/
- clearSelections : function(suppressEvent){
- if(this.nodes && (this.multiSelect || this.singleSelect) && this.selections.length > 0){
- this.cmp.elements = this.selections;
- this.cmp.removeClass(this.selectedClass);
- this.selections = [];
- if(!suppressEvent){
- this.fireEvent("selectionchange", this, this.selections);
+ getSortState : function(){
+ return this.sortInfo;
+ },
+
+ // private
+ applySort : function(){
+ if(this.sortInfo && !this.remoteSort){
+ var s = this.sortInfo, f = s.field;
+ var st = this.fields.get(f).sortType;
+ var fn = function(r1, r2){
+ var v1 = st(r1.data[f]), v2 = st(r2.data[f]);
+ return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
+ };
+ this.data.sort(s.direction, fn);
+ if(this.snapshot && this.snapshot != this.data){
+ this.snapshot.sort(s.direction, fn);
}
}
},
/**
- * Returns true if the passed node is selected
- * @param {HTMLElement/Number} node The node or node index
- * @return {Boolean}
+ * Sets the default sort column and order to be used by the next load operation.
+ * @param {String} fieldName The name of the field to sort by.
+ * @param {String} dir (optional) The sort order, "ASC" or "DESC" (defaults to "ASC")
*/
- isSelected : function(node){
- var s = this.selections;
- if(s.length < 1){
- return false;
- }
- node = this.getNode(node);
- return s.indexOf(node) !== -1;
+ setDefaultSort : function(field, dir){
+ this.sortInfo = {field: field, direction: dir ? dir.toUpperCase() : "ASC"};
},
/**
- * Selects nodes.
- * @param {Array/HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node, id of a template node or an array of any of those to select
- * @param {Boolean} keepExisting (optional) true to keep existing selections
- * @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange vent
+ * Sort the Records.
+ * If remote sorting is used, the sort is performed on the server, and the cache is
+ * reloaded. If local sorting is used, the cache is sorted internally.
+ * @param {String} fieldName The name of the field to sort by.
+ * @param {String} dir (optional) The sort order, "ASC" or "DESC" (defaults to "ASC")
*/
- select : function(nodeInfo, keepExisting, suppressEvent){
- if(nodeInfo instanceof Array){
- if(!keepExisting){
- this.clearSelections(true);
- }
- for(var i = 0, len = nodeInfo.length; i < len; i++){
- this.select(nodeInfo[i], true, true);
+ sort : function(fieldName, dir){
+ var f = this.fields.get(fieldName);
+ if(!dir){
+ this.sortToggle[f.name] = this.sortToggle[f.name] || f.sortDir;
+
+ if(this.multiSort || (this.sortInfo && this.sortInfo.field == f.name) ){ // toggle sort dir
+ dir = (this.sortToggle[f.name] || "ASC").toggle("ASC", "DESC");
+ }else{
+ dir = f.sortDir;
}
- return;
- }
- var node = this.getNode(nodeInfo);
- if(!node || this.isSelected(node)){
- return; // already selected.
}
- if(!keepExisting){
- this.clearSelections(true);
+ this.sortToggle[f.name] = dir;
+ this.sortInfo = {field: f.name, direction: dir};
+ if(!this.remoteSort){
+ this.applySort();
+ this.fireEvent("datachanged", this);
+ }else{
+ this.load(this.lastOptions);
}
- if(this.fireEvent("beforeselect", this, node, this.selections) !== false){
- Roo.fly(node).addClass(this.selectedClass);
- this.selections.push(node);
- if(!suppressEvent){
- this.fireEvent("selectionchange", this, this.selections);
+ },
+
+ /**
+ * Calls the specified function for each of the Records in the cache.
+ * @param {Function} fn The function to call. The Record is passed as the first parameter.
+ * Returning <em>false</em> aborts and exits the iteration.
+ * @param {Object} scope (optional) The scope in which to call the function (defaults to the Record).
+ */
+ each : function(fn, scope){
+ this.data.each(fn, scope);
+ },
+
+ /**
+ * Gets all records modified since the last commit. Modified records are persisted across load operations
+ * (e.g., during paging).
+ * @return {Roo.data.Record[]} An array of Records containing outstanding modifications.
+ */
+ getModifiedRecords : function(){
+ return this.modified;
+ },
+
+ // private
+ createFilterFn : function(property, value, anyMatch){
+ if(!value.exec){ // not a regex
+ value = String(value);
+ if(value.length == 0){
+ return false;
}
+ value = new RegExp((anyMatch === true ? '' : '^') + Roo.escapeRe(value), "i");
}
-
-
+ return function(r){
+ return value.test(r.data[property]);
+ };
},
- /**
- * Unselects nodes.
- * @param {Array/HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node, id of a template node or an array of any of those to select
- * @param {Boolean} keepExisting (optional) true IGNORED (for campatibility with select)
- * @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange vent
+
+ /**
+ * Sums the value of <i>property</i> for each record between start and end and returns the result.
+ * @param {String} property A field on your records
+ * @param {Number} start The record index to start at (defaults to 0)
+ * @param {Number} end The last record index to include (defaults to length - 1)
+ * @return {Number} The sum
*/
- unselect : function(nodeInfo, keepExisting, suppressEvent)
- {
- if(nodeInfo instanceof Array){
- Roo.each(this.selections, function(s) {
- this.unselect(s, nodeInfo);
- }, this);
- return;
- }
- var node = this.getNode(nodeInfo);
- if(!node || !this.isSelected(node)){
- Roo.log("not selected");
- return; // not selected.
+ sum : function(property, start, end){
+ var rs = this.data.items, v = 0;
+ start = start || 0;
+ end = (end || end === 0) ? end : rs.length-1;
+
+ for(var i = start; i <= end; i++){
+ v += (rs[i].data[property] || 0);
}
- // fireevent???
- var ns = [];
- Roo.each(this.selections, function(s) {
- if (s == node ) {
- Roo.fly(node).removeClass(this.selectedClass);
+ return v;
+ },
- return;
- }
- ns.push(s);
- },this);
-
- this.selections= ns;
- this.fireEvent("selectionchange", this, this.selections);
+ /**
+ * Filter the records by a specified property.
+ * @param {String} field A field on your records
+ * @param {String/RegExp} value Either a string that the field
+ * should start with or a RegExp to test against the field
+ * @param {Boolean} anyMatch True to match any part not just the beginning
+ */
+ filter : function(property, value, anyMatch){
+ var fn = this.createFilterFn(property, value, anyMatch);
+ return fn ? this.filterBy(fn) : this.clearFilter();
},
/**
- * Gets a template node.
- * @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node
- * @return {HTMLElement} The node or null if it wasn't found
+ * Filter by a function. The specified function will be called with each
+ * record in this data source. If the function returns true the record is included,
+ * otherwise it is filtered.
+ * @param {Function} fn The function to be called, it will receive 2 args (record, id)
+ * @param {Object} scope (optional) The scope of the function (defaults to this)
*/
- getNode : function(nodeInfo){
- if(typeof nodeInfo == "string"){
- return document.getElementById(nodeInfo);
- }else if(typeof nodeInfo == "number"){
- return this.nodes[nodeInfo];
- }
- return nodeInfo;
+ filterBy : function(fn, scope){
+ this.snapshot = this.snapshot || this.data;
+ this.data = this.queryBy(fn, scope||this);
+ this.fireEvent("datachanged", this);
},
/**
- * Gets a range template nodes.
- * @param {Number} startIndex
- * @param {Number} endIndex
- * @return {Array} An array of nodes
+ * Query the records by a specified property.
+ * @param {String} field A field on your records
+ * @param {String/RegExp} value Either a string that the field
+ * should start with or a RegExp to test against the field
+ * @param {Boolean} anyMatch True to match any part not just the beginning
+ * @return {MixedCollection} Returns an Roo.util.MixedCollection of the matched records
*/
- getNodes : function(start, end){
- var ns = this.nodes;
- start = start || 0;
- end = typeof end == "undefined" ? ns.length - 1 : end;
- var nodes = [];
- if(start <= end){
- for(var i = start; i <= end; i++){
- nodes.push(ns[i]);
- }
- } else{
- for(var i = start; i >= end; i--){
- nodes.push(ns[i]);
+ query : function(property, value, anyMatch){
+ var fn = this.createFilterFn(property, value, anyMatch);
+ return fn ? this.queryBy(fn) : this.data.clone();
+ },
+
+ /**
+ * Query by a function. The specified function will be called with each
+ * record in this data source. If the function returns true the record is included
+ * in the results.
+ * @param {Function} fn The function to be called, it will receive 2 args (record, id)
+ * @param {Object} scope (optional) The scope of the function (defaults to this)
+ @return {MixedCollection} Returns an Roo.util.MixedCollection of the matched records
+ **/
+ queryBy : function(fn, scope){
+ var data = this.snapshot || this.data;
+ return data.filterBy(fn, scope||this);
+ },
+
+ /**
+ * Collects unique values for a particular dataIndex from this store.
+ * @param {String} dataIndex The property to collect
+ * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
+ * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
+ * @return {Array} An array of the unique values
+ **/
+ collect : function(dataIndex, allowNull, bypassFilter){
+ var d = (bypassFilter === true && this.snapshot) ?
+ this.snapshot.items : this.data.items;
+ var v, sv, r = [], l = {};
+ for(var i = 0, len = d.length; i < len; i++){
+ v = d[i].data[dataIndex];
+ sv = String(v);
+ if((allowNull || !Roo.isEmpty(v)) && !l[sv]){
+ l[sv] = true;
+ r[r.length] = v;
}
}
- return nodes;
+ return r;
},
/**
- * Finds the index of the passed node
- * @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node
- * @return {Number} The index of the node or -1
+ * Revert to a view of the Record cache with no filtering applied.
+ * @param {Boolean} suppressEvent If true the filter is cleared silently without notifying listeners
*/
- indexOf : function(node){
- node = this.getNode(node);
- if(typeof node.nodeIndex == "number"){
- return node.nodeIndex;
- }
- var ns = this.nodes;
- for(var i = 0, len = ns.length; i < len; i++){
- if(ns[i] == node){
- return i;
+ clearFilter : function(suppressEvent){
+ if(this.snapshot && this.snapshot != this.data){
+ this.data = this.snapshot;
+ delete this.snapshot;
+ if(suppressEvent !== true){
+ this.fireEvent("datachanged", this);
}
}
- return -1;
- }
-});
-/*
- * - LGPL
- * *
- */
+ },
-/**
- * @class Roo.bootstrap.ComboBox
- * @extends Roo.bootstrap.TriggerField
- * A combobox control with support for autocomplete, remote-loading, paging and many other features.
- * @constructor
- * Create a new ComboBox.
- * @param {Object} config Configuration options
- */
-Roo.bootstrap.ComboBox = function(config){
- Roo.bootstrap.ComboBox.superclass.constructor.call(this, config);
+ // private
+ afterEdit : function(record){
+ if(this.modified.indexOf(record) == -1){
+ this.modified.push(record);
+ }
+ this.fireEvent("update", this, record, Roo.data.Record.EDIT);
+ },
+
+ // private
+ afterReject : function(record){
+ this.modified.remove(record);
+ this.fireEvent("update", this, record, Roo.data.Record.REJECT);
+ },
+
+ // private
+ afterCommit : function(record){
+ this.modified.remove(record);
+ this.fireEvent("update", this, record, Roo.data.Record.COMMIT);
+ },
+
+ /**
+ * Commit all Records with outstanding changes. To handle updates for changes, subscribe to the
+ * Store's "update" event, and perform updating when the third parameter is Roo.data.Record.COMMIT.
+ */
+ commitChanges : function(){
+ var m = this.modified.slice(0);
+ this.modified = [];
+ for(var i = 0, len = m.length; i < len; i++){
+ m[i].commit();
+ }
+ },
+
+ /**
+ * Cancel outstanding changes on all changed records.
+ */
+ rejectChanges : function(){
+ var m = this.modified.slice(0);
+ this.modified = [];
+ for(var i = 0, len = m.length; i < len; i++){
+ m[i].reject();
+ }
+ },
+
+ onMetaChange : function(meta, rtype, o){
+ this.recordType = rtype;
+ this.fields = rtype.prototype.fields;
+ delete this.snapshot;
+ this.sortInfo = meta.sortInfo || this.sortInfo;
+ this.modified = [];
+ this.fireEvent('metachange', this, this.reader.meta);
+ }
+});/*
+ * Based on:
+ * Ext JS Library 1.1.1
+ * Copyright(c) 2006-2007, Ext JS, LLC.
+ *
+ * Originally Released Under LGPL - original licence link has changed is not relivant.
+ *
+ * Fork - LGPL
+ * <script type="text/javascript">
+ */
+
+/**
+ * @class Roo.data.SimpleStore
+ * @extends Roo.data.Store
+ * Small helper class to make creating Stores from Array data easier.
+ * @cfg {Number} id The array index of the record id. Leave blank to auto generate ids.
+ * @cfg {Array} fields An array of field definition objects, or field name strings.
+ * @cfg {Array} data The multi-dimensional array of data
+ * @constructor
+ * @param {Object} config
+ */
+Roo.data.SimpleStore = function(config){
+ Roo.data.SimpleStore.superclass.constructor.call(this, {
+ isLocal : true,
+ reader: new Roo.data.ArrayReader({
+ id: config.id
+ },
+ Roo.data.Record.create(config.fields)
+ ),
+ proxy : new Roo.data.MemoryProxy(config.data)
+ });
+ this.load();
+};
+Roo.extend(Roo.data.SimpleStore, Roo.data.Store);/*
+ * Based on:
+ * Ext JS Library 1.1.1
+ * Copyright(c) 2006-2007, Ext JS, LLC.
+ *
+ * Originally Released Under LGPL - original licence link has changed is not relivant.
+ *
+ * Fork - LGPL
+ * <script type="text/javascript">
+ */
+
+/**
+/**
+ * @extends Roo.data.Store
+ * @class Roo.data.JsonStore
+ * Small helper class to make creating Stores for JSON data easier. <br/>
+<pre><code>
+var store = new Roo.data.JsonStore({
+ url: 'get-images.php',
+ root: 'images',
+ fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
+});
+</code></pre>
+ * <b>Note: Although they are not listed, this class inherits all of the config options of Store,
+ * JsonReader and HttpProxy (unless inline data is provided).</b>
+ * @cfg {Array} fields An array of field definition objects, or field name strings.
+ * @constructor
+ * @param {Object} config
+ */
+Roo.data.JsonStore = function(c){
+ Roo.data.JsonStore.superclass.constructor.call(this, Roo.apply(c, {
+ proxy: !c.data ? new Roo.data.HttpProxy({url: c.url}) : undefined,
+ reader: new Roo.data.JsonReader(c, c.fields)
+ }));
+};
+Roo.extend(Roo.data.JsonStore, Roo.data.Store);/*
+ * Based on:
+ * Ext JS Library 1.1.1
+ * Copyright(c) 2006-2007, Ext JS, LLC.
+ *
+ * Originally Released Under LGPL - original licence link has changed is not relivant.
+ *
+ * Fork - LGPL
+ * <script type="text/javascript">
+ */
+
+
+Roo.data.Field = function(config){
+ if(typeof config == "string"){
+ config = {name: config};
+ }
+ Roo.apply(this, config);
+
+ if(!this.type){
+ this.type = "auto";
+ }
+
+ var st = Roo.data.SortTypes;
+ // named sortTypes are supported, here we look them up
+ if(typeof this.sortType == "string"){
+ this.sortType = st[this.sortType];
+ }
+
+ // set default sortType for strings and dates
+ if(!this.sortType){
+ switch(this.type){
+ case "string":
+ this.sortType = st.asUCString;
+ break;
+ case "date":
+ this.sortType = st.asDate;
+ break;
+ default:
+ this.sortType = st.none;
+ }
+ }
+
+ // define once
+ var stripRe = /[\$,%]/g;
+
+ // prebuilt conversion function for this field, instead of
+ // switching every time we're reading a value
+ if(!this.convert){
+ var cv, dateFormat = this.dateFormat;
+ switch(this.type){
+ case "":
+ case "auto":
+ case undefined:
+ cv = function(v){ return v; };
+ break;
+ case "string":
+ cv = function(v){ return (v === undefined || v === null) ? '' : String(v); };
+ break;
+ case "int":
+ cv = function(v){
+ return v !== undefined && v !== null && v !== '' ?
+ parseInt(String(v).replace(stripRe, ""), 10) : '';
+ };
+ break;
+ case "float":
+ cv = function(v){
+ return v !== undefined && v !== null && v !== '' ?
+ parseFloat(String(v).replace(stripRe, ""), 10) : '';
+ };
+ break;
+ case "bool":
+ case "boolean":
+ cv = function(v){ return v === true || v === "true" || v == 1; };
+ break;
+ case "date":
+ cv = function(v){
+ if(!v){
+ return '';
+ }
+ if(v instanceof Date){
+ return v;
+ }
+ if(dateFormat){
+ if(dateFormat == "timestamp"){
+ return new Date(v*1000);
+ }
+ return Date.parseDate(v, dateFormat);
+ }
+ var parsed = Date.parse(v);
+ return parsed ? new Date(parsed) : null;
+ };
+ break;
+
+ }
+ this.convert = cv;
+ }
+};
+
+Roo.data.Field.prototype = {
+ dateFormat: null,
+ defaultValue: "",
+ mapping: null,
+ sortType : null,
+ sortDir : "ASC"
+};/*
+ * Based on:
+ * Ext JS Library 1.1.1
+ * Copyright(c) 2006-2007, Ext JS, LLC.
+ *
+ * Originally Released Under LGPL - original licence link has changed is not relivant.
+ *
+ * Fork - LGPL
+ * <script type="text/javascript">
+ */
+
+// Base class for reading structured data from a data source. This class is intended to be
+// extended (see ArrayReader, JsonReader and XmlReader) and should not be created directly.
+
+/**
+ * @class Roo.data.DataReader
+ * Base class for reading structured data from a data source. This class is intended to be
+ * extended (see {Roo.data.ArrayReader}, {Roo.data.JsonReader} and {Roo.data.XmlReader}) and should not be created directly.
+ */
+
+Roo.data.DataReader = function(meta, recordType){
+
+ this.meta = meta;
+
+ this.recordType = recordType instanceof Array ?
+ Roo.data.Record.create(recordType) : recordType;
+};
+
+Roo.data.DataReader.prototype = {
+ /**
+ * Create an empty record
+ * @param {Object} data (optional) - overlay some values
+ * @return {Roo.data.Record} record created.
+ */
+ newRow : function(d) {
+ var da = {};
+ this.recordType.prototype.fields.each(function(c) {
+ switch( c.type) {
+ case 'int' : da[c.name] = 0; break;
+ case 'date' : da[c.name] = new Date(); break;
+ case 'float' : da[c.name] = 0.0; break;
+ case 'boolean' : da[c.name] = false; break;
+ default : da[c.name] = ""; break;
+ }
+
+ });
+ return new this.recordType(Roo.apply(da, d));
+ }
+
+};/*
+ * Based on:
+ * Ext JS Library 1.1.1
+ * Copyright(c) 2006-2007, Ext JS, LLC.
+ *
+ * Originally Released Under LGPL - original licence link has changed is not relivant.
+ *
+ * Fork - LGPL
+ * <script type="text/javascript">
+ */
+
+/**
+ * @class Roo.data.DataProxy
+ * @extends Roo.data.Observable
+ * This class is an abstract base class for implementations which provide retrieval of
+ * unformatted data objects.<br>
+ * <p>
+ * DataProxy implementations are usually used in conjunction with an implementation of Roo.data.DataReader
+ * (of the appropriate type which knows how to parse the data object) to provide a block of
+ * {@link Roo.data.Records} to an {@link Roo.data.Store}.<br>
+ * <p>
+ * Custom implementations must implement the load method as described in
+ * {@link Roo.data.HttpProxy#load}.
+ */
+Roo.data.DataProxy = function(){
this.addEvents({
/**
- * @event expand
- * Fires when the dropdown list is expanded
- * @param {Roo.bootstrap.ComboBox} combo This combo box
- */
- 'expand' : true,
- /**
- * @event collapse
- * Fires when the dropdown list is collapsed
- * @param {Roo.bootstrap.ComboBox} combo This combo box
- */
- 'collapse' : true,
- /**
- * @event beforeselect
- * Fires before a list item is selected. Return false to cancel the selection.
- * @param {Roo.bootstrap.ComboBox} combo This combo box
- * @param {Roo.data.Record} record The data record returned from the underlying store
- * @param {Number} index The index of the selected item in the dropdown list
- */
- 'beforeselect' : true,
- /**
- * @event select
- * Fires when a list item is selected
- * @param {Roo.bootstrap.ComboBox} combo This combo box
- * @param {Roo.data.Record} record The data record returned from the underlying store (or false on clear)
- * @param {Number} index The index of the selected item in the dropdown list
- */
- 'select' : true,
+ * @event beforeload
+ * Fires before a network request is made to retrieve a data object.
+ * @param {Object} This DataProxy object.
+ * @param {Object} params The params parameter to the load function.
+ */
+ beforeload : true,
/**
- * @event beforequery
- * Fires before all queries are processed. Return false to cancel the query or set cancel to true.
- * The event object passed has these properties:
- * @param {Roo.bootstrap.ComboBox} combo This combo box
- * @param {String} query The query
- * @param {Boolean} forceAll true to force "all" query
- * @param {Boolean} cancel true to cancel the query
- * @param {Object} e The query event object
- */
- 'beforequery': true,
- /**
- * @event add
- * Fires when the 'add' icon is pressed (add a listener to enable add button)
- * @param {Roo.bootstrap.ComboBox} combo This combo box
- */
- 'add' : true,
+ * @event load
+ * Fires before the load method's callback is called.
+ * @param {Object} This DataProxy object.
+ * @param {Object} o The data object.
+ * @param {Object} arg The callback argument object passed to the load function.
+ */
+ load : true,
/**
- * @event edit
- * Fires when the 'edit' icon is pressed (add a listener to enable add button)
- * @param {Roo.bootstrap.ComboBox} combo This combo box
- * @param {Roo.data.Record|false} record The data record returned from the underlying store (or false on nothing selected)
- */
- 'edit' : true
-
-
+ * @event loadexception
+ * Fires if an Exception occurs during data retrieval.
+ * @param {Object} This DataProxy object.
+ * @param {Object} o The data object.
+ * @param {Object} arg The callback argument object passed to the load function.
+ * @param {Object} e The Exception.
+ */
+ loadexception : true
});
-
-
- this.selectedIndex = -1;
- if(this.mode == 'local'){
- if(config.queryDelay === undefined){
- this.queryDelay = 10;
- }
- if(config.minChars === undefined){
- this.minChars = 0;
+ Roo.data.DataProxy.superclass.constructor.call(this);
+};
+
+Roo.extend(Roo.data.DataProxy, Roo.util.Observable);
+
+ /**
+ * @cfg {void} listeners (Not available) Constructor blocks listeners from being set
+ */
+/*
+ * Based on:
+ * Ext JS Library 1.1.1
+ * Copyright(c) 2006-2007, Ext JS, LLC.
+ *
+ * Originally Released Under LGPL - original licence link has changed is not relivant.
+ *
+ * Fork - LGPL
+ * <script type="text/javascript">
+ */
+/**
+ * @class Roo.data.MemoryProxy
+ * An implementation of Roo.data.DataProxy that simply passes the data specified in its constructor
+ * to the Reader when its load method is called.
+ * @constructor
+ * @param {Object} data The data object which the Reader uses to construct a block of Roo.data.Records.
+ */
+Roo.data.MemoryProxy = function(data){
+ if (data.data) {
+ data = data.data;
+ }
+ Roo.data.MemoryProxy.superclass.constructor.call(this);
+ this.data = data;
+};
+
+Roo.extend(Roo.data.MemoryProxy, Roo.data.DataProxy, {
+ /**
+ * Load data from the requested source (in this case an in-memory
+ * data object passed to the constructor), read the data object into
+ * a block of Roo.data.Records using the passed Roo.data.DataReader implementation, and
+ * process that block using the passed callback.
+ * @param {Object} params This parameter is not used by the MemoryProxy class.
+ * @param {Roo.data.DataReader} reader The Reader object which converts the data
+ * object into a block of Roo.data.Records.
+ * @param {Function} callback The function into which to pass the block of Roo.data.records.
+ * The function must be passed <ul>
+ * <li>The Record block object</li>
+ * <li>The "arg" argument from the load function</li>
+ * <li>A boolean success indicator</li>
+ * </ul>
+ * @param {Object} scope The scope in which to call the callback
+ * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
+ */
+ load : function(params, reader, callback, scope, arg){
+ params = params || {};
+ var result;
+ try {
+ result = reader.readRecords(this.data);
+ }catch(e){
+ this.fireEvent("loadexception", this, arg, null, e);
+ callback.call(scope, null, arg, false);
+ return;
}
+ callback.call(scope, result, arg, true);
+ },
+
+ // private
+ update : function(params, records){
+
}
+});/*
+ * Based on:
+ * Ext JS Library 1.1.1
+ * Copyright(c) 2006-2007, Ext JS, LLC.
+ *
+ * Originally Released Under LGPL - original licence link has changed is not relivant.
+ *
+ * Fork - LGPL
+ * <script type="text/javascript">
+ */
+/**
+ * @class Roo.data.HttpProxy
+ * @extends Roo.data.DataProxy
+ * An implementation of {@link Roo.data.DataProxy} that reads a data object from an {@link Roo.data.Connection} object
+ * configured to reference a certain URL.<br><br>
+ * <p>
+ * <em>Note that this class cannot be used to retrieve data from a domain other than the domain
+ * from which the running page was served.<br><br>
+ * <p>
+ * For cross-domain access to remote data, use an {@link Roo.data.ScriptTagProxy}.</em><br><br>
+ * <p>
+ * Be aware that to enable the browser to parse an XML document, the server must set
+ * the Content-Type header in the HTTP response to "text/xml".
+ * @constructor
+ * @param {Object} conn Connection config options to add to each request (e.g. {url: 'foo.php'} or
+ * an {@link Roo.data.Connection} object. If a Connection config is passed, the singleton {@link Roo.Ajax} object
+ * will be used to make the request.
+ */
+Roo.data.HttpProxy = function(conn){
+ Roo.data.HttpProxy.superclass.constructor.call(this);
+ // is conn a conn config or a real conn?
+ this.conn = conn;
+ this.useAjax = !conn || !conn.events;
+
};
-Roo.extend(Roo.bootstrap.ComboBox, Roo.bootstrap.TriggerField, {
-
+Roo.extend(Roo.data.HttpProxy, Roo.data.DataProxy, {
+ // thse are take from connection...
+
/**
- * @cfg {Boolean} lazyRender True to prevent the ComboBox from rendering until requested (should always be used when
- * rendering into an Roo.Editor, defaults to false)
+ * @cfg {String} url (Optional) The default URL to be used for requests to the server. (defaults to undefined)
*/
/**
- * @cfg {Boolean/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to:
- * {tag: "input", type: "text", size: "24", autocomplete: "off"})
+ * @cfg {Object} extraParams (Optional) An object containing properties which are used as
+ * extra parameters to each request made by this object. (defaults to undefined)
*/
/**
- * @cfg {Roo.data.Store} store The data store to which this combo is bound (defaults to undefined)
+ * @cfg {Object} defaultHeaders (Optional) An object containing request headers which are added
+ * to each request made by this object. (defaults to undefined)
*/
/**
- * @cfg {String} title If supplied, a header element is created containing this text and added into the top of
- * the dropdown list (defaults to undefined, with no header element)
+ * @cfg {String} method (Optional) The default HTTP method to be used for requests. (defaults to undefined; if not set but parms are present will use POST, otherwise GET)
*/
-
- /**
- * @cfg {String/Roo.Template} tpl The template to use to render the output
+ /**
+ * @cfg {Number} timeout (Optional) The timeout in milliseconds to be used for requests. (defaults to 30000)
*/
-
/**
- * @cfg {Number} listWidth The width in pixels of the dropdown list (defaults to the width of the ComboBox field)
+ * @cfg {Boolean} autoAbort (Optional) Whether this request should abort any pending requests. (defaults to false)
+ * @type Boolean
*/
- listWidth: undefined,
- /**
- * @cfg {String} displayField The underlying data field name to bind to this CombBox (defaults to undefined if
- * mode = 'remote' or 'text' if mode = 'local')
- */
- displayField: undefined,
- /**
- * @cfg {String} valueField The underlying data value name to bind to this CombBox (defaults to undefined if
- * mode = 'remote' or 'value' if mode = 'local').
- * Note: use of a valueField requires the user make a selection
- * in order for a value to be mapped.
- */
- valueField: undefined,
-
-
- /**
- * @cfg {String} hiddenName If specified, a hidden form field with this name is dynamically generated to store the
- * field's data value (defaults to the underlying DOM element's name)
- */
- hiddenName: undefined,
- /**
- * @cfg {String} listClass CSS class to apply to the dropdown list element (defaults to '')
- */
- listClass: '',
- /**
- * @cfg {String} selectedClass CSS class to apply to the selected item in the dropdown list (defaults to 'x-combo-selected')
- */
- selectedClass: 'active',
-
- /**
- * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop" for bottom-right
- */
- shadow:'sides',
- /**
- * @cfg {String} listAlign A valid anchor position value. See {@link Roo.Element#alignTo} for details on supported
- * anchor positions (defaults to 'tl-bl')
- */
- listAlign: 'tl-bl?',
- /**
- * @cfg {Number} maxHeight The maximum height in pixels of the dropdown list before scrollbars are shown (defaults to 300)
- */
- maxHeight: 300,
- /**
- * @cfg {String} triggerAction The action to execute when the trigger field is activated. Use 'all' to run the
- * query specified by the allQuery config option (defaults to 'query')
- */
- triggerAction: 'query',
- /**
- * @cfg {Number} minChars The minimum number of characters the user must type before autocomplete and typeahead activate
- * (defaults to 4, does not apply if editable = false)
- */
- minChars : 4,
- /**
- * @cfg {Boolean} typeAhead True to populate and autoselect the remainder of the text being typed after a configurable
- * delay (typeAheadDelay) if it matches a known value (defaults to false)
- */
- typeAhead: false,
- /**
- * @cfg {Number} queryDelay The length of time in milliseconds to delay between the start of typing and sending the
- * query to filter the dropdown list (defaults to 500 if mode = 'remote' or 10 if mode = 'local')
- */
- queryDelay: 500,
- /**
- * @cfg {Number} pageSize If greater than 0, a paging toolbar is displayed in the footer of the dropdown list and the
- * filter queries will execute with page start and limit parameters. Only applies when mode = 'remote' (defaults to 0)
- */
- pageSize: 0,
- /**
- * @cfg {Boolean} selectOnFocus True to select any existing text in the field immediately on focus. Only applies
- * when editable = true (defaults to false)
- */
- selectOnFocus:false,
- /**
- * @cfg {String} queryParam Name of the query as it will be passed on the querystring (defaults to 'query')
- */
- queryParam: 'query',
- /**
- * @cfg {String} loadingText The text to display in the dropdown list while data is loading. Only applies
- * when mode = 'remote' (defaults to 'Loading...')
- */
- loadingText: 'Loading...',
- /**
- * @cfg {Boolean} resizable True to add a resize handle to the bottom of the dropdown list (defaults to false)
- */
- resizable: false,
+
+
/**
- * @cfg {Number} handleHeight The height in pixels of the dropdown list resize handle if resizable = true (defaults to 8)
+ * @cfg {Boolean} disableCaching (Optional) True to add a unique cache-buster param to GET requests. (defaults to true)
+ * @type Boolean
*/
- handleHeight : 8,
/**
- * @cfg {Boolean} editable False to prevent the user from typing text directly into the field, just like a
- * traditional select (defaults to true)
+ * Return the {@link Roo.data.Connection} object being used by this Proxy.
+ * @return {Connection} The Connection object. This object may be used to subscribe to events on
+ * a finer-grained basis than the DataProxy events.
*/
- editable: true,
+ getConnection : function(){
+ return this.useAjax ? Roo.Ajax : this.conn;
+ },
+
/**
- * @cfg {String} allQuery The text query to send to the server to return all records for the list with no filtering (defaults to '')
+ * Load data from the configured {@link Roo.data.Connection}, read the data object into
+ * a block of Roo.data.Records using the passed {@link Roo.data.DataReader} implementation, and
+ * process that block using the passed callback.
+ * @param {Object} params An object containing properties which are to be used as HTTP parameters
+ * for the request to the remote server.
+ * @param {Roo.data.DataReader} reader The Reader object which converts the data
+ * object into a block of Roo.data.Records.
+ * @param {Function} callback The function into which to pass the block of Roo.data.Records.
+ * The function must be passed <ul>
+ * <li>The Record block object</li>
+ * <li>The "arg" argument from the load function</li>
+ * <li>A boolean success indicator</li>
+ * </ul>
+ * @param {Object} scope The scope in which to call the callback
+ * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
*/
- allQuery: '',
+ load : function(params, reader, callback, scope, arg){
+ if(this.fireEvent("beforeload", this, params) !== false){
+ var o = {
+ params : params || {},
+ request: {
+ callback : callback,
+ scope : scope,
+ arg : arg
+ },
+ reader: reader,
+ callback : this.loadResponse,
+ scope: this
+ };
+ if(this.useAjax){
+ Roo.applyIf(o, this.conn);
+ if(this.activeRequest){
+ Roo.Ajax.abort(this.activeRequest);
+ }
+ this.activeRequest = Roo.Ajax.request(o);
+ }else{
+ this.conn.request(o);
+ }
+ }else{
+ callback.call(scope||this, null, arg, false);
+ }
+ },
+
+ // private
+ loadResponse : function(o, success, response){
+ delete this.activeRequest;
+ if(!success){
+ this.fireEvent("loadexception", this, o, response);
+ o.request.callback.call(o.request.scope, null, o.request.arg, false);
+ return;
+ }
+ var result;
+ try {
+ result = o.reader.read(response);
+ }catch(e){
+ this.fireEvent("loadexception", this, o, response, e);
+ o.request.callback.call(o.request.scope, null, o.request.arg, false);
+ return;
+ }
+
+ this.fireEvent("load", this, o, o.request.arg);
+ o.request.callback.call(o.request.scope, result, o.request.arg, true);
+ },
+
+ // private
+ update : function(dataSet){
+
+ },
+
+ // private
+ updateResponse : function(dataSet){
+
+ }
+});/*
+ * Based on:
+ * Ext JS Library 1.1.1
+ * Copyright(c) 2006-2007, Ext JS, LLC.
+ *
+ * Originally Released Under LGPL - original licence link has changed is not relivant.
+ *
+ * Fork - LGPL
+ * <script type="text/javascript">
+ */
+
+/**
+ * @class Roo.data.ScriptTagProxy
+ * An implementation of Roo.data.DataProxy that reads a data object from a URL which may be in a domain
+ * other than the originating domain of the running page.<br><br>
+ * <p>
+ * <em>Note that if you are retrieving data from a page that is in a domain that is NOT the same as the originating domain
+ * of the running page, you must use this class, rather than DataProxy.</em><br><br>
+ * <p>
+ * The content passed back from a server resource requested by a ScriptTagProxy is executable JavaScript
+ * source code that is used as the source inside a <script> tag.<br><br>
+ * <p>
+ * In order for the browser to process the returned data, the server must wrap the data object
+ * with a call to a callback function, the name of which is passed as a parameter by the ScriptTagProxy.
+ * Below is a Java example for a servlet which returns data for either a ScriptTagProxy, or an HttpProxy
+ * depending on whether the callback name was passed:
+ * <p>
+ * <pre><code>
+boolean scriptTag = false;
+String cb = request.getParameter("callback");
+if (cb != null) {
+ scriptTag = true;
+ response.setContentType("text/javascript");
+} else {
+ response.setContentType("application/x-json");
+}
+Writer out = response.getWriter();
+if (scriptTag) {
+ out.write(cb + "(");
+}
+out.print(dataBlock.toJsonString());
+if (scriptTag) {
+ out.write(");");
+}
+</pre></code>
+ *
+ * @constructor
+ * @param {Object} config A configuration object.
+ */
+Roo.data.ScriptTagProxy = function(config){
+ Roo.data.ScriptTagProxy.superclass.constructor.call(this);
+ Roo.apply(this, config);
+ this.head = document.getElementsByTagName("head")[0];
+};
+
+Roo.data.ScriptTagProxy.TRANS_ID = 1000;
+
+Roo.extend(Roo.data.ScriptTagProxy, Roo.data.DataProxy, {
/**
- * @cfg {String} mode Set to 'local' if the ComboBox loads local data (defaults to 'remote' which loads from the server)
+ * @cfg {String} url The URL from which to request the data object.
*/
- mode: 'remote',
/**
- * @cfg {Number} minListWidth The minimum width of the dropdown list in pixels (defaults to 70, will be ignored if
- * listWidth has a higher value)
+ * @cfg {Number} timeout (Optional) The number of milliseconds to wait for a response. Defaults to 30 seconds.
*/
- minListWidth : 70,
+ timeout : 30000,
/**
- * @cfg {Boolean} forceSelection True to restrict the selected value to one of the values in the list, false to
- * allow the user to set arbitrary text into the field (defaults to false)
+ * @cfg {String} callbackParam (Optional) The name of the parameter to pass to the server which tells
+ * the server the name of the callback function set up by the load call to process the returned data object.
+ * Defaults to "callback".<p>The server-side processing must read this parameter value, and generate
+ * javascript output which calls this named function passing the data object as its only parameter.
*/
- forceSelection:false,
+ callbackParam : "callback",
/**
- * @cfg {Number} typeAheadDelay The length of time in milliseconds to wait until the typeahead text is displayed
- * if typeAhead = true (defaults to 250)
+ * @cfg {Boolean} nocache (Optional) Defaults to true. Disable cacheing by adding a unique parameter
+ * name to the request.
*/
- typeAheadDelay : 250,
+ nocache : true,
+
/**
- * @cfg {String} valueNotFoundText When using a name/value combo, if the value passed to setValue is not found in
- * the store, valueNotFoundText will be displayed as the field text if defined (defaults to undefined)
+ * Load data from the configured URL, read the data object into
+ * a block of Roo.data.Records using the passed Roo.data.DataReader implementation, and
+ * process that block using the passed callback.
+ * @param {Object} params An object containing properties which are to be used as HTTP parameters
+ * for the request to the remote server.
+ * @param {Roo.data.DataReader} reader The Reader object which converts the data
+ * object into a block of Roo.data.Records.
+ * @param {Function} callback The function into which to pass the block of Roo.data.Records.
+ * The function must be passed <ul>
+ * <li>The Record block object</li>
+ * <li>The "arg" argument from the load function</li>
+ * <li>A boolean success indicator</li>
+ * </ul>
+ * @param {Object} scope The scope in which to call the callback
+ * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
*/
- valueNotFoundText : undefined,
+ load : function(params, reader, callback, scope, arg){
+ if(this.fireEvent("beforeload", this, params) !== false){
+
+ var p = Roo.urlEncode(Roo.apply(params, this.extraParams));
+
+ var url = this.url;
+ url += (url.indexOf("?") != -1 ? "&" : "?") + p;
+ if(this.nocache){
+ url += "&_dc=" + (new Date().getTime());
+ }
+ var transId = ++Roo.data.ScriptTagProxy.TRANS_ID;
+ var trans = {
+ id : transId,
+ cb : "stcCallback"+transId,
+ scriptId : "stcScript"+transId,
+ params : params,
+ arg : arg,
+ url : url,
+ callback : callback,
+ scope : scope,
+ reader : reader
+ };
+ var conn = this;
+
+ window[trans.cb] = function(o){
+ conn.handleResponse(o, trans);
+ };
+
+ url += String.format("&{0}={1}", this.callbackParam, trans.cb);
+
+ if(this.autoAbort !== false){
+ this.abort();
+ }
+
+ trans.timeoutId = this.handleFailure.defer(this.timeout, this, [trans]);
+
+ var script = document.createElement("script");
+ script.setAttribute("src", url);
+ script.setAttribute("type", "text/javascript");
+ script.setAttribute("id", trans.scriptId);
+ this.head.appendChild(script);
+
+ this.trans = trans;
+ }else{
+ callback.call(scope||this, null, arg, false);
+ }
+ },
+
+ // private
+ isLoading : function(){
+ return this.trans ? true : false;
+ },
+
/**
- * @cfg {Boolean} blockFocus Prevents all focus calls, so it can work with things like HTML edtor bar
+ * Abort the current server request.
*/
- blockFocus : false,
+ abort : function(){
+ if(this.isLoading()){
+ this.destroyTrans(this.trans);
+ }
+ },
+
+ // private
+ destroyTrans : function(trans, isLoaded){
+ this.head.removeChild(document.getElementById(trans.scriptId));
+ clearTimeout(trans.timeoutId);
+ if(isLoaded){
+ window[trans.cb] = undefined;
+ try{
+ delete window[trans.cb];
+ }catch(e){}
+ }else{
+ // if hasn't been loaded, wait for load to remove it to prevent script error
+ window[trans.cb] = function(){
+ window[trans.cb] = undefined;
+ try{
+ delete window[trans.cb];
+ }catch(e){}
+ };
+ }
+ },
+
+ // private
+ handleResponse : function(o, trans){
+ this.trans = false;
+ this.destroyTrans(trans, true);
+ var result;
+ try {
+ result = trans.reader.readRecords(o);
+ }catch(e){
+ this.fireEvent("loadexception", this, o, trans.arg, e);
+ trans.callback.call(trans.scope||window, null, trans.arg, false);
+ return;
+ }
+ this.fireEvent("load", this, o, trans.arg);
+ trans.callback.call(trans.scope||window, result, trans.arg, true);
+ },
+
+ // private
+ handleFailure : function(trans){
+ this.trans = false;
+ this.destroyTrans(trans, false);
+ this.fireEvent("loadexception", this, null, trans.arg);
+ trans.callback.call(trans.scope||window, null, trans.arg, false);
+ }
+});/*
+ * Based on:
+ * Ext JS Library 1.1.1
+ * Copyright(c) 2006-2007, Ext JS, LLC.
+ *
+ * Originally Released Under LGPL - original licence link has changed is not relivant.
+ *
+ * Fork - LGPL
+ * <script type="text/javascript">
+ */
+
+/**
+ * @class Roo.data.JsonReader
+ * @extends Roo.data.DataReader
+ * Data reader class to create an Array of Roo.data.Record objects from a JSON response
+ * based on mappings in a provided Roo.data.Record constructor.
+ *
+ * The default behaviour of a store is to send ?_requestMeta=1, unless the class has recieved 'metaData' property
+ * in the reply previously.
+ *
+ * <p>
+ * Example code:
+ * <pre><code>
+var RecordDef = Roo.data.Record.create([
+ {name: 'name', mapping: 'name'}, // "mapping" property not needed if it's the same as "name"
+ {name: 'occupation'} // This field will use "occupation" as the mapping.
+]);
+var myReader = new Roo.data.JsonReader({
+ totalProperty: "results", // The property which contains the total dataset size (optional)
+ root: "rows", // The property which contains an Array of row objects
+ id: "id" // The property within each row object that provides an ID for the record (optional)
+}, RecordDef);
+</code></pre>
+ * <p>
+ * This would consume a JSON file like this:
+ * <pre><code>
+{ 'results': 2, 'rows': [
+ { 'id': 1, 'name': 'Bill', occupation: 'Gardener' },
+ { 'id': 2, 'name': 'Ben', occupation: 'Horticulturalist' } ]
+}
+</code></pre>
+ * @cfg {String} totalProperty Name of the property from which to retrieve the total number of records
+ * in the dataset. This is only needed if the whole dataset is not passed in one go, but is being
+ * paged from the remote server.
+ * @cfg {String} successProperty Name of the property from which to retrieve the success attribute used by forms.
+ * @cfg {String} root name of the property which contains the Array of row objects.
+ * @cfg {String} id Name of the property within a row object that contains a record identifier value.
+ * @constructor
+ * Create a new JsonReader
+ * @param {Object} meta Metadata configuration options
+ * @param {Object} recordType Either an Array of field definition objects,
+ * or an {@link Roo.data.Record} object created using {@link Roo.data.Record#create}.
+ */
+Roo.data.JsonReader = function(meta, recordType){
+
+ meta = meta || {};
+ // set some defaults:
+ Roo.applyIf(meta, {
+ totalProperty: 'total',
+ successProperty : 'success',
+ root : 'data',
+ id : 'id'
+ });
+
+ Roo.data.JsonReader.superclass.constructor.call(this, meta, recordType||meta.fields);
+};
+Roo.extend(Roo.data.JsonReader, Roo.data.DataReader, {
/**
- * @cfg {Boolean} disableClear Disable showing of clear button.
+ * @prop {Boolean} metaFromRemote - if the meta data was loaded from the remote source.
+ * Used by Store query builder to append _requestMeta to params.
+ *
*/
- disableClear : false,
+ metaFromRemote : false,
/**
- * @cfg {Boolean} alwaysQuery Disable caching of results, and always send query
+ * This method is only used by a DataProxy which has retrieved data from a remote server.
+ * @param {Object} response The XHR object which contains the JSON data in its responseText.
+ * @return {Object} data A data block which is used by an Roo.data.Store object as
+ * a cache of Roo.data.Records.
*/
- alwaysQuery : false,
-
- //private
- addicon : false,
- editicon: false,
-
- // element that contains real text value.. (when hidden is used..)
-
- // private
- initEvents: function(){
-
- if (!this.store) {
- throw "can not find store for combo";
+ read : function(response){
+ var json = response.responseText;
+
+ var o = /* eval:var:o */ eval("("+json+")");
+ if(!o) {
+ throw {message: "JsonReader.read: Json object not found"};
}
- this.store = Roo.factory(this.store, Roo.data);
-
-
- Roo.bootstrap.ComboBox.superclass.initEvents.call(this);
-
-
- if(this.hiddenName){
-
- this.hiddenField = this.el.select('input.form-hidden-field',true).first();
+ if(o.metaData){
- this.hiddenField.dom.value =
- this.hiddenValue !== undefined ? this.hiddenValue :
- this.value !== undefined ? this.value : '';
-
- // prevent input submission
- this.el.dom.removeAttribute('name');
- this.hiddenField.dom.setAttribute('name', this.hiddenName);
-
-
+ delete this.ef;
+ this.metaFromRemote = true;
+ this.meta = o.metaData;
+ this.recordType = Roo.data.Record.create(o.metaData.fields);
+ this.onMetaChange(this.meta, this.recordType, o);
}
- //if(Roo.isGecko){
- // this.el.dom.setAttribute('autocomplete', 'off');
- //}
+ return this.readRecords(o);
+ },
- var cls = 'x-combo-list';
- this.list = this.el.select('ul',true).first();
+ // private function a store will implement
+ onMetaChange : function(meta, recordType, o){
- //this.list = new Roo.Layer({
- // shadow: this.shadow, cls: [cls, this.listClass].join(' '), constrain:false
- //});
-
- var lw = this.listWidth || Math.max(this.inputEl().getWidth(), this.minListWidth);
- this.list.setWidth(lw);
- /*
- this.list.swallowEvent('mousewheel');
- this.assetHeight = 0;
+ },
- if(this.title){
- this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
- this.assetHeight += this.header.getHeight();
- }
+ /**
+ * @ignore
+ */
+ simpleAccess: function(obj, subsc) {
+ return obj[subsc];
+ },
+
+ /**
+ * @ignore
+ */
+ getJsonAccessor: function(){
+ var re = /[\[\.]/;
+ return function(expr) {
+ try {
+ return(re.test(expr))
+ ? new Function("obj", "return obj." + expr)
+ : function(obj){
+ return obj[expr];
+ };
+ } catch(e){}
+ return Roo.emptyFn;
+ };
+ }(),
- this.innerList = this.list.createChild({cls:cls+'-inner'});
- this.innerList.on('mouseover', this.onViewOver, this);
- this.innerList.on('mousemove', this.onViewMove, this);
- this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
-
- if(this.allowBlank && !this.pageSize && !this.disableClear){
- this.footer = this.list.createChild({cls:cls+'-ft'});
- this.pageTb = new Roo.Toolbar(this.footer);
-
- }
- if(this.pageSize){
- this.footer = this.list.createChild({cls:cls+'-ft'});
- this.pageTb = new Roo.PagingToolbar(this.footer, this.store,
- {pageSize: this.pageSize});
-
- }
-
- if (this.pageTb && this.allowBlank && !this.disableClear) {
- var _this = this;
- this.pageTb.add(new Roo.Toolbar.Fill(), {
- cls: 'x-btn-icon x-btn-clear',
- text: ' ',
- handler: function()
- {
- _this.collapse();
- _this.clearValue();
- _this.onSelect(false, -1);
- }
- });
- }
- if (this.footer) {
- this.assetHeight += this.footer.getHeight();
- }
- */
-
- if(!this.tpl){
- this.tpl = '<li><a href="#">{' + this.displayField + '}</a></li>';
+ /**
+ * Create a data block containing Roo.data.Records from an XML document.
+ * @param {Object} o An object which contains an Array of row objects in the property specified
+ * in the config as 'root, and optionally a property, specified in the config as 'totalProperty'
+ * which contains the total size of the dataset.
+ * @return {Object} data A data block which is used by an Roo.data.Store object as
+ * a cache of Roo.data.Records.
+ */
+ readRecords : function(o){
+ /**
+ * After any data loads, the raw JSON data is available for further custom processing.
+ * @type Object
+ */
+ this.o = o;
+ var s = this.meta, Record = this.recordType,
+ f = Record.prototype.fields, fi = f.items, fl = f.length;
+
+// Generate extraction functions for the totalProperty, the root, the id, and for each field
+ if (!this.ef) {
+ if(s.totalProperty) {
+ this.getTotal = this.getJsonAccessor(s.totalProperty);
+ }
+ if(s.successProperty) {
+ this.getSuccess = this.getJsonAccessor(s.successProperty);
+ }
+ this.getRoot = s.root ? this.getJsonAccessor(s.root) : function(p){return p;};
+ if (s.id) {
+ var g = this.getJsonAccessor(s.id);
+ this.getId = function(rec) {
+ var r = g(rec);
+ return (r === undefined || r === "") ? null : r;
+ };
+ } else {
+ this.getId = function(){return null;};
+ }
+ this.ef = [];
+ for(var jj = 0; jj < fl; jj++){
+ f = fi[jj];
+ var map = (f.mapping !== undefined && f.mapping !== null) ? f.mapping : f.name;
+ this.ef[jj] = this.getJsonAccessor(map);
+ }
}
- this.view = new Roo.View(this.el.select('ul',true).first(), this.tpl, {
- singleSelect:true, store: this.store, selectedClass: this.selectedClass
- });
- //this.view.wrapEl.setDisplayed(false);
- this.view.on('click', this.onViewClick, this);
-
-
-
- this.store.on('beforeload', this.onBeforeLoad, this);
- this.store.on('load', this.onLoad, this);
- this.store.on('loadexception', this.onLoadException, this);
- /*
- if(this.resizable){
- this.resizer = new Roo.Resizable(this.list, {
- pinned:true, handles:'se'
- });
- this.resizer.on('resize', function(r, w, h){
- this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight;
- this.listWidth = w;
- this.innerList.setWidth(w - this.list.getFrameWidth('lr'));
- this.restrictHeight();
- }, this);
- this[this.pageSize?'footer':'innerList'].setStyle('margin-bottom', this.handleHeight+'px');
- }
- */
- if(!this.editable){
- this.editable = true;
- this.setEditable(false);
- }
-
- /*
-
- if (typeof(this.events.add.listeners) != 'undefined') {
-
- this.addicon = this.wrap.createChild(
- {tag: 'img', src: Roo.BLANK_IMAGE_URL, cls: 'x-form-combo-add' });
-
- this.addicon.on('click', function(e) {
- this.fireEvent('add', this);
- }, this);
+ var root = this.getRoot(o), c = root.length, totalRecords = c, success = true;
+ if(s.totalProperty){
+ var vt = parseInt(this.getTotal(o), 10);
+ if(!isNaN(vt)){
+ totalRecords = vt;
+ }
}
- if (typeof(this.events.edit.listeners) != 'undefined') {
-
- this.editicon = this.wrap.createChild(
- {tag: 'img', src: Roo.BLANK_IMAGE_URL, cls: 'x-form-combo-edit' });
- if (this.addicon) {
- this.editicon.setStyle('margin-left', '40px');
+ if(s.successProperty){
+ var vs = this.getSuccess(o);
+ if(vs === false || vs === 'false'){
+ success = false;
}
- this.editicon.on('click', function(e) {
-
- // we fire even if inothing is selected..
- this.fireEvent('edit', this, this.lastData );
-
- }, this);
}
- */
-
-
- this.keyNav = new Roo.KeyNav(this.inputEl(), {
- "up" : function(e){
- this.inKeyMode = true;
- this.selectPrev();
- },
-
- "down" : function(e){
- if(!this.isExpanded()){
- this.onTriggerClick();
- }else{
- this.inKeyMode = true;
- this.selectNext();
+ var records = [];
+ for(var i = 0; i < c; i++){
+ var n = root[i];
+ var values = {};
+ var id = this.getId(n);
+ for(var j = 0; j < fl; j++){
+ f = fi[j];
+ var v = this.ef[j](n);
+ if (!f.convert) {
+ Roo.log('missing convert for ' + f.name);
+ Roo.log(f);
+ continue;
}
- },
-
- "enter" : function(e){
- this.onViewClick();
- //return true;
- },
-
- "esc" : function(e){
- this.collapse();
- },
-
- "tab" : function(e){
- this.onViewClick(false);
- this.fireEvent("specialkey", this, e);
- return true;
- },
+ values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue);
+ }
+ var record = new Record(values, id);
+ record.json = n;
+ records[i] = record;
+ }
+ return {
+ raw : o,
+ success : success,
+ records : records,
+ totalRecords : totalRecords
+ };
+ }
+});/*
+ * Based on:
+ * Ext JS Library 1.1.1
+ * Copyright(c) 2006-2007, Ext JS, LLC.
+ *
+ * Originally Released Under LGPL - original licence link has changed is not relivant.
+ *
+ * Fork - LGPL
+ * <script type="text/javascript">
+ */
- scope : this,
+/**
+ * @class Roo.data.ArrayReader
+ * @extends Roo.data.DataReader
+ * Data reader class to create an Array of Roo.data.Record objects from an Array.
+ * Each element of that Array represents a row of data fields. The
+ * fields are pulled into a Record object using as a subscript, the <em>mapping</em> property
+ * of the field definition if it exists, or the field's ordinal position in the definition.<br>
+ * <p>
+ * Example code:.
+ * <pre><code>
+var RecordDef = Roo.data.Record.create([
+ {name: 'name', mapping: 1}, // "mapping" only needed if an "id" field is present which
+ {name: 'occupation', mapping: 2} // precludes using the ordinal position as the index.
+]);
+var myReader = new Roo.data.ArrayReader({
+ id: 0 // The subscript within row Array that provides an ID for the Record (optional)
+}, RecordDef);
+</code></pre>
+ * <p>
+ * This would consume an Array like this:
+ * <pre><code>
+[ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
+ </code></pre>
+ * @cfg {String} id (optional) The subscript within row Array that provides an ID for the Record
+ * @constructor
+ * Create a new JsonReader
+ * @param {Object} meta Metadata configuration options.
+ * @param {Object} recordType Either an Array of field definition objects
+ * as specified to {@link Roo.data.Record#create},
+ * or an {@link Roo.data.Record} object
+ * created using {@link Roo.data.Record#create}.
+ */
+Roo.data.ArrayReader = function(meta, recordType){
+ Roo.data.ArrayReader.superclass.constructor.call(this, meta, recordType);
+};
- doRelay : function(foo, bar, hname){
- if(hname == 'down' || this.scope.isExpanded()){
- return Roo.KeyNav.prototype.doRelay.apply(this, arguments);
- }
- return true;
- },
+Roo.extend(Roo.data.ArrayReader, Roo.data.JsonReader, {
+ /**
+ * Create a data block containing Roo.data.Records from an XML document.
+ * @param {Object} o An Array of row objects which represents the dataset.
+ * @return {Object} data A data block which is used by an Roo.data.Store object as
+ * a cache of Roo.data.Records.
+ */
+ readRecords : function(o){
+ var sid = this.meta ? this.meta.id : null;
+ var recordType = this.recordType, fields = recordType.prototype.fields;
+ var records = [];
+ var root = o;
+ for(var i = 0; i < root.length; i++){
+ var n = root[i];
+ var values = {};
+ var id = ((sid || sid === 0) && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
+ for(var j = 0, jlen = fields.length; j < jlen; j++){
+ var f = fields.items[j];
+ var k = f.mapping !== undefined && f.mapping !== null ? f.mapping : j;
+ var v = n[k] !== undefined ? n[k] : f.defaultValue;
+ v = f.convert(v);
+ values[f.name] = v;
+ }
+ var record = new recordType(values, id);
+ record.json = n;
+ records[records.length] = record;
+ }
+ return {
+ records : records,
+ totalRecords : records.length
+ };
+ }
+});/*
+ * - LGPL
+ * *
+ */
- forceKeyDown: true
- });
-
-
- this.queryDelay = Math.max(this.queryDelay || 10,
- this.mode == 'local' ? 10 : 250);
-
+/**
+ * @class Roo.bootstrap.ComboBox
+ * @extends Roo.bootstrap.TriggerField
+ * A combobox control with support for autocomplete, remote-loading, paging and many other features.
+ * @constructor
+ * Create a new ComboBox.
+ * @param {Object} config Configuration options
+ */
+Roo.bootstrap.ComboBox = function(config){
+ Roo.bootstrap.ComboBox.superclass.constructor.call(this, config);
+ this.addEvents({
+ /**
+ * @event expand
+ * Fires when the dropdown list is expanded
+ * @param {Roo.bootstrap.ComboBox} combo This combo box
+ */
+ 'expand' : true,
+ /**
+ * @event collapse
+ * Fires when the dropdown list is collapsed
+ * @param {Roo.bootstrap.ComboBox} combo This combo box
+ */
+ 'collapse' : true,
+ /**
+ * @event beforeselect
+ * Fires before a list item is selected. Return false to cancel the selection.
+ * @param {Roo.bootstrap.ComboBox} combo This combo box
+ * @param {Roo.data.Record} record The data record returned from the underlying store
+ * @param {Number} index The index of the selected item in the dropdown list
+ */
+ 'beforeselect' : true,
+ /**
+ * @event select
+ * Fires when a list item is selected
+ * @param {Roo.bootstrap.ComboBox} combo This combo box
+ * @param {Roo.data.Record} record The data record returned from the underlying store (or false on clear)
+ * @param {Number} index The index of the selected item in the dropdown list
+ */
+ 'select' : true,
+ /**
+ * @event beforequery
+ * Fires before all queries are processed. Return false to cancel the query or set cancel to true.
+ * The event object passed has these properties:
+ * @param {Roo.bootstrap.ComboBox} combo This combo box
+ * @param {String} query The query
+ * @param {Boolean} forceAll true to force "all" query
+ * @param {Boolean} cancel true to cancel the query
+ * @param {Object} e The query event object
+ */
+ 'beforequery': true,
+ /**
+ * @event add
+ * Fires when the 'add' icon is pressed (add a listener to enable add button)
+ * @param {Roo.bootstrap.ComboBox} combo This combo box
+ */
+ 'add' : true,
+ /**
+ * @event edit
+ * Fires when the 'edit' icon is pressed (add a listener to enable add button)
+ * @param {Roo.bootstrap.ComboBox} combo This combo box
+ * @param {Roo.data.Record|false} record The data record returned from the underlying store (or false on nothing selected)
+ */
+ 'edit' : true
- this.dqTask = new Roo.util.DelayedTask(this.initQuery, this);
- if(this.typeAhead){
- this.taTask = new Roo.util.DelayedTask(this.onTypeAhead, this);
- }
- if(this.editable !== false){
- this.inputEl().on("keyup", this.onKeyUp, this);
- }
- if(this.forceSelection){
- this.on('blur', this.doForce, this);
- }
- },
-
- onDestroy : function(){
- if(this.view){
- this.view.setStore(null);
- this.view.el.removeAllListeners();
- this.view.el.remove();
- this.view.purgeListeners();
- }
- if(this.list){
- this.list.dom.innerHTML = '';
+ });
+
+
+ this.selectedIndex = -1;
+ if(this.mode == 'local'){
+ if(config.queryDelay === undefined){
+ this.queryDelay = 10;
}
- if(this.store){
- this.store.un('beforeload', this.onBeforeLoad, this);
- this.store.un('load', this.onLoad, this);
- this.store.un('loadexception', this.onLoadException, this);
+ if(config.minChars === undefined){
+ this.minChars = 0;
}
- Roo.bootstrap.ComboBox.superclass.onDestroy.call(this);
- },
+ }
+};
- // private
- fireKey : function(e){
- if(e.isNavKeyPress() && !this.list.isVisible()){
- this.fireEvent("specialkey", this, e);
- }
- },
+Roo.extend(Roo.bootstrap.ComboBox, Roo.bootstrap.TriggerField, {
+
+ /**
+ * @cfg {Boolean} lazyRender True to prevent the ComboBox from rendering until requested (should always be used when
+ * rendering into an Roo.Editor, defaults to false)
+ */
+ /**
+ * @cfg {Boolean/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to:
+ * {tag: "input", type: "text", size: "24", autocomplete: "off"})
+ */
+ /**
+ * @cfg {Roo.data.Store} store The data store to which this combo is bound (defaults to undefined)
+ */
+ /**
+ * @cfg {String} title If supplied, a header element is created containing this text and added into the top of
+ * the dropdown list (defaults to undefined, with no header element)
+ */
+ /**
+ * @cfg {String/Roo.Template} tpl The template to use to render the output
+ */
+
+ /**
+ * @cfg {Number} listWidth The width in pixels of the dropdown list (defaults to the width of the ComboBox field)
+ */
+ listWidth: undefined,
+ /**
+ * @cfg {String} displayField The underlying data field name to bind to this CombBox (defaults to undefined if
+ * mode = 'remote' or 'text' if mode = 'local')
+ */
+ displayField: undefined,
+ /**
+ * @cfg {String} valueField The underlying data value name to bind to this CombBox (defaults to undefined if
+ * mode = 'remote' or 'value' if mode = 'local').
+ * Note: use of a valueField requires the user make a selection
+ * in order for a value to be mapped.
+ */
+ valueField: undefined,
+
+
+ /**
+ * @cfg {String} hiddenName If specified, a hidden form field with this name is dynamically generated to store the
+ * field's data value (defaults to the underlying DOM element's name)
+ */
+ hiddenName: undefined,
+ /**
+ * @cfg {String} listClass CSS class to apply to the dropdown list element (defaults to '')
+ */
+ listClass: '',
+ /**
+ * @cfg {String} selectedClass CSS class to apply to the selected item in the dropdown list (defaults to 'x-combo-selected')
+ */
+ selectedClass: 'active',
+
+ /**
+ * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop" for bottom-right
+ */
+ shadow:'sides',
+ /**
+ * @cfg {String} listAlign A valid anchor position value. See {@link Roo.Element#alignTo} for details on supported
+ * anchor positions (defaults to 'tl-bl')
+ */
+ listAlign: 'tl-bl?',
+ /**
+ * @cfg {Number} maxHeight The maximum height in pixels of the dropdown list before scrollbars are shown (defaults to 300)
+ */
+ maxHeight: 300,
+ /**
+ * @cfg {String} triggerAction The action to execute when the trigger field is activated. Use 'all' to run the
+ * query specified by the allQuery config option (defaults to 'query')
+ */
+ triggerAction: 'query',
+ /**
+ * @cfg {Number} minChars The minimum number of characters the user must type before autocomplete and typeahead activate
+ * (defaults to 4, does not apply if editable = false)
+ */
+ minChars : 4,
+ /**
+ * @cfg {Boolean} typeAhead True to populate and autoselect the remainder of the text being typed after a configurable
+ * delay (typeAheadDelay) if it matches a known value (defaults to false)
+ */
+ typeAhead: false,
+ /**
+ * @cfg {Number} queryDelay The length of time in milliseconds to delay between the start of typing and sending the
+ * query to filter the dropdown list (defaults to 500 if mode = 'remote' or 10 if mode = 'local')
+ */
+ queryDelay: 500,
+ /**
+ * @cfg {Number} pageSize If greater than 0, a paging toolbar is displayed in the footer of the dropdown list and the
+ * filter queries will execute with page start and limit parameters. Only applies when mode = 'remote' (defaults to 0)
+ */
+ pageSize: 0,
+ /**
+ * @cfg {Boolean} selectOnFocus True to select any existing text in the field immediately on focus. Only applies
+ * when editable = true (defaults to false)
+ */
+ selectOnFocus:false,
+ /**
+ * @cfg {String} queryParam Name of the query as it will be passed on the querystring (defaults to 'query')
+ */
+ queryParam: 'query',
+ /**
+ * @cfg {String} loadingText The text to display in the dropdown list while data is loading. Only applies
+ * when mode = 'remote' (defaults to 'Loading...')
+ */
+ loadingText: 'Loading...',
+ /**
+ * @cfg {Boolean} resizable True to add a resize handle to the bottom of the dropdown list (defaults to false)
+ */
+ resizable: false,
+ /**
+ * @cfg {Number} handleHeight The height in pixels of the dropdown list resize handle if resizable = true (defaults to 8)
+ */
+ handleHeight : 8,
+ /**
+ * @cfg {Boolean} editable False to prevent the user from typing text directly into the field, just like a
+ * traditional select (defaults to true)
+ */
+ editable: true,
+ /**
+ * @cfg {String} allQuery The text query to send to the server to return all records for the list with no filtering (defaults to '')
+ */
+ allQuery: '',
+ /**
+ * @cfg {String} mode Set to 'local' if the ComboBox loads local data (defaults to 'remote' which loads from the server)
+ */
+ mode: 'remote',
+ /**
+ * @cfg {Number} minListWidth The minimum width of the dropdown list in pixels (defaults to 70, will be ignored if
+ * listWidth has a higher value)
+ */
+ minListWidth : 70,
+ /**
+ * @cfg {Boolean} forceSelection True to restrict the selected value to one of the values in the list, false to
+ * allow the user to set arbitrary text into the field (defaults to false)
+ */
+ forceSelection:false,
+ /**
+ * @cfg {Number} typeAheadDelay The length of time in milliseconds to wait until the typeahead text is displayed
+ * if typeAhead = true (defaults to 250)
+ */
+ typeAheadDelay : 250,
+ /**
+ * @cfg {String} valueNotFoundText When using a name/value combo, if the value passed to setValue is not found in
+ * the store, valueNotFoundText will be displayed as the field text if defined (defaults to undefined)
+ */
+ valueNotFoundText : undefined,
+ /**
+ * @cfg {Boolean} blockFocus Prevents all focus calls, so it can work with things like HTML edtor bar
+ */
+ blockFocus : false,
+
+ /**
+ * @cfg {Boolean} disableClear Disable showing of clear button.
+ */
+ disableClear : false,
+ /**
+ * @cfg {Boolean} alwaysQuery Disable caching of results, and always send query
+ */
+ alwaysQuery : false,
+
+ //private
+ addicon : false,
+ editicon: false,
+
+ // element that contains real text value.. (when hidden is used..)
+
// private
- onResize: function(w, h){
- Roo.bootstrap.ComboBox.superclass.onResize.apply(this, arguments);
+ initEvents: function(){
- if(typeof w != 'number'){
- // we do not handle it!?!?
- return;
+ if (!this.store) {
+ throw "can not find store for combo";
}
- var tw = this.trigger.getWidth();
- // tw += this.addicon ? this.addicon.getWidth() : 0;
- // tw += this.editicon ? this.editicon.getWidth() : 0;
- var x = w - tw;
- this.inputEl().setWidth( this.adjustWidth('input', x));
-
- //this.trigger.setStyle('left', x+'px');
+ this.store = Roo.factory(this.store, Roo.data);
- if(this.list && this.listWidth === undefined){
- var lw = Math.max(x + this.trigger.getWidth(), this.minListWidth);
- this.list.setWidth(lw);
- this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
- }
-
- },
+ Roo.bootstrap.ComboBox.superclass.initEvents.call(this);
+
+
+ if(this.hiddenName){
+
+ this.hiddenField = this.el.select('input.form-hidden-field',true).first();
+
+ this.hiddenField.dom.value =
+ this.hiddenValue !== undefined ? this.hiddenValue :
+ this.value !== undefined ? this.value : '';
- /**
- * Allow or prevent the user from directly editing the field text. If false is passed,
- * the user will only be able to select from the items defined in the dropdown list. This method
- * is the runtime equivalent of setting the 'editable' config option at config time.
- * @param {Boolean} value True to allow the user to directly edit the field text
- */
- setEditable : function(value){
- if(value == this.editable){
- return;
+ // prevent input submission
+ this.el.dom.removeAttribute('name');
+ this.hiddenField.dom.setAttribute('name', this.hiddenName);
+
+
}
- this.editable = value;
- if(!value){
- this.inputEl().dom.setAttribute('readOnly', true);
- this.inputEl().on('mousedown', this.onTriggerClick, this);
- this.inputEl().addClass('x-combo-noedit');
- }else{
- this.inputEl().dom.setAttribute('readOnly', false);
- this.inputEl().un('mousedown', this.onTriggerClick, this);
+ //if(Roo.isGecko){
+ // this.el.dom.setAttribute('autocomplete', 'off');
+ //}
+
+ var cls = 'x-combo-list';
+ this.list = this.el.select('ul',true).first();
+
+ //this.list = new Roo.Layer({
+ // shadow: this.shadow, cls: [cls, this.listClass].join(' '), constrain:false
+ //});
+
+ var lw = this.listWidth || Math.max(this.inputEl().getWidth(), this.minListWidth);
+ this.list.setWidth(lw);
+ /*
+ this.list.swallowEvent('mousewheel');
+ this.assetHeight = 0;
+
+ if(this.title){
+ this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
+ this.assetHeight += this.header.getHeight();
+ }
+
+ this.innerList = this.list.createChild({cls:cls+'-inner'});
+ this.innerList.on('mouseover', this.onViewOver, this);
+ this.innerList.on('mousemove', this.onViewMove, this);
+ this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
+
+ if(this.allowBlank && !this.pageSize && !this.disableClear){
+ this.footer = this.list.createChild({cls:cls+'-ft'});
+ this.pageTb = new Roo.Toolbar(this.footer);
+
+ }
+ if(this.pageSize){
+ this.footer = this.list.createChild({cls:cls+'-ft'});
+ this.pageTb = new Roo.PagingToolbar(this.footer, this.store,
+ {pageSize: this.pageSize});
+
+ }
+
+ if (this.pageTb && this.allowBlank && !this.disableClear) {
+ var _this = this;
+ this.pageTb.add(new Roo.Toolbar.Fill(), {
+ cls: 'x-btn-icon x-btn-clear',
+ text: ' ',
+ handler: function()
+ {
+ _this.collapse();
+ _this.clearValue();
+ _this.onSelect(false, -1);
+ }
+ });
+ }
+ if (this.footer) {
+ this.assetHeight += this.footer.getHeight();
+ }
+ */
+
+ if(!this.tpl){
+ this.tpl = '<li><a href="#">{' + this.displayField + '}</a></li>';
+ }
+
+ this.view = new Roo.View(this.el.select('ul',true).first(), this.tpl, {
+ singleSelect:true, store: this.store, selectedClass: this.selectedClass
+ });
+ //this.view.wrapEl.setDisplayed(false);
+ this.view.on('click', this.onViewClick, this);
+
+
+
+ this.store.on('beforeload', this.onBeforeLoad, this);
+ this.store.on('load', this.onLoad, this);
+ this.store.on('loadexception', this.onLoadException, this);
+ /*
+ if(this.resizable){
+ this.resizer = new Roo.Resizable(this.list, {
+ pinned:true, handles:'se'
+ });
+ this.resizer.on('resize', function(r, w, h){
+ this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight;
+ this.listWidth = w;
+ this.innerList.setWidth(w - this.list.getFrameWidth('lr'));
+ this.restrictHeight();
+ }, this);
+ this[this.pageSize?'footer':'innerList'].setStyle('margin-bottom', this.handleHeight+'px');
+ }
+ */
+ if(!this.editable){
+ this.editable = true;
+ this.setEditable(false);
+ }
+
+ /*
+
+ if (typeof(this.events.add.listeners) != 'undefined') {
+
+ this.addicon = this.wrap.createChild(
+ {tag: 'img', src: Roo.BLANK_IMAGE_URL, cls: 'x-form-combo-add' });
+
+ this.addicon.on('click', function(e) {
+ this.fireEvent('add', this);
+ }, this);
+ }
+ if (typeof(this.events.edit.listeners) != 'undefined') {
+
+ this.editicon = this.wrap.createChild(
+ {tag: 'img', src: Roo.BLANK_IMAGE_URL, cls: 'x-form-combo-edit' });
+ if (this.addicon) {
+ this.editicon.setStyle('margin-left', '40px');
+ }
+ this.editicon.on('click', function(e) {
+
+ // we fire even if inothing is selected..
+ this.fireEvent('edit', this, this.lastData );
+
+ }, this);
+ }
+ */
+
+
+ this.keyNav = new Roo.KeyNav(this.inputEl(), {
+ "up" : function(e){
+ this.inKeyMode = true;
+ this.selectPrev();
+ },
+
+ "down" : function(e){
+ if(!this.isExpanded()){
+ this.onTriggerClick();
+ }else{
+ this.inKeyMode = true;
+ this.selectNext();
+ }
+ },
+
+ "enter" : function(e){
+ this.onViewClick();
+ //return true;
+ },
+
+ "esc" : function(e){
+ this.collapse();
+ },
+
+ "tab" : function(e){
+ this.onViewClick(false);
+ this.fireEvent("specialkey", this, e);
+ return true;
+ },
+
+ scope : this,
+
+ doRelay : function(foo, bar, hname){
+ if(hname == 'down' || this.scope.isExpanded()){
+ return Roo.KeyNav.prototype.doRelay.apply(this, arguments);
+ }
+ return true;
+ },
+
+ forceKeyDown: true
+ });
+
+
+ this.queryDelay = Math.max(this.queryDelay || 10,
+ this.mode == 'local' ? 10 : 250);
+
+
+ this.dqTask = new Roo.util.DelayedTask(this.initQuery, this);
+
+ if(this.typeAhead){
+ this.taTask = new Roo.util.DelayedTask(this.onTypeAhead, this);
+ }
+ if(this.editable !== false){
+ this.inputEl().on("keyup", this.onKeyUp, this);
+ }
+ if(this.forceSelection){
+ this.on('blur', this.doForce, this);
+ }
+ },
+
+ onDestroy : function(){
+ if(this.view){
+ this.view.setStore(null);
+ this.view.el.removeAllListeners();
+ this.view.el.remove();
+ this.view.purgeListeners();
+ }
+ if(this.list){
+ this.list.dom.innerHTML = '';
+ }
+ if(this.store){
+ this.store.un('beforeload', this.onBeforeLoad, this);
+ this.store.un('load', this.onLoad, this);
+ this.store.un('loadexception', this.onLoadException, this);
+ }
+ Roo.bootstrap.ComboBox.superclass.onDestroy.call(this);
+ },
+
+ // private
+ fireKey : function(e){
+ if(e.isNavKeyPress() && !this.list.isVisible()){
+ this.fireEvent("specialkey", this, e);
+ }
+ },
+
+ // private
+ onResize: function(w, h){
+ Roo.bootstrap.ComboBox.superclass.onResize.apply(this, arguments);
+
+ if(typeof w != 'number'){
+ // we do not handle it!?!?
+ return;
+ }
+ var tw = this.trigger.getWidth();
+ // tw += this.addicon ? this.addicon.getWidth() : 0;
+ // tw += this.editicon ? this.editicon.getWidth() : 0;
+ var x = w - tw;
+ this.inputEl().setWidth( this.adjustWidth('input', x));
+
+ //this.trigger.setStyle('left', x+'px');
+
+ if(this.list && this.listWidth === undefined){
+ var lw = Math.max(x + this.trigger.getWidth(), this.minListWidth);
+ this.list.setWidth(lw);
+ this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
+ }
+
+
+
+ },
+
+ /**
+ * Allow or prevent the user from directly editing the field text. If false is passed,
+ * the user will only be able to select from the items defined in the dropdown list. This method
+ * is the runtime equivalent of setting the 'editable' config option at config time.
+ * @param {Boolean} value True to allow the user to directly edit the field text
+ */
+ setEditable : function(value){
+ if(value == this.editable){
+ return;
+ }
+ this.editable = value;
+ if(!value){
+ this.inputEl().dom.setAttribute('readOnly', true);
+ this.inputEl().on('mousedown', this.onTriggerClick, this);
+ this.inputEl().addClass('x-combo-noedit');
+ }else{
+ this.inputEl().dom.setAttribute('readOnly', false);
+ this.inputEl().un('mousedown', this.onTriggerClick, this);
this.inputEl().removeClass('x-combo-noedit');
}
},
},
/**
- * Returns the currently selected field value or empty string if no value is set.
- * @return {String} value The selected value
+ * Returns the currently selected field value or empty string if no value is set.
+ * @return {String} value The selected value
+ */
+ getValue : function(){
+ if(this.valueField){
+ return typeof this.value != 'undefined' ? this.value : '';
+ }else{
+ return Roo.bootstrap.ComboBox.superclass.getValue.call(this);
+ }
+ },
+
+ /**
+ * Clears any text/value currently set in the field
+ */
+ clearValue : function(){
+ if(this.hiddenField){
+ this.hiddenField.dom.value = '';
+ }
+ this.value = '';
+ this.setRawValue('');
+ this.lastSelectionText = '';
+
+ },
+
+ /**
+ * Sets the specified value into the field. If the value finds a match, the corresponding record text
+ * will be displayed in the field. If the value does not match the data value of an existing item,
+ * and the valueNotFoundText config option is defined, it will be displayed as the default field text.
+ * Otherwise the field will be blank (although the value will still be set).
+ * @param {String} value The value to match
+ */
+ setValue : function(v){
+ var text = v;
+ if(this.valueField){
+ var r = this.findRecord(this.valueField, v);
+ if(r){
+ text = r.data[this.displayField];
+ }else if(this.valueNotFoundText !== undefined){
+ text = this.valueNotFoundText;
+ }
+ }
+ this.lastSelectionText = text;
+ if(this.hiddenField){
+ this.hiddenField.dom.value = v;
+ }
+ Roo.bootstrap.ComboBox.superclass.setValue.call(this, text);
+ this.value = v;
+ },
+ /**
+ * @property {Object} the last set data for the element
+ */
+
+ lastData : false,
+ /**
+ * Sets the value of the field based on a object which is related to the record format for the store.
+ * @param {Object} value the value to set as. or false on reset?
+ */
+ setFromData : function(o){
+ var dv = ''; // display value
+ var vv = ''; // value value..
+ this.lastData = o;
+ if (this.displayField) {
+ dv = !o || typeof(o[this.displayField]) == 'undefined' ? '' : o[this.displayField];
+ } else {
+ // this is an error condition!!!
+ Roo.log('no displayField value set for '+ (this.name ? this.name : this.id));
+ }
+
+ if(this.valueField){
+ vv = !o || typeof(o[this.valueField]) == 'undefined' ? dv : o[this.valueField];
+ }
+ if(this.hiddenField){
+ this.hiddenField.dom.value = vv;
+
+ this.lastSelectionText = dv;
+ Roo.bootstrap.ComboBox.superclass.setValue.call(this, dv);
+ this.value = vv;
+ return;
+ }
+ // no hidden field.. - we store the value in 'value', but still display
+ // display field!!!!
+ this.lastSelectionText = dv;
+ Roo.bootstrap.ComboBox.superclass.setValue.call(this, dv);
+ this.value = vv;
+
+
+ },
+ // private
+ reset : function(){
+ // overridden so that last data is reset..
+ this.setValue(this.originalValue);
+ this.clearInvalid();
+ this.lastData = false;
+ if (this.view) {
+ this.view.clearSelections();
+ }
+ },
+ // private
+ findRecord : function(prop, value){
+ var record;
+ if(this.store.getCount() > 0){
+ this.store.each(function(r){
+ if(r.data[prop] == value){
+ record = r;
+ return false;
+ }
+ return true;
+ });
+ }
+ return record;
+ },
+
+ getName: function()
+ {
+ // returns hidden if it's set..
+ if (!this.rendered) {return ''};
+ return !this.hiddenName && this.inputEl().dom.name ? this.inputEl().dom.name : (this.hiddenName || '');
+
+ },
+ // private
+ onViewMove : function(e, t){
+ this.inKeyMode = false;
+ },
+
+ // private
+ onViewOver : function(e, t){
+ if(this.inKeyMode){ // prevent key nav and mouse over conflicts
+ return;
+ }
+ var item = this.view.findItemFromChild(t);
+ if(item){
+ var index = this.view.indexOf(item);
+ this.select(index, false);
+ }
+ },
+
+ // private
+ onViewClick : function(doFocus)
+ {
+ var index = this.view.getSelectedIndexes()[0];
+ var r = this.store.getAt(index);
+ if(r){
+ this.onSelect(r, index);
+ }
+ if(doFocus !== false && !this.blockFocus){
+ this.inputEl().focus();
+ }
+ },
+
+ // private
+ restrictHeight : function(){
+ //this.innerList.dom.style.height = '';
+ //var inner = this.innerList.dom;
+ //var h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight);
+ //this.innerList.setHeight(h < this.maxHeight ? 'auto' : this.maxHeight);
+ //this.list.beginUpdate();
+ //this.list.setHeight(this.innerList.getHeight()+this.list.getFrameWidth('tb')+(this.resizable?this.handleHeight:0)+this.assetHeight);
+ this.list.alignTo(this.inputEl(), this.listAlign);
+ //this.list.endUpdate();
+ },
+
+ // private
+ onEmptyResults : function(){
+ this.collapse();
+ },
+
+ /**
+ * Returns true if the dropdown list is expanded, else false.
+ */
+ isExpanded : function(){
+ return this.list.isVisible();
+ },
+
+ /**
+ * Select an item in the dropdown list by its data value. This function does NOT cause the select event to fire.
+ * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
+ * @param {String} value The data value of the item to select
+ * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
+ * selected item if it is not currently in view (defaults to true)
+ * @return {Boolean} True if the value matched an item in the list, else false
+ */
+ selectByValue : function(v, scrollIntoView){
+ if(v !== undefined && v !== null){
+ var r = this.findRecord(this.valueField || this.displayField, v);
+ if(r){
+ this.select(this.store.indexOf(r), scrollIntoView);
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Select an item in the dropdown list by its numeric index in the list. This function does NOT cause the select event to fire.
+ * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
+ * @param {Number} index The zero-based index of the list item to select
+ * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
+ * selected item if it is not currently in view (defaults to true)
+ */
+ select : function(index, scrollIntoView){
+ this.selectedIndex = index;
+ this.view.select(index);
+ if(scrollIntoView !== false){
+ var el = this.view.getNode(index);
+ if(el){
+ //this.innerList.scrollChildIntoView(el, false);
+
+ }
+ }
+ },
+
+ // private
+ selectNext : function(){
+ var ct = this.store.getCount();
+ if(ct > 0){
+ if(this.selectedIndex == -1){
+ this.select(0);
+ }else if(this.selectedIndex < ct-1){
+ this.select(this.selectedIndex+1);
+ }
+ }
+ },
+
+ // private
+ selectPrev : function(){
+ var ct = this.store.getCount();
+ if(ct > 0){
+ if(this.selectedIndex == -1){
+ this.select(0);
+ }else if(this.selectedIndex != 0){
+ this.select(this.selectedIndex-1);
+ }
+ }
+ },
+
+ // private
+ onKeyUp : function(e){
+ if(this.editable !== false && !e.isSpecialKey()){
+ this.lastKey = e.getKey();
+ this.dqTask.delay(this.queryDelay);
+ }
+ },
+
+ // private
+ validateBlur : function(){
+ return !this.list || !this.list.isVisible();
+ },
+
+ // private
+ initQuery : function(){
+ this.doQuery(this.getRawValue());
+ },
+
+ // private
+ doForce : function(){
+ if(this.el.dom.value.length > 0){
+ this.el.dom.value =
+ this.lastSelectionText === undefined ? '' : this.lastSelectionText;
+
+ }
+ },
+
+ /**
+ * Execute a query to filter the dropdown list. Fires the beforequery event prior to performing the
+ * query allowing the query action to be canceled if needed.
+ * @param {String} query The SQL query to execute
+ * @param {Boolean} forceAll True to force the query to execute even if there are currently fewer characters
+ * in the field than the minimum specified by the minChars config option. It also clears any filter previously
+ * saved in the current store (defaults to false)
+ */
+ doQuery : function(q, forceAll){
+ if(q === undefined || q === null){
+ q = '';
+ }
+ var qe = {
+ query: q,
+ forceAll: forceAll,
+ combo: this,
+ cancel:false
+ };
+ if(this.fireEvent('beforequery', qe)===false || qe.cancel){
+ return false;
+ }
+ q = qe.query;
+ forceAll = qe.forceAll;
+ if(forceAll === true || (q.length >= this.minChars)){
+ if(this.lastQuery != q || this.alwaysQuery){
+ this.lastQuery = q;
+ if(this.mode == 'local'){
+ this.selectedIndex = -1;
+ if(forceAll){
+ this.store.clearFilter();
+ }else{
+ this.store.filter(this.displayField, q);
+ }
+ this.onLoad();
+ }else{
+ this.store.baseParams[this.queryParam] = q;
+ this.store.load({
+ params: this.getParams(q)
+ });
+ this.expand();
+ }
+ }else{
+ this.selectedIndex = -1;
+ this.onLoad();
+ }
+ }
+ },
+
+ // private
+ getParams : function(q){
+ var p = {};
+ //p[this.queryParam] = q;
+ if(this.pageSize){
+ p.start = 0;
+ p.limit = this.pageSize;
+ }
+ return p;
+ },
+
+ /**
+ * Hides the dropdown list if it is currently expanded. Fires the 'collapse' event on completion.
+ */
+ collapse : function(){
+ if(!this.isExpanded()){
+ return;
+ }
+ this.list.hide();
+ Roo.get(document).un('mousedown', this.collapseIf, this);
+ Roo.get(document).un('mousewheel', this.collapseIf, this);
+ if (!this.editable) {
+ Roo.get(document).un('keydown', this.listKeyPress, this);
+ }
+ this.fireEvent('collapse', this);
+ },
+
+ // private
+ collapseIf : function(e){
+ if(!e.within(this.el) && !e.within(this.el)){
+ this.collapse();
+ }
+ },
+
+ /**
+ * Expands the dropdown list if it is currently hidden. Fires the 'expand' event on completion.
+ */
+ expand : function(){
+ Roo.log('expand');
+ if(this.isExpanded() || !this.hasFocus){
+ return;
+ }
+ this.list.alignTo(this.inputEl(), this.listAlign);
+ this.list.show();
+ Roo.get(document).on('mousedown', this.collapseIf, this);
+ Roo.get(document).on('mousewheel', this.collapseIf, this);
+ if (!this.editable) {
+ Roo.get(document).on('keydown', this.listKeyPress, this);
+ }
+
+ this.fireEvent('expand', this);
+ },
+
+ // private
+ // Implements the default empty TriggerField.onTriggerClick function
+ onTriggerClick : function()
+ {
+ Roo.log('trigger click');
+
+ if(this.disabled){
+ return;
+ }
+ if(this.isExpanded()){
+ this.collapse();
+ if (!this.blockFocus) {
+ this.inputEl().focus();
+ }
+
+ }else {
+ this.hasFocus = true;
+ if(this.triggerAction == 'all') {
+ this.doQuery(this.allQuery, true);
+ } else {
+ this.doQuery(this.getRawValue());
+ }
+ if (!this.blockFocus) {
+ this.inputEl().focus();
+ }
+ }
+ },
+ listKeyPress : function(e)
+ {
+ //Roo.log('listkeypress');
+ // scroll to first matching element based on key pres..
+ if (e.isSpecialKey()) {
+ return false;
+ }
+ var k = String.fromCharCode(e.getKey()).toUpperCase();
+ //Roo.log(k);
+ var match = false;
+ var csel = this.view.getSelectedNodes();
+ var cselitem = false;
+ if (csel.length) {
+ var ix = this.view.indexOf(csel[0]);
+ cselitem = this.store.getAt(ix);
+ if (!cselitem.get(this.displayField) || cselitem.get(this.displayField).substring(0,1).toUpperCase() != k) {
+ cselitem = false;
+ }
+
+ }
+
+ this.store.each(function(v) {
+ if (cselitem) {
+ // start at existing selection.
+ if (cselitem.id == v.id) {
+ cselitem = false;
+ }
+ return true;
+ }
+
+ if (v.get(this.displayField) && v.get(this.displayField).substring(0,1).toUpperCase() == k) {
+ match = this.store.indexOf(v);
+ return false;
+ }
+ return true;
+ }, this);
+
+ if (match === false) {
+ return true; // no more action?
+ }
+ // scroll to?
+ this.view.select(match);
+ var sn = Roo.get(this.view.getSelectedNodes()[0])
+ //sn.scrollIntoView(sn.dom.parentNode, false);
+ }
+
+ /**
+ * @cfg {Boolean} grow
+ * @hide
+ */
+ /**
+ * @cfg {Number} growMin
+ * @hide
+ */
+ /**
+ * @cfg {Number} growMax
+ * @hide
+ */
+ /**
+ * @hide
+ * @method autoSize
+ */
+});/*
+ * Based on:
+ * Ext JS Library 1.1.1
+ * Copyright(c) 2006-2007, Ext JS, LLC.
+ *
+ * Originally Released Under LGPL - original licence link has changed is not relivant.
+ *
+ * Fork - LGPL
+ * <script type="text/javascript">
+ */
+
+/**
+ * @class Roo.View
+ * @extends Roo.util.Observable
+ * Create a "View" for an element based on a data model or UpdateManager and the supplied DomHelper template.
+ * This class also supports single and multi selection modes. <br>
+ * Create a data model bound view:
+ <pre><code>
+ var store = new Roo.data.Store(...);
+
+ var view = new Roo.View({
+ el : "my-element",
+ tpl : '<div id="{0}">{2} - {1}</div>', // auto create template
+
+ singleSelect: true,
+ selectedClass: "ydataview-selected",
+ store: store
+ });
+
+ // listen for node click?
+ view.on("click", function(vw, index, node, e){
+ alert('Node "' + node.id + '" at index: ' + index + " was clicked.");
+ });
+
+ // load XML data
+ dataModel.load("foobar.xml");
+ </code></pre>
+ For an example of creating a JSON/UpdateManager view, see {@link Roo.JsonView}.
+ * <br><br>
+ * <b>Note: The root of your template must be a single node. Table/row implementations may work but are not supported due to
+ * IE"s limited insertion support with tables and Opera"s faulty event bubbling.</b>
+ *
+ * Note: old style constructor is still suported (container, template, config)
+ *
+ * @constructor
+ * Create a new View
+ * @param {Object} config The config object
+ *
+ */
+Roo.View = function(config, depreciated_tpl, depreciated_config){
+
+ if (typeof(depreciated_tpl) == 'undefined') {
+ // new way.. - universal constructor.
+ Roo.apply(this, config);
+ this.el = Roo.get(this.el);
+ } else {
+ // old format..
+ this.el = Roo.get(config);
+ this.tpl = depreciated_tpl;
+ Roo.apply(this, depreciated_config);
+ }
+ this.wrapEl = this.el.wrap().wrap();
+ ///this.el = this.wrapEla.appendChild(document.createElement("div"));
+
+
+ if(typeof(this.tpl) == "string"){
+ this.tpl = new Roo.Template(this.tpl);
+ } else {
+ // support xtype ctors..
+ this.tpl = new Roo.factory(this.tpl, Roo);
+ }
+
+
+ this.tpl.compile();
+
+
+
+
+ /** @private */
+ this.addEvents({
+ /**
+ * @event beforeclick
+ * Fires before a click is processed. Returns false to cancel the default action.
+ * @param {Roo.View} this
+ * @param {Number} index The index of the target node
+ * @param {HTMLElement} node The target node
+ * @param {Roo.EventObject} e The raw event object
+ */
+ "beforeclick" : true,
+ /**
+ * @event click
+ * Fires when a template node is clicked.
+ * @param {Roo.View} this
+ * @param {Number} index The index of the target node
+ * @param {HTMLElement} node The target node
+ * @param {Roo.EventObject} e The raw event object
+ */
+ "click" : true,
+ /**
+ * @event dblclick
+ * Fires when a template node is double clicked.
+ * @param {Roo.View} this
+ * @param {Number} index The index of the target node
+ * @param {HTMLElement} node The target node
+ * @param {Roo.EventObject} e The raw event object
+ */
+ "dblclick" : true,
+ /**
+ * @event contextmenu
+ * Fires when a template node is right clicked.
+ * @param {Roo.View} this
+ * @param {Number} index The index of the target node
+ * @param {HTMLElement} node The target node
+ * @param {Roo.EventObject} e The raw event object
+ */
+ "contextmenu" : true,
+ /**
+ * @event selectionchange
+ * Fires when the selected nodes change.
+ * @param {Roo.View} this
+ * @param {Array} selections Array of the selected nodes
+ */
+ "selectionchange" : true,
+
+ /**
+ * @event beforeselect
+ * Fires before a selection is made. If any handlers return false, the selection is cancelled.
+ * @param {Roo.View} this
+ * @param {HTMLElement} node The node to be selected
+ * @param {Array} selections Array of currently selected nodes
+ */
+ "beforeselect" : true,
+ /**
+ * @event preparedata
+ * Fires on every row to render, to allow you to change the data.
+ * @param {Roo.View} this
+ * @param {Object} data to be rendered (change this)
+ */
+ "preparedata" : true
+
+
+ });
+
+
+
+ this.el.on({
+ "click": this.onClick,
+ "dblclick": this.onDblClick,
+ "contextmenu": this.onContextMenu,
+ scope:this
+ });
+
+ this.selections = [];
+ this.nodes = [];
+ this.cmp = new Roo.CompositeElementLite([]);
+ if(this.store){
+ this.store = Roo.factory(this.store, Roo.data);
+ this.setStore(this.store, true);
+ }
+
+ if ( this.footer && this.footer.xtype) {
+
+ var fctr = this.wrapEl.appendChild(document.createElement("div"));
+
+ this.footer.dataSource = this.store
+ this.footer.container = fctr;
+ this.footer = Roo.factory(this.footer, Roo);
+ fctr.insertFirst(this.el);
+
+ // this is a bit insane - as the paging toolbar seems to detach the el..
+// dom.parentNode.parentNode.parentNode
+ // they get detached?
+ }
+
+
+ Roo.View.superclass.constructor.call(this);
+
+
+};
+
+Roo.extend(Roo.View, Roo.util.Observable, {
+
+ /**
+ * @cfg {Roo.data.Store} store Data store to load data from.
+ */
+ store : false,
+
+ /**
+ * @cfg {String|Roo.Element} el The container element.
*/
- getValue : function(){
- if(this.valueField){
- return typeof this.value != 'undefined' ? this.value : '';
- }else{
- return Roo.bootstrap.ComboBox.superclass.getValue.call(this);
- }
- },
-
+ el : '',
+
/**
- * Clears any text/value currently set in the field
+ * @cfg {String|Roo.Template} tpl The template used by this View
*/
- clearValue : function(){
- if(this.hiddenField){
- this.hiddenField.dom.value = '';
- }
- this.value = '';
- this.setRawValue('');
- this.lastSelectionText = '';
-
- },
-
+ tpl : false,
/**
- * Sets the specified value into the field. If the value finds a match, the corresponding record text
- * will be displayed in the field. If the value does not match the data value of an existing item,
- * and the valueNotFoundText config option is defined, it will be displayed as the default field text.
- * Otherwise the field will be blank (although the value will still be set).
- * @param {String} value The value to match
+ * @cfg {String} dataName the named area of the template to use as the data area
+ * Works with domtemplates roo-name="name"
*/
- setValue : function(v){
- var text = v;
- if(this.valueField){
- var r = this.findRecord(this.valueField, v);
- if(r){
- text = r.data[this.displayField];
- }else if(this.valueNotFoundText !== undefined){
- text = this.valueNotFoundText;
- }
- }
- this.lastSelectionText = text;
- if(this.hiddenField){
- this.hiddenField.dom.value = v;
- }
- Roo.bootstrap.ComboBox.superclass.setValue.call(this, text);
- this.value = v;
- },
+ dataName: false,
/**
- * @property {Object} the last set data for the element
+ * @cfg {String} selectedClass The css class to add to selected nodes
+ */
+ selectedClass : "x-view-selected",
+ /**
+ * @cfg {String} emptyText The empty text to show when nothing is loaded.
*/
+ emptyText : "",
- lastData : false,
/**
- * Sets the value of the field based on a object which is related to the record format for the store.
- * @param {Object} value the value to set as. or false on reset?
+ * @cfg {String} text to display on mask (default Loading)
*/
- setFromData : function(o){
- var dv = ''; // display value
- var vv = ''; // value value..
- this.lastData = o;
- if (this.displayField) {
- dv = !o || typeof(o[this.displayField]) == 'undefined' ? '' : o[this.displayField];
- } else {
- // this is an error condition!!!
- Roo.log('no displayField value set for '+ (this.name ? this.name : this.id));
- }
+ mask : false,
+ /**
+ * @cfg {Boolean} multiSelect Allow multiple selection
+ */
+ multiSelect : false,
+ /**
+ * @cfg {Boolean} singleSelect Allow single selection
+ */
+ singleSelect: false,
+
+ /**
+ * @cfg {Boolean} toggleSelect - selecting
+ */
+ toggleSelect : false,
+
+ /**
+ * Returns the element this view is bound to.
+ * @return {Roo.Element}
+ */
+ getEl : function(){
+ return this.wrapEl;
+ },
+
+
+
+ /**
+ * Refreshes the view. - called by datachanged on the store. - do not call directly.
+ */
+ refresh : function(){
+ var t = this.tpl;
- if(this.valueField){
- vv = !o || typeof(o[this.valueField]) == 'undefined' ? dv : o[this.valueField];
- }
- if(this.hiddenField){
- this.hiddenField.dom.value = vv;
+ // if we are using something like 'domtemplate', then
+ // the what gets used is:
+ // t.applySubtemplate(NAME, data, wrapping data..)
+ // the outer template then get' applied with
+ // the store 'extra data'
+ // and the body get's added to the
+ // roo-name="data" node?
+ // <span class='roo-tpl-{name}'></span> ?????
+
+
+
+ this.clearSelections();
+ this.el.update("");
+ var html = [];
+ var records = this.store.getRange();
+ if(records.length < 1) {
- this.lastSelectionText = dv;
- Roo.bootstrap.ComboBox.superclass.setValue.call(this, dv);
- this.value = vv;
+ // is this valid?? = should it render a template??
+
+ this.el.update(this.emptyText);
return;
}
- // no hidden field.. - we store the value in 'value', but still display
- // display field!!!!
- this.lastSelectionText = dv;
- Roo.bootstrap.ComboBox.superclass.setValue.call(this, dv);
- this.value = vv;
-
-
- },
- // private
- reset : function(){
- // overridden so that last data is reset..
- this.setValue(this.originalValue);
- this.clearInvalid();
- this.lastData = false;
- if (this.view) {
- this.view.clearSelections();
+ var el = this.el;
+ if (this.dataName) {
+ this.el.update(t.apply(this.store.meta)); //????
+ el = this.el.child('.roo-tpl-' + this.dataName);
}
- },
- // private
- findRecord : function(prop, value){
- var record;
- if(this.store.getCount() > 0){
- this.store.each(function(r){
- if(r.data[prop] == value){
- record = r;
- return false;
- }
- return true;
- });
+
+ for(var i = 0, len = records.length; i < len; i++){
+ var data = this.prepareData(records[i].data, i, records[i]);
+ this.fireEvent("preparedata", this, data, i, records[i]);
+ html[html.length] = Roo.util.Format.trim(
+ this.dataName ?
+ t.applySubtemplate(this.dataName, data, this.store.meta) :
+ t.apply(data)
+ );
}
- return record;
- },
-
- getName: function()
- {
- // returns hidden if it's set..
- if (!this.rendered) {return ''};
- return !this.hiddenName && this.inputEl().dom.name ? this.inputEl().dom.name : (this.hiddenName || '');
+
+
+ el.update(html.join(""));
+ this.nodes = el.dom.childNodes;
+ this.updateIndexes(0);
},
- // private
- onViewMove : function(e, t){
- this.inKeyMode = false;
+
+ /**
+ * Function to override to reformat the data that is sent to
+ * the template for each node.
+ * DEPRICATED - use the preparedata event handler.
+ * @param {Array/Object} data The raw data (array of colData for a data model bound view or
+ * a JSON object for an UpdateManager bound view).
+ */
+ prepareData : function(data, index, record)
+ {
+ this.fireEvent("preparedata", this, data, index, record);
+ return data;
},
- // private
- onViewOver : function(e, t){
- if(this.inKeyMode){ // prevent key nav and mouse over conflicts
- return;
- }
- var item = this.view.findItemFromChild(t);
- if(item){
- var index = this.view.indexOf(item);
- this.select(index, false);
- }
+ onUpdate : function(ds, record){
+ this.clearSelections();
+ var index = this.store.indexOf(record);
+ var n = this.nodes[index];
+ this.tpl.insertBefore(n, this.prepareData(record.data, index, record));
+ n.parentNode.removeChild(n);
+ this.updateIndexes(index, index);
},
- // private
- onViewClick : function(doFocus)
+
+
+// --------- FIXME
+ onAdd : function(ds, records, index)
{
- var index = this.view.getSelectedIndexes()[0];
- var r = this.store.getAt(index);
- if(r){
- this.onSelect(r, index);
+ this.clearSelections();
+ if(this.nodes.length == 0){
+ this.refresh();
+ return;
}
- if(doFocus !== false && !this.blockFocus){
- this.inputEl().focus();
+ var n = this.nodes[index];
+ for(var i = 0, len = records.length; i < len; i++){
+ var d = this.prepareData(records[i].data, i, records[i]);
+ if(n){
+ this.tpl.insertBefore(n, d);
+ }else{
+
+ this.tpl.append(this.el, d);
+ }
}
+ this.updateIndexes(index);
},
- // private
- restrictHeight : function(){
- //this.innerList.dom.style.height = '';
- //var inner = this.innerList.dom;
- //var h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight);
- //this.innerList.setHeight(h < this.maxHeight ? 'auto' : this.maxHeight);
- //this.list.beginUpdate();
- //this.list.setHeight(this.innerList.getHeight()+this.list.getFrameWidth('tb')+(this.resizable?this.handleHeight:0)+this.assetHeight);
- this.list.alignTo(this.inputEl(), this.listAlign);
- //this.list.endUpdate();
- },
-
- // private
- onEmptyResults : function(){
- this.collapse();
+ onRemove : function(ds, record, index){
+ this.clearSelections();
+ var el = this.dataName ?
+ this.el.child('.roo-tpl-' + this.dataName) :
+ this.el;
+ el.dom.removeChild(this.nodes[index]);
+ this.updateIndexes(index);
},
/**
- * Returns true if the dropdown list is expanded, else false.
+ * Refresh an individual node.
+ * @param {Number} index
*/
- isExpanded : function(){
- return this.list.isVisible();
+ refreshNode : function(index){
+ this.onUpdate(this.store, this.store.getAt(index));
},
- /**
- * Select an item in the dropdown list by its data value. This function does NOT cause the select event to fire.
- * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
- * @param {String} value The data value of the item to select
- * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
- * selected item if it is not currently in view (defaults to true)
- * @return {Boolean} True if the value matched an item in the list, else false
- */
- selectByValue : function(v, scrollIntoView){
- if(v !== undefined && v !== null){
- var r = this.findRecord(this.valueField || this.displayField, v);
- if(r){
- this.select(this.store.indexOf(r), scrollIntoView);
- return true;
- }
+ updateIndexes : function(startIndex, endIndex){
+ var ns = this.nodes;
+ startIndex = startIndex || 0;
+ endIndex = endIndex || ns.length - 1;
+ for(var i = startIndex; i <= endIndex; i++){
+ ns[i].nodeIndex = i;
}
- return false;
},
/**
- * Select an item in the dropdown list by its numeric index in the list. This function does NOT cause the select event to fire.
- * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
- * @param {Number} index The zero-based index of the list item to select
- * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
- * selected item if it is not currently in view (defaults to true)
+ * Changes the data store this view uses and refresh the view.
+ * @param {Store} store
*/
- select : function(index, scrollIntoView){
- this.selectedIndex = index;
- this.view.select(index);
- if(scrollIntoView !== false){
- var el = this.view.getNode(index);
- if(el){
- //this.innerList.scrollChildIntoView(el, false);
-
- }
+ setStore : function(store, initial){
+ if(!initial && this.store){
+ this.store.un("datachanged", this.refresh);
+ this.store.un("add", this.onAdd);
+ this.store.un("remove", this.onRemove);
+ this.store.un("update", this.onUpdate);
+ this.store.un("clear", this.refresh);
+ this.store.un("beforeload", this.onBeforeLoad);
+ this.store.un("load", this.onLoad);
+ this.store.un("loadexception", this.onLoad);
+ }
+ if(store){
+
+ store.on("datachanged", this.refresh, this);
+ store.on("add", this.onAdd, this);
+ store.on("remove", this.onRemove, this);
+ store.on("update", this.onUpdate, this);
+ store.on("clear", this.refresh, this);
+ store.on("beforeload", this.onBeforeLoad, this);
+ store.on("load", this.onLoad, this);
+ store.on("loadexception", this.onLoad, this);
+ }
+
+ if(store){
+ this.refresh();
}
},
+ /**
+ * onbeforeLoad - masks the loading area.
+ *
+ */
+ onBeforeLoad : function()
+ {
+ this.el.update("");
+ this.el.mask(this.mask ? this.mask : "Loading" );
+ },
+ onLoad : function ()
+ {
+ this.el.unmask();
+ },
+
- // private
- selectNext : function(){
- var ct = this.store.getCount();
- if(ct > 0){
- if(this.selectedIndex == -1){
- this.select(0);
- }else if(this.selectedIndex < ct-1){
- this.select(this.selectedIndex+1);
+ /**
+ * Returns the template node the passed child belongs to or null if it doesn't belong to one.
+ * @param {HTMLElement} node
+ * @return {HTMLElement} The template node
+ */
+ findItemFromChild : function(node){
+ var el = this.dataName ?
+ this.el.child('.roo-tpl-' + this.dataName,true) :
+ this.el.dom;
+
+ if(!node || node.parentNode == el){
+ return node;
+ }
+ var p = node.parentNode;
+ while(p && p != el){
+ if(p.parentNode == el){
+ return p;
}
+ p = p.parentNode;
}
+ return null;
},
- // private
- selectPrev : function(){
- var ct = this.store.getCount();
- if(ct > 0){
- if(this.selectedIndex == -1){
- this.select(0);
- }else if(this.selectedIndex != 0){
- this.select(this.selectedIndex-1);
+ /** @ignore */
+ onClick : function(e){
+ var item = this.findItemFromChild(e.getTarget());
+ if(item){
+ var index = this.indexOf(item);
+ if(this.onItemClick(item, index, e) !== false){
+ this.fireEvent("click", this, index, item, e);
}
+ }else{
+ this.clearSelections();
}
},
- // private
- onKeyUp : function(e){
- if(this.editable !== false && !e.isSpecialKey()){
- this.lastKey = e.getKey();
- this.dqTask.delay(this.queryDelay);
+ /** @ignore */
+ onContextMenu : function(e){
+ var item = this.findItemFromChild(e.getTarget());
+ if(item){
+ this.fireEvent("contextmenu", this, this.indexOf(item), item, e);
}
},
- // private
- validateBlur : function(){
- return !this.list || !this.list.isVisible();
- },
-
- // private
- initQuery : function(){
- this.doQuery(this.getRawValue());
- },
-
- // private
- doForce : function(){
- if(this.el.dom.value.length > 0){
- this.el.dom.value =
- this.lastSelectionText === undefined ? '' : this.lastSelectionText;
-
+ /** @ignore */
+ onDblClick : function(e){
+ var item = this.findItemFromChild(e.getTarget());
+ if(item){
+ this.fireEvent("dblclick", this, this.indexOf(item), item, e);
}
},
- /**
- * Execute a query to filter the dropdown list. Fires the beforequery event prior to performing the
- * query allowing the query action to be canceled if needed.
- * @param {String} query The SQL query to execute
- * @param {Boolean} forceAll True to force the query to execute even if there are currently fewer characters
- * in the field than the minimum specified by the minChars config option. It also clears any filter previously
- * saved in the current store (defaults to false)
- */
- doQuery : function(q, forceAll){
- if(q === undefined || q === null){
- q = '';
- }
- var qe = {
- query: q,
- forceAll: forceAll,
- combo: this,
- cancel:false
- };
- if(this.fireEvent('beforequery', qe)===false || qe.cancel){
+ onItemClick : function(item, index, e)
+ {
+ if(this.fireEvent("beforeclick", this, index, item, e) === false){
return false;
}
- q = qe.query;
- forceAll = qe.forceAll;
- if(forceAll === true || (q.length >= this.minChars)){
- if(this.lastQuery != q || this.alwaysQuery){
- this.lastQuery = q;
- if(this.mode == 'local'){
- this.selectedIndex = -1;
- if(forceAll){
- this.store.clearFilter();
- }else{
- this.store.filter(this.displayField, q);
- }
- this.onLoad();
- }else{
- this.store.baseParams[this.queryParam] = q;
- this.store.load({
- params: this.getParams(q)
- });
- this.expand();
- }
+ if (this.toggleSelect) {
+ var m = this.isSelected(item) ? 'unselect' : 'select';
+ Roo.log(m);
+ var _t = this;
+ _t[m](item, true, false);
+ return true;
+ }
+ if(this.multiSelect || this.singleSelect){
+ if(this.multiSelect && e.shiftKey && this.lastSelection){
+ this.select(this.getNodes(this.indexOf(this.lastSelection), index), false);
}else{
- this.selectedIndex = -1;
- this.onLoad();
+ this.select(item, this.multiSelect && e.ctrlKey);
+ this.lastSelection = item;
}
+ e.preventDefault();
}
+ return true;
+ },
+
+ /**
+ * Get the number of selected nodes.
+ * @return {Number}
+ */
+ getSelectionCount : function(){
+ return this.selections.length;
},
- // private
- getParams : function(q){
- var p = {};
- //p[this.queryParam] = q;
- if(this.pageSize){
- p.start = 0;
- p.limit = this.pageSize;
- }
- return p;
+ /**
+ * Get the currently selected nodes.
+ * @return {Array} An array of HTMLElements
+ */
+ getSelectedNodes : function(){
+ return this.selections;
},
/**
- * Hides the dropdown list if it is currently expanded. Fires the 'collapse' event on completion.
+ * Get the indexes of the selected nodes.
+ * @return {Array}
*/
- collapse : function(){
- if(!this.isExpanded()){
- return;
- }
- this.list.hide();
- Roo.get(document).un('mousedown', this.collapseIf, this);
- Roo.get(document).un('mousewheel', this.collapseIf, this);
- if (!this.editable) {
- Roo.get(document).un('keydown', this.listKeyPress, this);
+ getSelectedIndexes : function(){
+ var indexes = [], s = this.selections;
+ for(var i = 0, len = s.length; i < len; i++){
+ indexes.push(s[i].nodeIndex);
}
- this.fireEvent('collapse', this);
+ return indexes;
},
- // private
- collapseIf : function(e){
- if(!e.within(this.el) && !e.within(this.el)){
- this.collapse();
+ /**
+ * Clear all selections
+ * @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange event
+ */
+ clearSelections : function(suppressEvent){
+ if(this.nodes && (this.multiSelect || this.singleSelect) && this.selections.length > 0){
+ this.cmp.elements = this.selections;
+ this.cmp.removeClass(this.selectedClass);
+ this.selections = [];
+ if(!suppressEvent){
+ this.fireEvent("selectionchange", this, this.selections);
+ }
}
},
/**
- * Expands the dropdown list if it is currently hidden. Fires the 'expand' event on completion.
+ * Returns true if the passed node is selected
+ * @param {HTMLElement/Number} node The node or node index
+ * @return {Boolean}
*/
- expand : function(){
- Roo.log('expand');
- if(this.isExpanded() || !this.hasFocus){
- return;
- }
- this.list.alignTo(this.inputEl(), this.listAlign);
- this.list.show();
- Roo.get(document).on('mousedown', this.collapseIf, this);
- Roo.get(document).on('mousewheel', this.collapseIf, this);
- if (!this.editable) {
- Roo.get(document).on('keydown', this.listKeyPress, this);
+ isSelected : function(node){
+ var s = this.selections;
+ if(s.length < 1){
+ return false;
}
-
- this.fireEvent('expand', this);
+ node = this.getNode(node);
+ return s.indexOf(node) !== -1;
},
- // private
- // Implements the default empty TriggerField.onTriggerClick function
- onTriggerClick : function()
- {
- Roo.log('trigger click');
-
- if(this.disabled){
- return;
- }
- if(this.isExpanded()){
- this.collapse();
- if (!this.blockFocus) {
- this.inputEl().focus();
+ /**
+ * Selects nodes.
+ * @param {Array/HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node, id of a template node or an array of any of those to select
+ * @param {Boolean} keepExisting (optional) true to keep existing selections
+ * @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange vent
+ */
+ select : function(nodeInfo, keepExisting, suppressEvent){
+ if(nodeInfo instanceof Array){
+ if(!keepExisting){
+ this.clearSelections(true);
}
-
- }else {
- this.hasFocus = true;
- if(this.triggerAction == 'all') {
- this.doQuery(this.allQuery, true);
- } else {
- this.doQuery(this.getRawValue());
+ for(var i = 0, len = nodeInfo.length; i < len; i++){
+ this.select(nodeInfo[i], true, true);
}
- if (!this.blockFocus) {
- this.inputEl().focus();
+ return;
+ }
+ var node = this.getNode(nodeInfo);
+ if(!node || this.isSelected(node)){
+ return; // already selected.
+ }
+ if(!keepExisting){
+ this.clearSelections(true);
+ }
+ if(this.fireEvent("beforeselect", this, node, this.selections) !== false){
+ Roo.fly(node).addClass(this.selectedClass);
+ this.selections.push(node);
+ if(!suppressEvent){
+ this.fireEvent("selectionchange", this, this.selections);
}
}
+
+
},
- listKeyPress : function(e)
+ /**
+ * Unselects nodes.
+ * @param {Array/HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node, id of a template node or an array of any of those to select
+ * @param {Boolean} keepExisting (optional) true IGNORED (for campatibility with select)
+ * @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange vent
+ */
+ unselect : function(nodeInfo, keepExisting, suppressEvent)
{
- //Roo.log('listkeypress');
- // scroll to first matching element based on key pres..
- if (e.isSpecialKey()) {
- return false;
+ if(nodeInfo instanceof Array){
+ Roo.each(this.selections, function(s) {
+ this.unselect(s, nodeInfo);
+ }, this);
+ return;
}
- var k = String.fromCharCode(e.getKey()).toUpperCase();
- //Roo.log(k);
- var match = false;
- var csel = this.view.getSelectedNodes();
- var cselitem = false;
- if (csel.length) {
- var ix = this.view.indexOf(csel[0]);
- cselitem = this.store.getAt(ix);
- if (!cselitem.get(this.displayField) || cselitem.get(this.displayField).substring(0,1).toUpperCase() != k) {
- cselitem = false;
- }
-
+ var node = this.getNode(nodeInfo);
+ if(!node || !this.isSelected(node)){
+ Roo.log("not selected");
+ return; // not selected.
}
+ // fireevent???
+ var ns = [];
+ Roo.each(this.selections, function(s) {
+ if (s == node ) {
+ Roo.fly(node).removeClass(this.selectedClass);
+
+ return;
+ }
+ ns.push(s);
+ },this);
- this.store.each(function(v) {
- if (cselitem) {
- // start at existing selection.
- if (cselitem.id == v.id) {
- cselitem = false;
- }
- return true;
+ this.selections= ns;
+ this.fireEvent("selectionchange", this, this.selections);
+ },
+
+ /**
+ * Gets a template node.
+ * @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node
+ * @return {HTMLElement} The node or null if it wasn't found
+ */
+ getNode : function(nodeInfo){
+ if(typeof nodeInfo == "string"){
+ return document.getElementById(nodeInfo);
+ }else if(typeof nodeInfo == "number"){
+ return this.nodes[nodeInfo];
+ }
+ return nodeInfo;
+ },
+
+ /**
+ * Gets a range template nodes.
+ * @param {Number} startIndex
+ * @param {Number} endIndex
+ * @return {Array} An array of nodes
+ */
+ getNodes : function(start, end){
+ var ns = this.nodes;
+ start = start || 0;
+ end = typeof end == "undefined" ? ns.length - 1 : end;
+ var nodes = [];
+ if(start <= end){
+ for(var i = start; i <= end; i++){
+ nodes.push(ns[i]);
}
-
- if (v.get(this.displayField) && v.get(this.displayField).substring(0,1).toUpperCase() == k) {
- match = this.store.indexOf(v);
- return false;
+ } else{
+ for(var i = start; i >= end; i--){
+ nodes.push(ns[i]);
}
- return true;
- }, this);
-
- if (match === false) {
- return true; // no more action?
}
- // scroll to?
- this.view.select(match);
- var sn = Roo.get(this.view.getSelectedNodes()[0])
- //sn.scrollIntoView(sn.dom.parentNode, false);
- }
+ return nodes;
+ },
- /**
- * @cfg {Boolean} grow
- * @hide
- */
- /**
- * @cfg {Number} growMin
- * @hide
- */
- /**
- * @cfg {Number} growMax
- * @hide
- */
/**
- * @hide
- * @method autoSize
+ * Finds the index of the passed node
+ * @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node
+ * @return {Number} The index of the node or -1
*/
-});/*
+ indexOf : function(node){
+ node = this.getNode(node);
+ if(typeof node.nodeIndex == "number"){
+ return node.nodeIndex;
+ }
+ var ns = this.nodes;
+ for(var i = 0, len = ns.length; i < len; i++){
+ if(ns[i] == node){
+ return i;
+ }
+ }
+ return -1;
+ }
+});
+/*
* - LGPL
*
* based on jquery fullcalendar