Merge branch 'tine20.com/2012.10' into 2013.03
[tine20] / tine20 / Tinebase / js / data / Record.js
1 /*
2  * Tine 2.0
3  * 
4  * @package     Tine
5  * @subpackage  Tinebase
6  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
7  * @author      Cornelius Weiss <c.weiss@metaways.de>
8  * @copyright   Copyright (c) 2007-2008 Metaways Infosystems GmbH (http://www.metaways.de)
9  */
10
11 Ext.ns('Tine.Tinebase', 'Tine.Tinebase.data');
12
13 Tine.Tinebase.data.Record = function(data, id) {
14     if (id || id === 0) {
15         this.id = id;
16     } else if (data[this.idProperty]) {
17         this.id = data[this.idProperty];
18     } else {
19         this.id = ++Ext.data.Record.AUTO_ID;
20     }
21     this.data = data;
22     this.ctime = new Date().getTime();
23 };
24
25 /**
26  * @namespace Tine.Tinebase.data
27  * @class     Tine.Tinebase.data.Record
28  * @extends   Ext.data.Record
29  * 
30  * Baseclass of Tine 2.0 models
31  */
32 Ext.extend(Tine.Tinebase.data.Record, Ext.data.Record, {
33     /**
34      * @cfg {String} appName
35      * internal/untranslated app name (required)
36      */
37     appName: null,
38     /**
39      * @cfg {String} modelName
40      * name of the model/record  (required)
41      */
42     modelName: null,
43     /**
44      * @cfg {String} idProperty
45      * property of the id of the record
46      */
47     idProperty: 'id',
48     /**
49      * @cfg {String} titleProperty
50      * property of the title attibute, used in generic getTitle function  (required)
51      */
52     titleProperty: null,
53     /**
54      * @cfg {String} recordName
55      * untranslated record/item name
56      */
57     recordName: 'record',
58     /**
59      * @cfg {String} recordName
60      * untranslated records/items (plural) name
61      */
62     recordsName: 'records',
63     /**
64      * @cfg {String} containerProperty
65      * name of the container property
66      */
67     containerProperty: null,
68     /**
69      * @cfg {String} containerName
70      * untranslated container name
71      */
72     containerName: 'container',
73     /**
74      * @cfg {string} containerName
75      * untranslated name of container (plural)
76      */
77     containersName: 'containers',
78     /**
79      * default filter
80      * @type {string}
81      */
82     defaultFilter: null,
83     
84     cfExp: /^#(.+)/,
85     
86     /**
87      * Get the value of the {@link Ext.data.Field#name named field}.
88      * @param {String} name The {@link Ext.data.Field#name name of the field} to get the value of.
89      * @return {Object} The value of the field.
90      */
91     get: function(name) {
92         var cfName = String(name).match(this.cfExp);
93         
94         if (cfName) {
95             return this.data.customfields ? this.data.customfields[cfName[1]] : null;
96         }
97         
98         return this.data[name];
99     },
100     
101     /**
102      * Set the value of the {@link Ext.data.Field#name named field}.
103      * @param {String} name The {@link Ext.data.Field#name name of the field} to get the value of.
104      * @return {Object} The value of the field.
105      */
106     set : function(name, value) {
107         var encode = Ext.isPrimitive(value) ? String : Ext.encode,
108             current = this.get(name),
109             cfName;
110             
111         if (encode(current) == encode(value)) {
112             return;
113         }
114         this.dirty = true;
115         if (!this.modified) {
116             this.modified = {};
117         }
118         if (this.modified[name] === undefined) {
119             this.modified[name] = current;
120         }
121         
122         if (cfName = String(name).match(this.cfExp)) {
123             this.data.customfields = this.data.customfields || {};
124             this.data.customfields[cfName[1]] = value;
125         } else {
126             this.data[name] = value;
127         }
128         
129         if (!this.editing) {
130             this.afterEdit();
131         }
132     },
133     
134     /**
135      * returns title of this record
136      * 
137      * @return {String}
138      */
139     getTitle: function() {
140         var s = this.titleProperty ? this.titleProperty.split('.') : [null];
141         return (s.length > 0 && this.get(s[0]) && this.get(s[0])[s[1]]) ? this.get(s[0])[s[1]] : s[0] ? this.get(this.titleProperty) : '';
142     },
143     /**
144      * returns the id of the record
145      */
146     getId: function() {
147         return this.get(this.idProperty ? this.idProperty : 'id');
148     },
149     
150     /**
151      * converts data to String
152      * 
153      * @return {String}
154      */
155     toString: function() {
156         return Ext.encode(this.data);
157     },
158     
159     /**
160      * returns true if given record obsoletes this one
161      * 
162      * @param {Tine.Tinebase.data.Record} record
163      * @return {Boolean}
164      */
165     isObsoletedBy: function(record) {
166         if (record.modelName !== this.modelName || record.getId() !== this.getId()) {
167             throw new Ext.Error('Records could not be compared');
168         }
169         
170         if (this.constructor.hasField('seq') && record.get('seq') != this.get('seq')) {
171             return record.get('seq') > this.get('seq');
172         }
173         
174         return (this.constructor.hasField('last_modified_time')) ? record.get('last_modified_time') > this.get('last_modified_time') : true;
175     }
176 });
177
178 /**
179  * Generate a constructor for a specific Record layout.
180  * 
181  * @param {Array} def see {@link Ext.data.Record#create}
182  * @param {Object} meta information see {@link Tine.Tinebase.data.Record}
183  * 
184  * <br>usage:<br>
185 <b>IMPORTANT: the ngettext comments are required for the translation system!</b>
186 <pre><code>
187 var TopicRecord = Tine.Tinebase.data.Record.create([
188     {name: 'summary', mapping: 'topic_title'},
189     {name: 'details', mapping: 'username'}
190 ], {
191     appName: 'Tasks',
192     modelName: 'Task',
193     idProperty: 'id',
194     titleProperty: 'summary',
195     // ngettext('Task', 'Tasks', n);
196     recordName: 'Task',
197     recordsName: 'Tasks',
198     containerProperty: 'container_id',
199     // ngettext('to do list', 'to do lists', n);
200     containerName: 'to do list',
201     containesrName: 'to do lists'
202 });
203 </code></pre>
204  * @static
205  */
206 Tine.Tinebase.data.Record.create = function(o, meta) {
207     var f = Ext.extend(Tine.Tinebase.data.Record, {});
208     var p = f.prototype;
209     Ext.apply(p, meta);
210     p.fields = new Ext.util.MixedCollection(false, function(field) {
211         return field.name;
212     });
213     for(var i = 0, len = o.length; i < len; i++) {
214         p.fields.add(new Ext.data.Field(o[i]));
215     }
216     f.getField = function(name) {
217         return p.fields.get(name);
218     };
219     f.getMeta = function(name) {
220         var value = null;
221         switch(name) {
222             case ('phpClassName'):
223                 value = p.appName + '_Model_' + p.modelName;
224                 break;
225             default:
226                 value = p[name];
227         }
228         return value;
229     };
230     f.getDefaultData = function() {
231         return {};
232     };
233     f.getFieldDefinitions = function() {
234         return p.fields.items;
235     };
236     f.getFieldNames = function() {
237         if (! p.fieldsarray) {
238             var arr = p.fieldsarray = [];
239             Ext.each(p.fields.items, function(item) {arr.push(item.name);});
240         }
241         return p.fieldsarray;
242     };
243     f.hasField = function(n) {
244         return p.fields.indexOfKey(n) >= 0;
245     };
246     f.getRecordName = function() {
247         var app = Tine.Tinebase.appMgr.get(p.appName),
248             i18n = app && app.i18n ? app.i18n :Tine.Tinebase.translation;
249             
250         return i18n.n_(p.recordName, p.recordsName, 1);
251     };
252     f.getRecordsName = function() {
253         var app = Tine.Tinebase.appMgr.get(p.appName),
254             i18n = app && app.i18n ? app.i18n :Tine.Tinebase.translation;
255             
256         return i18n.n_(p.recordName, p.recordsName, 50);
257     };
258     f.getContainerName = function() {
259         var app = Tine.Tinebase.appMgr.get(p.appName),
260             i18n = app && app.i18n ? app.i18n :Tine.Tinebase.translation;
261             
262         return i18n.n_(p.containerName, p.containersName, 1);
263     };
264     f.getContainersName = function() {
265         var app = Tine.Tinebase.appMgr.get(p.appName),
266             i18n = app && app.i18n ? app.i18n :Tine.Tinebase.translation;
267             
268         return i18n.n_(p.containerName, p.containersName, 50);
269     };
270     f.getAppName = function() {
271         return Tine.Tinebase.appMgr.get(p.appName).i18n._(p.appName);
272     };
273     /**
274      * returns the php class name of the record itself or by the application(name) and model(name)
275      * @param {mixed} app       the application instance or the application name or the record class
276      * @param {mixed} model     the model name
277      * @return {String} php class name
278      */
279     f.getPhpClassName = function(app, model) {
280         // without arguments the php class name of the this is returned
281         if (!app && !model) {
282             return f.getMeta('phpClassName');
283         }
284         // if var app is a record class, the getMeta method is called
285         if (Ext.isFunction(app.getMeta)) {
286             return app.getMeta('phpClassName');
287         }
288
289         var appName = (Ext.isObject(app) && app.hasOwnProperty('name')) ? app.name : app;
290         return appName + '_Model_' + model;
291     };
292     
293     // sanitize containerProperty label
294     var containerProperty = f.getMeta('containerProperty');
295     if (containerProperty) {
296         var field = p.fields.get(containerProperty);
297         if (field) {
298             field.label = p.containerName;
299         }
300     }
301     Tine.Tinebase.data.RecordMgr.add(f);
302     return f;
303 };
304
305 Tine.Tinebase.data.Record.generateUID = function(length) {
306     var uid = String(CryptoJS.SHA1(String(Math.floor(Math.random()*Math.pow(10, 16))) + String(new Date().getMilliseconds())));
307     
308     if (length) {
309         uid = uid.substring(0, length);
310     }
311     
312     return uid;
313 };
314 Tine.Tinebase.data.RecordManager = Ext.extend(Ext.util.MixedCollection, {
315     add: function(record) {
316         if (! Ext.isFunction(record.getMeta)) {
317             throw new Ext.Error('only records of type Tinebase.data.Record could be added');
318         }
319         var appName = record.getMeta('appName'),
320             modelName = record.getMeta('modelName');
321             
322         if (! appName && modelName) {
323             throw new Ext.Error('appName and modelName must be in the metadatas');
324         }
325         
326 //        console.log('register model "' + appName + '.' + modelName + '"');
327         Tine.Tinebase.data.RecordManager.superclass.add.call(this, appName + '.' + modelName, record);
328     },
329     
330     get: function(appName, modelName) {
331         if (Ext.isFunction(appName.getMeta)) {
332             return appName;
333         }
334         if (! modelName && appName.modelName) {
335             modelName = appName.modelName;
336         }
337         if (appName.appName) {
338             appName = appName.appName;
339         }
340             
341         if (! Ext.isString(appName)) {
342             throw new Ext.Error('appName must be a string');
343         }
344         
345         Ext.each([appName, modelName], function(what) {
346             if (! Ext.isString(what)) return;
347             var parts = what.split(/(?:_Model_)|(?:\.)/);
348             if (parts.length > 1) {
349                 appName = parts[0];
350                 modelName = parts[1];
351             }
352         });
353         
354         return Tine.Tinebase.data.RecordManager.superclass.get.call(this, appName + '.' + modelName);
355     }
356 });
357 Tine.Tinebase.data.RecordMgr = new Tine.Tinebase.data.RecordManager(true);