Only auto plural word after number if it's greater than 1
[ajatus.git] / js / ajatus.core.js
blob50182b0b8f6d4e18065a17fdb98cfc8b785b2187
1 /*
2  * This file is part of
3  *
4  * {@link http://ajatus.info Ajatus - Distributed CRM}
5  * @requires jQuery v1.2.1
6  * 
7  * Copyright (c) 2007 Jerry Jalava <jerry.jalava@gmail.com>
8  * Copyright (c) 2007 Nemein Oy <http://nemein.com>
9  * Website: http://ajatus.info
10  * Licensed under the GPL license
11  * http://www.gnu.org/licenses/gpl.html
12  * 
13  */
16     TODO Tags are links
17     TODO Additional fields [widget]
18     TODO Skype widget -bergie
19     TODO Formatter service
20     TODO XMMP & Email formatter
21     TODO Gravatar widget
22     TODO Implement arhive view
23     TODO Implement file attachments
24     TODO Documentation (API)
27 if (typeof console == 'undefined') {
28     var console = {};
29     console.log = function() {
30         return;
31     };
34 (function($){
35     $.ajatus = {};
36     $.ajatus.application_content_area = null;
37     
38     $.ajatus.version = [0, 5, 1];
39     
40     $.ajatus.active_type = null;
41     
42     /**
43      * Holds all converters available in Ajatus.
44      */
45     $.ajatus.converter = {};
47     /**
48      * Parses and evaluates JSON string to Javascript
49      * @param {String} json_str JSON String
50      * @returns Parsed JSON string or false on failure
51      */
52     $.ajatus.converter.parseJSON = function (json_str) {
53         try {
54             var re = new RegExp('[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]');
55             return !(re.test(json_str.replace(/"(\\.|[^"\\])*"/g, ''))) && eval('(' + json_str + ')');
56         } catch (e) {
57             return false;
58         }
59     }
61     /**
62      * Parses Javascript to JSON
63      * @param {Mixed} item Javascript to be parsed
64      * @param {String} type of the Javascript item to be parsed (Optional)
65      * @returns JSON string
66      * @type String
67      */
68     $.ajatus.converter.toJSON = function (item,item_type) {
69         var m = {
70                 '\b': '\\b',
71                 '\t': '\\t',
72                 '\n': '\\n',
73                 '\f': '\\f',
74                 '\r': '\\r',
75                 '"' : '\\"',
76                 '\\': '\\\\'
77             },
78             s = {
79                 arr: function (x) {
80                     var a = ['['], b, f, i, l = x.length, v;
81                     for (i = 0; i < l; i += 1) {
82                         v = x[i];
83                         v = conv(v);
84                         if (typeof v == 'string') {
85                             if (b) {
86                                 a[a.length] = ',';
87                             }
88                             a[a.length] = v;
89                             b = true;
90                         }
91                     }
92                     a[a.length] = ']';
93                     return a.join('');
94                 },
95                 bool: function (x) {
96                     return String(x);
97                 },
98                 nul: function (x) {
99                     return "null";
100                 },
101                 num: function (x) {
102                     return isFinite(x) ? String(x) : 'null';
103                 },
104                 obj: function (x) {
105                     if (x) {
106                         if (x instanceof Array) {
107                             return s.arr(x);
108                         }
109                         var a = ['{'], b, f, i, v;
110                         for (i in x) {
111                             v = x[i];
112                             v = conv(v);
113                             if (typeof v == 'string') {
114                                 if (b) {
115                                     a[a.length] = ',';
116                                 }
117                                 a.push(s.str(i), ':', v);
118                                 b = true;
119                             }
120                         }
121                         a[a.length] = '}';
122                         return a.join('');
123                     }
124                     return 'null';
125                 },
126                 str: function (x) {
127                     if (/["\\\x00-\x1f]/.test(x)) {
128                         x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
129                             var c = m[b];
130                             if (c) {
131                                 return c;
132                             }
133                             c = b.charCodeAt();
134                             return '\\u00' +
135                                 Math.floor(c / 16).toString(16) +
136                                 (c % 16).toString(16);
137                         });
138                     }
139                     return '"' + x + '"';
140                 }
141             };
142             conv = function (x) {
143                 var itemtype = typeof x;
144                 switch(itemtype) {
145                     case "array":
146                       return s.arr(x);
147                       break;
148                     case "object":
149                       return s.obj(x);
150                       break;
151                     case "string":
152                       return s.str(x);
153                       break;
154                     case "number":
155                       return s.num(x);
156                       break;
157                     case "null":
158                       return s.nul(x);
159                       break;
160                     case "boolean":
161                       return s.bool(x);
162                       break;
163                 }
164             }
166         var itemtype = item_type || typeof item;
167         switch(itemtype) {
168             case "array":
169               return s.arr(item);
170               break;
171             case "object":
172               return s.obj(item);
173               break;
174             case "string":
175               return s.str(item);
176               break;
177             case "number":
178               return s.num(item);
179               break;
180             case "null":
181               return s.nul(item);
182               break;
183             case "boolean":
184               return s.bool(item);
185               break;                
186             default:
187               throw("Unknown type for $.ajatus.converter.toJSON");
188             }
189     }
190     
191     /**
192      * Holds all formatters available in Ajatus.
193      * @constructor
194      */
195     $.ajatus.formatter = {};
197     /**
198      * Holds all date formatters available in Ajatus.
199      * @constructor
200      */
201     $.ajatus.formatter.date = {};
203     /**
204      * Formats numbers < 10 to two digit numbers
205      * @param {Number} number Number to format
206      * @returns Formatted number
207      * @type String
208      */
209     $.ajatus.formatter.date.fix_length = function(number){
210         return ((number < 10) ? '0' : '') + number;
211     };
213     /**
214      * Checks if given value is ISO8601 Formatted Date
215      * @param {String} value Value to check
216      * @returns True, False if value not string or ISO8601 formatted
217      * @type Boolean
218      */
219     $.ajatus.formatter.date.is_iso8601 = function(value) {
220         if (   typeof value != 'string'
221             || value == '')
222         {
223             return false;
224         }
225         
226         var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
227         var isod = value.match(new RegExp(regexp));
228         
229         if (   typeof isod == 'object'
230             && typeof isod[10] != 'undefined')
231         {
232             return true;
233         }
234         
235         return false;
236     };
238     /**
239      * Converts JS Date to ISO8601 Formatted JS Date
240      * @param {Date} date Javascript date to convert
241      * @returns ISO8601 formatted Date
242      * @type Date
243      */
244     $.ajatus.formatter.date.js_to_iso8601 = function(date) {
245         var str = "";
246         str += date.getFullYear();
247         str += "-" + $.ajatus.formatter.date.fix_length(date.getMonth() + 1);
248         str += "-" + $.ajatus.formatter.date.fix_length(date.getDate());
249         str += "T" + $.ajatus.formatter.date.fix_length(date.getHours());
250         str += ":" + $.ajatus.formatter.date.fix_length(date.getMinutes());
251         str += ":" + $.ajatus.formatter.date.fix_length(date.getSeconds());
252         //str += 'Z';
253         
254         return str;
255     };
257     /**
258      * Converts ISO8601 Formatted JS Date to JS Date
259      * @param {Date} iso_date ISO8601 formatted date to convert
260      * @returns Javascript Date
261      * @type Date
262      */
263     $.ajatus.formatter.date.iso8601_to_js = function(iso_date) {
264         var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
265         var isod = iso_date.match(new RegExp(regexp));
266         
267         if (   !isod
268             || isod.length < 11)
269         {
270             return new Date();
271         }
272         
273         var date = new Date(isod[1], 0, 1);
274         date.setMonth(isod[3] - 1);
275         date.setDate(isod[5]);
276         date.setHours(isod[7]);
277         date.setMinutes(isod[8]);
278         date.setSeconds(isod[10]);
279         
280         return date;
281     };
282     
283     /**
284      * Converts Date string from calendar widget to ISO8601 Formatted JS Date
285      * @param {String} caldate Date value from calendar widget
286      * @param {String} format Format used in conversion (Default: "MDY/")
287      * @param {String} caltime Time value from calendar widget
288      * @param {String} time_format Format used in conversion (Default: "HMS:")
289      * @returns ISO8601 formatted Javascript Date
290      * @type Date
291      */
292     $.ajatus.formatter.date.caldate_to_iso8601 = function(caldate, format, caltime, time_format) {
293         
294         var format = format || ($.ajatus.i10n.datetime.date || "MDY/");
295         var time_format = time_format || ($.ajatus.i10n.datetime.time || "HMS:");
296         
297         var cdparts = caldate.split(format.charAt(3));
298         var fparts = format.split("");
299         var date = new Date();
300         
301         $.each(cdparts, function(i,v){
302             v = Number(v);
303             if (fparts[i] == 'M') {
304                 date.setMonth(v-1);
305             }
306             if (fparts[i] == 'D') {
307                 date.setDate(v);
308             }
309             if (fparts[i] == 'Y') {
310                 date.setFullYear(v);
311             }
312         });
313         
314         if (caltime) {
315             var time_separator = time_format.charAt(time_format.length - 1);
316             var tfparts = time_format.split("");
317             var ctparts = caltime.split(time_separator);
319             $.each(ctparts, function(i,v){
320                 v = Number(v);
321                 if (tfparts[i] == 'H') {
322                     date.setHours(v);
323                 }
324                 if (tfparts[i] == 'M') {
325                     date.setMinutes(v);
326                 }
327                 if (tfparts[i] == 'S') {
328                     date.setSeconds(v);
329                 }
330             });
331         } else {
332             date.setHours(0);
333             date.setMinutes(0);
334             date.setSeconds(0);
335         }
336                 
337         return $.ajatus.formatter.date.js_to_iso8601(date);
338     };
339     
340     /**
341      * Converts ISO8601 Formatted JS Date to calendar widget date string
342      * @param {Date} iso_date ISO8601 Formatted Date
343      * @param {String} format Format used in conversion (Default: "MDY/")
344      * @returns Calendar widget supported value
345      * @type String
346      */
347     $.ajatus.formatter.date.iso8601_to_caldate = function(iso_date, format) {
348         var format = format || ($.ajatus.i10n.datetime.date || "MDY/");
349         
350         var date = $.ajatus.formatter.date.iso8601_to_js(iso_date);
351         var date_str = '';
352         
353         var day = date.getDate();
354         var month = date.getMonth();
355         var year = date.getFullYear();
356         month++;
357         
358         for (var i = 0; i < 3; i++) {
359             date_str += format.charAt(3) +
360             (format.charAt(i) == 'D' ? $.ajatus.formatter.date.fix_length(day) :
361             (format.charAt(i) == 'M' ? $.ajatus.formatter.date.fix_length(month) :
362             (format.charAt(i) == 'Y' ? year : '?')));
363         }
364         date_str = date_str.substring(format.charAt(3) ? 1 : 0);
365         
366         return date_str;
367     };
368     
369     /**
370      * Converts ISO8601 Formatted JS Date to calendar widget time string
371      * @param {Date} iso_date ISO8601 Formatted Date
372      * @param {String} format Format used in conversion (Default: "HMS:")
373      * @returns Calendar widget supported value
374      * @type String
375      */
376     $.ajatus.formatter.date.iso8601_to_caltime = function(iso_date, format) {
377         var format = format || ($.ajatus.i10n.datetime.time || "HMS:");
378         
379         var date = $.ajatus.formatter.date.iso8601_to_js(iso_date);
380         var time_str = '';
381         
382         var idparts = {
383             H: $.ajatus.formatter.date.fix_length(date.getHours()),
384             M: $.ajatus.formatter.date.fix_length(date.getMinutes()),
385             S: $.ajatus.formatter.date.fix_length(date.getSeconds())
386         };
388         var separator = format.charAt(format.length - 1);
389         var fparts = format.split("");
390         
391         $.each(fparts, function(i,k){
392             if (typeof idparts[k] != 'undefined') {
393                 time_str += idparts[k] + separator;
394             }
395         });
396         time_str = time_str.substring((time_str.length-1), -1);
397         
398         return time_str;
399     };
400     
401     $.ajatus.tabs = {};
402     $.ajatus.tabs.prepare = function(tab) {
403         tab.bind('mouseover', function(e){
404             tab.addClass('tabs-hover');
405         });
406         tab.bind('mouseout', function(e){
407             tab.removeClass('tabs-hover');
408         });
409     };
410     $.ajatus.tabs.set_active_by_hash = function(hash) {
411         // $.ajatus.debug('$.ajatus.tabs.set_active_by_hash('+hash+')');
412         
413         var views_tab_holder = $('#tabs-views ul');
414         var app_tab_holder = $('#tabs-application ul');
415         
416         var selected_tab = $('li a[@href$="' + hash + '"]', views_tab_holder).parent();
417         if (typeof selected_tab[0] != 'undefined') {
418             // $.ajatus.debug('views selected_tab:');
419             // $.ajatus.debug(selected_tab);
420             $('li', views_tab_holder).removeClass('tabs-selected').blur();
421             $('li', app_tab_holder).removeClass('tabs-selected').blur();
422             selected_tab.addClass('tabs-selected').focus();
423         } else {
424             selected_tab = $('li a[@href$="' + hash + '"]', app_tab_holder).parent();
425             // $.ajatus.debug('app selected_tab:');
426             // $.ajatus.debug(selected_tab);
427             if (typeof selected_tab[0] != 'undefined') {
428                 $('li', views_tab_holder).removeClass('tabs-selected').blur();
429                 $('li', app_tab_holder).removeClass('tabs-selected').blur();
430                 selected_tab.addClass('tabs-selected').focus();
431             } else {
432                 $('li', views_tab_holder).removeClass('tabs-selected').blur();
433                 $('li', app_tab_holder).removeClass('tabs-selected').blur();                
434             }            
435         }
436     };
437     $.ajatus.tabs.on_click = function(e) {        
438         var trgt = target(e);
439         $("li", parent(e)).removeClass("tabs-selected").index(trgt);
440         $(trgt).addClass("tabs-selected");
441         
442         var new_hash = $('a', trgt)[0].hash;
443         location.hash = new_hash;
444         
445         $.ajatus.history.update(new_hash);
446         
447         $.ajatus.views.on_change(new_hash);
448         
449         function parent(event) {
450             var element = event.target;
451             if (element)
452             {
453                 if (element.tagName == "UL")
454                 {
455                     return element;
456                 }
458                 while(element.tagName != "UL")
459                 {
460                     element = element.parentNode;
461                 }
462             }
463             return element;
464         };
466         function target(event) {
467             var element = event.target;
468             if (element)
469             {
470                 if (element.tagName == "UL")
471                 {
472                     element = $(element).find('li').eq(0);
473                     return element;
474                 }
476                 while(element.tagName != "LI")
477                 {
478                     element = element.parentNode;
479                 }
480             }
481             return element;
482         };
483     };
484     
485     $.ajatus.security_pass = function() {
486         try {
487             netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead UniversalBrowserWrite UniversalFileRead");
488         } catch (e) {
489             alert("Permission UniversalBrowserRead denied. with error "+e);
490         }
491     };
492     
493     $.ajatus.init = function(element, options) {
494         //$.ajatus.security_pass();
495         
496         var application_element = $(element);
497         $.ajatus.application_element = application_element;
498         $.ajatus.application_content_area = $('#middle #content', $.ajatus.application_element);
499         $.ajatus.application_dynamic_elements = $('#dynamic-elements', $.ajatus.application_element);
500         
501         $.ajatus.preferences.client.server_url = window.location.protocol + '//' + document.hostname + (window.location.port != '' ? ':'+window.location.port : '') + '/';
502         
503         $.ajatus.preferences.client = $.extend({}, $.ajatus.preferences.client_defaults, options);
504         //Force the system types to load:
505         $.ajatus.preferences.client.types.system = $.ajatus.preferences.client_defaults.types.system;
506         
507         $.jqCouch.set_defaults({
508             server_url: $.ajatus.preferences.client.server_url
509         });
510         
511         var a_db_suffix = $.ajatus.preferences.client.application_database_identifier != '' ? '_db' : 'db';
512         $.ajatus.preferences.client.application_database = 'ajatus_' + $.ajatus.preferences.client.application_database_identifier + a_db_suffix;
513         $.ajatus.preferences.client.content_database = $.ajatus.preferences.client.application_database + '_content';
514         
515         $.ajaxSetup({
516             type: "GET",
517             url: $.ajatus.preferences.client.server_url,
518             global: false,
519             cache: false,
520             dataType: "json"
521         });
522         
523         $.ajatus.installer.is_installed();        
524         if ($.ajatus.installer.installed == false) {
526             if ($.ajatus.preferences.client.language == null) {
527                 var lang = 'en_GB';
528                 $.ajatus.preferences.client.language = 'en_GB';
529             }
530             $.ajatus.i10n.init($.ajatus.preferences.client.language, function(){
531                 var status = $.ajatus.installer.install();
532                 $.ajatus.debug("Installer finished with status: "+status);
533                 if (status) {
534                     window.location = window.location + "#view.preferences";
535                 }
536             });
537         } else {
538             $.ajatus.preload();
539         }
540         // else
541         // {
542         //     $.ajatus.installer.uninstall();
543         // }
544     };
545     
546     $.ajatus.preload = function() {
547         var preference_loader = new $.ajatus.preferences.loader;
548         preference_loader.load();
550         if ($.ajatus.preferences.client.language == null) {
551             var lang = 'en_GB';
552             if (typeof($.ajatus.preferences.local.localization) != 'undefined') {
553                 lang = $.ajatus.preferences.local.localization.language;
554             }
555             $.ajatus.preferences.client.language = lang;
556         }
557         $.ajatus.i10n.init($.ajatus.preferences.client.language, function(){
558             $.ajatus.types.init(function(){
559                 $.ajatus.start();
560             });            
561         });       
562     };
563     
564     $.ajatus.start = function() {
565         $.ajatus.debug('started', 'Ajatus start');
567             $.ajatus.events.lock_pool.increase();
568         
569         if ($.ajatus.preferences.local.layout.theme_name != 'default') {
570             $.ajatus.preferences.client.theme_url = 'themes/' + $.ajatus.preferences.local.layout.theme_name + '/';
571             $.ajatus.preferences.client.theme_icons_url = 'themes/' + $.ajatus.preferences.local.layout.icon_set + '/images/icons/';
572                         
573             $.ajatus.layout.styles.load($.ajatus.preferences.client.theme_url + 'css/structure.css');
574             $.ajatus.layout.styles.load($.ajatus.preferences.client.theme_url + 'css/common.css');
575             $.ajatus.layout.styles.load($.ajatus.preferences.client.theme_url + 'css/elements.css');
576         }
577         
578             $.ajatus.locker = new $.ajatus.events.lock({
579                 watch: {
580                     validate: function(){return $.ajatus.events.lock_pool.count == 0;},
581                     interval: 200,
582                 safety_runs: 0
583             },
584             on_release: function(){
585                 $.ajatus.application_element.addClass('ajatus_initialized');
586                 
587                 var show_frontpage = !$.ajatus.history.check();
588                 
589                 if (show_frontpage) {
590                     $.ajatus.views.system.frontpage.render();                    
591                 }
592             }
593         });
594         
595         if (! $.jqCouch.connection('doc').get($.ajatus.preferences.client.application_database + '/version')._id) {
596             $.jqCouch.connection('doc').put($.ajatus.preferences.client.application_database + '/version', {value: $.ajatus.version});
597         }
598         
599         $('#header .app_version', $.ajatus.application_element).html($.ajatus.version.join('.'));
600         $('#header .tagline', $.ajatus.application_element).html($.ajatus.i10n.get('Distributed CRM'));
601         $('#new-item-button').text($.ajatus.i10n.get('new'));
602         
603         $.ajatus.toolbar.init();
604         $.ajatus.history.init();        
605         $.ajatus.tags.init();
606         $.ajatus.widgets.init();
607         $.ajatus.views.init();
608         
609         $.ajatus.extensions.init({
610             on_ready: function(){                
611                 $.ajatus.active_type = $.ajatus.preferences.client.content_types['note'];
613                 if ($.ajatus.preferences.modified) {
614                     $.ajatus.views.on_change_actions.add('$.ajatus.preferences.view.save($.ajatus.preferences.local)');
615                 }
616                                 
617                 // var gen_docs = $.ajatus.utils.doc_generator('notes', 200);
618                 // $.jqCouch.connection('doc').bulk_save($.ajatus.preferences.client.content_database, gen_docs);
619                 
620                 // Release the first lock
621                 $.ajatus.events.lock_pool.decrease();
622             }
623         });
624         
625         $.ajatus.elements.messages.set_holder();
626         
627         window.onbeforeunload = function() {
628             if ($.ajatus.events.named_lock_pool.count('unsaved') > 0) {
629                 return $.ajatus.i10n.get('You have unsaved changes.');
630             }
631         }
632         
633         $.ajatus.debug('ended', 'Ajatus start');
634     }
635     
636     $.ajatus.ajax_error = function(request, caller) {
637         $.ajatus.debug("Ajax error request.status: "+request.status);
638         
639         if (typeof caller != 'undefined') {
640             $.ajatus.debug("Caller method: "+caller.method);
641             $.ajatus.debug("Caller action:");
642             $.ajatus.debug(caller.action);
643             
644             if (caller.args != undefined) {
645                 $.ajatus.debug("Caller args:");
646                 $.ajatus.debug(caller.args);
647             }
648         }
649         
650         if (request.responseText != '') {
651             var response = eval("(" + request.responseText + ")");
652             $.ajatus.debug('response.error.reason: '+response.error.reason);            
653         }
654     };
655     $.ajatus.ajax_success = function(data, caller) {
656         $.ajatus.debug("Ajax success");
657         
658         if (typeof caller != 'undefined') {
659             $.ajatus.debug("Caller method: "+caller.method);
660             $.ajatus.debug("Caller action: "+caller.action);
661             
662             if (typeof caller.args != 'undefined') {
663                 $.ajatus.debug("Caller args: "+caller.args);
664             }
665         }
666         $.ajatus.debug("data: "+data);
667         
668         return false;
669     };
670     
671     $.ajatus.debug = function(msg, title, type) {
672         if ($.ajatus.preferences.client.debug == true) {
673             if (typeof(console.log) != 'undefined') {
674                 debug_console(msg, title, type);
675             } else {
676                 debug_static(msg, title, type);
677             }
678         }
680         function debug_console(msg, title, type) {
681             if (typeof type == 'undefined') {
682                 var type = 'log';
683             }
684             
685             if (typeof(console[type]) != 'undefined') {
686                 console.log(format(msg, title));
687             } else {
688                 console.log(format(msg, title));
689             }
690         }
691         
692         function debug_static(msg, title, type) {
693             // TODO: Implement Static debug handler
694         }
695         
696         function format(msg, title) {
697             var string = '';
698             
699             if (typeof title != 'undefined') {
700                 string += title += ': ';
701             }
702             
703             if (typeof msg != 'string') {
704                 string += $.ajatus.utils.pretty_print(msg);
705             } else {
706                 string += msg;
707             }
708             
709             return string;            
710         }
711     };
712     
713     $.fn.initialize_ajatus = function(options) {
714         if (! $(this).is('.ajatus_initialized')) {
715             return new $.ajatus.init(this, options);
716         }
717     };
718     
719 })(jQuery);
721 function initialize_ajatus() {
722     if (typeof ajatus_client_config == 'undefined') {
723         var ajatus_client_config = {};
724     }
725     
726     jQuery('#application').initialize_ajatus($.extend({
727         // debug: true,
728         custom_views: [
729             //'test'
730         ],
731         // types: {
732         //     in_use: [
733         //         'note', 'contact', 'event', 'expense', 'hour_report'
734         //     ]
735         // },
736         application_url: '/_utils/ajatus_dev/', // '/_utils/ajatus/'
737         application_database_identifier: 'dev' // ''
738     }, ajatus_client_config));
741 $(document).ready(function() {
742     try{
743         initialize_ajatus();        
744     } catch(e) {
745         alert("could not initialize ajatus! Reason: "+e);
746     }