1 // Common parts for angularjs plugins
2 // only one ng-app is allowed
4 var ManifoldApp = angular.module('ManifoldApp', []);
5 ManifoldApp.config(function ($interpolateProvider) {
6 $interpolateProvider.startSymbol('{[{').endSymbol('}]}');
9 ManifoldApp.factory('$exceptionHandler', function () {
10 return function (exception, cause) {
11 console.log(exception.message);
15 ManifoldApp.filter('offset', function() {
16 return function(input, start) {
17 start = parseInt(start, 10);
18 return input.slice(start);
22 // http://stackoverflow.com/questions/19992090/angularjs-group-by-directive
23 ManifoldApp.filter('groupBy', ['$parse', function ($parse) {
24 return function (list, group_by) {
28 var group_changed = false;
29 // this is a new field which is added to each item where we append "_CHANGED"
30 // to indicate a field change in the list
31 //was var new_field = group_by + '_CHANGED'; - JB 12/17/2013
32 var new_field = 'group_by_CHANGED';
34 // loop through each item in the list
35 angular.forEach(list, function (item) {
37 group_changed = false;
39 // if not the first item
40 if (prev_item !== null) {
42 // check if any of the group by field changed
44 //force group_by into Array
45 group_by = angular.isArray(group_by) ? group_by : [group_by];
47 //check each group by parameter
48 for (var i = 0, len = group_by.length; i < len; i++) {
49 if ($parse(group_by[i])(prev_item) !== $parse(group_by[i])(item)) {
55 }// otherwise we have the first item in the list which is new
60 // if the group changed, then add a new field to the item
63 item[new_field] = true;
65 item[new_field] = false;
77 // https://github.com/angular-ui/angular-ui-OLDREPO/blob/master/modules/filters/unique/unique.js
79 * Filters out all duplicate items from an array by checking the specified key
80 * @param [key] {string} the name of the attribute of each object to compare for uniqueness
81 if the key is empty, the entire object will be compared
82 if the key === false then no filtering will be performed
85 ManifoldApp.filter('unique', function () {
87 return function (items, filterOn) {
89 if (filterOn === false) {
93 if ((filterOn || angular.isUndefined(filterOn)) && angular.isArray(items)) {
94 var hashCheck = {}, newItems = [];
96 var extractValueToCompare = function (item) {
97 if (angular.isObject(item) && angular.isString(filterOn)) {
98 return item[filterOn];
104 angular.forEach(items, function (item) {
105 var valueToCheck, isDuplicate = false;
107 for (var i = 0; i < newItems.length; i++) {
108 if (angular.equals(extractValueToCompare(newItems[i]), extractValueToCompare(item))) {
125 // http://alexsexton.com/blog/2010/02/using-inheritance-patterns-to-organize-large-jquery-applications/
126 // We will use John Resig's proposal
128 // http://pastie.org/517177
130 // NOTE: missing a destroy function
132 $.plugin = function(name, object) {
133 $.fn[name] = function(options) {
134 var args = Array.prototype.slice.call(arguments, 1);
135 return this.each(function() {
136 var instance = $.data(this, name);
138 instance[options].apply(instance, args);
140 instance = $.data(this, name, new object(options, this));
147 // * false or undefined or none : no debug
148 // * true : trace all event calls
149 // * [ 'in_progress', 'query_done' ] : would only trace to these events
150 var plugin_debug=false;
151 plugin_debug = [ 'in_progress', 'query_done' ];
153 var Plugin = Class.extend({
155 init: function(options, element) {
156 // Mix in the passed in options with the default options
157 this.options = $.extend({}, this.default_options, options);
159 // Save the element reference, both as a jQuery
160 // reference and a normal reference
161 this.element = element;
162 this.$element = $(element);
163 // programmatically add specific class for publishing events
164 // used in manifold.js for triggering API events
165 if ( ! this.$element.hasClass('pubsub')) this.$element.addClass('pubsub');
167 // return this so we can chain/use the bridge with less code.
171 has_query_handler: function() {
172 return (typeof this.on_filter_added === 'function');
175 // do we need to log API calls ?
176 _is_in : function (obj, arr) {
177 for(var i=0; i<arr.length; i++) {
178 if (arr[i] == obj) return true;
181 _deserves_logging: function (event) {
182 if ( ! plugin_debug ) return false;
183 else if ( plugin_debug === true) return true;
184 else if (this._is_in (event, plugin_debug)) return true;
188 _query_handler: function(prefix, event_type, data) {
189 // We suppose this.query_handler_prefix has been defined if this
190 // callback is triggered
194 event = 'filter_added';
197 event = 'filter_removed';
200 event = 'filter_clear';
203 event = 'field_added';
206 event = 'field_removed';
209 event = 'field_clear';
215 fn = 'on_' + prefix + event;
216 if (typeof this[fn] === 'function') {
217 if (this._deserves_logging (event)) {
218 var classname=this.classname;
219 messages.debug("Plugin._query_handler: calling "+fn+" on "+classname);
221 // call with data as parameter
222 // XXX implement anti loop
227 _record_handler: function(prefix, event_type, record) {
228 // We suppose this.query_handler_prefix has been defined if this
229 // callback is triggered
233 event = 'new_record';
236 event = 'clear_records';
239 event = 'query_in_progress';
242 event = 'query_done';
244 case FIELD_STATE_CHANGED:
245 event = 'field_state_changed';
251 fn = 'on_' + prefix + event;
252 if (typeof this[fn] === 'function') {
253 if (this._deserves_logging (event)) {
254 var classname=this.classname;
255 messages.debug("Plugin._record_handler: calling "+fn+" on "+classname);
257 // call with data as parameter
258 // XXX implement anti loop
263 get_handler_function: function(type, prefix) {
265 return $.proxy(function(e, event_type, record) {
266 return this['_' + type + '_handler'](prefix, event_type, record);
270 listen_query: function(query_uuid, prefix) {
271 // default: prefix = ''
272 prefix = (typeof prefix === 'undefined') ? '' : (prefix + '_');
274 this.$element.on(manifold.get_channel('query', query_uuid), this.get_handler_function('query', prefix));
275 this.$element.on(manifold.get_channel('record', query_uuid), this.get_handler_function('record', prefix));
280 /* Helper functions for naming HTML elements (ID, classes), with support for filters and fields */
283 var ret = this.options.plugin_uuid;
284 for (var i = 0; i < arguments.length; i++) {
285 ret = ret + manifold.separator + arguments[i];
291 if (arguments.length == 0) {
292 return $('#' + this.id());
294 // We make sure to search _inside_ the dom tag of the plugin
295 return $('#' + this.id.apply(this, arguments), this.elmt());
299 elts: function(cls) {
300 return $('.' + cls, this.elmt());
303 id_from_filter: function(filter, use_value) {
304 use_value = typeof use_value !== 'undefined' ? use_value : true;
308 var value = filter[2];
309 var op_str = this.getOperatorLabel(op);
310 var s = manifold.separator;
313 return 'filter' + s + key + s + op_str + s + value;
315 return 'filter' + s + key + s + op_str;
319 str_from_filter: function(filter) {
320 return filter[0] + ' ' + filter[1] + ' ' + filter[2];
323 array_from_id: function(id) {
324 var ret = id.split(manifold.separator);
325 ret.shift(); // remove plugin_uuid at the beginning
329 id_from_field: function(field) {
330 return 'field' + manifold.separator + field;
333 field_from_id: function(id) {
335 if (typeof id === 'string') {
336 array = id.split(manifold.separator);
337 } else { // We suppose we have an array ('object')
340 // array = ['field', FIELD_NAME]
344 id_from_key: function(key_field, value) {
346 return key_field + manifold.separator + this.escape_id(value); //.replace(/\\/g, '');
350 // at some point in time we used to have a helper function named 'flat_id' here
351 // the goals was to sort of normalize id's but it turned out we can get rid of that
352 // in a nutshell, we would have an id (can be urn, hrn, whatever) and
353 // we want to be able to retrieve a DOM element based on that (e.g. a checkbox)
354 // so we did something like <tag id="some-id-that-comes-from-the-db">
355 // and then $("#some-id-that-comes-from-the-db")
356 // however the syntax for that selector prevents from using some characters in id
357 // and so for some of our ids this won't work
358 // instead of 'flattening' we now do this instead
359 // <tag some_id="then!we:can+use.what$we!want">
360 // and to retrieve it
361 // $("[some_id='then!we:can+use.what$we!want']")
362 // which thanks to the quotes, works; and you can use this with id as well in fact
363 // of course if now we have quotes in the id it's going to squeak, but well..
365 // escape (read: backslashes) some meta-chars in input
366 escape_id: function(id) {
367 if( id !== undefined){
368 return id.replace( /(:|\.|\[|\])/g, "\\$1" );
370 return "undefined-id";
374 id_from_record: function(method, record) {
375 var keys = manifold.metadata.get_key(method);
382 switch (Object.toType(key)) {
384 if (!(key in record))
386 return this.id_from_key(key, record[key]);
389 throw 'Not implemented';
393 key_from_id: function(id) {
394 // NOTE this works only for simple keys
397 if (typeof id === 'string') {
398 array = id.split(manifold.separator);
399 } else { // We suppose we have an array ('object')
403 // arguments has the initial id but lacks the key field name (see id_from_key), so we are even
404 // we finally add +1 for the plugin_uuid at the beginning
405 return array[arguments.length + 1];
409 // plugin-helper.js is about managing toggled state
410 // it would be beneficial to merge it in here
411 toggle_on: function () { return this.toggle("true"); },
412 toggle_off: function () { return this.toggle("false"); },
413 toggle: function (status) {
414 plugin_helper.set_toggle_status (this.options.plugin_uuid,status);
418 // use spin() to get our default spin settings (called presets)
419 // use spin(true) to get spin's builtin defaults
420 // you can also call spin_presets() yourself and tweak what you need to, like topmenuvalidation does
421 spin: function (message) {
423 message = 'Please be patient, this operation can take a minute or two.';
425 $('div.loading').fadeIn('fast');
426 $('div.loading').find('.message').text(message);
431 $('div.loading').fadeOut('fast');
437 load_template: function(name, ctx) {
438 return Mustache.render(this.elmt(name).html(), ctx);