~pvigo/+junk/owncloud-14.04

« back to all changes in this revision

Viewing changes to usr/share/owncloud/apps/contacts/js/contacts.js

  • Committer: Pablo Vigo
  • Date: 2014-12-15 13:36:46 UTC
  • Revision ID: pvigo@xtec.cat-20141215133646-7d6it90e1dbsijc2
2

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
OC.Contacts = OC.Contacts || {};
2
 
 
3
 
 
4
 
(function(window, $, OC) {
5
 
        'use strict';
6
 
        /**
7
 
        * An item which binds the appropriate html and event handlers
8
 
        * @param parent the parent ContactList
9
 
        * @param id The integer contact id.
10
 
        * @param metadata An metadata object containing and 'owner' string variable, a 'backend' string variable and an integer 'permissions' variable.
11
 
        * @param data the data used to populate the contact
12
 
        * @param listtemplate the jquery object used to render the contact list item
13
 
        * @param fulltemplate the jquery object used to render the entire contact
14
 
        * @param detailtemplates A map of jquery objects used to render the contact parts e.g. EMAIL, TEL etc.
15
 
        */
16
 
        var Contact = function(parent, id, metadata, data, listtemplate, dragtemplate, fulltemplate, detailtemplates) {
17
 
                //console.log('contact:', id, metadata, data); //parent, id, data, listtemplate, fulltemplate);
18
 
                this.parent = parent,
19
 
                        this.storage = parent.storage,
20
 
                        this.id = id,
21
 
                        this.metadata = metadata,
22
 
                        this.data = data,
23
 
                        this.$dragTemplate = dragtemplate,
24
 
                        this.$listTemplate = listtemplate,
25
 
                        this.$fullTemplate = fulltemplate;
26
 
                        this.detailTemplates = detailtemplates;
27
 
                        this.displayNames = {};
28
 
                        this.sortOrder = contacts_sortby || 'fn';
29
 
                this.undoQueue = [];
30
 
                this.multi_properties = ['EMAIL', 'TEL', 'IMPP', 'ADR', 'URL'];
31
 
        };
32
 
 
33
 
        Contact.prototype.metaData = function() {
34
 
                return {
35
 
                        contactId: this.id,
36
 
                        addressBookId: this.metadata.parent,
37
 
                        backend: this.metadata.backend
38
 
                }
39
 
        };
40
 
 
41
 
        Contact.prototype.getDisplayName = function() {
42
 
                return this.displayNames[this.sortOrder];
43
 
        };
44
 
 
45
 
        Contact.prototype.setDisplayMethod = function(method) {
46
 
                if(this.sortOrder === method) {
47
 
                        return;
48
 
                }
49
 
                this.sortOrder = method;
50
 
                // ~30% faster than jQuery.
51
 
                try {
52
 
                        this.$listelem.get(0).firstElementChild.getElementsByClassName('nametext')[0].innerHTML = escapeHTML(this.displayNames[method]);
53
 
                } catch(e) {
54
 
                        var $elem = this.$listelem.find('.nametext').text(escapeHTML(this.displayNames[method]));
55
 
                        $elem.text(escapeHTML(this.displayNames[method]));
56
 
                }
57
 
        };
58
 
 
59
 
        Contact.prototype.getId = function() {
60
 
                return this.id;
61
 
        };
62
 
 
63
 
        Contact.prototype.getOwner = function() {
64
 
                return this.metadata.owner;
65
 
        };
66
 
 
67
 
        Contact.prototype.setOwner = function(owner) {
68
 
                this.metadata.owner = owner;
69
 
        };
70
 
 
71
 
        Contact.prototype.getPermissions = function() {
72
 
                return this.metadata.permissions;
73
 
        };
74
 
 
75
 
        Contact.prototype.hasPermission = function(permission) {
76
 
                //console.log('hasPermission', this.getPermissions(), permission, this.getPermissions() & permission);
77
 
                return (this.getPermissions() & permission);
78
 
        };
79
 
 
80
 
        Contact.prototype.getParent = function() {
81
 
                return this.metadata.parent;
82
 
        };
83
 
 
84
 
        Contact.prototype.setParent = function(parent) {
85
 
                this.metadata.parent = parent;
86
 
        };
87
 
 
88
 
        Contact.prototype.getBackend = function() {
89
 
                return this.metadata.backend;
90
 
        };
91
 
 
92
 
        Contact.prototype.setBackend = function(backend) {
93
 
                this.metadata.backend = backend;
94
 
        };
95
 
 
96
 
        Contact.prototype.reload = function(data) {
97
 
                console.log('Contact.reload', data);
98
 
                this.id = data.metadata.id;
99
 
                this.metadata = data.metadata;
100
 
                this.data = data.data;
101
 
                /*if(this.$fullelem) {
102
 
                        this.$fullelem.replaceWith(this.renderContact(this.groupprops));
103
 
                }*/
104
 
        };
105
 
 
106
 
        Contact.prototype.merge = function(mergees) {
107
 
                console.log('Contact.merge, mergees', mergees);
108
 
                if(!mergees instanceof Array && !mergees instanceof Contact) {
109
 
                        throw new TypeError('BadArgument: Contact.merge() only takes Contacts');
110
 
                } else {
111
 
                        if(mergees instanceof Contact) {
112
 
                                mergees = [mergees];
113
 
                        }
114
 
                }
115
 
 
116
 
                // For multi_properties
117
 
                var addIfNotExists = function(name, newproperty) {
118
 
                        // If the property isn't set at all just add it and return.
119
 
                        if(!self.data[name]) {
120
 
                                self.data[name] = [newproperty];
121
 
                                return;
122
 
                        }
123
 
                        var found = false;
124
 
                        $.each(self.data[name], function(idx, property) {
125
 
                                if(name === 'ADR') {
126
 
                                        // Do a simple string comparison
127
 
                                        if(property.value.join(';').toLowerCase() === newproperty.value.join(';').toLowerCase()) {
128
 
                                                found = true;
129
 
                                                return false; // break loop
130
 
                                        }
131
 
                                } else {
132
 
                                        if(property.value.toLowerCase() === newproperty.value.toLowerCase()) {
133
 
                                                found = true;
134
 
                                                return false; // break loop
135
 
                                        }
136
 
                                }
137
 
                        });
138
 
                        if(found) {
139
 
                                return;
140
 
                        }
141
 
                        // Not found, so adding it.
142
 
                        self.data[name].push(newproperty);
143
 
                }
144
 
 
145
 
                var self = this;
146
 
                $.each(mergees, function(idx, mergee) {
147
 
                        console.log('Contact.merge, mergee', mergee);
148
 
                        if(!mergee instanceof Contact) {
149
 
                                throw new TypeError('BadArgument: Contact.merge() only takes Contacts');
150
 
                        }
151
 
                        if(mergee === self) {
152
 
                                throw new Error('BadArgument: Why should I merge with myself?');
153
 
                        }
154
 
                        $.each(mergee.data, function(name, properties) {
155
 
                                if(self.multi_properties.indexOf(name) === -1) {
156
 
                                        if(self.data[name] && self.data[name].length > 0) {
157
 
                                                // If the property exists don't touch it.
158
 
                                                return true; // continue
159
 
                                        } else {
160
 
                                                // Otherwise add it.
161
 
                                                self.data[name] = properties;
162
 
                                        }
163
 
                                } else {
164
 
                                        $.each(properties, function(idx, property) {
165
 
                                                addIfNotExists(name, property);
166
 
                                        });
167
 
                                }
168
 
                        });
169
 
                        console.log('Merged', self.data);
170
 
                });
171
 
                return true;
172
 
        };
173
 
 
174
 
        Contact.prototype.showActions = function(act) {
175
 
                this.$footer.children().hide();
176
 
                if(act && act.length > 0) {
177
 
                        this.$footer.children('.'+act.join(',.')).show();
178
 
                }
179
 
        };
180
 
 
181
 
        Contact.prototype.setAsSaving = function(obj, state) {
182
 
                if(!obj) {
183
 
                        return;
184
 
                }
185
 
                $(obj).prop('disabled', state);
186
 
                $(obj).toggleClass('loading', state);
187
 
                /*if(state) {
188
 
                        $(obj).addClass('loading');
189
 
                } else {
190
 
                        $(obj).removeClass('loading');
191
 
                }*/
192
 
        };
193
 
 
194
 
        Contact.prototype.handleURL = function(obj) {
195
 
                if(!obj) {
196
 
                        return;
197
 
                }
198
 
                var $container = this.propertyContainerFor(obj);
199
 
                $(document).trigger('request.openurl', {
200
 
                        type: $container.data('element'),
201
 
                        url: this.valueFor(obj)
202
 
                });
203
 
        };
204
 
 
205
 
        /**
206
 
         * Update group name internally. No saving as this is done by groups backend.
207
 
         */
208
 
        Contact.prototype.renameGroup = function(from, to) {
209
 
                if(!this.data.CATEGORIES.length) {
210
 
                        console.warn(this.getDisplayName(), 'had no groups!?!');
211
 
                        return;
212
 
                }
213
 
                var groups = this.data.CATEGORIES[0].value;
214
 
                var self = this;
215
 
                $.each(groups, function(idx, group) {
216
 
                        if(from.toLowerCase() === group.toLowerCase()) {
217
 
                                console.log('Updating group name for', self.getDisplayName(), group, to);
218
 
                                self.data.CATEGORIES[0].value[idx] = to;
219
 
                                return false; // break
220
 
                        }
221
 
                });
222
 
                $(document).trigger('status.contact.updated', {
223
 
                        property: 'CATEGORIES',
224
 
                        contact: this
225
 
                });
226
 
        };
227
 
 
228
 
        Contact.prototype.pushToUndo = function(params) {
229
 
                // Check if the same property has been changed before
230
 
                // and update it's checksum if so.
231
 
                if(typeof params.oldchecksum !== 'undefined') {
232
 
                        $.each(this.undoQueue, function(idx, item) {
233
 
                                if(item.checksum === params.oldchecksum) {
234
 
                                        item.checksum = params.newchecksum;
235
 
                                        if(params.action === 'delete') {
236
 
                                                item.action = 'delete';
237
 
                                        }
238
 
                                        return false; // Break loop
239
 
                                }
240
 
                        });
241
 
                }
242
 
                this.undoQueue.push({
243
 
                        action:params.action, 
244
 
                        name: params.name,
245
 
                        checksum: params.newchecksum,
246
 
                        newvalue: params.newvalue,
247
 
                        oldvalue: params.oldvalue
248
 
                });
249
 
                //console.log('undoQueue', this.undoQueue);
250
 
        }
251
 
        
252
 
        Contact.prototype.addProperty = function($option, name) {
253
 
                console.log('Contact.addProperty', name)
254
 
                var $elem;
255
 
                switch(name) {
256
 
                        case 'NICKNAME':
257
 
                        case 'TITLE':
258
 
                        case 'ORG':
259
 
                        case 'BDAY':
260
 
                        case 'NOTE':
261
 
                                $elem = this.$fullelem.find('[data-element="' + name.toLowerCase() + '"]');
262
 
                                $elem.addClass('new').show();
263
 
                                $elem.find('input:not(:checkbox),textarea').first().focus();
264
 
                                $option.prop('disabled', true);
265
 
                                break;
266
 
                        case 'TEL':
267
 
                        case 'URL':
268
 
                        case 'EMAIL':
269
 
                                var $elem = this.renderStandardProperty(name.toLowerCase());
270
 
                                var $list = this.$fullelem.find('ul.' + name.toLowerCase());
271
 
                                $list.show();
272
 
                                $list.append($elem);
273
 
                                $elem.find('input.value').addClass('new');
274
 
                                $elem.find('input:not(:checkbox)').first().focus();
275
 
                                break;
276
 
                        case 'ADR':
277
 
                                var $elem = this.renderAddressProperty();
278
 
                                var $list = this.$fullelem.find('ul.' + name.toLowerCase());
279
 
                                $list.show();
280
 
                                $list.append($elem);
281
 
                                $elem.find('.display').trigger('click');
282
 
                                $elem.find('input.value').addClass('new');
283
 
                                $elem.find('input:not(:checkbox)').first().focus();
284
 
                                break;
285
 
                        case 'IMPP':
286
 
                                var $elem = this.renderIMProperty();
287
 
                                var $list = this.$fullelem.find('ul.' + name.toLowerCase());
288
 
                                $list.show();
289
 
                                $list.append($elem);
290
 
                                $elem.find('input.value').addClass('new');
291
 
                                $elem.find('input:not(:checkbox)').first().focus();
292
 
                                break;
293
 
                }
294
 
 
295
 
                if($elem) {
296
 
                        // If there's already a property of this type enable setting as preferred.
297
 
                        if(this.multi_properties.indexOf(name) !== -1 && this.data[name] && this.data[name].length > 0) {
298
 
                                var selector = 'li[data-element="' + name.toLowerCase() + '"]';
299
 
                                $.each(this.$fullelem.find(selector), function(idx, elem) {
300
 
                                        $(elem).find('input.parameter[value="PREF"]').show();
301
 
                                });
302
 
                        } else if(this.multi_properties.indexOf(name) !== -1) {
303
 
                                $elem.find('input.parameter[value="PREF"]').hide();
304
 
                        }
305
 
                        $elem.find('select.type[name="parameters[TYPE][]"]')
306
 
                                .combobox({
307
 
                                        singleclick: true,
308
 
                                        classes: ['propertytype', 'float', 'label'],
309
 
                                });
310
 
                }
311
 
        };
312
 
 
313
 
        Contact.prototype.deleteProperty = function(params) {
314
 
                var obj = params.obj;
315
 
                if(!this.enabled) {
316
 
                        return;
317
 
                }
318
 
                var element = this.propertyTypeFor(obj);
319
 
                var $container = this.propertyContainerFor(obj);
320
 
                console.log('Contact.deleteProperty, element', element, $container);
321
 
                var params = {
322
 
                        name: element,
323
 
                        value: null
324
 
                };
325
 
                if(this.multi_properties.indexOf(element) !== -1) {
326
 
                        params['checksum'] = this.checksumFor(obj);
327
 
                        if(params['checksum'] === 'new' && $.trim(this.valueFor(obj)) === '') {
328
 
                                // If there's only one property of this type enable setting as preferred.
329
 
                                if(this.data[element].length === 1) {
330
 
                                        var selector = 'li[data-element="' + element.toLowerCase() + '"]';
331
 
                                        this.$fullelem.find(selector).find('input.parameter[value="PREF"]').hide();
332
 
                                }
333
 
                                $container.remove();
334
 
                                return;
335
 
                        }
336
 
                }
337
 
                this.setAsSaving(obj, true);
338
 
                var self = this;
339
 
                $.when(this.storage.patchContact(this.metadata.backend, this.metadata.parent, this.id, params))
340
 
                        .then(function(response) {
341
 
                        if(!response.error) {
342
 
                                if(self.multi_properties.indexOf(element) !== -1) {
343
 
                                        // First find out if an existing element by looking for checksum
344
 
                                        var checksum = self.checksumFor(obj);
345
 
                                        self.pushToUndo({
346
 
                                                action:'delete', 
347
 
                                                name: element,
348
 
                                                oldchecksum: self.checksumFor(obj),
349
 
                                                newvalue: self.valueFor(obj)
350
 
                                        });
351
 
                                        if(checksum) {
352
 
                                                for(var i in self.data[element]) {
353
 
                                                        if(self.data[element][i].checksum === checksum) {
354
 
                                                                // Found it
355
 
                                                                self.data[element].splice(self.data[element].indexOf(self.data[element][i]), 1);
356
 
                                                                break;
357
 
                                                        }
358
 
                                                }
359
 
                                        }
360
 
                                        // If there's only one property of this type enable setting as preferred.
361
 
                                        if(self.data[element].length === 1) {
362
 
                                                var selector = 'li[data-element="' + element.toLowerCase() + '"]';
363
 
                                                self.$fullelem.find(selector).find('input.parameter[value="PREF"]').hide();
364
 
                                        }
365
 
                                        $container.remove();
366
 
                                } else {
367
 
                                        self.pushToUndo({
368
 
                                                action:'delete', 
369
 
                                                name: element,
370
 
                                                newvalue: $container.find('input.value').val()
371
 
                                        });
372
 
                                        self.setAsSaving(obj, false);
373
 
                                        if(element === 'PHOTO') {
374
 
                                                self.data.PHOTO[0].value = false;
375
 
                                                self.data.thumbnail = null;
376
 
                                        } else {
377
 
                                                self.$fullelem.find('[data-element="' + element.toLowerCase() + '"]').hide();
378
 
                                                $container.find('input.value').val('');
379
 
                                                self.$addMenu.find('option[value="' + element.toUpperCase() + '"]').prop('disabled', false);
380
 
                                        }
381
 
                                }
382
 
                                $(document).trigger('status.contact.updated', {
383
 
                                        property: element,
384
 
                                        contact: self
385
 
                                });
386
 
                                return true;
387
 
                        } else {
388
 
                                $(document).trigger('status.contacts.error', response);
389
 
                                self.setAsSaving(obj, false);
390
 
                                return false;
391
 
                        }
392
 
                })
393
 
                .fail(function(response) {
394
 
                        console.log(response.message);
395
 
                        $(document).trigger('status.contacts.error', response);
396
 
                });
397
 
;
398
 
        };
399
 
 
400
 
        /**
401
 
         * @brief Save all properties. Used for merging contacts.
402
 
         * If this is a new contact it will first be saved to the datastore and a
403
 
         * new datastructure will be added to the object.
404
 
         */
405
 
        Contact.prototype.saveAll = function(cb) {
406
 
                console.log('Contact.saveAll');
407
 
                if(!this.id) {
408
 
                        var self = this;
409
 
                        this.add({isnew:true}, function(response) {
410
 
                                if(response.error) {
411
 
                                        console.warn('No response object');
412
 
                                        return false;
413
 
                                }
414
 
                                self.saveAll();
415
 
                        });
416
 
                        return;
417
 
                }
418
 
                var self = this;
419
 
                this.setAsSaving(this.$fullelem, true);
420
 
                var data = JSON.stringify(this.data);
421
 
                //console.log('stringified', data);
422
 
                $.when(this.storage.saveAllProperties(this.metadata.backend, this.metadata.parent, this.id, {data:this.data}))
423
 
                        .then(function(response) {
424
 
                        if(!response.error) {
425
 
                                self.data = response.data.data;
426
 
                                self.metadata = response.data.metadata;
427
 
                                if(typeof cb === 'function') {
428
 
                                        cb({error:false});
429
 
                                }
430
 
                        } else {
431
 
                                $(document).trigger('status.contacts.error', {
432
 
                                        message: response.message
433
 
                                });
434
 
                                if(typeof cb === 'function') {
435
 
                                        cb({error:true, message:response.message});
436
 
                                }
437
 
                        }
438
 
                        self.setAsSaving(self.$fullelem, false);
439
 
                });
440
 
        }
441
 
 
442
 
        /**
443
 
         * @brief Act on change of a property.
444
 
         * If this is a new contact it will first be saved to the datastore and a
445
 
         * new datastructure will be added to the object.
446
 
         * If the obj argument is not provided 'name' and 'value' MUST be provided
447
 
         * and this is only allowed for single elements like N, FN, CATEGORIES.
448
 
         * @param obj. The form form field that has changed.
449
 
         * @param name. The optional name of the element.
450
 
         * @param value. The optional value.
451
 
         */
452
 
        Contact.prototype.saveProperty = function(params) {
453
 
                console.log('Contact.saveProperty', params);
454
 
                if(!this.id) {
455
 
                        var self = this;
456
 
                        this.add({isnew:true}, function(response) {
457
 
                                if(!response || response.status === 'error') {
458
 
                                        console.warn('No response object');
459
 
                                        return false;
460
 
                                }
461
 
                                self.saveProperty(params);
462
 
                                self.showActions(['close', 'add', 'export', 'delete']);
463
 
                        });
464
 
                        return;
465
 
                }
466
 
                var obj = null;
467
 
                var element = null;
468
 
                var args = [];
469
 
                if(params.obj) {
470
 
                        obj = params.obj;
471
 
                        args = this.argumentsFor(obj);
472
 
                        //args['parameters'] = $.param(this.parametersFor(obj));
473
 
                        element = this.propertyTypeFor(obj);
474
 
                } else {
475
 
                        args = params;
476
 
                        element = params.name;
477
 
                        var value = utils.isArray(params.value)
478
 
                                ? $.param(params.value)
479
 
                                : encodeURIComponent(params.value);
480
 
                }
481
 
                if(!args) {
482
 
                        console.log('No arguments. returning');
483
 
                        return false;
484
 
                }
485
 
                console.log('args', args);
486
 
                var self = this;
487
 
                this.setAsSaving(obj, true);
488
 
                $.when(this.storage.patchContact(this.metadata.backend, this.metadata.parent, this.id, args))
489
 
                        .then(function(response) {
490
 
                        if(!response.error) {
491
 
                                if(!self.data[element]) {
492
 
                                        self.data[element] = [];
493
 
                                }
494
 
                                if(self.multi_properties.indexOf(element) !== -1) {
495
 
                                        // First find out if an existing element by looking for checksum
496
 
                                        var checksum = self.checksumFor(obj);
497
 
                                        var value = self.valueFor(obj);
498
 
                                        var parameters = self.parametersFor(obj);
499
 
                                        if(parameters['TYPE'] && parameters['TYPE'].indexOf('PREF') !== -1) {
500
 
                                                parameters['PREF'] = 1;
501
 
                                                parameters['TYPE'].splice(parameters['TYPE'].indexOf('PREF', 1));
502
 
                                        }
503
 
                                        if(checksum && checksum !== 'new') {
504
 
                                                self.pushToUndo({
505
 
                                                        action:'save', 
506
 
                                                        name: element,
507
 
                                                        newchecksum: response.data.checksum,
508
 
                                                        oldchecksum: checksum,
509
 
                                                        newvalue: value,
510
 
                                                        oldvalue: obj.defaultValue
511
 
                                                });
512
 
                                                $.each(self.data[element], function(i, el) {
513
 
                                                        if(el.checksum === checksum) {
514
 
                                                                self.data[element][i] = {
515
 
                                                                        name: element,
516
 
                                                                        value: value,
517
 
                                                                        parameters: parameters,
518
 
                                                                        checksum: response.data.checksum
519
 
                                                                };
520
 
                                                                return false;
521
 
                                                        }
522
 
                                                });
523
 
                                        } else {
524
 
                                                $(obj).removeClass('new');
525
 
                                                self.pushToUndo({
526
 
                                                        action:'add', 
527
 
                                                        name: element,
528
 
                                                        newchecksum: response.data.checksum,
529
 
                                                        newvalue: value,
530
 
                                                });
531
 
                                                self.data[element].push({
532
 
                                                        name: element,
533
 
                                                        value: value,
534
 
                                                        parameters: parameters,
535
 
                                                        checksum: response.data.checksum,
536
 
                                                });
537
 
                                        }
538
 
                                        self.propertyContainerFor(obj).data('checksum', response.data.checksum);
539
 
                                } else {
540
 
                                        // Save value and parameters internally
541
 
                                        var value = obj ? self.valueFor(obj) : params.value;
542
 
                                        self.pushToUndo({
543
 
                                                action: ((obj && obj.defaultValue) || self.data[element].length) ? 'save' : 'add', // FIXME
544
 
                                                name: element,
545
 
                                                newvalue: value,
546
 
                                        });
547
 
                                        switch(element) {
548
 
                                                case 'CATEGORIES':
549
 
                                                        // We deal with this in addToGroup()
550
 
                                                        break;
551
 
                                                case 'BDAY':
552
 
                                                        // reverse order again.
553
 
                                                        value = $.datepicker.formatDate('yy-mm-dd', $.datepicker.parseDate(datepickerFormatDate, value));
554
 
                                                        self.data[element][0] = {
555
 
                                                                name: element,
556
 
                                                                value: value,
557
 
                                                                parameters: self.parametersFor(obj),
558
 
                                                                checksum: response.data.checksum
559
 
                                                        };
560
 
                                                        break;
561
 
                                                case 'FN':
562
 
                                                        if(!self.data.FN || !self.data.FN.length) {
563
 
                                                                self.data.FN = [{name:'FN', value:'', parameters:[]}];
564
 
                                                        }
565
 
                                                        self.data.FN[0]['value'] = value;
566
 
                                                        var nempty = true;
567
 
                                                        if(!self.data.N) {
568
 
                                                                // TODO: Maybe add a method for constructing new elements?
569
 
                                                                self.data.N = [{name:'N',value:['', '', '', '', ''],parameters:[]}];
570
 
                                                        }
571
 
                                                        $.each(self.data.N[0]['value'], function(idx, val) {
572
 
                                                                if(val) {
573
 
                                                                        nempty = false;
574
 
                                                                        return false;
575
 
                                                                }
576
 
                                                        });
577
 
                                                        if(nempty) {
578
 
                                                                self.data.N[0]['value'] = ['', '', '', '', ''];
579
 
                                                                var nvalue = value.split(' ');
580
 
                                                                // Very basic western style parsing. I'm not gonna implement
581
 
                                                                // https://github.com/android/platform_packages_providers_contactsprovider/blob/master/src/com/android/providers/contacts/NameSplitter.java ;)
582
 
                                                                self.data.N[0]['value'][0] = nvalue.length > 2 && nvalue.slice(nvalue.length-1).toString() || nvalue[1] || '';
583
 
                                                                self.data.N[0]['value'][1] = nvalue[0] || '';
584
 
                                                                self.data.N[0]['value'][2] = nvalue.length > 2 && nvalue.slice(1, nvalue.length-1).join(' ') || '';
585
 
                                                                setTimeout(function() {
586
 
                                                                        self.saveProperty({name:'N', value:self.data.N[0].value.join(';')});
587
 
                                                                        setTimeout(function() {
588
 
                                                                                self.$fullelem.find('.fullname').next('.action.edit').trigger('click');
589
 
                                                                                OC.notify({message:t('contacts', 'Is this correct?')});
590
 
                                                                        }, 1000);
591
 
                                                                }
592
 
                                                                , 500);
593
 
                                                        }
594
 
                                                        break;
595
 
                                                case 'N':
596
 
                                                        if(!utils.isArray(value)) {
597
 
                                                                value = value.split(';');
598
 
                                                                // Then it is auto-generated from FN.
599
 
                                                                var $nelems = self.$fullelem.find('.n.editor input');
600
 
                                                                $.each(value, function(idx, val) {
601
 
                                                                        self.$fullelem.find('#n_' + idx).val(val).get(0).defaultValue = val;
602
 
                                                                });
603
 
                                                        }
604
 
                                                        var $fullname = self.$fullelem.find('.fullname'), fullname = '';
605
 
                                                        var update_fn = false;
606
 
                                                        if(!self.data.FN) {
607
 
                                                                self.data.FN = [{name:'FN', value:'', parameters:[]}];
608
 
                                                        }
609
 
                                                        /* If FN is empty fill it with the values from N.
610
 
                                                         * As N consists of several fields which each trigger a change/save
611
 
                                                         * also check if the contents of FN equals parts of N and fill
612
 
                                                         * out the rest.
613
 
                                                         */
614
 
                                                        if(self.data.FN[0]['value'] === '') {
615
 
                                                                self.data.FN[0]['value'] = value[1] + ' ' + value[0];
616
 
                                                                $fullname.val(self.data.FN[0]['value']);
617
 
                                                                update_fn = true;
618
 
                                                        } else if($fullname.val() == value[1] + ' ') {
619
 
                                                                self.data.FN[0]['value'] = value[1] + ' ' + value[0];
620
 
                                                                $fullname.val(self.data.FN[0]['value']);
621
 
                                                                update_fn = true;
622
 
                                                        } else if($fullname.val() == ' ' + value[0]) {
623
 
                                                                self.data.FN[0]['value'] = value[1] + ' ' + value[0];
624
 
                                                                $fullname.val(self.data.FN[0]['value']);
625
 
                                                                update_fn = true;
626
 
                                                        }
627
 
                                                        if(update_fn) {
628
 
                                                                setTimeout(function() {
629
 
                                                                        self.saveProperty({name:'FN', value:self.data.FN[0]['value']});
630
 
                                                                }, 1000);
631
 
                                                        }
632
 
                                                case 'NICKNAME':
633
 
                                                case 'ORG':
634
 
                                                        // Auto-fill FN if empty
635
 
                                                        if(!self.data.FN) {
636
 
                                                                self.data.FN = [{name:'FN', value:value, parameters:[]}];
637
 
                                                                self.$fullelem.find('.fullname').val(value).trigger('change');
638
 
                                                        }
639
 
                                                case 'TITLE':
640
 
                                                case 'NOTE':
641
 
                                                        self.data[element][0] = {
642
 
                                                                name: element,
643
 
                                                                value: value,
644
 
                                                                parameters: self.parametersFor(obj),
645
 
                                                                checksum: response.data.checksum
646
 
                                                        };
647
 
                                                        break;
648
 
                                                default:
649
 
                                                        break;
650
 
                                        }
651
 
                                }
652
 
                                self.setAsSaving(obj, false);
653
 
                                $(document).trigger('status.contact.updated', {
654
 
                                        property: element,
655
 
                                        contact: self
656
 
                                });
657
 
                                return true;
658
 
                        } else {
659
 
                                $(document).trigger('status.contacts.error', response);
660
 
                                self.setAsSaving(obj, false);
661
 
                                return false;
662
 
                        }
663
 
                });
664
 
        };
665
 
 
666
 
        /**
667
 
         * Hide contact list element.
668
 
         */
669
 
        Contact.prototype.hide = function() {
670
 
                this.getListItemElement().hide();
671
 
        };
672
 
 
673
 
        /**
674
 
         * Show contact list element.
675
 
         */
676
 
        Contact.prototype.show = function() {
677
 
                this.getListItemElement().show();
678
 
        };
679
 
 
680
 
        /**
681
 
         * Remove any open contact from the DOM.
682
 
         */
683
 
        Contact.prototype.close = function() {
684
 
                $(document).unbind('status.contact.photoupdated');
685
 
                console.log('Contact.close', this);
686
 
                if(this.$fullelem) {
687
 
                        this.$fullelem.hide().remove();
688
 
                        this.getListItemElement().show();
689
 
                        this.$fullelem = null;
690
 
                        return true;
691
 
                } else {
692
 
                        return false;
693
 
                }
694
 
        };
695
 
 
696
 
        /**
697
 
         * Remove any open contact from the DOM and detach it's list
698
 
         * element from the DOM.
699
 
         * @returns The contact object.
700
 
         */
701
 
        Contact.prototype.detach = function() {
702
 
                if(this.$fullelem) {
703
 
                        this.$fullelem.remove();
704
 
                }
705
 
                if(this.$listelem) {
706
 
                        this.$listelem.detach();
707
 
                        return this;
708
 
                }
709
 
        };
710
 
 
711
 
        /**
712
 
         * Set a contacts list element as (un)checked
713
 
         * @returns The contact object.
714
 
         */
715
 
        Contact.prototype.setChecked = function(checked) {
716
 
                if(this.$listelem) {
717
 
                        this.$listelem.find('input:checkbox').prop('checked', checked);
718
 
                        return this;
719
 
                }
720
 
        };
721
 
 
722
 
        /**
723
 
         * Set a contact to en/disabled depending on its permissions.
724
 
         * @param boolean enabled
725
 
         */
726
 
        Contact.prototype.setEnabled = function(enabled) {
727
 
                if(enabled) {
728
 
                        this.$fullelem.find('#addproperty').show();
729
 
                } else {
730
 
                        this.$fullelem.find('#addproperty,.action.delete,.action.edit').hide();
731
 
                }
732
 
                this.enabled = enabled;
733
 
                this.$fullelem.find('.value,.action,.parameter').each(function () {
734
 
                        $(this).prop('disabled', !enabled);
735
 
                });
736
 
                $(document).trigger('status.contact.enabled', enabled);
737
 
        };
738
 
 
739
 
        /**
740
 
         * Add a contact to data store.
741
 
         * @params params. An object which can contain the optional properties:
742
 
         *              aid: The id of the addressbook to add the contact to. Per default it will be added to the first.
743
 
         *              fn: The formatted name of the contact.
744
 
         * @param cb Optional callback function which
745
 
         * @returns The callback gets an object as argument with a variable 'status' of either 'success'
746
 
         * or 'error'. On success the 'data' property of that object contains the contact id as 'id', the
747
 
         * addressbook id as 'aid' and the contact data structure as 'details'.
748
 
         */
749
 
        Contact.prototype.add = function(params, cb) {
750
 
                var self = this;
751
 
                $.when(this.storage.addContact(this.metadata.backend, this.metadata.parent))
752
 
                        .then(function(response) {
753
 
                        if(!response.error) {
754
 
                                self.id = String(response.data.metadata.id);
755
 
                                self.metadata = response.data.metadata;
756
 
                                self.data = response.data.data;
757
 
                                self.$groupSelect.multiselect('enable');
758
 
                                // Add contact to current group
759
 
                                if(self.groupprops
760
 
                                        && ['all', 'fav', 'uncategorized'].indexOf(self.groupprops.currentgroup.id) === -1
761
 
                                ) {
762
 
                                        if(!self.data.CATEGORIES) {
763
 
                                                self.addToGroup(self.groupprops.currentgroup.name);
764
 
                                                $(document).trigger('request.contact.addtogroup', {
765
 
                                                        id: self.id,
766
 
                                                        groupid: self.groupprops.currentgroup.id
767
 
                                                });
768
 
                                                self.$groupSelect.find('option[value="' + self.groupprops.currentgroup.id + '"]')
769
 
                                                        .attr('selected', 'selected');
770
 
                                                self.$groupSelect.multiselect('refresh');
771
 
                                        }
772
 
                                }
773
 
                                $(document).trigger('status.contact.added', {
774
 
                                        id: self.id,
775
 
                                        contact: self
776
 
                                });
777
 
                        } else {
778
 
                                $(document).trigger('status.contacts.error', response);
779
 
                                return false;
780
 
                        }
781
 
                        if(typeof cb == 'function') {
782
 
                                cb(response);
783
 
                        }
784
 
                });
785
 
        };
786
 
        /**
787
 
         * Delete contact from data store and remove it from the DOM
788
 
         * @param cb Optional callback function which
789
 
         * @returns An object with a variable 'status' of either success
790
 
         *      or 'error'
791
 
         */
792
 
        Contact.prototype.destroy = function(cb) {
793
 
                var self = this;
794
 
                $.when(this.storage.deleteContact(
795
 
                        this.metadata.backend,
796
 
                        this.metadata.parent,
797
 
                        this.id)
798
 
                ).then(function(response) {
799
 
                //$.post(OC.filePath('contacts', 'ajax', 'contact/delete.php'),
800
 
                //         {id: this.id}, function(response) {
801
 
                        if(!response.error) {
802
 
                                if(self.$listelem) {
803
 
                                        self.$listelem.remove();
804
 
                                }
805
 
                                if(self.$fullelem) {
806
 
                                        self.$fullelem.remove();
807
 
                                }
808
 
                        }
809
 
                        if(typeof cb == 'function') {
810
 
                                if(response.error) {
811
 
                                        cb(response);
812
 
                                } else {
813
 
                                        cb({id:self.id});
814
 
                                }
815
 
                        }
816
 
                });
817
 
        };
818
 
 
819
 
        Contact.prototype.argumentsFor = function(obj) {
820
 
                console.log('Contact.argumentsFor', $(obj));
821
 
                var args = {};
822
 
                var ptype = this.propertyTypeFor(obj);
823
 
                args['name'] = ptype;
824
 
 
825
 
                if(this.multi_properties.indexOf(ptype) !== -1) {
826
 
                        args['checksum'] = this.checksumFor(obj);
827
 
                }
828
 
 
829
 
                if($(obj).hasClass('propertycontainer')) {
830
 
                        if($(obj).is('select[data-element="categories"]')) {
831
 
                                args['value'] = [];
832
 
                                $.each($(obj).find(':selected'), function(idx, e) {
833
 
                                        args['value'].push($(e).text());
834
 
                                });
835
 
                        } else {
836
 
                                args['value'] = $(obj).val();
837
 
                        }
838
 
                } else {
839
 
                        var $elements = this.propertyContainerFor(obj)
840
 
                                .find('input.value,select.value,textarea.value');
841
 
                        if($elements.length > 1) {
842
 
                                args['value'] = [];
843
 
                                $.each($elements, function(idx, e) {
844
 
                                        args['value'][parseInt($(e).attr('name').substr(6,1))] = $(e).val();
845
 
                                        //args['value'].push($(e).val());
846
 
                                });
847
 
                        } else {
848
 
                                var value = $elements.val();
849
 
                                switch(args['name']) {
850
 
                                        case 'BDAY':
851
 
                                                try {
852
 
                                                        args['value'] = $.datepicker.formatDate('yy-mm-dd', $.datepicker.parseDate(datepickerFormatDate, value));
853
 
                                                } catch(e) {
854
 
                                                        $(document).trigger(
855
 
                                                                'status.contacts.error',
856
 
                                                                {message:t('contacts', 'Error parsing date: {date}', {date:value})}
857
 
                                                        );
858
 
                                                        return false;
859
 
                                                }
860
 
                                                break;
861
 
                                        default:
862
 
                                                args['value'] = value;
863
 
                                                break;
864
 
                                }
865
 
                        }
866
 
                }
867
 
                args['parameters'] = this.parametersFor(obj);
868
 
                console.log('Contact.argumentsFor', args);
869
 
                return args;
870
 
        };
871
 
 
872
 
        Contact.prototype.queryStringFor = function(obj) {
873
 
                var q = 'id=' + this.id;
874
 
                var ptype = this.propertyTypeFor(obj);
875
 
                q += '&name=' + ptype;
876
 
 
877
 
                if(this.multi_properties.indexOf(ptype) !== -1) {
878
 
                        q += '&checksum=' + this.checksumFor(obj);
879
 
                }
880
 
 
881
 
                if($(obj).hasClass('propertycontainer')) {
882
 
                        if($(obj).is('select[data-element="categories"]')) {
883
 
                                $.each($(obj).find(':selected'), function(idx, e) {
884
 
                                        q += '&value=' + encodeURIComponent($(e).text());
885
 
                                });
886
 
                        } else {
887
 
                                q += '&value=' + encodeURIComponent($(obj).val());
888
 
                        }
889
 
                } else {
890
 
                        var $elements = this.propertyContainerFor(obj)
891
 
                                .find('input.value,select.value,textarea.value,.parameter');
892
 
                        if($elements.length > 1) {
893
 
                                q += '&' + $elements.serialize();
894
 
                        } else {
895
 
                                q += '&value=' + encodeURIComponent($elements.val());
896
 
                        }
897
 
                }
898
 
                return q;
899
 
        };
900
 
 
901
 
        Contact.prototype.propertyContainerFor = function(obj) {
902
 
                return $(obj).hasClass('propertycontainer')
903
 
                        ? $(obj)
904
 
                        : $(obj).parents('.propertycontainer').first();
905
 
        };
906
 
 
907
 
        Contact.prototype.checksumFor = function(obj) {
908
 
                return this.propertyContainerFor(obj).data('checksum');
909
 
        };
910
 
 
911
 
        Contact.prototype.valueFor = function(obj) {
912
 
                var $container = this.propertyContainerFor(obj);
913
 
                console.assert($container.length > 0, 'Couldn\'t find container for ' + $(obj));
914
 
                return $container.is('input.value')
915
 
                        ? $container.val()
916
 
                        : (function() {
917
 
                                var $elem = $container.find('textarea.value,input.value:not(:checkbox)');
918
 
                                console.assert($elem.length > 0, 'Couldn\'t find value for ' + $container.data('element'));
919
 
                                if($elem.length === 1) {
920
 
                                        return $elem.val();
921
 
                                } else if($elem.length > 1) {
922
 
                                        var retval = [];
923
 
                                        $.each($elem, function(idx, e) {
924
 
                                                retval[parseInt($(e).attr('name').substr(6,1))] = $(e).val();
925
 
                                        });
926
 
                                        return retval;
927
 
                                }
928
 
                        })();
929
 
        };
930
 
 
931
 
        Contact.prototype.parametersFor = function(obj, asText) {
932
 
                var parameters = {};
933
 
                $.each(this.propertyContainerFor(obj)
934
 
                        .find('select.parameter,input:checkbox:checked.parameter'),
935
 
                           function(i, elem) {
936
 
                        var $elem = $(elem);
937
 
                        var paramname = $elem.data('parameter');
938
 
                        if(!parameters[paramname]) {
939
 
                                parameters[paramname] = [];
940
 
                        }
941
 
                        if($elem.is(':checkbox')) {
942
 
                                if(asText) {
943
 
                                        parameters[paramname].push($elem.attr('title'));
944
 
                                } else {
945
 
                                        parameters[paramname].push($elem.attr('value'));
946
 
                                }
947
 
                        } else if($elem.is('select')) {
948
 
                                $.each($elem.find(':selected'), function(idx, e) {
949
 
                                        if(asText) {
950
 
                                                parameters[paramname].push($(e).text());
951
 
                                        } else {
952
 
                                                parameters[paramname].push($(e).val());
953
 
                                        }
954
 
                                });
955
 
                        }
956
 
                });
957
 
                return parameters;
958
 
        };
959
 
 
960
 
        Contact.prototype.propertyTypeFor = function(obj) {
961
 
                var ptype = this.propertyContainerFor(obj).data('element');
962
 
                return ptype ? ptype.toUpperCase() : null;
963
 
        };
964
 
 
965
 
        /**
966
 
         * Render an element item to be shown during drag.
967
 
         * @return A jquery object
968
 
         */
969
 
        Contact.prototype.renderDragItem = function() {
970
 
                if(typeof this.$dragelem === 'undefined') {
971
 
                        this.$dragelem = this.$dragTemplate.octemplate({
972
 
                                id: this.id,
973
 
                                name: this.getPreferredValue('FN', '')
974
 
                        });
975
 
                }
976
 
                this.setThumbnail(this.$dragelem);
977
 
                return this.$dragelem;
978
 
        }
979
 
 
980
 
        /**
981
 
         * Render the list item
982
 
         * @return A jquery object to be inserted in the DOM
983
 
         */
984
 
        Contact.prototype.renderListItem = function(isnew) {
985
 
                this.displayNames.fn = this.getPreferredValue('FN')
986
 
                        || this.getPreferredValue('ORG', []).pop()
987
 
                        || this.getPreferredValue('EMAIL')
988
 
                        || this.getPreferredValue('TEL');
989
 
 
990
 
                this.displayNames.fl = this.getPreferredValue('N', [this.displayNames.fn])
991
 
                        .slice(0, 2).reverse().join(' ');
992
 
 
993
 
                this.displayNames.lf = this.getPreferredValue('N', [this.displayNames.fn])
994
 
                        .slice(0, 2).join(', ');
995
 
 
996
 
                this.$listelem = this.$listTemplate.octemplate({
997
 
                        id: this.id,
998
 
                        parent: this.metadata.parent,
999
 
                        backend: this.metadata.backend,
1000
 
                        name: this.getDisplayName(),
1001
 
                        email: this.getPreferredValue('EMAIL', ''),
1002
 
                        tel: this.getPreferredValue('TEL', ''),
1003
 
                        adr: this.getPreferredValue('ADR', []).clean('').join(', '),
1004
 
                        categories: this.getPreferredValue('CATEGORIES', [])
1005
 
                                .clean('').join(' / ')
1006
 
                });
1007
 
                if(this.getOwner() !== OC.currentUser
1008
 
                                && !(this.metadata.permissions & OC.PERMISSION_UPDATE
1009
 
                                || this.metadata.permissions & OC.PERMISSION_DELETE)) {
1010
 
                        this.$listelem.find('input:checkbox').prop('disabled', true).css('opacity', '0');
1011
 
                } else {
1012
 
                        var self = this;
1013
 
                        this.$listelem.find('td.name')
1014
 
                                .draggable({
1015
 
                                        cursor: 'move',
1016
 
                                        distance: 10,
1017
 
                                        revert: 'invalid',
1018
 
                                        helper: function (e,ui) {
1019
 
                                                return self.renderDragItem().appendTo('body');
1020
 
                                        },
1021
 
                                        opacity: 1,
1022
 
                                        scope: 'contacts'
1023
 
                                });
1024
 
                }
1025
 
                if(isnew) {
1026
 
                        this.setThumbnail();
1027
 
                }
1028
 
                this.$listelem.data('obj', this);
1029
 
                return this.$listelem;
1030
 
        };
1031
 
 
1032
 
        /**
1033
 
         * Render the full contact
1034
 
         * @return A jquery object to be inserted in the DOM
1035
 
         */
1036
 
        Contact.prototype.renderContact = function(groupprops) {
1037
 
                var self = this;
1038
 
                this.groupprops = groupprops;
1039
 
                
1040
 
                var buildGroupSelect = function(availableGroups) {
1041
 
                        //this.$groupSelect.find('option').remove();
1042
 
                        $.each(availableGroups, function(idx, group) {
1043
 
                                var $option = $('<option value="' + group.id + '">' + group.name + '</option>');
1044
 
                                if(self.inGroup(group.name)) {
1045
 
                                        $option.attr('selected', 'selected');
1046
 
                                }
1047
 
                                self.$groupSelect.append($option);
1048
 
                        });
1049
 
                        self.$groupSelect.multiselect({
1050
 
                                header: false,
1051
 
                                selectedList: 3,
1052
 
                                noneSelectedText: self.$groupSelect.attr('title'),
1053
 
                                selectedText: t('contacts', '# groups')
1054
 
                        });
1055
 
                        self.$groupSelect.bind('multiselectclick', function(event, ui) {
1056
 
                                var action = ui.checked ? 'addtogroup' : 'removefromgroup';
1057
 
                                console.assert(typeof self.id === 'string', 'ID is not a string')
1058
 
                                $(document).trigger('request.contact.' + action, {
1059
 
                                        id: self.id,
1060
 
                                        groupid: parseInt(ui.value)
1061
 
                                });
1062
 
                                if(ui.checked) {
1063
 
                                        self.addToGroup(ui.text);
1064
 
                                } else {
1065
 
                                        self.removeFromGroup(ui.text);
1066
 
                                }
1067
 
                        });
1068
 
                        if(!self.id || !self.hasPermission(OC.PERMISSION_UPDATE)) {
1069
 
                                self.$groupSelect.multiselect('disable');
1070
 
                        }
1071
 
                };
1072
 
                
1073
 
                var buildAddressBookSelect = function(availableAddressBooks) {
1074
 
                        console.log('address books', availableAddressBooks.length, availableAddressBooks);
1075
 
                        $.each(availableAddressBooks, function(idx, addressBook) {
1076
 
                                //console.log('addressBook', idx, addressBook);
1077
 
                                var $option = $('<option />')
1078
 
                                        .val(addressBook.getId())
1079
 
                                        .text(addressBook.getDisplayName() + '(' + addressBook.getBackend() + ')')
1080
 
                                        .data('backend', addressBook.getBackend())
1081
 
                                        .data('owner', addressBook.getOwner());
1082
 
                                if(self.metadata.parent === addressBook.getId()
1083
 
                                        && self.metadata.backend === addressBook.getBackend()) {
1084
 
                                        $option.attr('selected', 'selected');
1085
 
                                }
1086
 
                                self.$addressBookSelect.append($option);
1087
 
                        });
1088
 
                        self.$addressBookSelect.multiselect({
1089
 
                                header: false,
1090
 
                                multiple: false,
1091
 
                                selectedList: 3,
1092
 
                                noneSelectedText: self.$addressBookSelect.attr('title')
1093
 
                        });
1094
 
                        self.$addressBookSelect.on('multiselectclick', function(event, ui) {
1095
 
                                console.log('AddressBook select', ui);
1096
 
                                self.$addressBookSelect.val(ui.value);
1097
 
                                var opt = self.$addressBookSelect.find(':selected');
1098
 
                                if(self.id) {
1099
 
                                        console.log('AddressBook', opt);
1100
 
                                        $(document).trigger('request.contact.move', {
1101
 
                                                contact: self,
1102
 
                                                from: {id:self.getParent(), backend:self.getBackend()},
1103
 
                                                target: {id:opt.val(), backend:opt.data('backend')}
1104
 
                                        });
1105
 
                                } else {
1106
 
                                        self.setBackend(opt.data('backend'));
1107
 
                                        self.setParent(opt.val());
1108
 
                                        self.setOwner(opt.data('owner'));
1109
 
                                }
1110
 
                        });
1111
 
                        if(self.id) {
1112
 
                                //self.$addressBookSelect.multiselect('disable');
1113
 
                        }
1114
 
                };
1115
 
 
1116
 
                var values;
1117
 
                if(this.data) {
1118
 
                        var n = this.getPreferredValue('N', ['', '', '', '', '']),
1119
 
                                bday = this.getPreferredValue('BDAY', '');
1120
 
                        if(bday.length >= 10) {
1121
 
                                try {
1122
 
                                        bday = $.datepicker.parseDate('yy-mm-dd', bday.substring(0, 10));
1123
 
                                        bday = $.datepicker.formatDate(datepickerFormatDate, bday);
1124
 
                                } catch (e) {
1125
 
                                        var message = t('contacts', 'Error parsing birthday {bday}: {error}', {bday:bday, error: e});
1126
 
                                        console.warn(message);
1127
 
                                        bday = '';
1128
 
                                        $(document).trigger('status.contacts.error', {
1129
 
                                                status: 'error',
1130
 
                                                message: message
1131
 
                                        });
1132
 
                                }
1133
 
                        }
1134
 
                        values = {
1135
 
                                id: this.id,
1136
 
                                favorite:groupprops.favorite ? 'active' : '',
1137
 
                                name: this.getPreferredValue('FN', ''),
1138
 
                                n0: n[0]||'', n1: n[1]||'', n2: n[2]||'', n3: n[3]||'', n4: n[4]||'',
1139
 
                                nickname: this.getPreferredValue('NICKNAME', ''),
1140
 
                                title: this.getPreferredValue('TITLE', ''),
1141
 
                                org: this.getPreferredValue('ORG', []).clean('').join(', '), // TODO Add parts if more than one.
1142
 
                                bday: bday,
1143
 
                                note: this.getPreferredValue('NOTE', '')
1144
 
                        }
1145
 
                } else {
1146
 
                        values = {id:'', favorite:'', name:'', nickname:'', title:'', org:'', bday:'', note:'', n0:'', n1:'', n2:'', n3:'', n4:''};
1147
 
                }
1148
 
                this.$fullelem = this.$fullTemplate.octemplate(values).data('contactobject', this);
1149
 
 
1150
 
                this.$footer = this.$fullelem.find('footer');
1151
 
 
1152
 
                this.$fullelem.find('.tooltipped.rightwards.onfocus').tipsy({trigger: 'focus', gravity: 'w'});
1153
 
                this.$fullelem.on('submit', function() {
1154
 
                        return false;
1155
 
                });
1156
 
                
1157
 
                if(this.getOwner() === OC.currentUser) {
1158
 
                        this.$groupSelect = this.$fullelem.find('#contactgroups');
1159
 
                        buildGroupSelect(groupprops.groups);
1160
 
                }
1161
 
                
1162
 
                var writeableAddressBooks = this.parent.addressBooks.selectByPermission(OC.PERMISSION_CREATE);
1163
 
                if(writeableAddressBooks.length > 1 && this.hasPermission(OC.PERMISSION_DELETE)) {
1164
 
                        this.$addressBookSelect = this.$fullelem.find('#contactaddressbooks');
1165
 
                        buildAddressBookSelect(writeableAddressBooks);
1166
 
                }
1167
 
 
1168
 
                this.$addMenu = this.$fullelem.find('#addproperty');
1169
 
                this.$addMenu.on('change', function(event) {
1170
 
                        //console.log('add', $(this).val());
1171
 
                        var $opt = $(this).find('option:selected');
1172
 
                        self.addProperty($opt, $(this).val());
1173
 
                        $(this).val('');
1174
 
                });
1175
 
                var $fullname = this.$fullelem.find('.fullname');
1176
 
                this.$fullelem.find('.singleproperties').on('mouseenter', function() {
1177
 
                        $fullname.next('.edit').css('opacity', '1');
1178
 
                }).on('mouseleave', function() {
1179
 
                        $fullname.next('.edit').css('opacity', '0');
1180
 
                });
1181
 
                $fullname.next('.edit').on('click keydown', function(event) {
1182
 
                        //console.log('edit name', event);
1183
 
                        $('.tipsy').remove();
1184
 
                        if(wrongKey(event)) {
1185
 
                                return;
1186
 
                        }
1187
 
                        $(this).css('opacity', '0');
1188
 
                        var $editor = $(this).next('.n.editor').first();
1189
 
                        var bodyListener = function(e) {
1190
 
                                if($editor.find($(e.target)).length == 0) {
1191
 
                                        $editor.toggle('blind');
1192
 
                                        $('body').unbind('click', bodyListener);
1193
 
                                }
1194
 
                        };
1195
 
                        $editor.toggle('blind', function() {
1196
 
                                $('body').bind('click', bodyListener);
1197
 
                        });
1198
 
                });
1199
 
 
1200
 
                this.$fullelem.on('click keydown', '.delete', function(event) {
1201
 
                        $('.tipsy').remove();
1202
 
                        if(wrongKey(event)) {
1203
 
                                return;
1204
 
                        }
1205
 
                        self.deleteProperty({obj:event.target});
1206
 
                });
1207
 
 
1208
 
                this.$fullelem.on('click keydown', '.globe,.mail', function(event) {
1209
 
                        $('.tipsy').remove();
1210
 
                        if(wrongKey(event)) {
1211
 
                                return;
1212
 
                        }
1213
 
                        self.handleURL(event.target);
1214
 
                });
1215
 
 
1216
 
                this.$footer.on('click keydown', 'button', function(event) {
1217
 
                        $('.tipsy').remove();
1218
 
                        if(wrongKey(event)) {
1219
 
                                return;
1220
 
                        }
1221
 
                        if($(this).is('.close') || $(this).is('.cancel')) {
1222
 
                                $(document).trigger('request.contact.close', {
1223
 
                                        id: self.id
1224
 
                                });
1225
 
                        } else if($(this).is('.export')) {
1226
 
                                $(document).trigger('request.contact.export', self.metaData());
1227
 
                        } else if($(this).is('.delete')) {
1228
 
                                $(document).trigger('request.contact.delete', self.metaData());
1229
 
                        }
1230
 
                        return false;
1231
 
                });
1232
 
                this.$fullelem.on('keypress', '.value,.parameter', function(event) {
1233
 
                        if(event.keyCode === 13 && $(this).is('input')) {
1234
 
                                $(this).trigger('change');
1235
 
                                // Prevent a second save on blur.
1236
 
                                this.previousValue = this.defaultValue || '';
1237
 
                                this.defaultValue = this.value;
1238
 
                                return false;
1239
 
                        } else if(event.keyCode === 27) {
1240
 
                                $(document).trigger('request.contact.close', {
1241
 
                                        id: self.id
1242
 
                                });
1243
 
                        }
1244
 
                });
1245
 
 
1246
 
                this.$fullelem.on('change', '.value,.parameter', function(event) {
1247
 
                        if($(this).hasClass('value') && this.value === this.defaultValue) {
1248
 
                                return;
1249
 
                        }
1250
 
                        //console.log('change', this.defaultValue, this.value);
1251
 
                        this.defaultValue = this.value;
1252
 
                        self.saveProperty({obj:event.target});
1253
 
                });
1254
 
 
1255
 
                var $bdayinput = this.$fullelem.find('[data-element="bday"]').find('input');
1256
 
                $bdayinput.datepicker({
1257
 
                                dateFormat : datepickerFormatDate
1258
 
                });
1259
 
                $bdayinput.attr('placeholder', $.datepicker.formatDate(datepickerFormatDate, new Date()));
1260
 
 
1261
 
                this.$fullelem.find('.favorite').on('click', function () {
1262
 
                        var state = $(this).hasClass('active');
1263
 
                        if(!self.data) {
1264
 
                                return;
1265
 
                        }
1266
 
                        if(state) {
1267
 
                                $(this).switchClass('active', 'inactive');
1268
 
                        } else {
1269
 
                                $(this).switchClass('inactive', 'active');
1270
 
                        }
1271
 
                        $(document).trigger('request.contact.setasfavorite', {
1272
 
                                id: self.id,
1273
 
                                state: !state
1274
 
                        });
1275
 
                });
1276
 
                this.loadPhoto();
1277
 
                if(!this.data) {
1278
 
                        // A new contact
1279
 
                        this.setEnabled(true);
1280
 
                        this.showActions(['cancel']);
1281
 
                        return this.$fullelem;
1282
 
                }
1283
 
                // Loop thru all single occurrence values. If not set hide the
1284
 
                // element, if set disable the add menu entry.
1285
 
                $.each(values, function(name, value) {
1286
 
                        if(typeof value === 'undefined') {
1287
 
                                return true; //continue
1288
 
                        }
1289
 
                        value = value.toString();
1290
 
                        if(self.multi_properties.indexOf(value.toUpperCase()) === -1) {
1291
 
                                if(!value.length) {
1292
 
                                        self.$fullelem.find('[data-element="' + name + '"]').hide();
1293
 
                                } else {
1294
 
                                        self.$addMenu.find('option[value="' + name.toUpperCase() + '"]').prop('disabled', true);
1295
 
                                }
1296
 
                        }
1297
 
                });
1298
 
                $.each(this.multi_properties, function(idx, name) {
1299
 
                        if(self.data[name]) {
1300
 
                                var $list = self.$fullelem.find('ul.' + name.toLowerCase());
1301
 
                                $list.show();
1302
 
                                for(var p in self.data[name]) {
1303
 
                                        if(typeof self.data[name][p] === 'object') {
1304
 
                                                var property = self.data[name][p];
1305
 
                                                //console.log(name, p, property);
1306
 
                                                var $property = null;
1307
 
                                                switch(name) {
1308
 
                                                        case 'TEL':
1309
 
                                                        case 'URL':
1310
 
                                                        case 'EMAIL':
1311
 
                                                                $property = self.renderStandardProperty(name.toLowerCase(), property);
1312
 
                                                                if(self.data[name].length === 1) {
1313
 
                                                                        $property.find('input:checkbox[value="PREF"]').hide();
1314
 
                                                                }
1315
 
                                                                break;
1316
 
                                                        case 'ADR':
1317
 
                                                                $property = self.renderAddressProperty(idx, property);
1318
 
                                                                break;
1319
 
                                                        case 'IMPP':
1320
 
                                                                $property = self.renderIMProperty(property);
1321
 
                                                                if(self.data[name].length === 1) {
1322
 
                                                                        $property.find('input:checkbox[value="PREF"]').hide();
1323
 
                                                                }
1324
 
                                                                break;
1325
 
                                                }
1326
 
                                                if(!$property) {
1327
 
                                                        continue;
1328
 
                                                }
1329
 
                                                //console.log('$property', $property);
1330
 
                                                var meta = [];
1331
 
                                                if(property.label) {
1332
 
                                                        if(!property.parameters['TYPE']) {
1333
 
                                                                property.parameters['TYPE'] = [];
1334
 
                                                        }
1335
 
                                                        property.parameters['TYPE'].push(property.label);
1336
 
                                                        meta.push(property.label);
1337
 
                                                }
1338
 
                                                for(var param in property.parameters) {
1339
 
                                                        //console.log('param', param);
1340
 
                                                        if(param.toUpperCase() == 'PREF') {
1341
 
                                                                var $cb = $property.find('input[type="checkbox"]');
1342
 
                                                                $cb.attr('checked', 'checked');
1343
 
                                                                meta.push($cb.attr('title'));
1344
 
                                                        }
1345
 
                                                        else if(param.toUpperCase() == 'TYPE') {
1346
 
                                                                for(var etype in property.parameters[param]) {
1347
 
                                                                        var found = false;
1348
 
                                                                        var et = property.parameters[param][etype];
1349
 
                                                                        if(typeof et !== 'string') {
1350
 
                                                                                continue;
1351
 
                                                                        }
1352
 
                                                                        $property.find('select.type option').each(function() {
1353
 
                                                                                if($(this).val().toUpperCase() === et.toUpperCase()) {
1354
 
                                                                                        $(this).attr('selected', 'selected');
1355
 
                                                                                        meta.push($(this).text());
1356
 
                                                                                        found = true;
1357
 
                                                                                }
1358
 
                                                                        });
1359
 
                                                                        if(!found) {
1360
 
                                                                                $property.find('select.type option:last-child').after('<option value="'+et+'" selected="selected">'+et+'</option>');
1361
 
                                                                        }
1362
 
                                                                }
1363
 
                                                        }
1364
 
                                                        else if(param.toUpperCase() == 'X-SERVICE-TYPE') {
1365
 
                                                                //console.log('setting', $property.find('select.impp'), 'to', property.parameters[param].toLowerCase());
1366
 
                                                                $property.find('select.impp').val(property.parameters[param].toLowerCase());
1367
 
                                                        }
1368
 
                                                }
1369
 
                                                var $meta = $property.find('.meta');
1370
 
                                                if($meta.length) {
1371
 
                                                        $meta.html(meta.join('/'));
1372
 
                                                }
1373
 
                                                if(self.metadata.owner === OC.currentUser
1374
 
                                                                || self.metadata.permissions & OC.PERMISSION_UPDATE
1375
 
                                                                || self.metadata.permissions & OC.PERMISSION_DELETE) {
1376
 
                                                        $property.find('select.type[name="parameters[TYPE][]"]')
1377
 
                                                                .combobox({
1378
 
                                                                        singleclick: true,
1379
 
                                                                        classes: ['propertytype', 'float', 'label']
1380
 
                                                                });
1381
 
                                                }
1382
 
                                                $list.append($property);
1383
 
                                        }
1384
 
                                }
1385
 
                        }
1386
 
                });
1387
 
                if(this.metadata.owner !== OC.currentUser
1388
 
                        && !(this.hasPermission(OC.PERMISSION_UPDATE)
1389
 
                                || this.hasPermission(OC.PERMISSION_DELETE))) {
1390
 
                        this.setEnabled(false);
1391
 
                        this.showActions(['close', 'export']);
1392
 
                } else {
1393
 
                        this.setEnabled(true);
1394
 
                        this.showActions(['close', 'add', 'export', 'delete']);
1395
 
                }
1396
 
                return this.$fullelem;
1397
 
        };
1398
 
 
1399
 
        Contact.prototype.isEditable = function() {
1400
 
                return ((this.metadata.owner === OC.currentUser)
1401
 
                        || (this.metadata.permissions & OC.PERMISSION_UPDATE
1402
 
                                || this.metadata.permissions & OC.PERMISSION_DELETE));
1403
 
        };
1404
 
 
1405
 
        /**
1406
 
         * Render a simple property. Used for EMAIL and TEL.
1407
 
         * @return A jquery object to be injected in the DOM
1408
 
         */
1409
 
        Contact.prototype.renderStandardProperty = function(name, property) {
1410
 
                if(!this.detailTemplates[name]) {
1411
 
                        console.error('No template for', name);
1412
 
                        return;
1413
 
                }
1414
 
                var values = property
1415
 
                        ? { value: property.value, checksum: property.checksum }
1416
 
                        : { value: '', checksum: 'new' };
1417
 
                return this.detailTemplates[name].octemplate(values);
1418
 
        };
1419
 
 
1420
 
        /**
1421
 
         * Render an ADR (address) property.
1422
 
         * @return A jquery object to be injected in the DOM
1423
 
         */
1424
 
        Contact.prototype.renderAddressProperty = function(idx, property) {
1425
 
                if(!this.detailTemplates['adr']) {
1426
 
                        console.warn('No template for adr', this.detailTemplates);
1427
 
                        return;
1428
 
                }
1429
 
                if(typeof idx === 'undefined') {
1430
 
                        if(this.data && this.data.ADR && this.data.ADR.length > 0) {
1431
 
                                idx = this.data.ADR.length - 1;
1432
 
                        } else {
1433
 
                                idx = 0;
1434
 
                        }
1435
 
                }
1436
 
                var values = property ? {
1437
 
                                value: property.value.clean('').join(', '),
1438
 
                                checksum: property.checksum,
1439
 
                                adr0: property.value[0] || '',
1440
 
                                adr1: property.value[1] || '',
1441
 
                                adr2: property.value[2] || '',
1442
 
                                adr3: property.value[3] || '',
1443
 
                                adr4: property.value[4] || '',
1444
 
                                adr5: property.value[5] || '',
1445
 
                                adr6: property.value[6] || '',
1446
 
                                idx: idx
1447
 
                        }
1448
 
                        : {value:'', checksum:'new', adr0:'', adr1:'', adr2:'', adr3:'', adr4:'', adr5:'', adr6:'', idx: idx};
1449
 
                var $elem = this.detailTemplates['adr'].octemplate(values);
1450
 
                var self = this;
1451
 
                $elem.find('.tooltipped.downwards:not(.onfocus)').tipsy({gravity: 'n'});
1452
 
                $elem.find('.tooltipped.rightwards.onfocus').tipsy({trigger: 'focus', gravity: 'w'});
1453
 
                $elem.find('.display').on('click', function() {
1454
 
                        $(this).next('.listactions').hide();
1455
 
                        var $editor = $(this).siblings('.adr.editor').first();
1456
 
                        var $viewer = $(this);
1457
 
                        var bodyListener = function(e) {
1458
 
                                if($editor.find($(e.target)).length == 0) {
1459
 
                                        $editor.toggle('blind');
1460
 
                                        $viewer.slideDown(400, function() {
1461
 
                                                var input = $editor.find('input').first();
1462
 
                                                var val = self.valueFor(input);
1463
 
                                                var params = self.parametersFor(input, true);
1464
 
                                                $(this).find('.meta').html(params['TYPE'].join('/'));
1465
 
                                                $(this).find('.adr').html(self.valueFor($editor.find('input').first()).clean('').join(', '));
1466
 
                                                $(this).next('.listactions').css('display', 'inline-block');
1467
 
                                                $('body').unbind('click', bodyListener);
1468
 
                                        });
1469
 
                                }
1470
 
                        };
1471
 
                        $viewer.slideUp();
1472
 
                        $editor.toggle('blind', function() {
1473
 
                                $('body').bind('click', bodyListener);
1474
 
                        });
1475
 
                });
1476
 
                $elem.find('.value.city')
1477
 
                        .autocomplete({
1478
 
                                source: function( request, response ) {
1479
 
                                        $.ajax({
1480
 
                                                url: "http://ws.geonames.org/searchJSON",
1481
 
                                                dataType: "jsonp",
1482
 
                                                data: {
1483
 
                                                        featureClass: "P",
1484
 
                                                        style: "full",
1485
 
                                                        maxRows: 12,
1486
 
                                                        lang: $elem.data('lang'),
1487
 
                                                        name_startsWith: request.term
1488
 
                                                },
1489
 
                                                success: function( data ) {
1490
 
                                                        response( $.map( data.geonames, function( item ) {
1491
 
                                                                return {
1492
 
                                                                        label: item.name + (item.adminName1 ? ", " + item.adminName1 : "") + ", " + item.countryName,
1493
 
                                                                        value: item.name,
1494
 
                                                                        country: item.countryName
1495
 
                                                                };
1496
 
                                                        }));
1497
 
                                                }
1498
 
                                        });
1499
 
                                },
1500
 
                                minLength: 2,
1501
 
                                select: function( event, ui ) {
1502
 
                                        if(ui.item && $.trim($elem.find('.value.country').val()).length == 0) {
1503
 
                                                $elem.find('.value.country').val(ui.item.country);
1504
 
                                        }
1505
 
                                }
1506
 
                        });
1507
 
                $elem.find('.value.country')
1508
 
                        .autocomplete({
1509
 
                                source: function( request, response ) {
1510
 
                                        $.ajax({
1511
 
                                                url: "http://ws.geonames.org/searchJSON",
1512
 
                                                dataType: "jsonp",
1513
 
                                                data: {
1514
 
                                                        /*featureClass: "A",*/
1515
 
                                                        featureCode: "PCLI",
1516
 
                                                        /*countryBias: "true",*/
1517
 
                                                        /*style: "full",*/
1518
 
                                                        lang: lang,
1519
 
                                                        maxRows: 12,
1520
 
                                                        name_startsWith: request.term
1521
 
                                                },
1522
 
                                                success: function( data ) {
1523
 
                                                        response( $.map( data.geonames, function( item ) {
1524
 
                                                                return {
1525
 
                                                                        label: item.name,
1526
 
                                                                        value: item.name
1527
 
                                                                };
1528
 
                                                        }));
1529
 
                                                }
1530
 
                                        });
1531
 
                                },
1532
 
                                minLength: 2
1533
 
                        });
1534
 
                return $elem;
1535
 
        };
1536
 
 
1537
 
        /**
1538
 
         * Render an IMPP (Instant Messaging) property.
1539
 
         * @return A jquery object to be injected in the DOM
1540
 
         */
1541
 
        Contact.prototype.renderIMProperty = function(property) {
1542
 
                if(!this.detailTemplates['impp']) {
1543
 
                        console.warn('No template for impp', this.detailTemplates);
1544
 
                        return;
1545
 
                }
1546
 
                var values = property ? {
1547
 
                        value: property.value,
1548
 
                        checksum: property.checksum
1549
 
                } : {value: '', checksum: 'new'};
1550
 
                return this.detailTemplates['impp'].octemplate(values);
1551
 
        };
1552
 
 
1553
 
        /**
1554
 
         * Set a thumbnail for the contact if a PHOTO property exists
1555
 
         */
1556
 
        Contact.prototype.setThumbnail = function($elem, refresh) {
1557
 
                if(!this.data.thumbnail && !refresh) {
1558
 
                        return;
1559
 
                }
1560
 
                if(!$elem) {
1561
 
                        $elem = this.getListItemElement().find('td.name');
1562
 
                }
1563
 
                if(!$elem.hasClass('thumbnail') && !refresh) {
1564
 
                        return;
1565
 
                }
1566
 
                if(this.data.thumbnail) {
1567
 
                        $elem.removeClass('thumbnail');
1568
 
                        $elem.css('background-image', 'url(data:image/png;base64,' + this.data.thumbnail + ')');
1569
 
                } else {
1570
 
                        $elem.addClass('thumbnail');
1571
 
                        $elem.removeAttr('style');
1572
 
                }
1573
 
        }
1574
 
 
1575
 
        /**
1576
 
         * Render the PHOTO property.
1577
 
         */
1578
 
        Contact.prototype.loadPhoto = function() {
1579
 
                var self = this;
1580
 
                var id = this.id || 'new',
1581
 
                        backend = this.metadata.backend,
1582
 
                        parent = this.metadata.parent,
1583
 
                        src;
1584
 
 
1585
 
                var $phototools = this.$fullelem.find('#phototools');
1586
 
                if(!this.$photowrapper) {
1587
 
                        this.$photowrapper = this.$fullelem.find('#photowrapper');
1588
 
                }
1589
 
 
1590
 
                var finishLoad = function(image) {
1591
 
                        console.log('finishLoad', self.getDisplayName(), image.width, image.height);
1592
 
                        $(image).addClass('contactphoto');
1593
 
                        self.$photowrapper.removeClass('loading wait');
1594
 
                        self.$photowrapper.css({width: image.width + 10, height: image.height + 10});
1595
 
                        $(image).insertAfter($phototools).fadeIn();
1596
 
                };
1597
 
 
1598
 
                this.$photowrapper.addClass('loading').addClass('wait');
1599
 
                if(this.getPreferredValue('PHOTO', null) === null) {
1600
 
                        $.when(this.storage.getDefaultPhoto())
1601
 
                                .then(function(image) {
1602
 
                                        $('img.contactphoto').detach();
1603
 
                                        finishLoad(image);
1604
 
                                });
1605
 
                } else {
1606
 
                        $.when(this.storage.getContactPhoto(backend, parent, id))
1607
 
                                .then(function(image) {
1608
 
                                        $('img.contactphoto').remove();
1609
 
                                        finishLoad(image);
1610
 
                                })
1611
 
                                .fail(function() {
1612
 
                                        console.log('Error getting photo, trying default image');
1613
 
                                        $('img.contactphoto').remove();
1614
 
                                        $.when(self.storage.getDefaultPhoto())
1615
 
                                                .then(function(image) {
1616
 
                                                        $('img.contactphoto').detach();
1617
 
                                                        finishLoad(image);
1618
 
                                                });
1619
 
                                });
1620
 
                }
1621
 
 
1622
 
                if(this.isEditable()) {
1623
 
                        this.$photowrapper.on('mouseenter', function(event) {
1624
 
                                if($(event.target).is('.favorite') || !self.data) {
1625
 
                                        return;
1626
 
                                }
1627
 
                                $phototools.slideDown(200);
1628
 
                        }).on('mouseleave', function() {
1629
 
                                $phototools.slideUp(200);
1630
 
                        });
1631
 
                        $phototools.hover( function () {
1632
 
                                $(this).removeClass('transparent');
1633
 
                        }, function () {
1634
 
                                $(this).addClass('transparent');
1635
 
                        });
1636
 
                        $phototools.find('li a').tipsy();
1637
 
 
1638
 
                        $phototools.find('.action').off('click');
1639
 
                        $phototools.find('.edit').on('click', function() {
1640
 
                                $(document).trigger('request.edit.contactphoto', self.metaData());
1641
 
                        });
1642
 
                        $phototools.find('.cloud').on('click', function() {
1643
 
                                $(document).trigger('request.select.contactphoto.fromcloud', self.metaData());
1644
 
                        });
1645
 
                        $phototools.find('.upload').on('click', function() {
1646
 
                                $(document).trigger('request.select.contactphoto.fromlocal', self.metaData());
1647
 
                        });
1648
 
                        if(this.getPreferredValue('PHOTO', false)) {
1649
 
                                $phototools.find('.delete').show();
1650
 
                                $phototools.find('.edit').show();
1651
 
                        } else {
1652
 
                                $phototools.find('.delete').hide();
1653
 
                                $phototools.find('.edit').hide();
1654
 
                        }
1655
 
                        $(document).bind('status.contact.photoupdated', function(e, data) {
1656
 
                                console.log('status.contact.photoupdated', data);
1657
 
                                if(!self.data.PHOTO) {
1658
 
                                        self.data.PHOTO = [];
1659
 
                                }
1660
 
                                if(data.thumbnail) {
1661
 
                                        self.data.thumbnail = data.thumbnail;
1662
 
                                        self.data.PHOTO[0] = {value:true};
1663
 
                                } else {
1664
 
                                        self.data.thumbnail = null;
1665
 
                                        self.data.PHOTO[0] = {value:false};
1666
 
                                }
1667
 
                                self.loadPhoto(true);
1668
 
                                self.setThumbnail(null, true);
1669
 
                        });
1670
 
                }
1671
 
        };
1672
 
 
1673
 
        /**
1674
 
         * Get the jquery element associated with this object
1675
 
         */
1676
 
        Contact.prototype.getListItemElement = function() {
1677
 
                if(!this.$listelem) {
1678
 
                        this.renderListItem();
1679
 
                }
1680
 
                return this.$listelem;
1681
 
        };
1682
 
 
1683
 
        /**
1684
 
         * Get the preferred value for a property.
1685
 
         * If a preferred value is not found the first one will be returned.
1686
 
         * @param string name The name of the property like EMAIL, TEL or ADR.
1687
 
         * @param def A default value to return if nothing is found.
1688
 
         */
1689
 
        Contact.prototype.getPreferredValue = function(name, def) {
1690
 
                var pref = def, found = false;
1691
 
                if(this.data && this.data[name]) {
1692
 
                        var props = this.data[name];
1693
 
                        //console.log('props', props);
1694
 
                        $.each(props, function( i, prop ) {
1695
 
                                //console.log('prop:', i, prop);
1696
 
                                if(i === 0) { // Choose first to start with
1697
 
                                        pref = prop.value;
1698
 
                                }
1699
 
                                for(var param in prop.parameters) {
1700
 
                                        if(param.toUpperCase() == 'PREF') {
1701
 
                                                found = true; //
1702
 
                                                break;
1703
 
                                        }
1704
 
                                }
1705
 
                                if(found) {
1706
 
                                        return false; // break out of loop
1707
 
                                }
1708
 
                        });
1709
 
                }
1710
 
                if(name === 'N' && pref.join('').trim() === '') {
1711
 
                        return def;
1712
 
                }
1713
 
                return pref;
1714
 
        };
1715
 
 
1716
 
        /**
1717
 
         * Returns true/false depending on the contact being in the
1718
 
         * specified group.
1719
 
         * @param String name The group name (not case-sensitive)
1720
 
         * @returns Boolean
1721
 
         */
1722
 
        Contact.prototype.inGroup = function(name) {
1723
 
                var categories = this.getPreferredValue('CATEGORIES', []);
1724
 
                var found = false;
1725
 
 
1726
 
                $.each(categories, function(idx, category) {
1727
 
                        if(name.toLowerCase() == $.trim(category).toLowerCase()) {
1728
 
                                found = true
1729
 
                                return false;
1730
 
                        }
1731
 
                });
1732
 
 
1733
 
                return found;
1734
 
        };
1735
 
 
1736
 
        /**
1737
 
         * Add this contact to a group
1738
 
         * @param String name The group name
1739
 
         */
1740
 
        Contact.prototype.addToGroup = function(name) {
1741
 
                console.log('addToGroup', name);
1742
 
                if(!this.data.CATEGORIES) {
1743
 
                        this.data.CATEGORIES = [{value:[name]}];
1744
 
                } else {
1745
 
                        if(this.inGroup(name)) {
1746
 
                                return;
1747
 
                        }
1748
 
                        this.data.CATEGORIES[0].value.push(name);
1749
 
                        if(this.$listelem) {
1750
 
                                this.$listelem.find('td.categories')
1751
 
                                        .text(this.getPreferredValue('CATEGORIES', []).clean('').join(' / '));
1752
 
                        }
1753
 
                }
1754
 
        };
1755
 
 
1756
 
        /**
1757
 
         * Remove this contact from a group
1758
 
         * @param String name The group name
1759
 
         */
1760
 
        Contact.prototype.removeFromGroup = function(name) {
1761
 
                name = name.trim();
1762
 
                if(!this.data.CATEGORIES) {
1763
 
                        console.warn('removeFromGroup. No groups found');
1764
 
                        return;
1765
 
                } else {
1766
 
                        var found = false;
1767
 
                        var categories = [];
1768
 
                        $.each(this.data.CATEGORIES[0].value, function(idx, category) {
1769
 
                                category = category.trim();
1770
 
                                if(name.toLowerCase() === category.toLowerCase()) {
1771
 
                                        found = true;
1772
 
                                } else {
1773
 
                                        categories.push(category);
1774
 
                                }
1775
 
                        });
1776
 
                        if(!found) {
1777
 
                                return;
1778
 
                        }
1779
 
                        this.data.CATEGORIES[0].value = categories;
1780
 
                        if(this.$listelem) {
1781
 
                                this.$listelem.find('td.categories')
1782
 
                                        .text(categories.join(' / '));
1783
 
                        }
1784
 
                }
1785
 
        };
1786
 
 
1787
 
        Contact.prototype.setCurrent = function(on) {
1788
 
                if(on) {
1789
 
                        this.$listelem.addClass('active');
1790
 
                } else {
1791
 
                        this.$listelem.removeClass('active');
1792
 
                }
1793
 
                $(document).trigger('status.contact.currentlistitem', {
1794
 
                        id: this.id,
1795
 
                        pos: Math.round(this.$listelem.position().top),
1796
 
                        height: Math.round(this.$listelem.height())
1797
 
                });
1798
 
        };
1799
 
 
1800
 
        Contact.prototype.setSelected = function(state) {
1801
 
                //console.log('Selecting', this.getId(), state);
1802
 
                var $elem = this.getListItemElement();
1803
 
                var $input = $elem.find('input:checkbox');
1804
 
                $input.prop('checked', state).trigger('change');
1805
 
        };
1806
 
 
1807
 
        Contact.prototype.next = function() {
1808
 
                // This used to work..?
1809
 
                //var $next = this.$listelem.next('tr:visible');
1810
 
                var $next = this.$listelem.nextAll('tr').filter(':visible').first();
1811
 
                if($next.length > 0) {
1812
 
                        this.$listelem.removeClass('active');
1813
 
                        $next.addClass('active');
1814
 
                        $(document).trigger('status.contact.currentlistitem', {
1815
 
                                id: String($next.data('id')),
1816
 
                                pos: Math.round($next.position().top),
1817
 
                                height: Math.round($next.height())
1818
 
                        });
1819
 
                }
1820
 
        };
1821
 
 
1822
 
        Contact.prototype.prev = function() {
1823
 
                //var $prev = this.$listelem.prev('tr:visible');
1824
 
                var $prev = this.$listelem.prevAll('tr').filter(':visible').first();
1825
 
                if($prev.length > 0) {
1826
 
                        this.$listelem.removeClass('active');
1827
 
                        $prev.addClass('active');
1828
 
                        $(document).trigger('status.contact.currentlistitem', {
1829
 
                                id: String($prev.data('id')),
1830
 
                                pos: Math.round($prev.position().top),
1831
 
                                height: Math.round($prev.height())
1832
 
                        });
1833
 
                }
1834
 
        };
1835
 
 
1836
 
        var ContactList = function(
1837
 
                        storage,
1838
 
                        addressBooks,
1839
 
                        contactlist,
1840
 
                        contactlistitemtemplate,
1841
 
                        contactdragitemtemplate,
1842
 
                        contactfulltemplate,
1843
 
                        contactdetailtemplates
1844
 
                ) {
1845
 
                //console.log('ContactList', contactlist, contactlistitemtemplate, contactfulltemplate, contactdetailtemplates);
1846
 
                var self = this;
1847
 
                this.length = 0;
1848
 
                this.contacts = {};
1849
 
                this.addressBooks = addressBooks;
1850
 
                this.deletionQueue = [];
1851
 
                this.storage = storage;
1852
 
                this.$contactList = contactlist;
1853
 
                this.$contactDragItemTemplate = contactdragitemtemplate;
1854
 
                this.$contactListItemTemplate = contactlistitemtemplate;
1855
 
                this.$contactFullTemplate = contactfulltemplate;
1856
 
                this.contactDetailTemplates = contactdetailtemplates;
1857
 
                this.$contactList.scrollTop(0);
1858
 
                //this.getAddressBooks();
1859
 
                $(document).bind('status.contact.added', function(e, data) {
1860
 
                        self.length += 1;
1861
 
                        self.contacts[String(data.id)] = data.contact;
1862
 
                        //self.insertContact(data.contact.renderListItem(true));
1863
 
                });
1864
 
                $(document).bind('status.contact.moved', function(e, data) {
1865
 
                        var contact = data.contact;
1866
 
                        var oldid = contact.getId();
1867
 
                        contact.close();
1868
 
                        contact.reload(data.data);
1869
 
                        self.contacts[contact.getId()] = contact;
1870
 
                        $(document).trigger('request.contact.open', {
1871
 
                                id: contact.getId()
1872
 
                        });
1873
 
                        console.log('status.contact.moved', data);
1874
 
                });
1875
 
                $(document).bind('request.contact.close', function(e, data) {
1876
 
                        self.currentContact = null;
1877
 
                });
1878
 
                $(document).bind('status.contact.updated', function(e, data) {
1879
 
                        if(['FN', 'EMAIL', 'TEL', 'ADR', 'CATEGORIES'].indexOf(data.property) !== -1) {
1880
 
                                data.contact.getListItemElement().remove();
1881
 
                                self.insertContact(data.contact.renderListItem(true));
1882
 
                        } else if(data.property === 'PHOTO') {
1883
 
                                $(document).trigger('status.contact.photoupdated', {
1884
 
                                        id: data.contact.getId()
1885
 
                                });
1886
 
                        }
1887
 
                });
1888
 
                $(document).bind('status.addressbook.removed', function(e, data) {
1889
 
                        var addressBook = data.addressbook;
1890
 
                        self.purgeFromAddressbook(addressBook);
1891
 
                        $(document).trigger('request.groups.reload');
1892
 
                        $(document).trigger('status.contacts.deleted', {
1893
 
                                numcontacts: self.length
1894
 
                        });
1895
 
                });
1896
 
                $(document).bind('status.addressbook.imported', function(e, data) {
1897
 
                        console.log('status.addressbook.imported', data);
1898
 
                        var addressBook = data.addressbook;
1899
 
                        self.purgeFromAddressbook(addressBook);
1900
 
                        $.when(self.loadContacts(addressBook.getBackend(), addressBook.getId(), true))
1901
 
                        .then(function() {
1902
 
                                self.setSortOrder();
1903
 
                                $(document).trigger('request.groups.reload');
1904
 
                        });
1905
 
                });
1906
 
                $(document).bind('status.addressbook.activated', function(e, data) {
1907
 
                        console.log('status.addressbook.activated', data);
1908
 
                        var addressBook = data.addressbook;
1909
 
                        if(!data.state) {
1910
 
                                self.purgeFromAddressbook(addressBook);
1911
 
                                $(document).trigger('status.contacts.deleted', {
1912
 
                                        numcontacts: self.length
1913
 
                                });
1914
 
                        } else {
1915
 
                                $.when(self.loadContacts(addressBook.getBackend(), addressBook.getId(), true))
1916
 
                                .then(function() {
1917
 
                                        self.setSortOrder();
1918
 
                                        $(document).trigger('request.groups.reload');
1919
 
                                });
1920
 
                        }
1921
 
                });
1922
 
        };
1923
 
 
1924
 
        /**
1925
 
         * Get the number of contacts in the list
1926
 
         * @return integer
1927
 
         */
1928
 
        ContactList.prototype.count = function() {
1929
 
                return Object.keys(this.contacts.contacts).length
1930
 
        };
1931
 
 
1932
 
        /**
1933
 
        * Remove contacts from the internal list and the DOM
1934
 
        *
1935
 
        * @param AddressBook addressBook
1936
 
        */
1937
 
        ContactList.prototype.purgeFromAddressbook = function(addressBook) {
1938
 
                var self = this;
1939
 
                $.each(this.contacts, function(idx, contact) {
1940
 
                        if(contact.getBackend() === addressBook.getBackend()
1941
 
                                && contact.getParent() === addressBook.getId()) {
1942
 
                                //console.log('Removing', contact);
1943
 
                                delete self.contacts[contact.getId()];
1944
 
                                //var c = self.contacts.splice(self.contacts.indexOf(contact.getId()), 1);
1945
 
                                //console.log('Removed', c);
1946
 
                                contact.detach();
1947
 
                                contact = null;
1948
 
                                self.length -= 1;
1949
 
                        }
1950
 
                });
1951
 
                $(document).trigger('status.contacts.count', {
1952
 
                        count: self.length
1953
 
                });
1954
 
        }
1955
 
 
1956
 
        /**
1957
 
        * Show/hide contacts belonging to an addressbook.
1958
 
        * @param int aid. Addressbook id.
1959
 
        * @param boolean show. Whether to show or hide.
1960
 
        * @param boolean hideothers. Used when showing shared addressbook as a group.
1961
 
        */
1962
 
        ContactList.prototype.showFromAddressbook = function(aid, show, hideothers) {
1963
 
                console.log('ContactList.showFromAddressbook', aid, show);
1964
 
                aid = String(aid);
1965
 
                for(var contact in this.contacts) {
1966
 
                        if(this.contacts[contact].getParent() === aid) {
1967
 
                                this.contacts[contact].getListItemElement().toggle(show);
1968
 
                        } else if(hideothers) {
1969
 
                                this.contacts[contact].getListItemElement().hide();
1970
 
                        }
1971
 
                }
1972
 
                this.setSortOrder();
1973
 
        };
1974
 
 
1975
 
        /**
1976
 
        * Show only uncategorized contacts.
1977
 
        * @param int aid. Addressbook id.
1978
 
        * @param boolean show. Whether to show or hide.
1979
 
        * @param boolean hideothers. Used when showing shared addressbook as a group.
1980
 
        */
1981
 
        ContactList.prototype.showUncategorized = function() {
1982
 
                console.log('ContactList.showUncategorized');
1983
 
                for(var contact in this.contacts) {
1984
 
                        if(this.contacts[contact].getPreferredValue('CATEGORIES', []).length === 0) {
1985
 
                                this.contacts[contact].getListItemElement().show();
1986
 
                        } else {
1987
 
                                this.contacts[contact].getListItemElement().hide();
1988
 
                        }
1989
 
                }
1990
 
                this.setSortOrder();
1991
 
        };
1992
 
 
1993
 
        /**
1994
 
        * Show/hide contacts belonging to shared addressbooks.
1995
 
        * @param boolean show. Whether to show or hide.
1996
 
        */
1997
 
        ContactList.prototype.showSharedAddressbooks = function(show) {
1998
 
                console.log('ContactList.showSharedAddressbooks', show);
1999
 
                for(var contact in this.contacts) {
2000
 
                        if(this.contacts[contact].metadata.owner !== OC.currentUser) {
2001
 
                                if(show) {
2002
 
                                        this.contacts[contact].getListItemElement().show();
2003
 
                                } else {
2004
 
                                        this.contacts[contact].getListItemElement().hide();
2005
 
                                }
2006
 
                        }
2007
 
                }
2008
 
                this.setSortOrder();
2009
 
        };
2010
 
 
2011
 
        /**
2012
 
        * Show contacts in list
2013
 
        * @param Array contacts. A list of contact ids.
2014
 
        */
2015
 
        ContactList.prototype.showContacts = function(contacts) {
2016
 
                console.log('showContacts', contacts);
2017
 
                var self = this;
2018
 
                if(contacts.length === 0) {
2019
 
                        // ~5 times faster
2020
 
                        $('tr:visible.contact').hide();
2021
 
                        return;
2022
 
                }
2023
 
                if(contacts === 'all') {
2024
 
                        // ~2 times faster
2025
 
                        var $elems = $('tr.contact:not(:visible)');
2026
 
                        $elems.show();
2027
 
                        $.each($elems, function(idx, elem) {
2028
 
                                try {
2029
 
                                        var id = $(elem).data('id');
2030
 
                                        self.contacts[id].setThumbnail();
2031
 
                                } catch(e) {
2032
 
                                        console.warn('Failed getting id from', $elem, e);
2033
 
                                }
2034
 
                        });
2035
 
                        this.setSortOrder();
2036
 
                        return;
2037
 
                }
2038
 
                console.time('show');
2039
 
                $('tr.contact').filter(':visible').hide();
2040
 
                $.each(contacts, function(idx, id) {
2041
 
                        var contact =  self.findById(id);
2042
 
                        if(contact === null) {
2043
 
                                return true; // continue
2044
 
                        }
2045
 
                        contact.getListItemElement().show();
2046
 
                        contact.setThumbnail();
2047
 
                });
2048
 
                console.timeEnd('show');
2049
 
 
2050
 
                // Amazingly this is slightly faster
2051
 
                //console.time('show');
2052
 
                for(var id in this.contacts) {
2053
 
                        var contact = this.findById(id);
2054
 
                        if(contact === null) {
2055
 
                                continue;
2056
 
                        }
2057
 
                        if(contacts.indexOf(String(id)) === -1) {
2058
 
                                contact.getListItemElement().hide();
2059
 
                        } else {
2060
 
                                contact.getListItemElement().show();
2061
 
                                contact.setThumbnail();
2062
 
                        }
2063
 
                }
2064
 
                //console.timeEnd('show');*/
2065
 
 
2066
 
                this.setSortOrder();
2067
 
        };
2068
 
 
2069
 
        ContactList.prototype.contactPos = function(id) {
2070
 
                var contact = this.findById(id);
2071
 
                if(!contact) {
2072
 
                        return 0;
2073
 
                }
2074
 
                
2075
 
                var $elem = contact.getListItemElement();
2076
 
                var pos = Math.round($elem.offset().top - (this.$contactList.offset().top + this.$contactList.scrollTop()));
2077
 
                console.log('contactPos', pos);
2078
 
                return pos;
2079
 
        };
2080
 
 
2081
 
        ContactList.prototype.hideContact = function(id) {
2082
 
                var contact = this.findById(id);
2083
 
                if(contact === null) {
2084
 
                        return false;
2085
 
                }
2086
 
                contact.hide();
2087
 
        };
2088
 
 
2089
 
        ContactList.prototype.closeContact = function(id) {
2090
 
                var contact = this.findById(id);
2091
 
                if(contact === null) {
2092
 
                        return false;
2093
 
                }
2094
 
                contact.close();
2095
 
        };
2096
 
 
2097
 
        /**
2098
 
        * Returns a Contact object by searching for its id
2099
 
        * @param id the id of the node
2100
 
        * @return the Contact object or undefined if not found.
2101
 
        * FIXME: If continious loading is reintroduced this will have
2102
 
        * to load the requested contact if not in list.
2103
 
        */
2104
 
        ContactList.prototype.findById = function(id) {
2105
 
                if(!id) {
2106
 
                        console.warn('ContactList.findById: id missing');
2107
 
                        return false;
2108
 
                }
2109
 
                id = String(id);
2110
 
                if(typeof this.contacts[id] === 'undefined') {
2111
 
                        console.warn('Could not find contact with id', id);
2112
 
                        //console.trace();
2113
 
                        return null;
2114
 
                }
2115
 
                return this.contacts[String(id)];
2116
 
        };
2117
 
 
2118
 
        /**
2119
 
         * TODO: Instead of having a timeout the contacts should be moved to a "Trash" backend/address book
2120
 
         * https://github.com/owncloud/contacts/issues/107
2121
 
         * @param object|object[] data An object or array of objects containing contact identification
2122
 
         * {
2123
 
         *      contactid: '1234',
2124
 
         *      addressbookid: '4321',
2125
 
         *      backend: 'local'
2126
 
         * }
2127
 
         */
2128
 
        ContactList.prototype.delayedDelete = function(data) {
2129
 
                console.log('delayedDelete, data:', typeof data, data);
2130
 
                var self = this;
2131
 
                if(!utils.isArray(data)) {
2132
 
                        this.currentContact = null;
2133
 
                        //self.$contactList.show();
2134
 
                        if(data instanceof Contact) {
2135
 
                                this.deletionQueue.push(data);
2136
 
                        } else {
2137
 
                                var contact = this.findById(data.contactId);
2138
 
                                if(contact instanceof Contact) {
2139
 
                                        this.deletionQueue.push(contact);
2140
 
                                }
2141
 
                        }
2142
 
                } else if(utils.isArray(data)) {
2143
 
                        $.each(data, function(idx, contact) {
2144
 
                                //console.log('delayedDelete, meta:', contact);
2145
 
                                if(contact instanceof Contact) {
2146
 
                                        self.deletionQueue.push(contact);
2147
 
                                }
2148
 
                        });
2149
 
                        //$.extend(this.deletionQueue, data);
2150
 
                } else {
2151
 
                        throw { name: 'WrongParameterType', message: 'ContactList.delayedDelete only accept objects or arrays.'};
2152
 
                }
2153
 
                //console.log('delayedDelete, deletionQueue', this.deletionQueue);
2154
 
                $.each(this.deletionQueue, function(idx, contact) {
2155
 
                        //console.log('delayedDelete', contact);
2156
 
                        contact && contact.detach().setChecked(false);
2157
 
                });
2158
 
                //console.log('deletionQueue', this.deletionQueue);
2159
 
                if(!window.onbeforeunload) {
2160
 
                        window.onbeforeunload = function(e) {
2161
 
                                e = e || window.event;
2162
 
                                var warn = t('contacts', 'Some contacts are marked for deletion, but not deleted yet. Please wait for them to be deleted.');
2163
 
                                if (e) {
2164
 
                                        e.returnValue = String(warn);
2165
 
                                }
2166
 
                                return warn;
2167
 
                        };
2168
 
                }
2169
 
                if(this.$contactList.find('tr:visible').length === 0) {
2170
 
                        $(document).trigger('status.visiblecontacts');
2171
 
                }
2172
 
                OC.notify({
2173
 
                        message:t('contacts','Click to undo deletion of {num} contacts', {num: self.deletionQueue.length}),
2174
 
                        //timeout:5,
2175
 
                        timeouthandler:function() {
2176
 
                                //console.log('timeout');
2177
 
                                self.deleteContacts();
2178
 
                        },
2179
 
                        clickhandler:function() {
2180
 
                                //console.log('clickhandler');
2181
 
                                //OC.notify({cancel:true});
2182
 
                                OC.notify({cancel:true, message:t('contacts', 'Cancelled deletion of {num} contacts', {num: self.deletionQueue.length})});
2183
 
                                $.each(self.deletionQueue, function(idx, contact) {
2184
 
                                        self.insertContact(contact.getListItemElement());
2185
 
                                });
2186
 
                                self.deletionQueue = [];
2187
 
                                window.onbeforeunload = null;
2188
 
                        }
2189
 
                });
2190
 
        };
2191
 
 
2192
 
        /**
2193
 
        * Delete contacts in the queue
2194
 
        * TODO: Batch delete contacts instead of sending multiple requests.
2195
 
        */
2196
 
        ContactList.prototype.deleteContacts = function() {
2197
 
                var self = this,
2198
 
                        contact,
2199
 
                        contactMap = {};
2200
 
                console.log('ContactList.deleteContacts, deletionQueue', this.deletionQueue);
2201
 
 
2202
 
                if(this.deletionQueue.length === 1) {
2203
 
                        contact = this.deletionQueue.shift()
2204
 
                        // Let contact remove itself.
2205
 
                        var id = contact.getId();
2206
 
                        contact.destroy(function(response) {
2207
 
                                console.log('deleteContact', response, self.length);
2208
 
                                if(!response.error) {
2209
 
                                        delete self.contacts[id];
2210
 
                                        $(document).trigger('status.contact.deleted', {
2211
 
                                                id: id
2212
 
                                        });
2213
 
                                        self.length -= 1;
2214
 
                                        if(self.length === 0) {
2215
 
                                                $(document).trigger('status.nomorecontacts');
2216
 
                                        }
2217
 
                                } else {
2218
 
                                        self.insertContact(contact.getListItemElement());
2219
 
                                        OC.notify({message:response.message});
2220
 
                                }
2221
 
                        });
2222
 
                } else {
2223
 
 
2224
 
                        // Make a map of backends, address books and contacts for easier processing.
2225
 
                        while(contact = this.deletionQueue.shift()) {
2226
 
                                if(!contactMap[contact.getBackend()]) {
2227
 
                                        contactMap[contact.getBackend()] = {};
2228
 
                                }
2229
 
                                if(!contactMap[contact.getBackend()][contact.getParent()]) {
2230
 
                                        contactMap[contact.getBackend()][contact.getParent()] = [];
2231
 
                                }
2232
 
                                contactMap[contact.getBackend()][contact.getParent()].push(contact.getId());
2233
 
                        }
2234
 
                        console.log('map', contactMap);
2235
 
 
2236
 
                        // Call each backend/addressBook to delete contacts.
2237
 
                        $.each(contactMap, function(backend, addressBooks) {
2238
 
                                console.log(backend, addressBooks);
2239
 
                                $.each(addressBooks, function(addressBook, contacts) {
2240
 
                                        console.log(addressBook, contacts);
2241
 
                                        var ab = self.addressBooks.find({backend:backend, id:addressBook});
2242
 
                                        ab.deleteContacts(contacts, function(response) {
2243
 
                                                console.log('response', response);
2244
 
                                                if(!response.error) {
2245
 
                                                        // We get a result set back, so process all of them.
2246
 
                                                        $.each(response.data.result, function(idx, result) {
2247
 
                                                                console.log('deleting', idx, result.id);
2248
 
                                                                if(result.status === 'success') {
2249
 
                                                                        delete self.contacts[result.id];
2250
 
                                                                        $(document).trigger('status.contact.deleted', {
2251
 
                                                                                id: result.id
2252
 
                                                                        });
2253
 
                                                                        self.length -= 1;
2254
 
                                                                        if(self.length === 0) {
2255
 
                                                                                $(document).trigger('status.nomorecontacts');
2256
 
                                                                        }
2257
 
                                                                } else {
2258
 
                                                                        // Error deleting, so re-insert element.
2259
 
                                                                        // TODO: Collect errors and display them when done.
2260
 
                                                                        self.insertContact(self.contacts[result.id].getListItemElement());
2261
 
                                                                }
2262
 
                                                        });
2263
 
                                                }
2264
 
                                        });
2265
 
                                });
2266
 
                        });
2267
 
                }
2268
 
 
2269
 
                window.onbeforeunload = null;
2270
 
                return;
2271
 
 
2272
 
        };
2273
 
 
2274
 
        /**
2275
 
         * Insert a rendered contact list item into the list
2276
 
         * @param contact jQuery object.
2277
 
         */
2278
 
        ContactList.prototype.insertContact = function($contact) {
2279
 
                $contact.find('td.name').draggable({
2280
 
                        distance: 10,
2281
 
                        revert: 'invalid',
2282
 
                        //containment: '#content',
2283
 
                        helper: function (e,ui) {
2284
 
                                return $(this).clone().appendTo('body').css('zIndex', 5).show();
2285
 
                        },
2286
 
                        opacity: 0.8,
2287
 
                        scope: 'contacts'
2288
 
                });
2289
 
                var name = $contact.find('.nametext').text().toLowerCase();
2290
 
                var added = false;
2291
 
                this.$contactList.find('tr').each(function() {
2292
 
                        if ($(this).find('.nametext').text().toLowerCase().localeCompare(name) > 0) {
2293
 
                                $(this).before($contact);
2294
 
                                added = true;
2295
 
                                return false;
2296
 
                        }
2297
 
                });
2298
 
                if(!added) {
2299
 
                        this.$contactList.append($contact);
2300
 
                }
2301
 
                $contact.show();
2302
 
                return $contact;
2303
 
        };
2304
 
 
2305
 
        /**
2306
 
        * Add contact
2307
 
        * @param object props
2308
 
        */
2309
 
        ContactList.prototype.addContact = function(props) {
2310
 
                // Get first address book
2311
 
                var addressBooks = this.addressBooks.selectByPermission(OC.PERMISSION_UPDATE);
2312
 
                var addressBook = addressBooks[0];
2313
 
                var metadata = {
2314
 
                        parent: addressBook.getId(),
2315
 
                        backend: addressBook.getBackend(),
2316
 
                        permissions: addressBook.getPermissions(),
2317
 
                        owner: addressBook.getOwner()
2318
 
                };
2319
 
                var contact = new Contact(
2320
 
                        this,
2321
 
                        null,
2322
 
                        metadata,
2323
 
                        null,
2324
 
                        this.$contactListItemTemplate,
2325
 
                        this.$contactDragItemTemplate,
2326
 
                        this.$contactFullTemplate,
2327
 
                        this.contactDetailTemplates
2328
 
                );
2329
 
                if(this.currentContact) {
2330
 
                        this.contacts[this.currentContact].close();
2331
 
                }
2332
 
                return contact.renderContact(props);
2333
 
        };
2334
 
 
2335
 
        /**
2336
 
         * Get contacts selected in list
2337
 
         *
2338
 
         * @returns array of contact objects.
2339
 
         */
2340
 
        ContactList.prototype.getSelectedContacts = function() {
2341
 
                var contacts = [];
2342
 
 
2343
 
                var self = this;
2344
 
                $.each(this.$contactList.find('tbody > tr > td > input:checkbox:visible:checked'), function(idx, checkbox) {
2345
 
                        var id = String($(checkbox).val());
2346
 
                        var contact = self.contacts[id];
2347
 
                        if(contact) {
2348
 
                                contacts.push(contact);
2349
 
                        }
2350
 
                });
2351
 
                return contacts;
2352
 
        };
2353
 
 
2354
 
        ContactList.prototype.setCurrent = function(id, deselect_other) {
2355
 
                console.log('ContactList.setCurrent', id);
2356
 
                if(!id) {
2357
 
                        return;
2358
 
                }
2359
 
                var self = this;
2360
 
                if(deselect_other === true) {
2361
 
                        $.each(this.contacts, function(contact) {
2362
 
                                self.contacts[contact].setCurrent(false);
2363
 
                        });
2364
 
                }
2365
 
                this.contacts[String(id)].setCurrent(true);
2366
 
        };
2367
 
 
2368
 
        /**
2369
 
         * (De)-select a contact
2370
 
         *
2371
 
         * @param string id
2372
 
         * @param bool state
2373
 
         * @param bool reverseOthers
2374
 
         */
2375
 
        ContactList.prototype.setSelected = function(id, state, reverseOthers) {
2376
 
                console.log('ContactList.setSelected', id);
2377
 
                if(!id) {
2378
 
                        return;
2379
 
                }
2380
 
                var self = this;
2381
 
                if(reverseOthers === true) {
2382
 
                        var $rows = this.$contactList.find('tr:visible.contact');
2383
 
                        $.each($rows, function(idx, row) {
2384
 
                                self.contacts[$(row).data('id')].setSelected(!state);
2385
 
                        });
2386
 
                }
2387
 
                this.contacts[String(id)].setSelected(state);
2388
 
        };
2389
 
 
2390
 
        /**
2391
 
         * Select a range of contacts by their id.
2392
 
         *
2393
 
         * @param string from
2394
 
         * @param string to
2395
 
         */
2396
 
        ContactList.prototype.selectRange = function(from, to) {
2397
 
                var self = this;
2398
 
                var $rows = this.$contactList.find('tr:visible.contact');
2399
 
                var index1 = $rows.index(this.contacts[String(from)].getListItemElement());
2400
 
                var index2 = $rows.index(this.contacts[String(to)].getListItemElement());
2401
 
                from = Math.min(index1, index2);
2402
 
                to = Math.max(index1, index2)+1;
2403
 
                $rows = $rows.slice(from, to);
2404
 
                $.each($rows, function(idx, row) {
2405
 
                        self.contacts[$(row).data('id')].setSelected(true);
2406
 
                });
2407
 
        };
2408
 
 
2409
 
        ContactList.prototype.setSortOrder = function(order) {
2410
 
                order = order || contacts_sortby;
2411
 
                //console.time('set name');
2412
 
                var $rows = this.$contactList.find('tr:visible.contact');
2413
 
                var self = this;
2414
 
                $.each($rows, function(idx, row) {
2415
 
                        self.contacts[$(row).data('id')].setDisplayMethod(order);
2416
 
                });
2417
 
                //console.timeEnd('set name');
2418
 
                if($rows.length > 1) {
2419
 
                        //console.time('sort');
2420
 
                        var rows = $rows.get();
2421
 
                        if(rows[0].firstElementChild && rows[0].firstElementChild.textContent) {
2422
 
                                rows.sort(function(a, b) {
2423
 
                                        // 10 (TEN!) times faster than using jQuery!
2424
 
                                        return a.firstElementChild.textContent.trim().toUpperCase()
2425
 
                                                .localeCompare(b.firstElementChild.textContent.trim().toUpperCase());
2426
 
                                });
2427
 
                        } else {
2428
 
                                // IE8 doesn't support firstElementChild or textContent
2429
 
                                rows.sort(function(a, b) {
2430
 
                                        return $(a).find('.nametext').text().toUpperCase()
2431
 
                                                .localeCompare($(b).find('td.name').text().toUpperCase());
2432
 
                                });
2433
 
                        }
2434
 
                        this.$contactList.prepend(rows);
2435
 
                        //console.timeEnd('sort');
2436
 
                }
2437
 
        };
2438
 
 
2439
 
        ContactList.prototype.insertContacts = function(contacts) {
2440
 
                var self = this, items = [];
2441
 
                $.each(contacts, function(c, contact) {
2442
 
                        var id = String(contact.metadata.id);
2443
 
                        self.contacts[id]
2444
 
                                = new Contact(
2445
 
                                        self,
2446
 
                                        id,
2447
 
                                        contact.metadata,
2448
 
                                        contact.data,
2449
 
                                        self.$contactListItemTemplate,
2450
 
                                        self.$contactDragItemTemplate,
2451
 
                                        self.$contactFullTemplate,
2452
 
                                        self.contactDetailTemplates
2453
 
                                );
2454
 
                        self.length +=1;
2455
 
                        var $item = self.contacts[id].renderListItem();
2456
 
                        if(!$item) {
2457
 
                                console.warn('Contact', contact, 'could not be rendered!');
2458
 
                                return true; // continue
2459
 
                        }
2460
 
                        items.push($item.get(0));
2461
 
                });
2462
 
                if(items.length > 0) {
2463
 
                        self.$contactList.append(items);
2464
 
                }
2465
 
                $(document).trigger('status.contacts.count', {
2466
 
                        count: self.length
2467
 
                });
2468
 
        }
2469
 
 
2470
 
        /**
2471
 
        * Load contacts
2472
 
        * @param string backend Name of the backend ('local', 'ldap' etc.)
2473
 
        * @param string addressBookId
2474
 
        */
2475
 
        ContactList.prototype.loadContacts = function(backend, addressBookId, isActive) {
2476
 
                if(!isActive) {
2477
 
                        return;
2478
 
                }
2479
 
                var self = this,
2480
 
                        contacts;
2481
 
 
2482
 
                return $.when(self.storage.getAddressBook(backend, addressBookId, false))
2483
 
                        .then(function(response) {
2484
 
                        console.log('ContactList.loadContacts - fetching', response);
2485
 
                        if(!response.error) {
2486
 
                                if(response.data) {
2487
 
                                        self.insertContacts(response.data.contacts);
2488
 
                                }
2489
 
                        } else {
2490
 
                                console.warn('ContactList.loadContacts - no data!!');
2491
 
                        }
2492
 
                })
2493
 
                .fail(function(response) {
2494
 
                        console.warn('Request Failed:', response.message);
2495
 
                        defer.reject({error: true, message: response.message});
2496
 
                });
2497
 
 
2498
 
        };
2499
 
 
2500
 
        OC.Contacts.ContactList = ContactList;
2501
 
 
2502
 
})(window, jQuery, OC);