2 openerp.web.data = function(instance) {
5 * Serializes the sort criterion array of a dataset into a form which can be
6 * consumed by OpenERP's RPC APIs.
8 * @param {Array} criterion array of fields, from first to last criteria, prefixed with '-' for reverse sorting
9 * @returns {String} SQL-like sorting string (``ORDER BY``) clause
11 instance.web.serialize_sort = function (criterion) {
12 return _.map(criterion,
14 if (criteria[0] === '-') {
15 return criteria.slice(1) + ' DESC';
17 return criteria + ' ASC';
21 instance.web.Query = instance.web.Class.extend({
22 init: function (model, fields) {
24 this._fields = fields;
31 clone: function (to_set) {
32 to_set = to_set || {};
33 var q = new instance.web.Query(this._model, this._fields);
34 q._context = this._context;
35 q._filter = this._filter;
36 q._limit = this._limit;
37 q._offset = this._offset;
38 q._order_by = this._order_by;
40 for(var key in to_set) {
41 if (!to_set.hasOwnProperty(key)) { continue; }
44 q._filter = new instance.web.CompoundDomain(
45 q._filter, to_set.filter);
48 q._context = new instance.web.CompoundContext(
49 q._context, to_set.context);
54 q['_' + key] = to_set[key];
59 _execute: function () {
61 return instance.session.rpc('/web/dataset/search_read', {
62 model: this._model.name,
63 fields: this._fields || false,
64 domain: instance.web.pyeval.eval('domains',
65 [this._model.domain(this._filter)]),
66 context: instance.web.pyeval.eval('contexts',
67 [this._model.context(this._context)]),
70 sort: instance.web.serialize_sort(this._order_by)
71 }).then(function (results) {
72 self._count = results.length;
73 return results.records;
77 * Fetches the first record matching the query, or null
79 * @returns {jQuery.Deferred<Object|null>}
83 return this.clone({limit: 1})._execute().then(function (records) {
85 if (records.length) { return records[0]; }
90 * Fetches all records matching the query
92 * @returns {jQuery.Deferred<Array<>>}
95 return this._execute();
98 * Fetches the number of records matching the query in the database
100 * @returns {jQuery.Deferred<Number>}
103 if (this._count != undefined) { return $.when(this._count); }
104 return this._model.call(
105 'search_count', [this._filter], {
106 context: this._model.context(this._context)});
109 * Performs a groups read according to the provided grouping criterion
111 * @param {String|Array<String>} grouping
112 * @returns {jQuery.Deferred<Array<openerp.web.QueryGroup>> | null}
114 group_by: function (grouping) {
115 var ctx = instance.web.pyeval.eval(
116 'context', this._model.context(this._context));
118 // undefined passed in explicitly (!)
119 if (_.isUndefined(grouping)) {
123 if (!(grouping instanceof Array)) {
124 grouping = _.toArray(arguments);
126 if (_.isEmpty(grouping) && !ctx['group_by_no_leaf']) {
131 return this._model.call('read_group', {
133 fields: _.uniq(grouping.concat(this._fields || [])),
134 domain: this._model.domain(this._filter),
136 offset: this._offset,
138 orderby: instance.web.serialize_sort(this._order_by) || false
139 }).then(function (results) {
140 return _(results).map(function (result) {
141 // FIX: querygroup initialization
142 result.__context = result.__context || {};
143 result.__context.group_by = result.__context.group_by || [];
144 _.defaults(result.__context, ctx);
145 return new instance.web.QueryGroup(
146 self._model.name, grouping[0], result);
151 * Creates a new query with the union of the current query's context and
154 * @param context context data to add to the query
155 * @returns {openerp.web.Query}
157 context: function (context) {
158 if (!context) { return this; }
159 return this.clone({context: context});
162 * Creates a new query with the union of the current query's filter and
165 * @param domain domain data to AND with the current query filter
166 * @returns {openerp.web.Query}
168 filter: function (domain) {
169 if (!domain) { return this; }
170 return this.clone({filter: domain});
173 * Creates a new query with the provided limit replacing the current
176 * @param {Number} limit maximum number of records the query should retrieve
177 * @returns {openerp.web.Query}
179 limit: function (limit) {
180 return this.clone({limit: limit});
183 * Creates a new query with the provided offset replacing the current
186 * @param {Number} offset number of records the query should skip before starting its retrieval
187 * @returns {openerp.web.Query}
189 offset: function (offset) {
190 return this.clone({offset: offset});
193 * Creates a new query with the provided ordering parameters replacing
194 * those of the current query
196 * @param {String...} fields ordering clauses
197 * @returns {openerp.web.Query}
199 order_by: function (fields) {
200 if (fields === undefined) { return this; }
201 if (!(fields instanceof Array)) {
202 fields = _.toArray(arguments);
204 if (_.isEmpty(fields)) { return this; }
205 return this.clone({order_by: fields});
209 instance.web.QueryGroup = instance.web.Class.extend({
210 init: function (model, grouping_field, read_group_group) {
211 // In cases where group_by_no_leaf and no group_by, the result of
212 // read_group has aggregate fields but no __context or __domain.
213 // Create default (empty) values for those so that things don't break
214 var fixed_group = _.extend(
215 {__context: {group_by: []}, __domain: []},
219 _(fixed_group).each(function (value, key) {
220 if (key.indexOf('__') === 0
221 || key === grouping_field
222 || key === grouping_field + '_count') {
225 aggregates[key] = value || 0;
228 this.model = new instance.web.Model(
229 model, fixed_group.__context, fixed_group.__domain);
231 var group_size = fixed_group[grouping_field + '_count'] || fixed_group.__count || 0;
232 var leaf_group = fixed_group.__context.group_by.length === 0;
234 folded: !!(fixed_group.__fold),
235 grouped_on: grouping_field,
236 // if terminal group (or no group) and group_by_no_leaf => use group.__count
238 value: fixed_group[grouping_field],
239 // A group is open-able if it's not a leaf in group_by_no_leaf mode
240 has_children: !(leaf_group && fixed_group.__context['group_by_no_leaf']),
242 aggregates: aggregates
245 get: function (key) {
246 return this.attributes[key];
248 subgroups: function () {
249 return this.model.query().group_by(this.model.context().group_by);
252 return this.model.query.apply(this.model, arguments);
256 instance.web.Model = instance.web.Class.extend({
258 * @constructs instance.web.Model
259 * @extends instance.web.Class
261 * @param {String} model_name name of the OpenERP model this object is bound to
262 * @param {Object} [context]
263 * @param {Array} [domain]
265 init: function (model_name, context, domain) {
266 this.name = model_name;
267 this._context = context || {};
268 this._domain = domain || [];
271 * @deprecated does not allow to specify kwargs, directly use call() instead
273 get_func: function (method_name) {
276 return self.call(method_name, _.toArray(arguments));
280 * Call a method (over RPC) on the bound OpenERP model.
282 * @param {String} method name of the method to call
283 * @param {Array} [args] positional arguments
284 * @param {Object} [kwargs] keyword arguments
285 * @param {Object} [options] additional options for the rpc() method
286 * @returns {jQuery.Deferred<>} call result
288 call: function (method, args, kwargs, options) {
290 kwargs = kwargs || {};
291 if (!_.isArray(args)) {
292 // call(method, kwargs)
296 instance.web.pyeval.ensure_evaluated(args, kwargs);
297 var debug = instance.session.debug ? '/'+this.name+':'+method : '';
298 return instance.session.rpc('/web/dataset/call_kw' + debug, {
306 * Fetches a Query instance bound to this model, for searching
308 * @param {Array<String>} [fields] fields to ultimately fetch during the search
309 * @returns {instance.web.Query}
311 query: function (fields) {
312 return new instance.web.Query(this, fields);
315 * Executes a signal on the designated workflow, on the bound OpenERP model
317 * @param {Number} id workflow identifier
318 * @param {String} signal signal to trigger on the workflow
320 exec_workflow: function (id, signal) {
321 return instance.session.rpc('/web/dataset/exec_workflow', {
328 * Fetches the model's domain, combined with the provided domain if any
330 * @param {Array} [domain] to combine with the model's internal domain
331 * @returns {instance.web.CompoundDomain} The model's internal domain, or the AND-ed union of the model's internal domain and the provided domain
333 domain: function (domain) {
334 if (!domain) { return this._domain; }
335 return new instance.web.CompoundDomain(
336 this._domain, domain);
339 * Fetches the combination of the user's context and the domain context,
340 * combined with the provided context if any
342 * @param {Object} [context] to combine with the model's internal context
343 * @returns {instance.web.CompoundContext} The union of the user's context and the model's internal context, as well as the provided context if any. In that order.
345 context: function (context) {
346 return new instance.web.CompoundContext(
347 instance.session.user_context, this._context, context || {});
350 * Button action caller, needs to perform cleanup if an action is returned
351 * from the button (parsing of context and domain, and fixup of the views
352 * collection for act_window actions)
354 * FIXME: remove when evaluator integrated
356 call_button: function (method, args) {
357 instance.web.pyeval.ensure_evaluated(args, {});
358 return instance.session.rpc('/web/dataset/call_button', {
361 // Should not be necessary anymore. Integrate remote in this?
363 context_id: args.length - 1,
369 instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin, {
371 * Collection of OpenERP records, used to share records and the current selection between views.
373 * @constructs instance.web.DataSet
375 * @param {String} model the OpenERP model this dataset will manage
377 init: function(parent, model, context) {
378 instance.web.PropertiesMixin.init.call(this);
380 this.context = context || {};
383 this._model = new instance.web.Model(model, context);
385 previous: function () {
387 if (!this.ids.length) {
389 } else if (this.index < 0) {
390 this.index = this.ids.length - 1;
396 if (!this.ids.length) {
398 } else if (this.index >= this.ids.length) {
403 select_id: function(id) {
404 var idx = this.get_id_index(id);
412 get_id_index: function(id) {
413 for (var i=0, ii=this.ids.length; i<ii; i++) {
414 // Here we use type coercion because of the mess potentially caused by
415 // OpenERP ids fetched from the DOM as string. (eg: dhtmlxcalendar)
416 // OpenERP ids can be non-numeric too ! (eg: recursive events in calendar)
417 if (id == this.ids[i]) {
426 * @param {Array} ids identifiers of the records to read
427 * @param {Array} fields fields to read and return, by default all fields are returned
428 * @returns {$.Deferred}
430 read_ids: function (ids, fields, options) {
431 options = options || {};
432 // TODO: reorder results to match ids list
433 return this._model.call('read',
434 [ids, fields || false],
435 {context: this.get_context(options.context)});
438 * Read a slice of the records represented by this DataSet, based on its
439 * domain and context.
441 * @param {Array} [fields] fields to read and return, by default all fields are returned
442 * @params {Object} [options]
443 * @param {Number} [options.offset=0] The index from which selected records should be returned
444 * @param {Number} [options.limit=null] The maximum number of records to return
445 * @returns {$.Deferred}
447 read_slice: function (fields, options) {
449 options = options || {};
450 return this._model.query(fields)
451 .limit(options.limit || false)
452 .offset(options.offset || 0)
453 .all().done(function (records) {
454 self.ids = _(records).pluck('id');
458 * Reads the current dataset record (from its index)
460 * @params {Array} [fields] fields to read and return, by default all fields are returned
461 * @param {Object} [options.context] context data to add to the request payload, on top of the DataSet's own context
462 * @returns {$.Deferred}
464 read_index: function (fields, options) {
465 options = options || {};
466 return this.read_ids([this.ids[this.index]], fields, options).then(function (records) {
467 if (_.isEmpty(records)) { return $.Deferred().reject().promise(); }
472 * Reads default values for the current model
474 * @param {Array} [fields] fields to get default values for, by default all defaults are read
475 * @param {Object} [options.context] context data to add to the request payload, on top of the DataSet's own context
476 * @returns {$.Deferred}
478 default_get: function(fields, options) {
479 options = options || {};
480 return this._model.call('default_get',
481 [fields], {context: this.get_context(options.context)});
484 * Creates a new record in db
486 * @param {Object} data field values to set on the new record
487 * @param {Object} options Dictionary that can contain the following keys:
488 * - readonly_fields: Values from readonly fields that were updated by
489 * on_changes. Only used by the BufferedDataSet to make the o2m work correctly.
490 * @returns {$.Deferred}
492 create: function(data, options) {
494 return this._model.call('create', [data], {
495 context: this.get_context()
496 }).done(function () {
497 self.trigger('dataset_changed', data, options)
501 * Saves the provided data in an existing db record
503 * @param {Number|String} id identifier for the record to alter
504 * @param {Object} data field values to write into the record
505 * @param {Object} options Dictionary that can contain the following keys:
506 * - context: The context to use in the server-side call.
507 * - readonly_fields: Values from readonly fields that were updated by
508 * on_changes. Only used by the BufferedDataSet to make the o2m work correctly.
509 * @returns {$.Deferred}
511 write: function (id, data, options) {
512 options = options || {};
514 return this._model.call('write', [[id], data], {
515 context: this.get_context(options.context)
516 }).done(function () {
517 self.trigger('dataset_changed', id, data, options)
521 * Deletes an existing record from the database
523 * @param {Number|String} ids identifier of the record to delete
525 unlink: function(ids) {
527 return this._model.call('unlink', [ids], {
528 context: this.get_context()
529 }).done(function () {
530 self.trigger('dataset_changed', ids)
534 * Calls an arbitrary RPC method
536 * @param {String} method name of the method (on the current model) to call
537 * @param {Array} [args] arguments to pass to the method
538 * @param {Function} callback
539 * @param {Function} error_callback
540 * @returns {$.Deferred}
542 call: function (method, args) {
543 return this._model.call(method, args);
546 * Calls a button method, usually returning some sort of action
548 * @param {String} method
549 * @param {Array} [args]
550 * @returns {$.Deferred}
552 call_button: function (method, args) {
553 return this._model.call_button(method, args);
556 * Fetches the "readable name" for records, based on intrinsic rules
559 * @returns {$.Deferred}
561 name_get: function(ids) {
562 return this._model.call('name_get', [ids], {context: this.get_context()});
566 * @param {String} name name to perform a search for/on
567 * @param {Array} [domain=[]] filters for the objects returned, OpenERP domain
568 * @param {String} [operator='ilike'] matching operator to use with the provided name value
569 * @param {Number} [limit=0] maximum number of matches to return
570 * @param {Function} callback function to call with name_search result
571 * @returns {$.Deferred}
573 name_search: function (name, domain, operator, limit) {
574 return this._model.call('name_search', {
576 args: domain || false,
577 operator: operator || 'ilike',
578 context: this._model.context(),
585 name_create: function(name) {
586 return this._model.call('name_create', [name], {context: this.get_context()});
588 exec_workflow: function (id, signal) {
589 return this._model.exec_workflow(id, signal);
591 get_context: function(request_context) {
592 return this._model.context(request_context);
595 * Reads or changes sort criteria on the dataset.
597 * If not provided with any argument, serializes the sort criteria to
598 * an SQL-like form usable by OpenERP's ORM.
600 * If given a field, will set that field as first sorting criteria or,
601 * if the field is already the first sorting criteria, will reverse it.
603 * @param {String} [field] field to sort on, reverses it (toggle from ASC to DESC) if already the main sort criteria
604 * @param {Boolean} [force_reverse=false] forces inserting the field as DESC
605 * @returns {String|undefined}
607 sort: function (field, force_reverse) {
609 return instance.web.serialize_sort(this._sort);
611 var reverse = force_reverse || (this._sort[0] === field);
612 this._sort.splice.apply(
613 this._sort, [0, this._sort.length].concat(
614 _.without(this._sort, field, '-' + field)));
616 this._sort.unshift((reverse ? '-' : '') + field);
620 return this.ids.length;
622 alter_ids: function(n_ids) {
625 remove_ids: function (ids) {
626 this.alter_ids(_(this.ids).difference(ids));
628 add_ids: function(ids, at) {
629 var args = [at, 0].concat(_.difference(ids, this.ids));
630 this.ids.splice.apply(this.ids, args);
633 * Resequence records.
635 * @param {Array} ids identifiers of the records to resequence
636 * @returns {$.Deferred}
638 resequence: function (ids, options) {
639 options = options || {};
640 return instance.session.rpc('/web/dataset/resequence', {
643 context: instance.web.pyeval.eval(
644 'context', this.get_context(options.context)),
645 }).then(function (results) {
651 instance.web.DataSetStatic = instance.web.DataSet.extend({
652 init: function(parent, model, context, ids) {
654 this._super(parent, model, context);
656 this.ids = ids || [];
658 read_slice: function (fields, options) {
659 options = options || {};
660 fields = fields || {};
661 var offset = options.offset || 0,
662 limit = options.limit || false;
663 var end_pos = limit && limit !== -1 ? offset + limit : this.ids.length;
664 return this.read_ids(this.ids.slice(offset, end_pos), fields);
666 set_ids: function (ids) {
668 if (ids.length === 0) {
670 } else if (this.index >= ids.length - 1) {
671 this.index = ids.length - 1;
674 unlink: function(ids) {
675 this.set_ids(_.without.apply(null, [this.ids].concat(ids)));
676 this.trigger('unlink', ids);
677 return $.Deferred().resolve({result: true});
681 instance.web.DataSetSearch = instance.web.DataSet.extend({
683 * @constructs instance.web.DataSetSearch
684 * @extends instance.web.DataSet
686 * @param {Object} parent
687 * @param {String} model
688 * @param {Object} context
689 * @param {Array} domain
691 init: function(parent, model, context, domain) {
692 this._super(parent, model, context);
693 this.domain = domain || [];
696 this._model = new instance.web.Model(model, context, domain);
699 * Read a slice of the records represented by this DataSet, based on its
700 * domain and context.
702 * @params {Object} options
703 * @param {Array} [options.fields] fields to read and return, by default all fields are returned
704 * @param {Object} [options.context] context data to add to the request payload, on top of the DataSet's own context
705 * @param {Array} [options.domain] domain data to add to the request payload, ANDed with the dataset's domain
706 * @param {Number} [options.offset=0] The index from which selected records should be returned
707 * @param {Number} [options.limit=null] The maximum number of records to return
708 * @returns {$.Deferred}
710 read_slice: function (fields, options) {
711 options = options || {};
713 var q = this._model.query(fields || false)
714 .filter(options.domain)
715 .context(options.context)
716 .offset(options.offset || 0)
717 .limit(options.limit || false);
718 q = q.order_by.apply(q, this._sort);
720 return q.all().done(function (records) {
721 // FIXME: not sure about that one, *could* have discarded count
722 q.count().done(function (count) { self._length = count; });
723 self.ids = _(records).pluck('id');
726 get_domain: function (other_domain) {
727 return this._model.domain(other_domain);
729 alter_ids: function (ids) {
731 if (this.index !== null && this.index >= this.ids.length) {
732 this.index = this.ids.length > 0 ? this.ids.length - 1 : 0;
735 remove_ids: function (ids) {
736 var before = this.ids.length;
739 this._length -= (before - this.ids.length);
742 unlink: function(ids, callback, error_callback) {
744 return this._super(ids).done(function(result) {
745 self.remove_ids( ids);
746 self.trigger("dataset_changed", ids, callback, error_callback);
750 if (this._length != null) {
753 return this._super();
757 instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
758 virtual_id_prefix: "one2many_v_id_",
761 this._super.apply(this, arguments);
763 this.last_default_get = {};
765 default_get: function(fields, options) {
767 return this._super(fields, options).done(function(res) {
768 self.last_default_get = res;
771 create: function(data, options) {
773 id:_.uniqueId(this.virtual_id_prefix),
774 values: _.extend({}, data, (options || {}).readonly_fields || {}),
775 defaults: this.last_default_get
777 this.to_create.push(_.extend(_.clone(cached), {values: _.clone(data)}));
778 this.cache.push(cached);
779 return $.Deferred().resolve(cached.id).promise();
781 write: function (id, data, options) {
783 var record = _.detect(this.to_create, function(x) {return x.id === id;});
784 record = record || _.detect(this.to_write, function(x) {return x.id === id;});
787 for (var k in data) {
788 if (record.values[k] === undefined || record.values[k] !== data[k]) {
793 $.extend(record.values, data);
796 record = {id: id, values: data};
797 self.to_write.push(record);
799 var cached = _.detect(this.cache, function(x) {return x.id === id;});
801 cached = {id: id, values: {}};
802 this.cache.push(cached);
804 $.extend(cached.values, _.extend({}, record.values, (options || {}).readonly_fields || {}));
806 this.trigger("dataset_changed", id, data, options);
807 return $.Deferred().resolve(true).promise();
809 unlink: function(ids, callback, error_callback) {
811 _.each(ids, function(id) {
812 if (! _.detect(self.to_create, function(x) { return x.id === id; })) {
813 self.to_delete.push({id: id})
816 this.to_create = _.reject(this.to_create, function(x) { return _.include(ids, x.id);});
817 this.to_write = _.reject(this.to_write, function(x) { return _.include(ids, x.id);});
818 this.cache = _.reject(this.cache, function(x) { return _.include(ids, x.id);});
819 this.set_ids(_.without.apply(_, [this.ids].concat(ids)));
820 this.trigger("dataset_changed", ids, callback, error_callback);
821 return $.async_when({result: true}).done(callback);
823 reset_ids: function(ids) {
829 this.delete_all = false;
831 read_ids: function (ids, fields, options) {
834 _.each(ids, function(id) {
835 var cached = _.detect(self.cache, function(x) {return x.id === id;});
836 var created = _.detect(self.to_create, function(x) {return x.id === id;});
838 _.each(fields, function(x) {if (cached.values[x] === undefined)
839 cached.values[x] = created.defaults[x] || false;});
841 if (!cached || !_.all(fields, function(x) {return cached.values[x] !== undefined}))
845 var completion = $.Deferred();
846 var return_records = function() {
847 var records = _.map(ids, function(id) {
848 return _.extend({}, _.detect(self.cache, function(c) {return c.id === id;}).values, {"id": id});
850 if (self.debug_mode) {
851 if (_.include(records, undefined)) {
852 throw "Record not correctly loaded";
855 var sort_fields = self._sort,
856 compare = function (v1, v2) {
857 return (v1 < v2) ? -1
861 // Array.sort is not necessarily stable. We must be careful with this because
862 // sorting an array where all items are considered equal is a worst-case that
863 // will randomize the array with an unstable sort! Therefore we must avoid
864 // sorting if there are no sort_fields (i.e. all items are considered equal)
865 // See also: http://ecma262-5.com/ELS5_Section_15.htm#Section_15.4.4.11
866 // http://code.google.com/p/v8/issues/detail?id=90
867 if (sort_fields.length) {
868 records.sort(function (a, b) {
869 return _.reduce(sort_fields, function (acc, field) {
870 if (acc) { return acc; }
872 if (field[0] === '-') {
874 field = field.slice(1);
876 return sign * compare(a[field], b[field]);
880 completion.resolve(records);
882 if(to_get.length > 0) {
883 var rpc_promise = this._super(to_get, fields, options).done(function(records) {
884 _.each(records, function(record, index) {
885 var id = to_get[index];
886 var cached = _.detect(self.cache, function(x) {return x.id === id;});
888 self.cache.push({id: id, values: record});
890 // I assume cache value is prioritary
891 cached.values = _.defaults(_.clone(cached.values), record);
896 $.when(rpc_promise).fail(function() {completion.reject();});
900 return completion.promise();
903 * Invalidates caching of a record in the dataset to ensure the next read
904 * of that record will hit the server.
906 * Of use when an action is going to remote-alter a record which will then
907 * need to be reloaded, e.g. action button.
909 * @param {Object} id record to remove from the BDS's cache
911 evict_record: function (id) {
912 for(var i=0, len=this.cache.length; i<len; ++i) {
913 var record = this.cache[i];
914 // if record we call the button upon is in the cache
915 if (record.id === id) {
916 // evict it so it gets reloaded from server
917 this.cache.splice(i, 1);
922 call_button: function (method, args) {
923 this.evict_record(args[0][0]);
924 return this._super(method, args);
926 exec_workflow: function (id, signal) {
927 this.evict_record(id);
928 return this._super(id, signal);
930 alter_ids: function(n_ids) {
932 this.trigger("dataset_changed", n_ids);
935 instance.web.BufferedDataSet.virtual_id_regex = /^one2many_v_id_.*$/;
937 instance.web.ProxyDataSet = instance.web.DataSetSearch.extend({
939 this._super.apply(this, arguments);
940 this.create_function = null;
941 this.write_function = null;
942 this.read_function = null;
943 this.default_get_function = null;
944 this.unlink_function = null;
946 read_ids: function (ids, fields, options) {
947 if (this.read_function) {
948 return this.read_function(ids, fields, options, this._super);
950 return this._super.apply(this, arguments);
953 default_get: function(fields, options) {
954 if (this.default_get_function) {
955 return this.default_get_function(fields, options, this._super);
957 return this._super.apply(this, arguments);
960 create: function(data, options) {
961 if (this.create_function) {
962 return this.create_function(data, options, this._super);
964 return this._super.apply(this, arguments);
967 write: function (id, data, options) {
968 if (this.write_function) {
969 return this.write_function(id, data, options, this._super);
971 return this._super.apply(this, arguments);
974 unlink: function(ids) {
975 if (this.unlink_function) {
976 return this.unlink_function(ids, this._super);
978 return this._super.apply(this, arguments);
983 instance.web.CompoundContext = instance.web.Class.extend({
985 this.__ref = "compound_context";
986 this.__contexts = [];
987 this.__eval_context = null;
989 _.each(arguments, function(x) {
993 add: function (context) {
994 this.__contexts.push(context);
997 set_eval_context: function (eval_context) {
998 this.__eval_context = eval_context;
1001 get_eval_context: function () {
1002 return this.__eval_context;
1005 return instance.web.pyeval.eval('context', this, undefined, {no_user_context: true});
1009 instance.web.CompoundDomain = instance.web.Class.extend({
1011 this.__ref = "compound_domain";
1012 this.__domains = [];
1013 this.__eval_context = null;
1015 _.each(arguments, function(x) {
1019 add: function(domain) {
1020 this.__domains.push(domain);
1023 set_eval_context: function(eval_context) {
1024 this.__eval_context = eval_context;
1027 get_eval_context: function() {
1028 return this.__eval_context;
1031 return instance.web.pyeval.eval('domain', this);
1035 instance.web.DropMisordered = instance.web.Class.extend({
1037 * @constructs instance.web.DropMisordered
1038 * @extends instance.web.Class
1040 * @param {Boolean} [failMisordered=false] whether mis-ordered responses should be failed or just ignored
1042 init: function (failMisordered) {
1043 // local sequence number, for requests sent
1045 // remote sequence number, seqnum of last received request
1047 this.failMisordered = failMisordered || false;
1050 * Adds a deferred (usually an async request) to the sequencer
1052 * @param {$.Deferred} deferred to ensure add
1053 * @returns {$.Deferred}
1055 add: function (deferred) {
1056 var res = $.Deferred();
1058 var self = this, seq = this.lsn++;
1059 deferred.done(function () {
1060 if (seq > self.rsn) {
1062 res.resolve.apply(res, arguments);
1063 } else if (self.failMisordered) {
1066 }).fail(function () {
1067 res.reject.apply(res, arguments);
1070 return res.promise();
1076 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: