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