Merge branch 'master' of ssh://git.onelab.eu/git/myslice
[unfold.git] / manifold / static / js / plugin.js
1 // INHERITANCE
2 // http://alexsexton.com/blog/2010/02/using-inheritance-patterns-to-organize-large-jquery-applications/
3 // We will use John Resig's proposal
4
5 // http://pastie.org/517177
6
7 // NOTE: missing a destroy function
8
9 $.plugin = function(name, object) {
10     $.fn[name] = function(options) {
11         var args = Array.prototype.slice.call(arguments, 1);
12         return this.each(function() {
13             var instance = $.data(this, name);
14             if (instance) {
15                 instance[options].apply(instance, args);
16             } else {
17                 instance = $.data(this, name, new object(options, this));
18             }
19         });
20     };
21 };
22
23 var Plugin = Class.extend({
24
25     init: function(options, element) {
26         // Mix in the passed in options with the default options
27         this.options = $.extend({}, this.default_options, options);
28
29         // Save the element reference, both as a jQuery
30         // reference and a normal reference
31         this.element  = element;
32         this.$element = $(element);
33
34         // return this so we can chain/use the bridge with less code.
35         return this;
36     },
37
38     has_query_handler: function() {
39         return (typeof this.on_filter_added === 'function');
40     },
41
42     _query_handler: function(prefix, event_type, data) {
43         // We suppose this.query_handler_prefix has been defined if this
44         // callback is triggered    
45         var fn;
46         switch(event_type) {
47         case FILTER_ADDED:
48             fn = 'filter_added';
49             break;
50         case FILTER_REMOVED:
51             fn = 'filter_removed';
52             break;
53         case CLEAR_FILTERS:
54             fn = 'filter_clear';
55             break;
56         case FIELD_ADDED:
57             fn = 'field_added';
58             break;
59         case FIELD_REMOVED:
60             fn = 'field_removed';
61             break;
62         case CLEAR_FIELDS:
63             fn = 'field_clear';
64             break;
65         default:
66             return;
67         } // switch
68         
69         fn = 'on_' + prefix + fn;
70         if (typeof this[fn] === 'function') {
71             // call with data as parameter
72             // XXX implement anti loop
73             this[fn](data);
74         }
75     },
76
77     _record_handler: function(prefix, event_type, record) {
78         // We suppose this.query_handler_prefix has been defined if this
79         // callback is triggered    
80         var fn;
81         switch(event_type) {
82         case NEW_RECORD:
83             fn = 'new_record';
84             break;
85         case CLEAR_RECORDS:
86             fn = 'clear_records';
87             break;
88         case IN_PROGRESS:
89             fn = 'query_in_progress';
90             break;
91         case DONE:
92             fn = 'query_done';
93             break;
94         case FIELD_STATE_CHANGED:
95             fn = 'field_state_changed';
96             break;
97         default:
98             return;
99         } // switch
100         
101         fn = 'on_' + prefix + fn;
102         if (typeof this[fn] === 'function') {
103             // call with data as parameter
104             // XXX implement anti loop
105             this[fn](record);
106         }
107     },
108
109     get_handler_function: function(type, prefix) {
110         
111         return $.proxy(function(e, event_type, record) {
112             return this['_' + type + '_handler'](prefix, event_type, record);
113         }, this);
114     },
115
116     listen_query: function(query_uuid, prefix) {
117         // default: prefix = ''
118         prefix = (typeof prefix === 'undefined') ? '' : (prefix + '_');
119
120         this.$element.on(manifold.get_channel('query', query_uuid),  this.get_handler_function('query',  prefix));
121         this.$element.on(manifold.get_channel('record', query_uuid),  this.get_handler_function('record', prefix));
122     },
123
124     default_options: {},
125
126     /* Helper functions for naming HTML elements (ID, classes), with support for filters and fields */
127
128     id: function() {
129         var ret = this.options.plugin_uuid;
130         for (var i = 0; i < arguments.length; i++) {
131             ret = ret + manifold.separator + arguments[i];
132         }
133         return ret;
134     },
135
136     elmt: function() {
137         if (arguments.length == 0) {
138             return $('#' + this.id());
139         } else {
140             // We make sure to search _inside_ the dom tag of the plugin
141             return $('#' + this.id.apply(this, arguments), this.elmt());
142         }
143     },
144
145     elts: function(cls) {
146         return $('.' + cls, this.elmt());
147     },
148
149     id_from_filter: function(filter, use_value) {
150         use_value = typeof use_value !== 'undefined' ? use_value : true;
151
152         var key    = filter[0];
153         var op     = filter[1];
154         var value  = filter[2];
155         var op_str = this.getOperatorLabel(op);
156         var s      = manifold.separator;
157
158         if (use_value) {
159             return 'filter' + s + key + s + op_str + s + value;
160         } else {
161             return 'filter' + s + key + s + op_str;
162         }
163     },
164
165     str_from_filter: function(filter) {
166         return filter[0] + ' ' + filter[1] + ' ' + filter[2];
167     },
168
169     array_from_id: function(id) {
170         var ret = id.split(manifold.separator);
171         ret.shift(); // remove plugin_uuid at the beginning
172         return ret;
173     },
174
175     id_from_field: function(field) {
176         return 'field' + manifold.separator + field;
177     },
178
179     field_from_id: function(id) {
180         var array;
181         if (typeof id === 'string') {
182             array = id.split(manifold.separator);
183         } else { // We suppose we have an array ('object')
184             array = id;
185         }
186         // array = ['field', FIELD_NAME]
187         return array[1];
188     },
189
190     id_from_key: function(key_field, value) {
191         
192         return key_field + manifold.separator + this.escape_id(value).replace(/\\/g, '');
193     },
194
195     // NOTE
196     // at some point in time we used to have a helper function named 'flat_id' here
197     // the goals was to sort of normalize id's but it turned out we can get rid of that
198     // in a nutshell, we would have an id (can be urn, hrn, whatever) and 
199     // we want to be able to retrieve a DOM element based on that (e.g. a checkbox)
200     // so we did something like <tag id="some-id-that-comes-from-the-db">
201     // and then $("#some-id-that-comes-from-the-db")
202     // however the syntax for that selector prevents from using some characters in id
203     // and so for some of our ids this won't work
204     // instead of 'flattening' we now do this instead
205     // <tag some_id="then!we:can+use.what$we!want">
206     // and to retrieve it
207     // $("[some_id='then!we:can+use.what$we!want']")
208     // which thanks to the quotes, works; and you can use this with id as well in fact
209     // of course if now we have quotes in the id it's going to squeak, but well..
210
211     // escape (read: backslashes) some meta-chars in input
212     escape_id: function(id) {
213         if( id !== undefined){
214             return id.replace( /(:|\.|\[|\])/g, "\\$1" );
215         }else{
216             return "undefined-id";
217         }
218     },
219
220     id_from_record: function(method, record) {
221         var keys = manifold.metadata.get_key(method);
222         if (!keys)
223             return;
224         if (keys.length > 1)
225             return;
226
227         var key = keys[0];
228         switch (Object.toType(key)) {
229         case 'string':
230             if (!(key in record))
231                 return null;
232             return this.id_from_key(key, record[key]);
233             
234         default:
235             throw 'Not implemented';
236         }
237     },
238
239     key_from_id: function(id) {
240         // NOTE this works only for simple keys
241
242         var array;
243         if (typeof id === 'string') {
244             array = id.split(manifold.separator);
245         } else { // We suppose we have an array ('object')
246             array = id;
247         }
248
249         // arguments has the initial id but lacks the key field name (see id_from_key), so we are even
250         // we finally add +1 for the plugin_uuid at the beginning
251         return array[arguments.length + 1];
252     },
253
254     // TOGGLE
255     // plugin-helper.js is about managing toggled state
256     // it would be beneficial to merge it in here
257     toggle_on: function () { return this.toggle("true"); },
258     toggle_off: function () { return this.toggle("false"); },
259     toggle: function (status) {
260         plugin_helper.set_toggle_status (this.options.plugin_uuid,status);
261     },
262
263     /* SPIN */
264
265     spin: function() {
266         manifold.spin(this.element);
267     },
268
269     unspin: function() {
270         manifold.spin(this.element, false);
271     },
272
273     /* TEMPLATE */
274
275     load_template: function(name, ctx) {
276         return Mustache.render(this.elmt(name).html(), ctx);
277     },
278
279 });