adding some tracability into the plugins code
[myslice.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 // set to either
24 // * false or undefined or none : no debug
25 // * true : trace all event calls
26 // * [ 'in_progress', 'query_done' ] : would only trace to these events
27 var plugin_debug=false;
28 plugin_debug = [ 'in_progress', 'query_done' ];
29
30 var Plugin = Class.extend({
31
32     init: function(options, element) {
33         // Mix in the passed in options with the default options
34         this.options = $.extend({}, this.default_options, options);
35
36         // Save the element reference, both as a jQuery
37         // reference and a normal reference
38         this.element  = element;
39         this.$element = $(element);
40         // programmatically add specific class for publishing events
41         // used in manifold.js for triggering API events
42         if ( ! this.$element.hasClass('pubsub')) this.$element.addClass('pubsub');
43
44         // return this so we can chain/use the bridge with less code.
45         return this;
46     },
47
48     has_query_handler: function() {
49         return (typeof this.on_filter_added === 'function');
50     },
51
52     // do we need to log API calls ?
53     _is_in : function (obj, arr) {
54         for(var i=0; i<arr.length; i++) {
55             if (arr[i] == obj) return true;
56         }
57     },
58     _deserves_logging: function (event) {
59         if ( ! plugin_debug )                           return false;
60         else if ( plugin_debug === true)                return true;
61         else if (this._is_in (event, plugin_debug))     return true;
62         return false;
63     },
64
65     _query_handler: function(prefix, event_type, data) {
66         // We suppose this.query_handler_prefix has been defined if this
67         // callback is triggered    
68         var event, fn;
69         switch(event_type) {
70         case FILTER_ADDED:
71             event = 'filter_added';
72             break;
73         case FILTER_REMOVED:
74             event = 'filter_removed';
75             break;
76         case CLEAR_FILTERS:
77             event = 'filter_clear';
78             break;
79         case FIELD_ADDED:
80             event = 'field_added';
81             break;
82         case FIELD_REMOVED:
83             event = 'field_removed';
84             break;
85         case CLEAR_FIELDS:
86             event = 'field_clear';
87             break;
88         default:
89             return;
90         } // switch
91         
92         fn = 'on_' + prefix + event;
93         if (typeof this[fn] === 'function') {
94             if (this._deserves_logging (event)) {
95                 var classname=this.classname;
96                 messages.debug("Plugin._query_handler: calling "+fn+" on "+classname);
97             }
98             // call with data as parameter
99             // XXX implement anti loop
100             this[fn](data);
101         }
102     },
103
104     _record_handler: function(prefix, event_type, record) {
105         // We suppose this.query_handler_prefix has been defined if this
106         // callback is triggered    
107         var event, fn;
108         switch(event_type) {
109         case NEW_RECORD:
110             event = 'new_record';
111             break;
112         case CLEAR_RECORDS:
113             event = 'clear_records';
114             break;
115         case IN_PROGRESS:
116             event = 'query_in_progress';
117             break;
118         case DONE:
119             event = 'query_done';
120             break;
121         case FIELD_STATE_CHANGED:
122             event = 'field_state_changed';
123             break;
124         default:
125             return;
126         } // switch
127         
128         fn = 'on_' + prefix + event;
129         if (typeof this[fn] === 'function') {
130             if (this._deserves_logging (event)) {
131                 var classname=this.classname;
132                 messages.debug("Plugin._record_handler: calling "+fn+" on "+classname);
133             }
134             // call with data as parameter
135             // XXX implement anti loop
136             this[fn](record);
137         }
138     },
139
140     get_handler_function: function(type, prefix) {
141         
142         return $.proxy(function(e, event_type, record) {
143             return this['_' + type + '_handler'](prefix, event_type, record);
144         }, this);
145     },
146
147     listen_query: function(query_uuid, prefix) {
148         // default: prefix = ''
149         prefix = (typeof prefix === 'undefined') ? '' : (prefix + '_');
150
151         this.$element.on(manifold.get_channel('query', query_uuid),  this.get_handler_function('query',  prefix));
152         this.$element.on(manifold.get_channel('record', query_uuid),  this.get_handler_function('record', prefix));
153     },
154
155     default_options: {},
156
157     /* Helper functions for naming HTML elements (ID, classes), with support for filters and fields */
158
159     id: function() {
160         var ret = this.options.plugin_uuid;
161         for (var i = 0; i < arguments.length; i++) {
162             ret = ret + manifold.separator + arguments[i];
163         }
164         return ret;
165     },
166
167     elmt: function() {
168         if (arguments.length == 0) {
169             return $('#' + this.id());
170         } else {
171             // We make sure to search _inside_ the dom tag of the plugin
172             return $('#' + this.id.apply(this, arguments), this.elmt());
173         }
174     },
175
176     elts: function(cls) {
177         return $('.' + cls, this.elmt());
178     },
179
180     id_from_filter: function(filter, use_value) {
181         use_value = typeof use_value !== 'undefined' ? use_value : true;
182
183         var key    = filter[0];
184         var op     = filter[1];
185         var value  = filter[2];
186         var op_str = this.getOperatorLabel(op);
187         var s      = manifold.separator;
188
189         if (use_value) {
190             return 'filter' + s + key + s + op_str + s + value;
191         } else {
192             return 'filter' + s + key + s + op_str;
193         }
194     },
195
196     str_from_filter: function(filter) {
197         return filter[0] + ' ' + filter[1] + ' ' + filter[2];
198     },
199
200     array_from_id: function(id) {
201         var ret = id.split(manifold.separator);
202         ret.shift(); // remove plugin_uuid at the beginning
203         return ret;
204     },
205
206     id_from_field: function(field) {
207         return 'field' + manifold.separator + field;
208     },
209
210     field_from_id: function(id) {
211         var array;
212         if (typeof id === 'string') {
213             array = id.split(manifold.separator);
214         } else { // We suppose we have an array ('object')
215             array = id;
216         }
217         // array = ['field', FIELD_NAME]
218         return array[1];
219     },
220
221     id_from_key: function(key_field, value) {
222         
223         return key_field + manifold.separator + this.escape_id(value).replace(/\\/g, '');
224     },
225
226     // NOTE
227     // at some point in time we used to have a helper function named 'flat_id' here
228     // the goals was to sort of normalize id's but it turned out we can get rid of that
229     // in a nutshell, we would have an id (can be urn, hrn, whatever) and 
230     // we want to be able to retrieve a DOM element based on that (e.g. a checkbox)
231     // so we did something like <tag id="some-id-that-comes-from-the-db">
232     // and then $("#some-id-that-comes-from-the-db")
233     // however the syntax for that selector prevents from using some characters in id
234     // and so for some of our ids this won't work
235     // instead of 'flattening' we now do this instead
236     // <tag some_id="then!we:can+use.what$we!want">
237     // and to retrieve it
238     // $("[some_id='then!we:can+use.what$we!want']")
239     // which thanks to the quotes, works; and you can use this with id as well in fact
240     // of course if now we have quotes in the id it's going to squeak, but well..
241
242     // escape (read: backslashes) some meta-chars in input
243     escape_id: function(id) {
244         if( id !== undefined){
245             return id.replace( /(:|\.|\[|\])/g, "\\$1" );
246         }else{
247             return "undefined-id";
248         }
249     },
250
251     id_from_record: function(method, record) {
252         var keys = manifold.metadata.get_key(method);
253         if (!keys)
254             return;
255         if (keys.length > 1)
256             return;
257
258         var key = keys[0];
259         switch (Object.toType(key)) {
260         case 'string':
261             if (!(key in record))
262                 return null;
263             return this.id_from_key(key, record[key]);
264             
265         default:
266             throw 'Not implemented';
267         }
268     },
269
270     key_from_id: function(id) {
271         // NOTE this works only for simple keys
272
273         var array;
274         if (typeof id === 'string') {
275             array = id.split(manifold.separator);
276         } else { // We suppose we have an array ('object')
277             array = id;
278         }
279
280         // arguments has the initial id but lacks the key field name (see id_from_key), so we are even
281         // we finally add +1 for the plugin_uuid at the beginning
282         return array[arguments.length + 1];
283     },
284
285     // TOGGLE
286     // plugin-helper.js is about managing toggled state
287     // it would be beneficial to merge it in here
288     toggle_on: function () { return this.toggle("true"); },
289     toggle_off: function () { return this.toggle("false"); },
290     toggle: function (status) {
291         plugin_helper.set_toggle_status (this.options.plugin_uuid,status);
292     },
293
294     /* SPIN */
295     // use spin() to get our default spin settings (called presets)
296     // use spin(true) to get spin's builtin defaults
297     // you can also call spin_presets() yourself and tweak what you need to, like topmenuvalidation does
298     spin: function (presets) {
299         var presets = ( presets === undefined ) ? spin_presets() : presets;
300         try { this.$element.spin(presets); }
301         catch (err) { messages.debug("Cannot turn on spin " + err); }
302     },
303
304     unspin: function() {
305         try { this.$element.spin(false); }
306         catch (err) { messages.debug("Cannot turn off spin " + err); }
307     },
308
309     /* TEMPLATE */
310
311     load_template: function(name, ctx) {
312         return Mustache.render(this.elmt(name).html(), ctx);
313     },
314
315 });