'use strict'; /* jshint nonew: false */ /* global ActivityHandler */ /* global COMMS_APP_ORIGIN */ /* global Contacts */ /* global ContactsButtons */ /* global ContactPhotoHelper */ /* globals ContactToVcardBlob */ /* global fb */ /* global NFC */ /* global ICEData */ /* global LazyLoader */ /* global MozActivity */ /* global Normalizer */ /* global SCALE_RATIO */ /* global TAG_OPTIONS */ /* global utils */ /* global VcardFilename */ /* global MatchService */ /* global WebrtcClient */ /* global MainNavigation */ /* global ContactsService */ var contacts = window.contacts || {}; contacts.Details = (function() { var photoPos = 7; var initMargin = 8; var DEFAULT_TEL_TYPE = 'other'; // If it is a favourite on/off change, I cancel the render var isAFavoriteChange = false; var contactData, contactDetails, listContainer, detailsName, detailsNameText, orgTitle, datesTemplate, addressesTemplate, socialTemplate, duplicateTemplate, notesTemplate, isFbContact, isFbLinked, editContactButton, cover, favoriteMessage, detailsInner, dom, currentSocial, header, _; var init = function cd_init(currentDom) { _ = navigator.mozL10n.get; dom = currentDom || document; header = dom.querySelector('#details-view-header'); contactDetails = dom.querySelector('#contact-detail'); listContainer = dom.querySelector('#details-list'); detailsName = dom.querySelector('#contact-name-title'); detailsNameText = dom.querySelector('#contact-name-title bdi'); orgTitle = dom.querySelector('#org-title'); datesTemplate = dom.querySelector('#dates-template-\\#i\\#'); addressesTemplate = dom.querySelector('#address-details-template-\\#i\\#'); socialTemplate = dom.querySelector('#social-template-\\#i\\#'); duplicateTemplate = dom.querySelector('#duplicate-contacts-template'); editContactButton = dom.querySelector('#edit-contact-button'); cover = dom.querySelector('#cover-img'); detailsInner = dom.querySelector('#contact-detail-inner'); favoriteMessage = dom.querySelector('#toggle-favorite'); notesTemplate = dom.querySelector('#note-details-template-\\#i\\#'); initPullEffect(cover); // to avoid race conditions with NFC, we load it before handleDetails LazyLoader.load('/contacts/js/nfc.js', () => { favoriteMessage.addEventListener('click', toggleFavorite); editContactButton.addEventListener('click', showEditContact); header.addEventListener('action', handleDetailsBack); }); ContactsButtons.init(listContainer, contactDetails, ActivityHandler); }; var getWebrtcClientResources = function getWebrtcClientResources(cb) { if (typeof cb !== 'function') { return; } LazyLoader.load( [ '/contacts/style/webrtc-client/webrtc_client.css', '/contacts/js/webrtc-client/webrtc_client.js' ], cb ); }; var handleDetailsBack = function handleDetailsBack() { // disable NFC listeners when going out of Details view stopNFC(); if (WebrtcClient) { getWebrtcClientResources(WebrtcClient.stop); } if (ActivityHandler.currentActivityIsNot(['import'])) { ActivityHandler.postCancel(); MainNavigation.home(); } else if (window.ICEView && window.ICEView.iceListDisplayed) { ICEData.getActiveIceContacts().then(function(list) { if (!Array.isArray(list) || list.length === 0) { MainNavigation.home(); } else { doHandleDetailsBack(); } }, doHandleDetailsBack); } else { doHandleDetailsBack(); } }; var doHandleDetailsBack = function() { var hashParams = window.location.hash.split('?'); var params = hashParams.length > 1 ? utils.extractParams(hashParams[1]) : -1; // post message to parent page included Contacts app. if (params.back_to_previous_tab === '1') { var message = { 'type': 'contactsiframe', 'message': 'back' }; window.parent.postMessage(message, COMMS_APP_ORIGIN); } else { MainNavigation.back(resetPhoto); } }; var showEditContact = function showEditContact() { // Disable NFC listeners when editing a contact stopNFC(); Contacts.showForm(true, contactData); }; var setContact = function cd_setContact(currentContact) { contactData = currentContact; startNFC(currentContact); }; // Needed for now because of external call from contacts.js var startNFC = function(contact) { LazyLoader.load('/contacts/js/nfc.js', () => { NFC.startListening(contact); }); }; var stopNFC = function() { LazyLoader.load('/contacts/js/nfc.js', () => { NFC.stopListening(); }); }; var initPullEffect = function cd_initPullEffect(cover) { var maxPosition = Math.round(150 * SCALE_RATIO); var startPosition = 0; function onTouchStart(e) { if (contactDetails.classList.contains('no-photo')) { return; } e.preventDefault(); startPosition = e.changedTouches[0].clientY; contactDetails.classList.add('up'); cover.classList.add('up'); window.addEventListener('touchmove', onTouchMove, true); window.addEventListener('touchend', onTouchEnd, true); } function onTouchEnd(e) { e.preventDefault(); contactDetails.style.transform = null; contactDetails.classList.add('up'); cover.style.transform = null; cover.classList.add('up'); window.removeEventListener('touchmove', onTouchMove, true); window.removeEventListener('touchend', onTouchEnd, true); } function onTouchMove(e) { e.preventDefault(); var deltaY = e.changedTouches[0].clientY - startPosition; deltaY = Math.min(maxPosition, Math.max(0, deltaY)); var calc = 'calc(' + initMargin + 'rem + ' + deltaY + 'px)'; contactDetails.style.transform = 'translateY(' + calc + ')'; contactDetails.classList.remove('up'); // Divide by 40 (4 times slower and in rems) var coverPosition = (-photoPos + (deltaY / 40)) + 'rem'; cover.style.transform = 'translateY(' + coverPosition + ')'; cover.classList.remove('up'); } cover.addEventListener('touchstart', onTouchStart, true); }; // readOnly tells us if we should allow editing the rendered contact. var render = function cd_render(currentContact, fbContactData, readOnly) { if(isAFavoriteChange){ isAFavoriteChange = false; return Promise.resolve(isAFavoriteChange); } contactData = currentContact || contactData; startNFC(contactData); isFbContact = fb.isFbContact(contactData); isFbLinked = fb.isFbLinked(contactData); // Initially enabled and only disabled if necessary editContactButton.removeAttribute('disabled'); editContactButton.classList.remove('hide'); header.setAttribute('action', 'back'); socialTemplate.classList.remove('hide'); if (readOnly) { editContactButton.classList.add('hide'); header.setAttribute('action', 'close'); socialTemplate.classList.add('hide'); } if (!fbContactData && isFbContact) { var fbContact = new fb.Contact(contactData); var req = fbContact.getData(); req.onsuccess = function do_reload() { doReloadContactDetails(req.result); }; req.onerror = function() { window.console.error('FB: Error while loading FB contact data'); doReloadContactDetails(contactData); }; } else { doReloadContactDetails(fbContactData || contactData); } }; // Fills the contact data to display if no givenName and familyName var getDisplayName = function getDisplayName(contact) { var name = _('noName'); if (hasName(contact)) { name = contact.name[0]; } else if (hasContent(contact.tel)) { name = contact.tel[0].value; } else if (hasContent(contact.email)) { name = contact.email[0].value; } return name; }; function hasContent(field) { return (Array.isArray(field) && field.length > 0 && field[0].value && field[0].value.trim()); } function hasName(contact) { return (Array.isArray(contact.name) && contact.name[0] && contact.name[0].trim()); } // // Method that generates HTML markup for the contact // var doReloadContactDetails = function doReloadContactDetails(contact) { detailsNameText.textContent = getDisplayName(contact); contactDetails.classList.remove('no-photo'); contactDetails.classList.remove('fb-contact'); contactDetails.classList.remove('up'); utils.dom.removeChildNodes(listContainer); renderFavorite(contact); renderOrg(contact); ContactsButtons.renderPhones(contact); ContactsButtons.renderEmails(contact); renderWebrtcClient(contactData);// Don't share the FB info renderAddresses(contact); renderDates(contact); renderNotes(contact); renderShareButton(contact); if (!fb.isFbContact(contact) || fb.isFbLinked(contact)) { renderDuplicate(contact); } renderPhoto(contact); }; var renderFavorite = function cd_renderFavorite(contact) { var favorite = isFavorite(contact); toggleFavoriteMessage(favorite); header.classList.toggle('favorite', !!favorite); }; var isFavorite = function isFavorite(contact) { return contact != null && contact.category != null && contact.category.indexOf('favorite') != -1; }; var toggleFavorite = function toggleFavorite() { var contact = contactData; var favorite = !isFavorite(contact); toggleFavoriteMessage(favorite); if (favorite) { contact.category = contact.category || []; contact.category.push('favorite'); } else { if (!contact.category) { return; } var pos = contact.category.indexOf('favorite'); if (pos > -1) { contact.category.splice(pos, 1); } } // Disabling button while saving the contact favoriteMessage.style.pointerEvents = 'none'; var promise = new Promise(function(resolve, reject) { ContactsService.save( utils.misc.toMozContact(contact), function(e) { if (e) { favoriteMessage.style.pointerEvents = 'auto'; console.error('Error saving favorite'); reject('Error saving favorite'); resolve(false); return; } isAFavoriteChange = true; ContactsService.get( contact.id, function onSuccess(savedContact) { renderFavorite(savedContact); setContact(savedContact); favoriteMessage.style.pointerEvents = 'auto'; }, function onError() { console.error('Error reloading contact'); favoriteMessage.style.pointerEvents = 'auto'; } ); resolve(isAFavoriteChange); } ); }).then(); return promise; }; var toggleFavoriteMessage = function toggleFavMessage(isFav) { var cList = favoriteMessage.classList; var l10nId = isFav ? 'removeFavorite' : 'addFavorite'; favoriteMessage.setAttribute('data-l10n-id', l10nId); isFav ? cList.add('on') : cList.remove('on'); }; var renderOrg = function cd_renderOrg(contact) { if (contact.org && contact.org.length > 0 && contact.org[0] !== '') { orgTitle.textContent = contact.org[0]; orgTitle.classList.remove('hide'); } else { orgTitle.classList.add('hide'); orgTitle.textContent = ''; } }; var renderDates = function cd_renderDates(contact) { if (!contact.bday && !contact.anniversary) { return; } var dateString = ''; var fields = ['bday', 'anniversary']; var l10nIds = ['birthday', 'anniversary']; var rendered = 0; for (var j = 0; j < fields.length; j++) { var value = contact[fields[j]]; if (!value) { continue; } try { dateString = utils.misc.formatDate(value); } catch (err) { console.error('Error parsing date'); continue; } var element = utils.templates.render(datesTemplate, { i: ++rendered, date: dateString, type: _(l10nIds[j]) }); listContainer.appendChild(element); } }; var renderShareButton = function cd_renderShareButton(contact) { var social = utils.templates.render(socialTemplate, { i: contact.id }); currentSocial = social; var shareButton = social.querySelector('#share_button'); shareButton.addEventListener('click', shareContact); shareButton.classList.remove('hide'); listContainer.appendChild(social); }; var renderWebrtcClient = function renderWebrtcClient(contact) { getWebrtcClientResources(function onLoaded() { WebrtcClient.start(contact); }); }; var renderAddresses = function cd_renderAddresses(contact) { if (!contact.adr) { return; } for (var i = 0; i < contact.adr.length; i++) { var currentAddress = contact.adr[i]; // Sanity check if (utils.mozContact.haveEmptyFields(currentAddress, ['streetAddress', 'postalCode', 'locality', 'countryName'])) { continue; } var address = currentAddress.streetAddress || ''; var escapedStreet = Normalizer.escapeHTML(address, true); var locality = currentAddress.locality; var escapedLocality = Normalizer.escapeHTML(locality, true); var escapedType = Normalizer.escapeHTML(currentAddress.type, true); var country = currentAddress.countryName || ''; var escapedCountry = Normalizer.escapeHTML(country, true); var postalCode = currentAddress.postalCode || ''; var escapedPostalCode = Normalizer.escapeHTML(postalCode, true); var addressField = { streetAddress: escapedStreet, postalCode: escapedPostalCode, locality: escapedLocality || '', countryName: escapedCountry, type: _(escapedType) || escapedType || TAG_OPTIONS['address-type'][0].value, 'type_l10n_id': currentAddress.type, i: i }; var template = utils.templates.render(addressesTemplate, addressField); listContainer.appendChild(template); } }; var renderDuplicate = function cd_renderDuplicate(contact) { var dupItem = utils.templates.render(duplicateTemplate, {}); var findMergeButton = dupItem.querySelector('#find-merge-button'); findMergeButton.disabled = true; if (contacts.List.total > 1) { // Only have this active if contact list has more than one entry findMergeButton.disabled = false; findMergeButton.addEventListener('click', function finding() { MatchService.match(contact.id); }); } listContainer.appendChild(dupItem); }; var renderNotes = function cd_rederNotes(contact) { if (!contact.note || contact.note.length === 0) { return; } var container = document.createElement('li'); var title = document.createElement('h2'); title.setAttribute('data-l10n-id', 'comments'); container.appendChild(title); for (var i = 0; i < contact.note.length; i++) { var currentNote = contact.note[i]; var noteField = { note: Normalizer.escapeHTML(currentNote, true) || '', i: i }; var template = utils.templates.render(notesTemplate, noteField); container.appendChild(template); listContainer.appendChild(container); } }; var calculateHash = function calculateHash(photo, cb) { var START_BYTES = 127; var BYTES_HASH = 16; var out = [photo.type, photo.size]; // We skip the first bytes that typically are headers var chunk = photo.slice(START_BYTES, START_BYTES + BYTES_HASH); var reader = new FileReader(); reader.onloadend = function() { out.push(reader.result); cb(out.join('')); }; reader.onerror = function() { window.console.error('Error while calculating the hash: ', reader.error.name); cb(out.join('')); }; reader.readAsDataURL(chunk); }; var updateHash = function updateHash(photo, cover) { calculateHash(photo, function(hash) { cover.dataset.imgHash = hash; }); }; var renderPhoto = function cd_renderPhoto(contact) { contactDetails.classList.remove('up'); if (isFbContact) { contactDetails.classList.add('fb-contact'); } var photo = ContactPhotoHelper.getFullResolution(contact); if (photo) { var currentHash = cover.dataset.imgHash; if (!currentHash) { utils.dom.updatePhoto(photo, cover); updateHash(photo, cover); } else { // Need to recalculate the hash and see whether the images changed calculateHash(photo, function(newHash) { if (currentHash !== newHash) { utils.dom.updatePhoto(photo, cover); cover.dataset.imgHash = newHash; } else { // Only for testing purposes cover.dataset.photoReady = 'true'; } }); } contactDetails.classList.add('up'); cover.classList.add('translated'); contactDetails.classList.add('translated'); var clientHeight = contactDetails.clientHeight - (initMargin * 10 * SCALE_RATIO); if (detailsInner.offsetHeight < clientHeight) { cover.style.overflow = 'hidden'; } else { cover.style.overflow = 'auto'; } } else { resetPhoto(); } }; var resetPhoto = function cd_resetPhoto() { cover.classList.remove('translated'); contactDetails.classList.remove('translated'); cover.style.backgroundImage = ''; cover.style.overflow = 'auto'; contactDetails.style.transform = ''; contactDetails.classList.add('no-photo'); cover.dataset.imgHash = ''; }; var shareContact = function cd_shareContact() { const VCARD_DEPS = [ '/shared/js/text_normalizer.js', '/shared/js/contact2vcard.js', '/shared/js/setImmediate.js' ]; LazyLoader.load(VCARD_DEPS,function vcardLoaded() { ContactToVcardBlob([contactData], function blobReady(vcardBlob) { VcardFilename(contactData).then(filename => { new MozActivity({ name: 'share', data: { type: 'text/vcard', number: 1, blobs: [new window.File([vcardBlob], filename, { type: 'text/x-vcard' })], filenames: [filename] } }); }); // The MIME of the blob should be this for some MMS gateways }, { type: 'text/x-vcard'} ); }); }; return { 'init': init, 'setContact': setContact, 'toggleFavorite': toggleFavorite, 'render': render, 'defaultTelType': DEFAULT_TEL_TYPE, 'startNFC': startNFC }; })();