/* CardDavMATE - CardDav Web Client Copyright (C) 2011-2012 Jan Mate This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ function loadAddressbook(inputCollection, forceLoad) { if(forceLoad!=true && globalWindowFocus==false) return false; netLoadCollection(inputCollection, forceLoad, false, null); } // AddressbookList Class function AddressbookList() { this.contacts=new Array(); this.contact_groups=new Array(); this.contact_categories=new Object(); this.contact_companies=new Object(); this.contactLoaded=null; this.contactGroupLoaded=null; this.reset=function() { this.contacts.splice(0,this.contacts.length); this.contact_groups.splice(0,this.contact_groups.length); // these are not removed from the interface (it's OK) this.contact_categories=new Object(); this.contact_companies=new Object(); this.contactLoaded=null; this.contactGroupLoaded=null; } this.getNewUID=function() { var newUID=null; var found=true; while(found==true) { newUID=generateUID(); found=false; for(i=0;i"group.", [2]->"name", [3]->";param;param", [4]->"value" var parsed=vcard_element[0].match(vCard.pre['contentline_parse']); // parsed_value = [0]->Family, [1]->Given, [2]->Middle, [3]->Prefix, [4]->Suffix var parsed_value=vcardSplitValue(parsed[4],';'); var sort_value=''; for(var i=0;i"group.", [2]->"name", [3]->";param;param", [4]->"value" var parsed=vcard_element2[0].match(vCard.pre['contentline_parse']); var sort_value=parsed[4]; } } return sort_value; } else return false; } this.isContactGroup=function(vcard_clean) { if(('\r\n'+vcard_clean).match(vCard.pre['X-ADDRESSBOOKSERVER-KIND'])!=null) return true; else return false; } this.getRemoveMeFromContactGroups=function(inputUid, inputContactGroupsUidArr) { for(var i=0;i"group.", [2]->"name", [3]->";param;param", [4]->"value" parsed=vcard_element[0].match(vCard.pre['contentline_parse']); for(var j=0;j"group.", [2]->"name", [3]->";param;param", [4]->"value" parsed=vcard_element[0].match(vCard.pre['contentline_parse']); var changedContactGroups=new Array(); for(var j=0;j'); // the onclick event is disabled until the last drag&drop operation is completed (the class*="r_" is a little bit weak but works) newElement=newElement.attr('onclick','if($(this).parents(\':eq(2)\').find(\'[class*="r_"]\').length>0) return false; else globalResourceList.loadAddressbookByUID(this.getAttribute(\'data-id\'));'); newElement=newElement.attr('data-id',inputContact.uid); newElement.text(vcardUnescapeValue(inputContact.displayvalue)); newElement.css('display',''); newElement=newElement.parent().html(); // insert the contact group only if it not exists in interface (these are not removed from the interface when the users switch to different collection) if($('#ResourceList').find('[data-id="'+jqueryEscapeSelector(inputContact.uid.replace(RegExp('/[^/]*$',''),'/'))+'"]').parent().find('.contact_group').find('[data-id="'+jqueryEscapeSelector(inputContact.uid)+'"]').length==0) $('#ResourceList').find('[data-id="'+jqueryEscapeSelector(inputContact.uid.replace(RegExp('/[^/]*$',''),'/'))+'"]').parent().find('.contact_group').children().eq(insertIndex).after(newElement); // make the area droppable if the collection is not read-only if(globalResourceList.getCollectionPrivByUID(inputContact.uid.replace(RegExp('[^/]*$',''),''))==false) $('#ResourceList').find('[data-id="'+jqueryEscapeSelector(inputContact.uid.replace(RegExp('[^/]*$',''),''))+'"]').parent().find('.contact_group').children().eq(insertIndex+1).droppable({ accept: '.ablist_item', tolerance: 'pointer', hoverClass: 'group_dropped_to', drop: function(event, ui){ // animate the clone of the dropped (draggable) element var tmp=ui.helper.clone(); tmp.appendTo('body') .animate({opacity: 0, color: 'transparent', height: 0, width: 0, fontSize: 0, lineHeight: 0, paddingLeft: 0, paddingRight: 0},750,function(){tmp.remove()}); // disallow to drag the original dropped element until the processing is finished ui.draggable.draggable('option', 'disabled', true); // animate the original dropped element ui.draggable.animate({opacity: 0.3}, 750); // disallow to drop any new element until the processing is finished $(this).droppable('option', 'disabled', true); // show the loader icon $(this).addClass('r_operate'); var tmp2=globalAddressbookList.getContactByUID(ui.draggable.attr('data-id')); tmp2.addToContactGroupUID=$(this).attr('data-id'); tmp2.uiObjects={contact: ui.draggable, resource: $(this)}; lockAndPerformToCollection(tmp2, $('#AddContact').attr('data-filter-url'), 'ADD_TO_GROUP'); } }); // load the contact group if it was selected if(makeActive!=null) { $('#ResourceList').find('.resource_item').find('.resource_selected').removeClass('resource_selected'); $('#ResourceList').find('[data-id='+jqueryEscapeSelector(makeActive.replace(RegExp('[^/]*$',''),''))+']').addClass('resource_selected'); $('#ResourceList').find('[data-id='+jqueryEscapeSelector(makeActive)+']').addClass('resource_selected'); this.applyABFilter(makeActive, false); } } // hide/show contacts in the interface according to contactGroupUid or search filter in the interface (contactGroupUid==false) this.applyABFilter=function(contactGroupUid, inputForceLoadNext) { var vcard=null; if(contactGroupUid===false) { if(this.contactGroupLoaded!=null) vcard=this.contactGroupLoaded.vcard; } else { this.contactGroupLoaded=null; // remember the loaded contact group if(contactGroupUid!='') for(var i=0;i"group.", [2]->"name", [3]->";param;param", [4]->"value" parsed=vcard_element[0].match(vCard.pre['contentline_parse']); vcardUIDList[vcardUIDList.length]=parsed[4].replace('urn:uuid:',''); // remove the processed parameter vcard=vcard.replace(vcard_element[0],'\r\n'); } // update the contacts' "show" attribute for(var i=0;i"group.", [2]->"name", [3]->";param;param", [4]->"value" parsed=vcard_element[0].match(vCard.pre['contentline_parse']); if(vcardUIDList[i]==parsed[4] && $('#ABList div[data-id="'+jqueryEscapeSelector(this.contacts[j].uid)+'"]').hasClass('search_hide')==false) this.contacts[j].show=true; } } } var lastActive=null; var prevHeader=null; var lastContactForHeader=this.contacts.length-1; // the show attribute is now set, we can make changes in the interface for(var i=this.contacts.length-1;i>=0;i--) { if(this.contacts[i].headerOnly==undefined) { // find the previous header index for(var j=i-1;j>=0;j--) if(this.contacts[j].headerOnly==true) { prevHeader=j; break; } switch(this.contacts[i].show) { case false: $('#ABList').children().eq(i+1).css('display','none'); if($('#ABList').children().eq(i+1).hasClass('ablist_item_selected')) lastActive=i; var hideHeader=true; for(j=prevHeader+1;j<=lastContactForHeader;j++) if(this.contacts[j].show==true) hideHeader=false; if(hideHeader) $('#ABList').children().eq(prevHeader+1).css('display','none'); break; case true: // set the contact header to visible $('#ABList').children().eq(prevHeader+1).css('display',''); // set the contact to visible $('#ABList').children().eq(i+1).css('display',''); break; } } else lastContactForHeader=i-1; } // the previously loaded contact is hidden or not exists we need to select a new one if(inputForceLoadNext==true || $('[id=vcard_editor]').attr('data-editor-state')!='edit' && (lastActive!=null || $('#ABList').find('.ablist_item_selected').length==0)) { var nextCandidateToLoad=null; // get the nearest candidate to load // if we can go forward for(j=(previousActiveIndex == null ? 0 : previousActiveIndex)+1;j=0;j--) if(this.contacts[j].headerOnly!=true && this.contacts[j].show==true) { nextCandidateToLoad=this.contacts[j]; break; } } // make the contact active $('#ABList').find('.ablist_item').removeClass('ablist_item_selected'); if(nextCandidateToLoad!=null) this.loadContactByUID(nextCandidateToLoad.uid); else { this.contactLoaded=null; $('#ABContact').html(''); } } } this.removeContactGroup=function(inputUid, loadNext) { for(var i=this.contact_groups.length-1;i>=0;i--) if(this.contact_groups[i].uid==inputUid) { var uidRemoved=this.contact_groups[i].uid; var item=$('#ResourceList').find('[data-id^="'+jqueryEscapeSelector(this.contact_groups[i].uid)+'"]'); // remove the item item.remove(); this.contact_groups.splice(i,1); if(loadNext && this.contactGroupLoaded!=null && this.contactGroupLoaded.uid==inputUid) { this.contactGroupLoaded=null; // set the whole collection as active var tmp=uidRemoved.match(RegExp('(^.*/)'),''); globalResourceList.loadAddressbookByUID(tmp[1]); } break; } } this.getABCategories=function() { var categoriesArr=[]; for(var category in this.contact_categories) categoriesArr.push(category); return categoriesArr.sort( function(x,y){ var a = x.toLowerCase(); var b = y.toLowerCase(); if (a > b) return 1; if (a < b) return -1; return 0; }); } this.getABCompanies=function() { var companiesArr=[]; for(var company in this.contact_companies) companiesArr.push(company); return companiesArr.sort( function(x,y){ var a = x.toLowerCase(); var b = y.toLowerCase(); if (a > b) return 1; if (a < b) return -1; return 0; }); } this.getABCompanyDepartments=function(inputCompany) { if(this.contact_companies[inputCompany]!=undefined) return this.contact_companies[inputCompany].departments; else return []; } // Contact list is not sorted, instead "insert sort" is performed this.insertContact=function(inputContact, forceReload) { // Apple "group" vCards if(this.isContactGroup(inputContact.vcard)) return this.insertContactGroup(inputContact); if((inputContact.sortkey=this.getSortKey(inputContact.vcard,globalCollectionSort))===false || (inputContact.displayvalue=this.getSortKey(inputContact.vcard,globalCollectionDisplay))===false) return false; //invalid vcard // CATEGORIES suggestion var categoriesArr=(inputContact.categories=='' ? [] : vcardSplitValue(inputContact.categories,',')); var allCategoriesArr=this.getABCategories(); // The search funcionality uses this ASCII value (you can add additional data here) // ORG attribute var tmp=inputContact.vcard; var orgArr=[]; var depArr=[]; while((vcard_element=tmp.match(vCard.pre['contentline_ORG']))!=null) { // parsed (contentline_parse) = [1]->"group.", [2]->"name", [3]->";param;param", [4]->"value" parsed=vcard_element[0].match(vCard.pre['contentline_parse']); tmp_in=vcardUnescapeValue(vcardSplitValue(parsed[4],';')[0]); if(tmp_in!='') orgArr[orgArr.length]=tmp_in; tmp_in=vcardUnescapeValue(vcardSplitValue(parsed[4],';')[1]); if(tmp_in!='') depArr[depArr.length]=tmp_in; // remove the processed parameter tmp=tmp.replace(vcard_element[0],'\r\n'); } var allOrgArr=this.getABCompanies(); // EMAIL attribute var emailArr=[]; while((vcard_element=tmp.match(vCard.pre['contentline_EMAIL']))!=null) { // parsed (contentline_parse) = [1]->"group.", [2]->"name", [3]->";param;param", [4]->"value" parsed=vcard_element[0].match(vCard.pre['contentline_parse']); emailArr[emailArr.length]=parsed[4]; // remove the processed parameter tmp=tmp.replace(vcard_element[0],'\r\n'); } // Search data (displayvalue+categories+orgs+emails) inputContact.searchvalue=(inputContact.displayvalue+' '+categoriesArr.join(' ')+' '+orgArr.join(' ')+' '+emailArr.join(' ')).multiReplace(globalSearchTransformAlphabet); // CATEGORIES suggestion for(var i=0;i"group.", [2]->"name", [3]->";param;param", [4]->"value" parsed=vcard_element[0].match(vCard.pre['contentline_parse']); if(vcardUnescapeValue(parsed[4]).match(RegExp('^company$','i'))) inputContact.isCompany=true; } makeActive=null; // do not insert entry with duplicate UID for(var i=0;i90 && unicodeValue<97) || (unicodeValue>122 && unicodeValue<127)) headerChar='#'; else headerChar=inputContact.sortkey.charAt(0).toUpperCase(); } else headerChar='#'; // create the header var headerObject={headerOnly: true, displayvalue: headerChar}; // find the index where to insert the new contact var insertIndex=this.contacts.length; for(var i=0;i0 && this.contacts[i-1].headerOnly==true && this.contacts[i-1].displayvalue!=headerObject.displayvalue) --insertIndex; break; } // check for header existence var headerMiss=1; for(var i=0;i'); newElement=newElement.text(headerObject.displayvalue); newElement=newElement.parent().html(); $('#ABList').children().eq(insertIndex).after(newElement); } // insert the contact to interface var newElement=$('#ABListTemplate').find('.ablist_item').clone().wrap('
'); newElement.attr('onclick','if($(this).hasClass(\'ablist_item_selected\')) return false; else globalAddressbookList.loadContactByUID(this.getAttribute(\'data-id\'));'); newElement.attr('data-id',inputContact.uid); newElement.find('.ablist_item_data').text(vcardUnescapeValue(inputContact.displayvalue)); newElement.find('div[data-type="searchable_data"]').text(vcardUnescapeValue(inputContact.searchvalue)); // set the company icon if(inputContact.isCompany==true) newElement.addClass('company'); newElement=newElement.parent().html(); $('#ABList').children().eq(insertIndex+headerMiss).after(newElement); // if the collection is not read-only the element is draggable if(globalResourceList.getCollectionPrivByUID(inputContact.uid.replace(RegExp('[^/]*$'),''))==false) $('#ABList').children().eq(insertIndex+headerMiss+1).draggable({ delay: 250, revert: 'invalid', scroll: false, opacity: 0.8, stack: '#System', containment: '#System', appendTo: 'body', helper: function(){ $('#ResourceList').find('.resource').droppable( 'option', 'accept', false); $('#ResourceList').find('.group').droppable( 'option', 'accept', false); $('#ResourceList').find('.resource[data-id!='+jqueryEscapeSelector($(this).attr('data-id').replace(RegExp('[^/]+$'),''))+']').droppable( 'option', 'accept', '.ablist_item'); $('#ResourceList').find('.group[data-id^='+jqueryEscapeSelector($(this).attr('data-id').replace(RegExp('[^/]+$'),''))+']').not('.resource_selected').droppable( 'option', 'accept', '.ablist_item'); var tmp = $(this).clone(); tmp.addClass('ablist_item_dragged'); // we cannot use .css() here, because we need to add !important (problem with Gecko based browsers) var tmp_style='max-width: '+$(this).outerWidth()+'px;'; if($(this).css('background-image')!='none') tmp_style+='background-image: url(' + OC.imagePath('carddavmate', 'company_s_w.svg') + ') !important;'; tmp.attr('style', tmp_style); return tmp; } }); // load the updated contact (because we first deleted it, we need to set it active) if(makeActive!=null) { // make the contact active $('#ABList').find('.ablist_item').removeClass('ablist_item_selected'); $('#ABList').children().eq(insertIndex+headerMiss+1).addClass('ablist_item_selected'); this.loadContactByUID(makeActive); } } this.removeCollectionContacts=function(inputUid) { for(var i=this.contacts.length-1;i>=0;i--) if(this.contacts[i].uid!=undefined && this.contacts[i].uid.replace(RegExp('[^/]+$',''),'')==inputUid) this.removeContact(this.contacts[i].uid,true); } this.removeContact=function(inputUid, loadNext) { // Apple "group" vCards for(var i=this.contact_groups.length-1;i>=0;i--) if(this.contact_groups[i].uid==inputUid) return this.removeContactGroup(inputUid, loadNext); for(var i=this.contacts.length-1;i>=0;i--) if(this.contacts[i].uid==inputUid) { // CATEGORIES suggestion var categoriesArr=vcardSplitValue(this.contacts[i].categories,','); for(var j=0;j"group.", [2]->"name", [3]->";param;param", [4]->"value" parsed=vcard_element[0].match(vCard.pre['contentline_parse']); orgArr[orgArr.length]=vcardUnescapeValue(vcardSplitValue(parsed[4],';')[0]); // remove the processed parameter tmp=tmp.replace(vcard_element[0],'\r\n'); } for(var j=0;j=0;j--) if(this.contacts[j].headerOnly!=true && this.contacts[j].show==true) { nextCandidateToLoad=this.contacts[j]; break; } } // remove the item item.remove(); this.contacts.splice(i,1); // remove the header if there is no more contact var removeHeader=true; var prevHeader=null; // find the previous header index for(var j=i-1;j>=0;j--) if(this.contacts[j].headerOnly==true) { prevHeader=j; break; } // check for visible contact existence for the found header if((prevHeader+1)=0;i--) if(this.contacts[i]!=undefined /* because the header can be deleted with the contact */ && this.contacts[i].timestamp!=undefined && this.contacts[i].uid.indexOf(inputUidBase)==0 && this.contacts[i].timestamp