Fixed and enhanced creation and updating
[ajatus.git] / js / ajatus.tags.js
blobe823829ff1789fd1754282be51c6bd60bc344655
1 /*
2  * This file is part of
3  *
4  * 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  */
15 (function($){
16     $.ajatus = $.ajatus || {};
17     
18     /**
19      * Holds all tag methods
20      * @accessor {String} cache Holds all cached tag names
21      * @accessor {String} active Currently active tag name
22      * @constructor
23      */
24     $.ajatus.tags = {
25         cache: [],
26         used_contexts: [],
27         active: ''
28     };
29     $.extend($.ajatus.tags, {
31         /**
32          * Initializes the tags
33          * @see ajatus.core.init
34          * @see ajatus.tags.load
35          * @see ajatus.tags.update_app_tag_list
36          */
37         init: function() {
38             $.ajatus.events.lock_pool.increase();
39             $.ajatus.tags.load();
40             $.ajatus.events.lock_pool.decrease();
41             $.ajatus.tags.update_app_tag_list();
42             $.ajatus.tags.get_active();
43         },
44         load: function() {
45             $.ajatus.tags.used_contexts = [];
46             $.ajatus.tags.cache = [];
47             
48             var legacy_tags_db = $.ajatus.preferences.client.application_database + '_tags';
49             if ($.jqCouch.connection('db').exists(legacy_tags_db)) {
50                 $.ajatus.events.lock_pool.increase();
51                 
52                 var on_success = function(data) {
53                     if (data.total_rows == 0) {
54                         return data;
55                     }
56                     
57                     $.each(data.rows, function(i,doc){
58                         var doc = new $.ajatus.document(doc);
59                         if (! $.ajatus.tags.exists(doc.value.title.val, doc.value.title.widget.config.context, doc.value.title.widget.config.value)) {
60                             var tag = {
61                                 value: {
62                                     _type: 'tag',
63                                     title: {
64                                         val: doc.value.title.val,
65                                         widget: {
66                                             name: 'tag',
67                                             config: {
68                                                 color: (doc.value.title.widget.config.color || '8596b6'),
69                                                 context: (doc.value.title.widget.config.context || ''),
70                                                 value: (doc.value.title.widget.config.value || '')
71                                             }
72                                         }
73                                     }
74                                 }
75                             };
76                             tag.value.metadata = doc.value.metadata;
77                             $.jqCouch.connection('doc').post($.ajatus.preferences.client.content_database, tag);
78                         }
79                     });
80                 };
81                 
82                 var list_map = function(doc) {
83                     if (   doc.value.metadata.archived.val != false
84                         && doc.value.metadata.deleted.val != false)
85                     {
86                         map(doc._id, doc);
87                     }
88                 };
89                 $.jqCouch.connection('view', on_success).temp(legacy_tags_db, list_map);
90                 
91                 $.jqCouch.connection('db').del(legacy_tags_db);                
92                 $.ajatus.events.lock_pool.decrease();
93             }
94             
95             var on_success = function(data) {
96                 if (data.total_rows <= 0) {
97                     return false;
98                 }
100                 $.each(data.rows, function(i,n){
101                     if (   typeof(n.value.title.widget.config['context']) != 'undefined'
102                         && n.value.title.widget.config['context'] != '')
103                     {
104                         var found = $.inArray(n.value.title.widget.config['context'], $.ajatus.tags.used_contexts);
105                         if (found == -1) {
106                             $.ajatus.tags.used_contexts.push(n.value.title.widget.config['context']);
107                         }
108                     }
109                     $.ajatus.tags.cache.push(n);
110                 });
111                 
112                 return data;
113             };
114             
115             $.jqCouch.connection('view', on_success).temp($.ajatus.preferences.client.content_database, function(doc){
116                 if (   doc.value._type == 'tag'
117                     && doc.value.metadata.archived.val == false
118                     && doc.value.metadata.deleted.val == false)
119                 {
120                     map(doc._id, doc.value);
121                 }
122             });
123         },
124         refresh_cache: function() {
125             $.ajatus.tags.load();
126             $.ajatus.tags.update_cache();
127         },
128         update_cache: function() {
129             $.ajatus.tags.update_app_tag_list();
130             if ($.ajatus.tags.active != '') {
131                 $.ajatus.tags.set_active($.ajatus.tags.active);
132             }
133         },
134         set_active: function(active) {
135             var id = active;
136             if (typeof active == 'object') {
137                 id = active.id || active._id;
138             }
139             $.cookie("ajatus.tags.active", active);
140             window.location.reload();
141             // $.ajatus.tags.get_active();
142         },
143         get_active: function() {
144             var id = $.cookie("ajatus.tags.active");
145             
146             if (   typeof id == 'undefined'
147                 || id == ''
148                 || id == null)
149             {
150                 return;
151             }
152             
153             var select = $('#application-tags select');
154             $('option', select).each(function(i,o){
155                 if (o.value == id) {
156                     o.selected = 'selected';
157                 }
158             });
159                         
160             if (id == '') {
161                 $.ajatus.tags.active = '';
162                 return;
163             }
164             
165             var tag = $.ajatus.tags.get(id);
166             if (tag) {                
167                 $.ajatus.tags.active = tag;
168                 $.ajatus.events.signals.trigger('active_tag_changed', $.ajatus.tags.active);
169             }
170         },
171         update_app_tag_list: function() {            
172             var select = $('#application-tags select');
173             select.html('');
174             
175             var opt_groups = {};                        
176             var def_opt_grp = $('<optgroup label="' + $.ajatus.i10n.get('No context') + '" />');
177             def_opt_grp.appendTo(select);
178             
179             $.each($.ajatus.tags.used_contexts, function(i){
180                 var opt_grp = $('<optgroup label="' + this + '" />');
181                 opt_grp.prependTo(select);
182                 opt_groups[this] = opt_grp;
183             });
184             
185             $.each($.ajatus.tags.cache, function(i){
186                 var title = this.value.title.val;
187                 if (   typeof this.value.title.widget.config.value != 'undefined'
188                     && this.value.title.widget.config.value != '')
189                 {
190                     title += " = " + this.value.title.widget.config.value;
191                 }
192                 var opt = $('<option>'+title+'</option>').attr({
193                     value: this.id
194                 });
195                 
196                 if (   typeof(this.value.title.widget.config['context']) != 'undefined'
197                     && this.value.title.widget.config['context'] != '')
198                 {
199                     var context = this.value.title.widget.config.context;
200                     opt.appendTo(opt_groups[context]);
201                 } else {
202                     opt.appendTo(def_opt_grp);
203                 }
204             });
205             
206             var def = $('<option />').attr({
207                 value: '',
208                 selected: 'selected'
209             }).html($.ajatus.i10n.get('Global Tag') + '&nbsp;');
210             def.prependTo(select);
211             
212             select.bind('change', function(e){
213                 $.ajatus.tags.set_active(e.currentTarget.value);
214             });
215         },
216         search: function(query, context, max_res) {
217             // console.log("Search tags with: "+query);
218             
219             var results = [];
220             var context_key = false;
221             
222             if (typeof max_res != 'number') {
223                 var max_res = 20;
224             }
226             query = $.ajatus.tags._prepare_query(query);
228             var on_success = function(data) {
229                 if (data.total_rows == 0) {
230                     return [];
231                 }
232                 
233                 $.each(data.rows, function(i,n){
234                     var doc = new $.ajatus.document(n);
235                     results.push(doc);
236                 });
237                 
238                 return data;
239             };
240             
241             var rel_view = 'if (doc.value._type == "tag" ';
242             rel_view += '&& doc.value.title.val.match(/\\\\b'+query+'(.*?)\\\\b/i) ';
243             if (   typeof context != 'undefined'
244                 && context != '')
245             {
246                 rel_view += '&& typeof(doc.value.title.widget.config["context"]) != "undefined" ';
247                 rel_view += '&& doc.value.title.widget.config.context == "+context+" ';
248             }
249             rel_view += '&& doc.value.metadata.archived.val == false && doc.value.metadata.deleted.val == false){';
250             rel_view += 'map( doc._id, doc.value );}';
252             $.jqCouch.connection('view', on_success).temp($.ajatus.preferences.client.content_database,
253                 "$.ajatus.views.generate('"+rel_view+"')", {
254                     count: max_res
255                 }
256             );
258             return results;                        
259         },
260         search_cache: function(query, context, max_res) {
261             // console.log("Search tags cache with: "+query);
263             if (typeof max_res != 'number') {
264                 var max_res = 20;
265             }
266             
267             var results = [];
268             query = $.ajatus.tags._prepare_query(query);
269             var re = new RegExp(query+"(.*?)", "gi");
270             
271             $.each($.ajatus.tags.cache, function(i, tag){
272                 if (results.length > max_res) {
273                     return;
274                 }
275                 
276                 if (tag.value.title.val.toString().match(re)) {
277                     if (   typeof context != 'undefined'
278                         && context != '')
279                     {
280                         if (   typeof(tag.value.title.widget.config['context']) != 'undefined'
281                             && tag.value.title.widget.config['context'] == context)
282                         {
283                             results.push(tag);
284                         }
285                     } else {
286                         results.push(tag);
287                     }
288                 }
289             });
290             
291             return results;
292         },
293         _prepare_query: function(query) {
294             query = query.replace(/[\(|\)|\.|\?|\;|\/]/, '');
295             query = query.replace('\\', '');
296             query = query.replace('*', '(.*?)');
297             
298             return query;
299         },
300         get: function(tag, rev, skip_cache, callback) {
301             var result = false;
302             if (typeof skip_cache == 'undefined') {
303                 var skip_cache = false;
304             }
305             
306             if (   typeof rev == 'undefined'
307                 && !skip_cache)
308             {
309                 $.each($.ajatus.tags.cache, function(i,t){
310                     if (t.id == tag) {
311                         result = t;
312                     }
313                 });
315                 if (result) {
316                     if (typeof callback == 'function') {
317                         var fn = eval('('+callback+')');
318                         fn.apply(fn, [result]);
319                     }
320                     
321                     return result;
322                 }
323             }
324             
325             var on_success = function(data) {
326                 var doc = new $.ajatus.document(data);
327                 result = doc;
328                 result.id = doc._id;
329                 
330                 if (typeof callback == 'function') {
331                     var fn = eval('('+callback+')');
332                     fn.apply(fn, [result]);
333                 }
334                 
335                 return data;
336             };
338             var args = {};
339             if (typeof rev != 'undefined') {
340                 args['_rev'] = rev;
341             }
343             $.jqCouch.connection('doc', on_success).get($.ajatus.preferences.client.content_database + '/' + tag, args);
344             
345             return result;
346         },
347         exists: function(tag, context, value) {            
348             var tag_ids = [];
349             var found = false;
350             
351             var match_tag_id = $.ajatus.utils.md5.encode(context + ':' + tag + '=' + value);
352             
353             $.each($.ajatus.tags.cache, function(i,t){
354                 if (t.id == match_tag_id) {
355                     found = true;
356                 } else {
357                     // Legacy matching
358                     if (t.value.title.val == tag) {
359                         found = true;
361                         if (   typeof context != 'undefined'
362                             && context != '')
363                         {
364                             if (t.value.title.widget.config.context == context) {
365                                 found = true;
366                             } else {
367                                 found = false;
368                             }
369                         }
371                         if (   typeof value != 'undefined'
372                             && value != '')
373                         {
374                             if (t.value.title.widget.config.value == value) {
375                                 found = true;
376                             } else {
377                                 found = false;
378                             }
379                         }
380                     }                    
381                 }
382             });
384             return found;
385         },        
386         create: function(name, data, dc) {
387             if (typeof data == 'undefined') {
388                 var data = {
389                     color: '',
390                     context: '',
391                     value: ''
392                 };
393             }
394             if (typeof data == 'string') {
395                 var data = {
396                     color: '',
397                     context: data,
398                     value: ''
399                 };
400             }
401             
402             var color = data.color;
403             var context = data.context;
404             var value = data.value;
405             
406             if ($.ajatus.tags.exists(name, context, value)) {
407                 return false;
408             }
409             
410             if (typeof dc == 'undefined') {
411                 var on_success = function(data) {
412                     if (data.ok) {
413                         var msg = $.ajatus.elements.messages.create(
414                             $.ajatus.i10n.get('New tag created'),
415                             $.ajatus.i10n.get("Created new tag '%s'", [data.value.title.val])
416                         );
418                         $.ajatus.debug("saved tag: "+data.id);
420                         var tag = $.ajatus.tags.get(data.id, data.rev, true);
421                         $.ajatus.tags.cache.push(tag);
422                     }
423                     
424                     return data;
425                 };
427                 var dc = $.jqCouch.connection('doc', on_success);
428                 $.ajatus.views.on_change_actions.add('$.ajatus.tags.update_cache();');
429             }
430             
431             var tag_id = $.ajatus.tags.calculate_hash(name, context, value);
432             
433             var tag = {
434                 _id: tag_id,
435                 value: {
436                     _type: 'tag',
437                     title: {
438                         val: name,
439                         widget: {
440                             name: 'tag',
441                             config: {
442                                 color: (color || '8596b6'),
443                                 context: (context || ''),
444                                 value: value
445                             }
446                         }
447                     }
448                 }
449             };
450             tag = new $.ajatus.document(tag);
451             
452             var now = $.ajatus.formatter.date.js_to_iso8601(new Date());
453             var new_metadata = {
454                 created: now,
455                 creator: $.ajatus.preferences.local.user.email,
456                 revised: now,
457                 revisor: $.ajatus.preferences.local.user.email
458             };
459             tag = $.ajatus.document.modify_metadata(tag, new_metadata);
460             dc.save($.ajatus.preferences.client.content_database, tag);
461             
462             return true;
463         },
464         remove: function(tag, dc) {
465             if (typeof dc == 'undefined') {
466                 var on_success = function(data) {
467                     var msg = $.ajatus.elements.messages.create(
468                         $.ajatus.i10n.get('Object deleted'),
469                         $.ajatus.i10n.get('Tag %s removed from Ajatus.', [tag.value.title.val])
470                     );
471                 };
472                 var dc = $.jqCouch.connection('doc', on_success);
473             }
475             dc.del($.ajatus.preferences.client.content_database + '/' + tag.id);
476         },
477         related: function(tags, skip_docs) {
478             var related = [];
480             if (typeof skip_docs != 'object') {
481                 var skip_docs = [];
482             }
483             
484             if (tags.length == 0) {
485                 return related;
486             }
488             var on_success = function(data) {
489                 if (data.total_rows == 0) {
490                     return;
491                 }
492                 
493                 $.each(data.rows, function(i,row){
494                     related.push(new $.ajatus.document(row));
495                 });
496                 
497                 return data;
498             };
499             
500             var rel_view = 'if (typeof(doc.value.tags) != "undefined" && doc.value.tags.val.length > 0 ';
501             rel_view += '&& doc.value.metadata.archived.val == false && doc.value.metadata.deleted.val == false){';
502             
503             if (skip_docs.length > 0) {
504                 rel_view += 'if (';
505                 for (var i=0; i<skip_docs.length; i++) {
506                     rel_view += 'doc._id != "'+skip_docs[i]+'" ';
507                     if (i < skip_docs.length-1) {
508                         rel_view += '&& ';
509                     }
510                 };
511                 rel_view += ') {';
512             }
513             
514             rel_view += 'var hm = false;';
515             rel_view += 'for (var i=0;i<doc.value.tags.val.length;i++){';
516             rel_view += 'if (';
517             for (var i=0; i<tags.length; i++) {                                
518                 rel_view += 'doc.value.tags.val[i] == "'+tags[i]+'" ';
519                 if (i < tags.length-1) {
520                     rel_view += '|| ';
521                 }
522             };
523             rel_view += ') {';
524             rel_view += 'hm = true;}';
525             rel_view += '}';
526             
527             rel_view += 'if (hm) {';
528             
529             rel_view += 'map( doc.value._type, {"_id": doc._id, "_rev": doc._rev, "_type": doc.value._type,';
530             rel_view += '"title": doc.value.title,';
531             
532             $.each($.ajatus.preferences.client.content_types, function(key,type){
533                 if (type['title_item']) {
534                     $.each(type['title_item'], function(i,part){
535                         if (type.schema[part]) {
536                             rel_view += '"'+part+'": doc.value.'+part+',';
537                         }
538                     });
539                 }
540             });
542             rel_view += '"tags": doc.value.tags });';
543             
544             rel_view += '}';
546             if (skip_docs.length > 0) {
547                 rel_view += '}';
548             }
549             
550             rel_view += '}';
551             
552             $.jqCouch.connection('view', on_success).temp($.ajatus.preferences.client.content_database, "$.ajatus.views.generate('"+rel_view+"')");
553             
554             return related;
555         },
556         render_title: function(tag) {
557             var tag_title = '';
559             if (typeof tag.value == 'undefined') {
560                 return tag_title;
561             }
562             
563             if (   typeof(tag.value.title.widget.config['context']) != 'undefined'
564                 && tag.value.title.widget.config['context'] != '')
565             {
566                 tag_title += tag.value.title.widget.config.context + ':';
567             }
568             
569             tag_title += tag.value.title.val;
570             
571             if (   typeof(tag.value.title.widget.config['value']) != 'undefined'
572                 && tag.value.title.widget.config['value'] != '')
573             {
574                 tag_title += '=' + tag.value.title.widget.config.value;
575             }
576             
577             return tag_title;
578         },
579         calculate_hash: function() { // tag / title, context, value
580             var hash = null;
581             
582             if (arguments.length == 1) {
583                 var tag = arguments[0];
584                 hash = $.ajatus.utils.md5.encode(tag.value.title.widget.config.context + ':' + tag.value.title.val + '=' + tag.value.title.widget.config.value);
585             } else {
586                 var title = arguments[0];
587                 var context = '';
588                 var value = '';
589                 if (arguments.length > 1) {
590                     context = arguments[1];
591                 }
592                 if (arguments.length > 2) {
593                     value = arguments[2];
594                 }
595                 
596                 hash = $.ajatus.utils.md5.encode(context + ':' + title + '=' + value);
597             }
599             return hash;
600         },
601         validate_title: function(title) {
602             var context_key = '';
603             var value = '';
604             
605             if (   !title.match(/:/)
606                 && !title.match(/\=/))
607             {
608                 return title;
609             }
610             
611             if (title.match(/:/)) {
612                 var tag_parts = title.split(':');
613                 context_key = tag_parts[0];
614                 title = tag_parts[1];
615             }
616             
617             if (title.match(/\=/)) {
618                 var tag_parts = title.split('=');
619                 value = tag_parts[1];
620                 title = tag_parts[0];
621             }
622             
623             var data = {
624                 title: title,
625                 context: context_key,
626                 value: value
627             };
628             
629             return data;
630         }
631     });
632     
633     $.ajatus.tags.merger = function(dubs, dubs_with, tags) {
634         this.merge_list = {};
635         this.merge_count = 0;
636         this.button_generated = false;
637         this.button_visible = false;
638         this.button_holder = null;
639         this.dub_rows = {};
640         
641         this.all_tags = [];
642         this.dub_tags = [];
643         this.dub_tags_with = {};
644         
645         this.init(dubs, dubs_with, tags);
646     };
647     $.extend($.ajatus.tags.merger.prototype, {
648         init: function(dubs, dubs_with, tags) {
649             this.all_tags = tags;
650             this.dub_tags = dubs;
651             this.dub_tags_with = dubs_with;
652             
653             var _self = this;
654             $.each(this.dub_tags, function(i,dub){
655                 var row = $('#object_'+dub._id+'_duplicate');
656                 row.bind('click', function(){
657                     _self.toggle_selection($(this), dub._id, dub.value.real_hash.val);
658                 });
659             });            
660         },
661         add_to_list: function(real_hash, with_tag, source_tag, row) {
662             // console.log("add_to_list source_tag: "+source_tag+" with_tag: "+with_tag+" real_hash: "+real_hash);
663             
664             // if (source_tag == with_tag) {
665             //     this.remove_from_list(real_hash, with_tag, source_tag);
666             //     return;
667             // }
668             
669             if (typeof this.dub_rows[with_tag] == 'undefined') {
670                 this.dub_rows[with_tag] = row;                
671             }
672             
673             if (typeof this.merge_list[real_hash] == 'undefined') {
674                 this.merge_list[real_hash] = [with_tag];
675                 this.merge_count += 1;
676             } else {
677                 this.merge_list[real_hash].push(with_tag);
678             }
679             
680             this.merge_count += 1;
681             
682             // console.log(this.merge_list);
683             
684             this._gen_button();
685             this._show_button();
686         },
687         remove_from_list: function(real_hash, with_tag, source_tag) {
688             // console.log("remove_from_list source_tag: "+source_tag+" with_tag: "+with_tag+" real_hash: "+real_hash);
689             var _self = this;
690             this.merge_list[real_hash] = $.grep(this.merge_list[real_hash], function(n,i){
691                 if (n == with_tag) {
692                     _self.merge_count -= 1;
693                     return false;
694                 }
695                 return true;
696             });
697             
698             if (this.merge_list[real_hash].length == 0) {
699                 var tmp_list = {};
700                 for (var hash in this.merge_list) {
701                     if (hash != real_hash) {
702                         tmp_list[hash] = this.merge_list[hash];
703                     }
704                 }
705                 this.merge_list = tmp_list;
706                 _self.merge_count -= 1;
707             }
708             
709             // console.log(this.merge_list);
710             
711             if (this.merge_count <= 0) {
712                 this._hide_button();
713             }
714         },
715         merge_selections: function() {
716             var _self = this;
717             
718             var dialog = this._gen_dialog(this.merge_count);
719             dialog.open();
720             
721             var status_holder = $('.status_holder', dialog);
722             
723             var row = '';
724             var row_failed = '<span class="status_failed">' + $.ajatus.i10n.get('failed').toUpperCase() + '</span><br />';
725             var row_ok = '<span class="status_ok">' + $.ajatus.i10n.get('ok').toUpperCase() + '</span><br />';
726             
727             $.each(this.merge_list, function(hash, dubs){
728                 
729                 var tag = _self.all_tags[hash];
730                 var tag_title = '"' + $.ajatus.tags.render_title(tag) + '"';
731                 
732                 row = $.ajatus.i10n.get('Merging tag %s', [tag_title]) + '... ';
733                 status_holder.append(row);
734                 
735                 row = row_failed;
736                 if (_self._execute_merge(tag, dubs)) {
737                     row = row_ok;
738                 }
739                 status_holder.append(row);
740                 
741                 _self.toggle_selection(_self.dub_rows[dubs[0]], dubs[0], hash);
742             });
743             
744             $('#submit_ok', dialog).show();
745             
746             this._destroy_button();
747         },
748         
749         toggle_selection: function(row, dub_id, real_hash) {
750             // console.log("toggle_selection dub_id: "+dub_id+" real_hash: "+real_hash);
751             
752             // console.log("this.dub_tags_with["+real_hash+"]: "+this.dub_tags_with[real_hash].length);
753             // console.log(this.dub_tags_with[real_hash]);
754             
755             if (row.hasClass('duplicate_row_autoselected')) {
756                 return;
757             }
758             
759             var bg_color = "#73d216";
760             var fg_color = "#000";
761             
762             var ohdrow = $('#object_'+real_hash);
763             
764             row.toggleClass('duplicate_row_indicator');
765             if (row.hasClass('duplicate_row_indicator')) {
766                 this.add_to_list(real_hash, dub_id, dub_id, row);
767                 row.css({
768                     "backgroundColor": bg_color,
769                     "color": fg_color
770                 });
771                 
772                 ohdrow.css({
773                     "backgroundColor": bg_color,
774                     "color": fg_color
775                 });
776             } else {
777                 this.remove_from_list(real_hash, dub_id, dub_id);
778                 row.css({
779                     "backgroundColor": "",
780                     "color": ""
781                 });
782                 
783                 ohdrow.css({
784                     "backgroundColor": "",
785                     "color": ""
786                 });
787             }
788             
789             var odrow = $('#object_'+dub_id).toggleClass('duplicate_row_indicator');
790             if (odrow.hasClass('duplicate_row_indicator')) {
791                 odrow.css({
792                     "backgroundColor": bg_color,
793                     "color": fg_color
794                 });
795             } else {
796                 odrow.css({
797                     "backgroundColor": "",
798                     "color": ""
799                 });                            
800             }
801             
802             var dub_with_ids = this.dub_tags_with[real_hash];
803             
804             for (var i=0; i<dub_with_ids.length; i++) {
805                 if (dub_with_ids[i] == dub_id) {
806                     continue;
807                 }
808                 var drow = $('#object_'+dub_with_ids[i]).toggleClass('duplicate_row_indicator');
809                 if (drow.hasClass('duplicate_row_indicator')) {
810                     drow.css({
811                         "backgroundColor": bg_color,
812                         "color": fg_color
813                     });
814                 } else {                    
815                     drow.css({
816                         "backgroundColor": "",
817                         "color": ""
818                     });                            
819                 }
820                 var drow2 = $('#object_'+dub_with_ids[i]+'_duplicate').toggleClass('duplicate_row_indicator').addClass('duplicate_row_autoselected');
821                 if (drow2.hasClass('duplicate_row_indicator')) {
822                     this.add_to_list(real_hash, dub_with_ids[i], dub_id, drow2);
823                     drow2.css({
824                         "backgroundColor": bg_color,
825                         "color": fg_color
826                     });
827                 } else {
828                     this.remove_from_list(real_hash, dub_with_ids[i], dub_id);
829                     drow2.removeClass('duplicate_row_autoselected');
830                     drow2.css({
831                         "backgroundColor": "",
832                         "color": ""
833                     });                            
834                 }
835             }
836         },
837         
838         _gen_button: function() {
839             if (this.button_generated) {
840                 return;
841             }
842             
843             var _self = this;
844             
845             this.button_holder = $('<div class="tag_merger_button_holder" />').hide();
846             var button = $('<div class="tag_merger_button" />').html($.ajatus.i10n.get('Merge selected!'));
847             
848             button.bind('click', function(e){
849                 _self.merge_selections();
850             });
851             
852             button.appendTo(this.button_holder);
853             
854             $.ajatus.application_dynamic_elements.append(this.button_holder);
855             
856             this.button_generated = true;
857         },
858         _destroy_button: function() {
859             if (! this.button_generated) {
860                 return;
861             }
862             
863             this.button_holder.remove();
864             this.button_generated = false;
865             this.button_visible = false;
866         },
867         _show_button: function() {
868             this.button_visible = true;
869             this.button_holder.show();
870         },
871         _hide_button: function() {
872             this.button_visible = false;
873             this.button_holder.hide();            
874         },
875         
876         _gen_dialog: function(total_merge_count) {
877             
878             var dialog = new $.ajatus.elements.dialog($.ajatus.i10n.get('Merging %d tag', [total_merge_count]), '', {
879                 modal: true,
880                 dialog: {
881                     height: 280
882                 }
883             });
884             var cholder = dialog.get_content_holder();
885             
886             var status_holder = $('<div class="status_holder" />');
887             status_holder.appendTo(cholder);
888             
889             var done_btn = $('<input type="submit" id="submit_ok" name="ok" value="' + $.ajatus.i10n.get('ok').toUpperCase() + '" />').hide();
890             
891             done_btn.appendTo(cholder);
892             
893             $('#submit_ok', cholder).bind('click', function(e){
894                 dialog.close();
895                 return false;
896             });
897             
898             return dialog;
899         },
900         
901         _hide_rows_for: function(id) {
902             if (typeof this.dub_rows[id] != 'undefined') {
903                 var row = this.dub_rows[id];
904                 row.hide();                
905             }
906             
907             $('#object_'+id).hide();
908             $('#object_'+id+'_duplicate').hide();
909         },
910         
911         _execute_merge: function(source_tag, targets) {
912             var _self = this;
913             
914             var items = {};
915             var on_success = function(data) {
916                 var tag_id = null;
917                 
918                 $.each(data.rows, function(i,n){
919                     if (n.key[1] == 0) {
920                         tag_id = n.key[0];
921                     } else {
922                         items[n.key[0]].push(n);
923                     }
924                 });
925                 
926                 return data;
927             };
928             
929             for (var i=0; i<targets.length; i++) {
930                 items[targets[i]] = [];
931                 $.jqCouch.connection('view', on_success).get($.ajatus.preferences.client.content_database, 'tag/list_with_docs', {
932                     startkey: [targets[i], 0],
933                     endkey: [targets[i], 1]
934                 });
935             }
936             
937             var on_save_success = null;
938             
939             $.each(items, function(hash, objs){
940                 on_save_success = function(data) {
941                     _self._hide_rows_for(hash);
942                     
943                     return data;
944                 };
945                 
946                 if (objs.length > 0) {
947                     for (var i=0; i<objs.length; i++) {                    
948                         var doc = new $.ajatus.document($.jqCouch.connection('doc').get($.ajatus.preferences.client.content_database + '/' + objs[i].id));
950                         doc.value.tags.val = $.grep(doc.value.tags.val, function(t,x){
951                             if (t == hash) {
952                                 return false;
953                             }
954                             return true;
955                         });
956                         doc.value.tags.val.push(source_tag._id);
958                         doc = $.ajatus.document.modify_metadata(doc, {
959                             revised: $.ajatus.formatter.date.js_to_iso8601(new Date()),
960                             revisor: $.ajatus.preferences.local.user.email
961                         });
963                         $.jqCouch.connection('doc', on_save_success).save($.ajatus.preferences.client.content_database, doc);
964                     }                    
965                 } else {
966                     _self._hide_rows_for(hash);
967                 }
968                 
969                 $.ajatus.document.actions.execute('delete', {
970                     _id: hash
971                 });
972             });
973                         
974             return true;
975         }
976     });
977     
978     $.ajatus.tags.fixer = function(btags, bhashes, tags, settings) {
979         this.all_tags = [];
980         this.broken_tags = [];
981         this.broken_hashes = {};
982         
983         this.settings = $.extend({
984             show_toolbar: false,
985             refresh_on_finish: false            
986         }, (settings || {}));
987         
988         this.init(btags, bhashes, tags);
989     };
990     $.extend($.ajatus.tags.fixer.prototype, {
991         init: function(btags, bhashes, tags) {
992             this.all_tags = tags;
993             this.broken_tags = btags;
994             this.broken_hashes = bhashes;
995             
996             if (this.settings.show_toolbar) {
997                 var _self = this;
998                 $.ajatus.toolbar.add_item($.ajatus.i10n.get('Fix all broken tags'), 'repair.png', function(){
999                     _self.fix_all();
1000                 });                
1001             }
1002         },
1003         validate: function(curr_hash, data) {            
1004             var hash = $.ajatus.tags.calculate_hash(data.title, data.context, data.value);
1005             if (curr_hash == hash) {
1006                 return true;
1007             }
1008             
1009             return false;
1010         },
1011         fix_single: function(tag) {            
1012             var is_valid = this.validate(tag._id, {
1013                 title: tag.value.title.val,
1014                 context: tag.value.title.widget.config.context,
1015                 value: tag.value.title.widget.config.value
1016             });
1017             
1018             if (is_valid) {
1019                 return false;
1020             }
1022             var data = this._prepare_tag_data(tag.value.title);
1023             var hash = $.ajatus.tags.calculate_hash(data.title.val, data.title.widget.config.context, data.title.widget.config.value);
1024                         
1025             var real_tag = this._create_real_tag(hash, data);
1026             
1027             var items = this._fetch_orig_items(tag._id);
1028             this._update_items(items, tag._id, hash);
1029             
1030             $.ajatus.document.actions.execute('delete_final', {
1031                 _id: tag._id
1032             });
1033             
1034             return real_tag;
1035         },
1036         fix_all: function() {
1037             var _self = this;
1038             $.each(this.broken_hashes, function(hash,ids){
1039                 if (ids.length > 1) {
1040                     var data = _self._prepare_tag_data(_self.broken_tags[ids[0]].value.title);
1041                     var real_tag = _self._create_real_tag(hash, data);
1042                     
1043                     for (var i=0; i<ids.length; i++) {
1044                         var items = _self._fetch_orig_items(ids[i]);
1045                         _self._update_items(items, ids[i], hash);
1046                         
1047                         $.ajatus.document.actions.execute('delete', {
1048                             _id: ids[i]
1049                         });
1050                     }
1051                     
1052                 } else {
1053                     var data = _self._prepare_tag_data(_self.broken_tags[ids[0]].value.title);
1054                     var real_tag = _self._create_real_tag(hash, data);
1055                     var items = _self._fetch_orig_items(ids[0]);
1056                     _self._update_items(items, ids[0], hash);
1058                     $.ajatus.document.actions.execute('delete', {
1059                         _id: ids[0]
1060                     });
1061                 }
1062             });
1063             
1064             window.location.reload();
1065         },
1066         _update_items: function(items, old_hash, new_hash) {
1067             if (items.length > 0) {
1068                 for (var i=0; i<items.length; i++) {                    
1069                     var doc = new $.ajatus.document($.jqCouch.connection('doc').get($.ajatus.preferences.client.content_database + '/' + items[i].id));
1071                     doc.value.tags.val = $.grep(doc.value.tags.val, function(t,x){
1072                         if (t == old_hash) {
1073                             return false;
1074                         }
1075                         return true;
1076                     });
1077                     doc.value.tags.val.push(new_hash);
1079                     doc = $.ajatus.document.modify_metadata(doc, {
1080                         revised: $.ajatus.formatter.date.js_to_iso8601(new Date()),
1081                         revisor: $.ajatus.preferences.local.user.email
1082                     });
1084                     $.jqCouch.connection('doc').save($.ajatus.preferences.client.content_database, doc);
1085                 }                    
1086             }
1087         },
1088         _fetch_orig_items: function(id) {
1089             var items = [];
1090             var on_success = function(data) {
1091                 var tag_id = null;
1092                 
1093                 $.each(data.rows, function(i,n){
1094                     if (n.key[1] == 1) {
1095                         items.push(n);
1096                     }
1097                 });
1098                 
1099                 return data;
1100             };
1101             
1102             $.jqCouch.connection('view', on_success).get($.ajatus.preferences.client.content_database, 'tag/list_with_docs', {
1103                 startkey: [id, 0],
1104                 endkey: [id, 1]
1105             });
1106             
1107             return items;
1108         },
1109         _prepare_tag_data: function(title_value) {
1110             
1111             var title_data = $.ajatus.tags.validate_title(title_value.val);
1112             if (typeof title_data == 'object') {
1113                 title_value.val = title_data.title;
1114                 title_value.widget.config.context = title_data.context;
1115                 title_value.widget.config.value = title_data.value;
1116             }
1117             
1118             var data = {
1119                 _type: 'tag',
1120                 title: title_value
1121             };
1122             return data;
1123         },
1124         _create_real_tag: function(hash, data) {
1125             var doc = {
1126                 _id: hash,
1127                 value: data
1128             };
1129             doc = new $.ajatus.document(doc);       
1130             var now = $.ajatus.formatter.date.js_to_iso8601(new Date());
1131             var new_metadata = {
1132                 revised: now,
1133                 revisor: $.ajatus.preferences.local.user.email,
1134                 created: now,
1135                 creator: $.ajatus.preferences.local.user.email
1136             };
1137                 doc = $.ajatus.document.modify_metadata(doc, new_metadata);
1138             
1139             $.jqCouch.connection('doc').save($.ajatus.preferences.client.content_database, doc);
1140             doc = new $.ajatus.document(doc);
1141             
1142             return doc;
1143         }
1144     });
1145     
1146 })(jQuery);