var utils = {}; /** * utils.isArray * * Best guess if object is an array. */ utils.isArray = function(obj) { // do an instanceof check first if (obj instanceof Array) { return true; } // then check for obvious falses if (typeof obj !== 'object') { return false; } if (utils.type(obj) === 'array') { return true; } return false; }; utils.isInt = function(s) { return typeof s === 'number' && (s.toString().search(/^-?[0-9]+$/) === 0); }; utils.isUInt = function(s) { return typeof s === 'number' && (s.toString().search(/^[0-9]+$/) === 0); }; /** * utils.type * * Attempt to ascertain actual object type. */ utils.type = function(obj) { if (obj === null || typeof obj === 'undefined') { return String (obj); } return Object.prototype.toString.call(obj) .replace(/\[object ([a-zA-Z]+)\]/, '$1').toLowerCase(); }; utils.moveCursorToEnd = function(el) { if (typeof el.selectionStart === 'number') { el.selectionStart = el.selectionEnd = el.value.length; } else if (typeof el.createTextRange !== 'undefined') { el.focus(); var range = el.createTextRange(); range.collapse(false); range.select(); } }; if (typeof Object.create !== 'function') { Object.create = function (o) { function F() {} F.prototype = o; return new F(); }; } Array.prototype.clone = function() { return this.slice(0); }; Array.prototype.clean = function(deleteValue) { var arr = this.clone(); for (var i = 0; i < arr.length; i++) { if (arr[i] == deleteValue) { arr.splice(i, 1); i--; } } return arr; }; // Keep it DRY ;) var wrongKey = function(event) { return ((event.type === 'keydown' || event.type === 'keypress') && (event.keyCode !== 32 && event.keyCode !== 13)); }; /** * Simply notifier * Arguments: * @param message The text message to show. * @param timeout The timeout in seconds before the notification disappears. Default 10. * @param timeouthandler A function to run on timeout. * @param clickhandler A function to run on click. If a timeouthandler is given it will be cancelled on click. * @param data An object that will be passed as argument to the timeouthandler and clickhandler functions. * @param cancel If set cancel all ongoing timer events and hide the notification. */ OC.notify = function(params) { var self = this; if(!self.notifier) { self.notifier = $('#notification'); if(!self.notifier.length) { $('#content').prepend('
'); self.notifier = $('#notification'); } } if(params.cancel) { self.notifier.off('click'); for(var id in self.notifier.data()) { if($.isNumeric(id)) { clearTimeout(parseInt(id)); } } self.notifier.text('').fadeOut().removeData(); return; } self.notifier.text(params.message); self.notifier.fadeIn(); self.notifier.on('click', function() { $(this).fadeOut();}); var timer = setTimeout(function() { /*if(!self || !self.notifier) { var self = OC.Contacts; self.notifier = $('#notification'); }*/ self.notifier.fadeOut(); if(params.timeouthandler && $.isFunction(params.timeouthandler)) { params.timeouthandler(self.notifier.data(dataid)); self.notifier.off('click'); self.notifier.removeData(dataid); } }, params.timeout && $.isNumeric(params.timeout) ? parseInt(params.timeout)*1000 : 10000); var dataid = timer.toString(); if(params.data) { self.notifier.data(dataid, params.data); } if(params.clickhandler && $.isFunction(params.clickhandler)) { self.notifier.on('click', function() { /*if(!self || !self.notifier) { var self = OC.Contacts; self.notifier = $(this); }*/ clearTimeout(timer); self.notifier.off('click'); params.clickhandler(self.notifier.data(dataid)); self.notifier.removeData(dataid); }); } }; var GroupList = function(groupList, listItemTmpl) { this.$groupList = groupList; var self = this; var numtypes = ['category', 'fav', 'all']; this.$groupList.on('click', 'h3', function(event) { $('.tipsy').remove(); if(wrongKey(event)) { return; } console.log($(event.target)); if($(event.target).is('.action.delete')) { var id = $(event.target).parents('h3').first().data('id'); self.deleteGroup(id, function(response) { if(response.status !== 'success') { OC.notify({message:response.data.message}); } }); } else { self.selectGroup({element:$(this)}); } }); this.$groupListItemTemplate = listItemTmpl; this.categories = []; }; GroupList.prototype.nameById = function(id) { return this.findById(id).contents().filter(function(){ return(this.nodeType == 3); }).text().trim(); }; GroupList.prototype.findById = function(id) { return this.$groupList.find('h3[data-id="' + id + '"]'); }; GroupList.prototype.isFavorite = function(contactid) { return this.inGroup(contactid, 'fav'); }; GroupList.prototype.selectGroup = function(params) { var id, $elem; if(typeof params.id !== 'undefined') { id = params.id; $elem = this.findById(id); } else if(typeof params.element !== 'undefined') { id = params.element.data('id'); $elem = params.element; } if(!$elem) { self.selectGroup('all'); return; } console.log('selectGroup', id, $elem); this.$groupList.find('h3').removeClass('active'); $elem.addClass('active'); if(id === 'new') { return; } this.lastgroup = id; $(document).trigger('status.group.selected', { id: this.lastgroup, type: $elem.data('type'), contacts: $elem.data('contacts') }); }; GroupList.prototype.inGroup = function(contactid, groupid) { var $groupelem = this.findById(groupid); var contacts = $groupelem.data('contacts'); return (contacts.indexOf(contactid) !== -1); }; GroupList.prototype.setAsFavorite = function(contactid, state, cb) { contactid = parseInt(contactid); var $groupelem = this.findById('fav'); var contacts = $groupelem.data('contacts'); if(state) { OCCategories.addToFavorites(contactid, 'contact', function(jsondata) { if(jsondata.status === 'success') { contacts.push(contactid); $groupelem.data('contacts', contacts); $groupelem.find('.numcontacts').text(contacts.length); if(contacts.length > 0 && $groupelem.is(':hidden')) { $groupelem.show(); } } if(typeof cb === 'function') { cb(jsondata); } else if(jsondata.status !== 'success') { OC.notify({message:t('contacts', jsondata.data.message)}); } }); } else { OCCategories.removeFromFavorites(contactid, 'contact', function(jsondata) { if(jsondata.status === 'success') { contacts.splice(contacts.indexOf(contactid), 1); //console.log('contacts', contacts, contacts.indexOf(id), contacts.indexOf(String(id))); $groupelem.data('contacts', contacts); $groupelem.find('.numcontacts').text(contacts.length); if(contacts.length === 0 && $groupelem.is(':visible')) { $groupelem.hide(); } } if(typeof cb === 'function') { cb(jsondata); } else if(jsondata.status !== 'success') { OC.notify({message:t('contacts', jsondata.data.message)}); } }); } }; /** * Add one or more contact ids to a group * @param contactid An integer id or an array of integer ids. * @param groupid The integer id of the group * @param cb Optional call-back function */ GroupList.prototype.addTo = function(contactid, groupid, cb) { console.log('GroupList.addTo', contactid, groupid); var $groupelem = this.findById(groupid); var contacts = $groupelem.data('contacts'); var ids = []; if(!contacts) { console.log('Contacts not found, adding list!!!'); contacts = []; } var self = this; var doPost = false; if(typeof contactid === 'number') { if(contacts.indexOf(contactid) === -1) { ids.push(contactid); doPost = true; } else { if(typeof cb == 'function') { cb({status:'error', message:t('contacts', 'Contact is already in this group.')}); } } } else if(utils.isArray(contactid)) { $.each(contactid, function(i, id) { if(contacts.indexOf(id) === -1) { ids.push(id); } }); if(ids.length > 0) { doPost = true; } else { if(typeof cb == 'function') { cb({status:'error', message:t('contacts', 'Contacts are already in this group.')}); } } } if(doPost) { $.post(OC.filePath('contacts', 'ajax', 'categories/addto.php'), {contactids: ids, categoryid: groupid},function(jsondata) { if(!jsondata) { if(typeof cb === 'function') { cb({status:'error', message:'Network or server error. Please inform administrator.'}); } return; } if(jsondata.status === 'success') { contacts = contacts.concat(ids).sort(); $groupelem.data('contacts', contacts); var $numelem = $groupelem.find('.numcontacts'); $numelem.text(contacts.length).switchClass('', 'active', 200); setTimeout(function() { $numelem.switchClass('active', '', 1000); }, 2000); if(typeof cb === 'function') { cb({status:'success', ids:ids}); } else { $(document).trigger('status.group.contactadded', { contactid: contactid, groupid: groupid, groupname: self.nameById(groupid) }); } } else { if(typeof cb == 'function') { cb({status:'error', message:jsondata.data.message}); } } }); } }; GroupList.prototype.removeFrom = function(contactid, groupid, cb) { console.log('GroupList.removeFrom', contactid, groupid); var $groupelem = this.findById(groupid); var contacts = $groupelem.data('contacts'); var ids = []; // If it's the 'all' group simply decrement the number if(groupid === 'all') { var $numelem = $groupelem.find('.numcontacts'); $numelem.text(parseInt($numelem.text()-1)).switchClass('', 'active', 200); setTimeout(function() { $numelem.switchClass('active', '', 1000); }, 2000); if(typeof cb === 'function') { cb({status:'success', ids:[id]}); } } // If the contact is in the category remove it from internal list. if(!contacts) { if(typeof cb === 'function') { cb({status:'error', message:t('contacts', 'Couldn\'t get contact list.')}); } return; } var doPost = false; if(typeof contactid === 'number') { if(contacts.indexOf(contactid) !== -1) { ids.push(contactid); doPost = true; } else { if(typeof cb == 'function') { cb({status:'error', message:t('contacts', 'Contact is not in this group.')}); } } } else if(utils.isArray(contactid)) { $.each(contactid, function(i, id) { if(contacts.indexOf(id) !== -1) { ids.push(id); } }); if(ids.length > 0) { doPost = true; } else { console.log(contactid, 'not in', contacts); if(typeof cb == 'function') { cb({status:'error', message:t('contacts', 'Contacts are not in this group.')}); } } } if(doPost) { $.post(OC.filePath('contacts', 'ajax', 'categories/removefrom.php'), {contactids: ids, categoryid: groupid},function(jsondata) { if(!jsondata) { if(typeof cb === 'function') { cb({status:'error', message:'Network or server error. Please inform administrator.'}); } return; } if(jsondata.status === 'success') { $.each(ids, function(idx, id) { contacts.splice(contacts.indexOf(id), 1); }); //console.log('contacts', contacts, contacts.indexOf(id), contacts.indexOf(String(id))); $groupelem.data('contacts', contacts); var $numelem = $groupelem.find('.numcontacts'); $numelem.text(contacts.length).switchClass('', 'active', 200); setTimeout(function() { $numelem.switchClass('active', '', 1000); }, 2000); if(typeof cb === 'function') { cb({status:'success', ids:ids}); } } else { if(typeof cb == 'function') { cb({status:'error', message:jsondata.data.message}); } } }); } }; GroupList.prototype.removeFromAll = function(contactid, alsospecial) { var self = this; var selector = alsospecial ? 'h3' : 'h3[data-type="category"]'; $.each(this.$groupList.find(selector), function(i, group) { self.removeFrom(contactid, $(this).data('id')); }); }; GroupList.prototype.categoriesChanged = function(newcategories) { console.log('GroupList.categoriesChanged, I should do something'); }; GroupList.prototype.contactDropped = function(event, ui) { var dragitem = ui.draggable, droptarget = $(this); console.log('dropped', dragitem); if(dragitem.is('tr')) { console.log('tr dropped', dragitem.data('id'), 'on', $(this).data('id')); if($(this).data('type') === 'fav') { $(this).data('obj').setAsFavorite(dragitem.data('id'), true); } else { $(this).data('obj').addTo(dragitem.data('id'), $(this).data('id')); } } }; GroupList.prototype.deleteGroup = function(groupid, cb) { var $elem = this.findById(groupid); var $newelem = $elem.prev('h3'); var name = this.nameById(groupid); var contacts = $elem.data('contacts'); var self = this; console.log('delete group', groupid, contacts); $.post(OC.filePath('contacts', 'ajax', 'categories/delete.php'), {categories: name}, function(jsondata) { if (jsondata && jsondata.status == 'success') { $(document).trigger('status.group.groupremoved', { groupid: groupid, newgroupid: parseInt($newelem.data('id')), groupname: self.nameById(groupid), contacts: contacts }); $elem.remove(); self.selectGroup({element:$newelem}); } else { // } if(typeof cb === 'function') { cb(jsondata); } }); }; GroupList.prototype.editGroup = function(id) { var self = this; if(this.$editelem) { console.log('Already editing, returning'); return; } // NOTE: Currently this only works for adding, not renaming var saveChanges = function($elem, $input) { console.log('saveChanges', $input.val()); var name = $input.val().trim(); if(name.length === 0) { return false; } $input.prop('disabled', true); $elem.data('name', ''); self.addGroup({name:name, element:$elem}, function(response) { if(response.status === 'success') { $elem.prepend(name).removeClass('editing').attr('data-id', response.id); $input.next('.checked').remove(); $input.remove(); self.$editelem = null; } else { $input.prop('disabled', false); OC.notify({message:response.message}); } }); }; if(typeof id === 'undefined') { // Add new group var tmpl = this.$groupListItemTemplate; self.$editelem = (tmpl).octemplate({ id: 'new', type: 'category', num: 0, name: '' }); var $input = $(''); self.$editelem.prepend($input).addClass('editing'); self.$editelem.data('contacts', []); this.$groupList.find('h3.group[data-type="category"]').first().before(self.$editelem); this.selectGroup({element:self.$editelem}); $input.on('input', function(event) { if($(this).val().length > 0) { $(this).next('.checked').removeClass('disabled'); } else { $(this).next('.checked').addClass('disabled'); } }); $input.on('keyup', function(event) { var keyCode = Math.max(event.keyCode, event.which); if(keyCode === 13) { saveChanges(self.$editelem, $(this)); } else if(keyCode === 27) { self.$editelem.remove(); self.$editelem = null; } }); $input.next('.checked').on('click keydown', function(event) { console.log('clicked', event); if(wrongKey(event)) { return; } saveChanges(self.$editelem, $input); }); $input.focus(); } else if(utils.isUInt(id)) { var $elem = this.findById(id); var $text = $elem.contents().filter(function(){ return(this.nodeType == 3); }); var name = $text.text(); console.log('Group name', $text, name); $text.remove(); var $input = $(' 0) { $(this).before($elem); added = true; return false; } }); if(!added) { $elem.insertAfter(self.$groupList.find('h3.group[data-type="category"]').last()); } self.selectGroup({element:$elem}); $elem.tipsy({trigger:'manual', gravity:'w', fallback: t('contacts', 'You can drag groups to\narrange them as you like.')}); $elem.tipsy('show'); if(typeof cb === 'function') { cb({status:'success', id:parseInt(jsondata.data.id), name:name}); } } else { if(typeof cb === 'function') { cb({status:'error', message:jsondata.data.message}); } } }); }; GroupList.prototype.loadGroups = function(numcontacts, cb) { var self = this; var acceptdrop = 'tr.contact'; var $groupList = this.$groupList; var tmpl = this.$groupListItemTemplate; tmpl.octemplate({id: 'all', type: 'all', num: numcontacts, name: t('contacts', 'All')}).appendTo($groupList); $.getJSON(OC.filePath('contacts', 'ajax', 'categories/list.php'), {}, function(jsondata) { if (jsondata && jsondata.status == 'success') { self.lastgroup = jsondata.data.lastgroup; self.sortorder = jsondata.data.sortorder.length > 0 ? $.map(jsondata.data.sortorder.split(','), function(c) {return parseInt(c);}) : []; console.log('sortorder', self.sortorder); // Favorites var contacts = $.map(jsondata.data.favorites, function(c) {return parseInt(c);}); var $elem = tmpl.octemplate({ id: 'fav', type: 'fav', num: contacts.length, name: t('contacts', 'Favorites') }).appendTo($groupList); $elem.data('obj', self); $elem.data('contacts', contacts).find('.numcontacts').before(''); $elem.droppable({ drop: self.contactDropped, activeClass: 'ui-state-active', hoverClass: 'ui-state-hover', accept: acceptdrop }); if(contacts.length === 0) { $elem.hide(); } console.log('favorites', $elem.data('contacts')); // Normal groups $.each(jsondata.data.categories, function(c, category) { var contacts = $.map(category.contacts, function(c) {return parseInt(c);}); var $elem = (tmpl).octemplate({ id: category.id, type: 'category', num: contacts.length, name: category.name }); self.categories.push({id: category.id, name: category.name}); $elem.data('obj', self); $elem.data('contacts', contacts); $elem.data('name', category.name); $elem.data('id', category.id); $elem.droppable({ drop: self.contactDropped, activeClass: 'ui-state-hover', accept: acceptdrop }); $elem.appendTo($groupList); }); var elems = $groupList.find('h3[data-type="category"]').get(); elems.sort(function(a, b) { return self.sortorder.indexOf(parseInt($(a).data('id'))) > self.sortorder.indexOf(parseInt($(b).data('id'))); }); $.each(elems, function(index, elem) { $groupList.append(elem); }); // Shared addressbook $.each(jsondata.data.shared, function(c, shared) { var sharedindicator = '').append(this.eq(0).clone()).html(); }; /** * Object Template * Inspired by micro templating done by e.g. underscore.js */ var Template = { init: function(options, elem) { // Mix in the passed in options with the default options this.options = $.extend({},this.options,options); // Save the element reference, both as a jQuery // reference and a normal reference this.elem = elem; this.$elem = $(elem); var _html = this._build(this.options); //console.log('html', this.$elem.html()); return $(_html); }, // From stackoverflow.com/questions/1408289/best-way-to-do-variable-interpolation-in-javascript _build: function(o){ var data = this.$elem.attr('type') === 'text/template' ? this.$elem.html() : this.$elem.outerHTML(); return data.replace(/{([^{}]*)}/g, function (a, b) { var r = o[b]; return typeof r === 'string' || typeof r === 'number' ? r : a; } ); }, options: { }, }; $.fn.octemplate = function(options) { if ( this.length ) { var _template = Object.create(Template); return _template.init(options, this); } }; })( jQuery ); $(document).ready(function() { OC.Contacts.init(id); });