[MERGE] MErge with trunk upto revision no 1202.
[odoo/odoo.git] / addons / web / static / src / js / data.js
1
2 openerp.web.data = function(openerp) {
3
4 /**
5  * Serializes the sort criterion array of a dataset into a form which can be
6  * consumed by OpenERP's RPC APIs.
7  *
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
10  */
11 openerp.web.serialize_sort = function (criterion) {
12     return _.map(criterion,
13         function (criteria) {
14             if (criteria[0] === '-') {
15                 return criteria.slice(1) + ' DESC';
16             }
17             return criteria + ' ASC';
18         }).join(', ');
19 };
20
21 openerp.web.DataGroup =  openerp.web.Widget.extend( /** @lends openerp.web.DataGroup# */{
22     /**
23      * Management interface between views and grouped collections of OpenERP
24      * records.
25      *
26      * The root DataGroup is instantiated with the relevant information
27      * (a session, a model, a domain, a context and a group_by sequence), the
28      * domain and context may be empty. It is then interacted with via
29      * :js:func:`~openerp.web.DataGroup.list`, which is used to read the
30      * content of the current grouping level.
31      *
32      * @constructs openerp.web.DataGroup
33      * @extends openerp.web.Widget
34      *
35      * @param {openerp.web.Session} session Current OpenERP session
36      * @param {String} model name of the model managed by this DataGroup
37      * @param {Array} domain search domain for this DataGroup
38      * @param {Object} context context of the DataGroup's searches
39      * @param {Array} group_by sequence of fields by which to group
40      * @param {Number} [level=0] nesting level of the group
41      */
42     init: function(parent, model, domain, context, group_by, level) {
43         this._super(parent, null);
44         if (group_by) {
45             if (group_by.length || context['group_by_no_leaf']) {
46                 return new openerp.web.ContainerDataGroup( this, model, domain, context, group_by, level);
47             } else {
48                 return new openerp.web.GrouplessDataGroup( this, model, domain, context, level);
49             }
50         }
51
52         this.model = model;
53         this.context = context;
54         this.domain = domain;
55
56         this.level = level || 0;
57     },
58     cls: 'DataGroup'
59 });
60 openerp.web.ContainerDataGroup = openerp.web.DataGroup.extend( /** @lends openerp.web.ContainerDataGroup# */ {
61     /**
62      *
63      * @constructs openerp.web.ContainerDataGroup
64      * @extends openerp.web.DataGroup
65      *
66      * @param session
67      * @param model
68      * @param domain
69      * @param context
70      * @param group_by
71      * @param level
72      */
73     init: function (parent, model, domain, context, group_by, level) {
74         this._super(parent, model, domain, context, null, level);
75
76         this.group_by = group_by;
77     },
78     /**
79      * The format returned by ``read_group`` is absolutely dreadful:
80      *
81      * * A ``__context`` key provides future grouping levels
82      * * A ``__domain`` key provides the domain for the next search
83      * * The current grouping value is provided through the name of the
84      *   current grouping name e.g. if currently grouping on ``user_id``, then
85      *   the ``user_id`` value for this group will be provided through the
86      *   ``user_id`` key.
87      * * Similarly, the number of items in the group (not necessarily direct)
88      *   is provided via ``${current_field}_count``
89      * * Other aggregate fields are just dumped there
90      *
91      * This function slightly improves the grouping records by:
92      *
93      * * Adding a ``grouped_on`` property providing the current grouping field
94      * * Adding a ``value`` and a ``length`` properties which replace the
95      *   ``$current_field`` and ``${current_field}_count`` ones
96      * * Moving aggregate values into an ``aggregates`` property object
97      *
98      * Context and domain keys remain as-is, they should not be used externally
99      * but in case they're needed...
100      *
101      * @param {Object} group ``read_group`` record
102      */
103     transform_group: function (group) {
104         var field_name = this.group_by[0];
105         // In cases where group_by_no_leaf and no group_by, the result of
106         // read_group has aggregate fields but no __context or __domain.
107         // Create default (empty) values for those so that things don't break
108         var fixed_group = _.extend(
109                 {__context: {group_by: []}, __domain: []},
110                 group);
111
112         var aggregates = {};
113         _(fixed_group).each(function (value, key) {
114             if (key.indexOf('__') === 0
115                     || key === field_name
116                     || key === field_name + '_count') {
117                 return;
118             }
119             aggregates[key] = value || 0;
120         });
121
122         return {
123             __context: fixed_group.__context,
124             __domain: fixed_group.__domain,
125
126             grouped_on: field_name,
127             // if terminal group (or no group) and group_by_no_leaf => use group.__count
128             length: fixed_group[field_name + '_count'] || fixed_group.__count,
129             value: fixed_group[field_name],
130
131             openable: !(this.context['group_by_no_leaf']
132                        && fixed_group.__context.group_by.length === 0),
133
134             aggregates: aggregates
135         };
136     },
137     fetch: function (fields) {
138         // internal method
139         var d = new $.Deferred();
140         var self = this;
141
142         this.rpc('/web/group/read', {
143             model: this.model,
144             context: this.context,
145             domain: this.domain,
146             fields: _.uniq(this.group_by.concat(fields)),
147             group_by_fields: this.group_by,
148             sort: openerp.web.serialize_sort(this.sort)
149         }, function () { }).then(function (response) {
150             var data_groups = _(response).map(
151                     _.bind(self.transform_group, self));
152             self.groups = data_groups;
153             d.resolveWith(self, [data_groups]);
154         }, function () {
155             d.rejectWith.apply(d, [self, arguments]);
156         });
157         return d.promise();
158     },
159     /**
160      * The items of a list have the following properties:
161      *
162      * ``length``
163      *     the number of records contained in the group (and all of its
164      *     sub-groups). This does *not* provide the size of the "next level"
165      *     of the group, unless the group is terminal (no more groups within
166      *     it).
167      * ``grouped_on``
168      *     the name of the field this level was grouped on, this is mostly
169      *     used for display purposes, in order to know the name of the current
170      *     level of grouping. The ``grouped_on`` should be the same for all
171      *     objects of the list.
172      * ``value``
173      *     the value which led to this group (this is the value all contained
174      *     records have for the current ``grouped_on`` field name).
175      * ``aggregates``
176      *     a mapping of other aggregation fields provided by ``read_group``
177      *
178      * @param {Array} fields the list of fields to aggregate in each group, can be empty
179      * @param {Function} ifGroups function executed if any group is found (DataGroup.group_by is non-null and non-empty), called with a (potentially empty) list of groups as parameters.
180      * @param {Function} ifRecords function executed if there is no grouping left to perform, called with a DataSet instance as parameter
181      */
182     list: function (fields, ifGroups, ifRecords) {
183         var self = this;
184         this.fetch(fields).then(function (group_records) {
185             ifGroups(_(group_records).map(function (group) {
186                 var child_context = _.extend({}, self.context, group.__context);
187                 return _.extend(
188                     new openerp.web.DataGroup(
189                         self, self.model, group.__domain,
190                         child_context, child_context.group_by,
191                         self.level + 1),
192                     group, {sort: self.sort});
193             }));
194         });
195     }
196 });
197 openerp.web.GrouplessDataGroup = openerp.web.DataGroup.extend( /** @lends openerp.web.GrouplessDataGroup# */ {
198     /**
199      *
200      * @constructs openerp.web.GrouplessDataGroup
201      * @extends openerp.web.DataGroup
202      *
203      * @param session
204      * @param model
205      * @param domain
206      * @param context
207      * @param level
208      */
209     init: function (parent, model, domain, context, level) {
210         this._super(parent, model, domain, context, null, level);
211     },
212     list: function (fields, ifGroups, ifRecords) {
213         ifRecords(_.extend(
214             new openerp.web.DataSetSearch(this, this.model),
215             {domain: this.domain, context: this.context, _sort: this.sort}));
216     }
217 });
218 openerp.web.StaticDataGroup = openerp.web.GrouplessDataGroup.extend( /** @lends openerp.web.StaticDataGroup# */ {
219     /**
220      * A specialization of groupless data groups, relying on a single static
221      * dataset as its records provider.
222      *
223      * @constructs openerp.web.StaticDataGroup
224      * @extends openerp.web.GrouplessDataGroup
225      * @param {openep.web.DataSetStatic} dataset a static dataset backing the groups
226      */
227     init: function (dataset) {
228         this.dataset = dataset;
229     },
230     list: function (fields, ifGroups, ifRecords) {
231         ifRecords(this.dataset);
232     }
233 });
234
235 openerp.web.DataSet =  openerp.web.Widget.extend( /** @lends openerp.web.DataSet# */{
236     identifier_prefix: "dataset",
237     /**
238      * DateaManagement interface between views and the collection of selected
239      * OpenERP records (represents the view's state?)
240      *
241      * @constructs openerp.web.DataSet
242      * @extends openerp.web.Widget
243      *
244      * @param {String} model the OpenERP model this dataset will manage
245      */
246     init: function(parent, model, context) {
247         this._super(parent);
248         this.model = model;
249         this.context = context || {};
250         this.index = null;
251     },
252     previous: function () {
253         this.index -= 1;
254         if (this.index < 0) {
255             this.index = this.ids.length - 1;
256         }
257         return this;
258     },
259     next: function () {
260         this.index += 1;
261         if (this.index >= this.ids.length) {
262             this.index = 0;
263         }
264         return this;
265     },
266     /**
267      * Read records.
268      *
269      * @param {Array} ids identifiers of the records to read
270      * @param {Array} fields fields to read and return, by default all fields are returned
271      * @param {Function} callback function called with read result
272      * @returns {$.Deferred}
273      */
274     read_ids: function (ids, fields, callback) {
275         return this.rpc('/web/dataset/get', {
276             model: this.model,
277             ids: ids,
278             fields: fields,
279             context: this.get_context()
280         }, callback);
281     },
282     /**
283      * Read a slice of the records represented by this DataSet, based on its
284      * domain and context.
285      *
286      * @param {Array} [fields] fields to read and return, by default all fields are returned
287      * @params {Object} options
288      * @param {Number} [options.offset=0] The index from which selected records should be returned
289      * @param {Number} [options.limit=null] The maximum number of records to return
290      * @param {Function} callback function called with read_slice result
291      * @returns {$.Deferred}
292      */
293     read_slice: function (fields, options, callback) { 
294         return null; 
295     },
296     /**
297      * Reads the current dataset record (from its index)
298      *
299      * @params {Array} [fields] fields to read and return, by default all fields are returned
300      * @params {Function} callback function called with read_index result
301      * @returns {$.Deferred}
302      */
303     read_index: function (fields, callback) {
304         var def = $.Deferred().then(callback);
305         if (_.isEmpty(this.ids)) {
306             def.reject();
307         } else {
308             fields = fields || false;
309             return this.read_ids([this.ids[this.index]], fields).then(function(records) {
310                 def.resolve(records[0]);
311             }, function() {
312                 def.reject.apply(def, arguments);
313             });
314         }
315         return def.promise();
316     },
317     /**
318      * Reads default values for the current model
319      *
320      * @param {Array} [fields] fields to get default values for, by default all defaults are read
321      * @param {Function} callback function called with default_get result
322      * @returns {$.Deferred}
323      */
324     default_get: function(fields, callback) {
325         return this.rpc('/web/dataset/default_get', {
326             model: this.model,
327             fields: fields,
328             context: this.get_context()
329         }, callback);
330     },
331     /**
332      * Creates a new record in db
333      *
334      * @param {Object} data field values to set on the new record
335      * @param {Function} callback function called with operation result
336      * @param {Function} error_callback function called in case of creation error
337      * @returns {$.Deferred}
338      */
339     create: function(data, callback, error_callback) {
340         return this.rpc('/web/dataset/create', {
341             model: this.model,
342             data: data,
343             context: this.get_context()
344         }, callback, error_callback);
345     },
346     /**
347      * Saves the provided data in an existing db record
348      *
349      * @param {Number|String} id identifier for the record to alter
350      * @param {Object} data field values to write into the record
351      * @param {Function} callback function called with operation result
352      * @param {Function} error_callback function called in case of write error
353      * @returns {$.Deferred}
354      */
355     write: function (id, data, options, callback, error_callback) {
356         options = options || {};
357         return this.rpc('/web/dataset/save', {
358             model: this.model,
359             id: id,
360             data: data,
361             context: this.get_context(options.context)
362         }, callback, error_callback);
363     },
364     /**
365      * Deletes an existing record from the database
366      *
367      * @param {Number|String} ids identifier of the record to delete
368      * @param {Function} callback function called with operation result
369      * @param {Function} error_callback function called in case of deletion error
370      */
371     unlink: function(ids, callback, error_callback) {
372         var self = this;
373         return this.call_and_eval("unlink", [ids, this.get_context()], null, 1,
374             callback, error_callback);
375     },
376     /**
377      * Calls an arbitrary RPC method
378      *
379      * @param {String} method name of the method (on the current model) to call
380      * @param {Array} [args] arguments to pass to the method
381      * @param {Function} callback
382      * @param {Function} error_callback
383      * @returns {$.Deferred}
384      */
385     call: function (method, args, callback, error_callback) {
386         return this.rpc('/web/dataset/call', {
387             model: this.model,
388             method: method,
389             args: args || []
390         }, callback, error_callback);
391     },
392     /**
393      * Calls an arbitrary method, with more crazy
394      *
395      * @param {String} method
396      * @param {Array} [args]
397      * @param {Number} [domain_index] index of a domain to evaluate in the args array
398      * @param {Number} [context_index] index of a context to evaluate in the args array
399      * @param {Function} callback
400      * @param {Function }error_callback
401      * @returns {$.Deferred}
402      */
403     call_and_eval: function (method, args, domain_index, context_index, callback, error_callback) {
404         return this.rpc('/web/dataset/call', {
405             model: this.model,
406             method: method,
407             domain_id: domain_index || null,
408             context_id: context_index || null,
409             args: args || []
410         }, callback, error_callback);
411     },
412     /**
413      * Calls a button method, usually returning some sort of action
414      *
415      * @param {String} method
416      * @param {Array} [args]
417      * @param {Function} callback
418      * @param {Function} error_callback
419      * @returns {$.Deferred}
420      */
421     call_button: function (method, args, callback, error_callback) {
422         return this.rpc('/web/dataset/call_button', {
423             model: this.model,
424             method: method,
425             domain_id: null,
426             context_id: 1,
427             args: args || []
428         }, callback, error_callback);
429     },
430     /**
431      * Fetches the "readable name" for records, based on intrinsic rules
432      *
433      * @param {Array} ids
434      * @param {Function} callback
435      * @returns {$.Deferred}
436      */
437     name_get: function(ids, callback) {
438         return this.call_and_eval('name_get', [ids, this.get_context()], null, 1, callback);
439     },
440     /**
441      * 
442      * @param {String} name name to perform a search for/on
443      * @param {Array} [domain=[]] filters for the objects returned, OpenERP domain
444      * @param {String} [operator='ilike'] matching operator to use with the provided name value
445      * @param {Number} [limit=100] maximum number of matches to return
446      * @param {Function} callback function to call with name_search result
447      * @returns {$.Deferred}
448      */
449     name_search: function (name, domain, operator, limit, callback) {
450         return this.call_and_eval('name_search',
451             [name || '', domain || false, operator || 'ilike', this.get_context(), limit || 100],
452             1, 3, callback);
453     },
454     /**
455      * @param name
456      * @param callback
457      */
458     name_create: function(name, callback) {
459         return this.call_and_eval('name_create', [name, this.get_context()], null, 1, callback);
460     },
461     exec_workflow: function (id, signal, callback) {
462         return this.rpc('/web/dataset/exec_workflow', {
463             model: this.model,
464             id: id,
465             signal: signal
466         }, callback);
467     },
468     get_context: function(request_context) {
469         if (request_context) {
470             return new openerp.web.CompoundContext(this.context, request_context);
471         }
472         return this.context;
473     }
474 });
475 openerp.web.DataSetStatic =  openerp.web.DataSet.extend({
476     init: function(parent, model, context, ids) {
477         this._super(parent, model, context);
478         // all local records
479         this.ids = ids || [];
480     },
481     read_slice: function (fields, options, callback) {
482         // TODO remove fields from options
483         var self = this,
484             offset = options.offset || 0,
485             limit = options.limit || false,
486             fields = fields || false;
487         var end_pos = limit && limit !== -1 ? offset + limit : undefined;
488         return this.read_ids(this.ids.slice(offset, end_pos), fields, callback);
489     },
490     set_ids: function (ids) {
491         this.ids = ids;
492         if (this.index !== null) {
493             this.index = this.index <= this.ids.length - 1 ?
494                 this.index : (this.ids.length > 0 ? this.length - 1 : 0);
495         }
496     },
497     unlink: function(ids) {
498         this.on_unlink(ids);
499         return $.Deferred().resolve({result: true});
500     },
501     on_unlink: function(ids) {
502         this.set_ids(_.without.apply(null, [this.ids].concat(ids)));
503     }
504 });
505 openerp.web.DataSetSearch =  openerp.web.DataSet.extend(/** @lends openerp.web.DataSetSearch */{
506     /**
507      * @constructs openerp.web.DataSetSearch
508      * @extends openerp.web.DataSet
509      *
510      * @param {Object} parent
511      * @param {String} model
512      * @param {Object} context
513      * @param {Array} domain
514      */
515     init: function(parent, model, context, domain) {
516         this._super(parent, model, context);
517         this.domain = domain || [];
518         this._sort = [];
519         this.offset = 0;
520         // subset records[offset:offset+limit]
521         // is it necessary ?
522         this.ids = [];
523     },
524     /**
525      * Read a slice of the records represented by this DataSet, based on its
526      * domain and context.
527      *
528      * @params {Object} options
529      * @param {Array} [options.fields] fields to read and return, by default all fields are returned
530      * @param {Object} [options.context] context data to add to the request payload, on top of the DataSet's own context
531      * @param {Array} [options.domain] domain data to add to the request payload, ANDed with the dataset's domain
532      * @param {Number} [options.offset=0] The index from which selected records should be returned
533      * @param {Number} [options.limit=null] The maximum number of records to return
534      * @param {Function} callback function called with read_slice result
535      * @returns {$.Deferred}
536      */
537     read_slice: function (fields, options, callback) {
538         var self = this;
539         var options = options || {};
540         var offset = options.offset || 0;
541         return this.rpc('/web/dataset/search_read', {
542             model: this.model,
543             fields: fields || false,
544             domain: this.get_domain(options.domain),
545             context: this.get_context(options.context),
546             sort: this.sort(),
547             offset: offset,
548             limit: options.limit || false
549         }).pipe(function (result) {
550             self.ids = result.ids;
551             self.offset = offset;
552             return result.records;
553         }).then(callback);
554     },
555     get_domain: function (other_domain) {
556         if (other_domain) {
557             return new openerp.web.CompoundDomain(this.domain, other_domain);
558         }
559         return this.domain;
560     },
561     /**
562      * Reads or changes sort criteria on the dataset.
563      *
564      * If not provided with any argument, serializes the sort criteria to
565      * an SQL-like form usable by OpenERP's ORM.
566      *
567      * If given a field, will set that field as first sorting criteria or,
568      * if the field is already the first sorting criteria, will reverse it.
569      *
570      * @param {String} [field] field to sort on, reverses it (toggle from ASC to DESC) if already the main sort criteria
571      * @param {Boolean} [force_reverse=false] forces inserting the field as DESC
572      * @returns {String|undefined}
573      */
574     sort: function (field, force_reverse) {
575         if (!field) {
576             return openerp.web.serialize_sort(this._sort);
577         }
578         var reverse = force_reverse || (this._sort[0] === field);
579         this._sort.splice.apply(
580             this._sort, [0, this._sort.length].concat(
581                 _.without(this._sort, field, '-' + field)));
582
583         this._sort.unshift((reverse ? '-' : '') + field);
584         return undefined;
585     },
586     unlink: function(ids, callback, error_callback) {
587         var self = this;
588         return this._super(ids, function(result) {
589             self.ids = _.without.apply(_, [self.ids].concat(ids));
590             if (this.index !== null) {
591                 self.index = self.index <= self.ids.length - 1 ?
592                     self.index : (self.ids.length > 0 ? self.ids.length -1 : 0);
593             }
594             if (callback)
595                 callback(result);
596         }, error_callback);
597     }
598 });
599 openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({
600     virtual_id_prefix: "one2many_v_id_",
601     debug_mode: true,
602     init: function() {
603         this._super.apply(this, arguments);
604         this.reset_ids([]);
605         this.last_default_get = {};
606     },
607     default_get: function(fields, callback) {
608         return this._super(fields).then(this.on_default_get).then(callback);
609     },
610     on_default_get: function(res) {
611         this.last_default_get = res;
612     },
613     create: function(data, callback, error_callback) {
614         var cached = {id:_.uniqueId(this.virtual_id_prefix), values: data,
615             defaults: this.last_default_get};
616         this.to_create.push(_.extend(_.clone(cached), {values: _.clone(cached.values)}));
617         this.cache.push(cached);
618         this.on_change();
619         var prom = $.Deferred().then(callback);
620         prom.resolve({result: cached.id});
621         return prom.promise();
622     },
623     write: function (id, data, options, callback) {
624         var self = this;
625         var record = _.detect(this.to_create, function(x) {return x.id === id;});
626         record = record || _.detect(this.to_write, function(x) {return x.id === id;});
627         var dirty = false;
628         if (record) {
629             for (var k in data) {
630                 if (record.values[k] === undefined || record.values[k] !== data[k]) {
631                     dirty = true;
632                     break;
633                 }
634             }
635             $.extend(record.values, data);
636         } else {
637             dirty = true;
638             record = {id: id, values: data};
639             self.to_write.push(record);
640         }
641         var cached = _.detect(this.cache, function(x) {return x.id === id;});
642         $.extend(cached.values, record.values);
643         if (dirty)
644             this.on_change();
645         var to_return = $.Deferred().then(callback);
646         to_return.resolve({result: true});
647         return to_return.promise();
648     },
649     unlink: function(ids, callback, error_callback) {
650         var self = this;
651         _.each(ids, function(id) {
652             if (! _.detect(self.to_create, function(x) { return x.id === id; })) {
653                 self.to_delete.push({id: id})
654             }
655         });
656         this.to_create = _.reject(this.to_create, function(x) { return _.include(ids, x.id);});
657         this.to_write = _.reject(this.to_write, function(x) { return _.include(ids, x.id);});
658         this.cache = _.reject(this.cache, function(x) { return _.include(ids, x.id);});
659         this.set_ids(_.without.apply(_, [this.ids].concat(ids)));
660         this.on_change();
661         var to_return = $.Deferred().then(callback);
662         setTimeout(function () {to_return.resolve({result: true});}, 0);
663         return to_return.promise();
664     },
665     reset_ids: function(ids) {
666         this.set_ids(ids);
667         this.to_delete = [];
668         this.to_create = [];
669         this.to_write = [];
670         this.cache = [];
671         this.delete_all = false;
672     },
673     on_change: function() {},
674     read_ids: function (ids, fields, callback) {
675         var self = this;
676         var to_get = [];
677         _.each(ids, function(id) {
678             var cached = _.detect(self.cache, function(x) {return x.id === id;});
679             var created = _.detect(self.to_create, function(x) {return x.id === id;});
680             if (created) {
681                 _.each(fields, function(x) {if (cached.values[x] === undefined)
682                     cached.values[x] = created.defaults[x] || false;});
683             } else {
684                 if (!cached || !_.all(fields, function(x) {return cached.values[x] !== undefined}))
685                     to_get.push(id);
686             }
687         });
688         var completion = $.Deferred().then(callback);
689         var return_records = function() {
690             var records = _.map(ids, function(id) {
691                 return _.extend({}, _.detect(self.cache, function(c) {return c.id === id;}).values, {"id": id});
692             });
693             if (self.debug_mode) {
694                 if (_.include(records, undefined)) {
695                     throw "Record not correctly loaded";
696                 }
697             }
698             completion.resolve(records);
699         };
700         if(to_get.length > 0) {
701             var rpc_promise = this._super(to_get, fields, function(records) {
702                 _.each(records, function(record, index) {
703                     var id = to_get[index];
704                     var cached = _.detect(self.cache, function(x) {return x.id === id;});
705                     if (!cached) {
706                         self.cache.push({id: id, values: record});
707                     } else {
708                         // I assume cache value is prioritary
709                         _.defaults(cached.values, record);
710                     }
711                 });
712                 return_records();
713             });
714             $.when(rpc_promise).fail(function() {completion.reject();});
715         } else {
716             return_records();
717         }
718         return completion.promise();
719     }
720 });
721 openerp.web.BufferedDataSet.virtual_id_regex = /^one2many_v_id_.*$/;
722
723 openerp.web.ProxyDataSet = openerp.web.DataSetSearch.extend({
724     init: function() {
725         this._super.apply(this, arguments);
726         this.create_function = null;
727         this.write_function = null;
728         this.read_function = null;
729     },
730     read_ids: function () {
731         if (this.read_function) {
732             return this.read_function.apply(null, arguments);
733         } else {
734             return this._super.apply(this, arguments);
735         }
736     },
737     default_get: function(fields, callback) {
738         return this._super(fields, callback).then(this.on_default_get);
739     },
740     on_default_get: function(result) {},
741     create: function(data, callback, error_callback) {
742         this.on_create(data);
743         if (this.create_function) {
744             return this.create_function(data, callback, error_callback);
745         } else {
746             console.warn("trying to create a record using default proxy dataset behavior");
747             var to_return = $.Deferred().then(callback);
748             setTimeout(function () {to_return.resolve({"result": undefined});}, 0);
749             return to_return.promise();
750         }
751     },
752     on_create: function(data) {},
753     write: function (id, data, options, callback) {
754         this.on_write(id, data);
755         if (this.write_function) {
756             return this.write_function(id, data, options, callback);
757         } else {
758             console.warn("trying to write a record using default proxy dataset behavior");
759             var to_return = $.Deferred().then(callback);
760             setTimeout(function () {to_return.resolve({"result": true});}, 0);
761             return to_return.promise();
762         }
763     },
764     on_write: function(id, data) {},
765     unlink: function(ids, callback, error_callback) {
766         this.on_unlink(ids);
767         console.warn("trying to unlink a record using default proxy dataset behavior");
768         var to_return = $.Deferred().then(callback);
769         setTimeout(function () {to_return.resolve({"result": true});}, 0);
770         return to_return.promise();
771     },
772     on_unlink: function(ids) {}
773 });
774
775 openerp.web.Model = openerp.web.SessionAware.extend({
776     init: function(session, model_name) {
777         this._super(session);
778         this.model_name = model_name;
779     },
780     get_func: function(method_name) {
781         var self = this;
782         return function() {
783             if (method_name == "search_read")
784                 return self._search_read.apply(self, arguments);
785             return self._call(method_name, _.toArray(arguments));
786         };
787     },
788     _call: function (method, args) {
789         return this.rpc('/web/dataset/call', {
790             model: this.model_name,
791             method: method,
792             args: args
793         }).pipe(function(result) {
794             if (method == "read" && result instanceof Array && result.length > 0 && result[0]["id"]) {
795                 var index = {};
796                 _.each(_.range(result.length), function(i) {
797                     index[result[i]["id"]] = result[i];
798                 })
799                 result = _.map(args[0], function(x) {return index[x];});
800             }
801             return result;
802         });
803     },
804     _search_read: function(domain, fields, offset, limit, order, context) {
805         return this.rpc('/web/dataset/search_read', {
806             model: this.model_name,
807             fields: fields,
808             offset: offset,
809             limit: limit,
810             domain: domain,
811             sort: order,
812             context: context
813         }).pipe(function(result) {
814             return result.records;
815         });;
816     }
817 });
818
819 openerp.web.CompoundContext = openerp.web.Class.extend({
820     init: function () {
821         this.__ref = "compound_context";
822         this.__contexts = [];
823         this.__eval_context = null;
824         var self = this;
825         _.each(arguments, function(x) {
826             self.add(x);
827         });
828     },
829     add: function (context) {
830         this.__contexts.push(context);
831         return this;
832     },
833     set_eval_context: function (eval_context) {
834         this.__eval_context = eval_context;
835         return this;
836     },
837     get_eval_context: function () {
838         return this.__eval_context;
839     }
840 });
841
842 openerp.web.CompoundDomain = openerp.web.Class.extend({
843     init: function () {
844         this.__ref = "compound_domain";
845         this.__domains = [];
846         this.__eval_context = null;
847         var self = this;
848         _.each(arguments, function(x) {
849             self.add(x);
850         });
851     },
852     add: function(domain) {
853         this.__domains.push(domain);
854         return this;
855     },
856     set_eval_context: function(eval_context) {
857         this.__eval_context = eval_context;
858         return this;
859     },
860     get_eval_context: function() {
861         return this.__eval_context;
862     }
863 });
864 };
865
866 // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: