Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
60
gaia/apps/communications/contacts/views/form/js/boot.js
Executable file
60
gaia/apps/communications/contacts/views/form/js/boot.js
Executable file
|
|
@ -0,0 +1,60 @@
|
|||
/* global LazyLoader, FormUI, FormController */
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* This class is the one in charge of loading the minimum set of
|
||||
* resources needed for the view to load. Any other JS/CSS/Element
|
||||
* not needed in the critical path *must* be lazy loaded when needed.
|
||||
*
|
||||
* Once localization and all the basic JS/CSS/Elements are loaded,
|
||||
* we will initialize UI and Controller. Both JS classes *must* be
|
||||
* independent and will communicate through events.
|
||||
*/
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
LazyLoader.load([
|
||||
'/shared/js/l10n.js'], function() {
|
||||
LazyLoader.load([
|
||||
document.getElementById('view-contact-form')
|
||||
], function() {
|
||||
// TODO Add if needed
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
var dependencies = [
|
||||
'/contacts/js/navigation.js',
|
||||
'/contacts/services/contacts.js',
|
||||
'/shared/js/contact_photo_helper.js',
|
||||
'/shared/js/contacts/utilities/templates.js',
|
||||
'/contacts/js/contacts_tag.js',
|
||||
'/contacts/js/tag_options.js',
|
||||
'/shared/js/text_normalizer.js',
|
||||
'/shared/js/contacts/import/utilities/status.js',
|
||||
'/shared/js/contacts/utilities/dom.js',
|
||||
'/shared/js/contacts/import/utilities/misc.js',
|
||||
'/contacts/views/form/js/form_ui.js',
|
||||
'/contacts/views/form/js/form_controller.js',
|
||||
'/contacts/views/form/js/main_navigation.js'
|
||||
];
|
||||
|
||||
LazyLoader.load(
|
||||
dependencies,
|
||||
function() {
|
||||
FormUI.init();
|
||||
FormController.init();
|
||||
navigator.mozSetMessageHandler(
|
||||
'activity',
|
||||
function(activity) {
|
||||
FormController.setActivity(
|
||||
activity
|
||||
);
|
||||
FormUI.render(
|
||||
activity.source.data.params
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
217
gaia/apps/communications/contacts/views/form/js/form_controller.js
Executable file
217
gaia/apps/communications/contacts/views/form/js/form_controller.js
Executable file
|
|
@ -0,0 +1,217 @@
|
|||
/* global LazyLoader, MergeHelper, ContactsService, utils,
|
||||
Matcher, MatchService */
|
||||
|
||||
|
||||
/*
|
||||
* Once critical path is loaded, we will listen events dispatched
|
||||
* from the UI. This events will come with the info needed in order
|
||||
* to execute actions related with the UI (close, save, update...).
|
||||
*
|
||||
* Controller will *not* contain any code related with the DOM/UI,
|
||||
* and will rely on the info provided by the event.
|
||||
*/
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
var _;
|
||||
var _activity;
|
||||
const CONTACTS_APP_ORIGIN = location.origin;
|
||||
|
||||
function close() {
|
||||
if (_activity) {
|
||||
_activity.postResult({});
|
||||
} else {
|
||||
window.history.back();
|
||||
}
|
||||
}
|
||||
|
||||
function hasName(contact) {
|
||||
return (Array.isArray(contact.givenName) && contact.givenName[0] &&
|
||||
contact.givenName[0].trim()) ||
|
||||
(Array.isArray(contact.familyName) && contact.familyName[0] &&
|
||||
contact.familyName[0].trim());
|
||||
}
|
||||
|
||||
|
||||
function doMerge(contact, list, cb) {
|
||||
var callbacks = {
|
||||
success: cb,
|
||||
error: function(e) {
|
||||
console.error('Failed merging duplicate contacts: ', e.name);
|
||||
cb();
|
||||
}
|
||||
};
|
||||
|
||||
LazyLoader.load('/contacts/js/utilities/merge_helper.js', function() {
|
||||
MergeHelper.merge(contact, list).then(callbacks.success, callbacks.error);
|
||||
});
|
||||
}
|
||||
|
||||
function getCompleteName(contact) {
|
||||
var givenName = Array.isArray(contact.givenName) ?
|
||||
contact.givenName[0] : '';
|
||||
|
||||
var familyName = Array.isArray(contact.familyName) ?
|
||||
contact.familyName[0] : '';
|
||||
|
||||
var completeName = givenName && familyName ?
|
||||
givenName + ' ' + familyName :
|
||||
givenName || familyName;
|
||||
|
||||
return completeName;
|
||||
}
|
||||
// Fills the contact data to display if no givenName and familyName
|
||||
function getDisplayName(contact) {
|
||||
if (hasName(contact)) {
|
||||
return { givenName: contact.givenName, familyName: contact.familyName };
|
||||
}
|
||||
|
||||
// TODO Remove l10n deprecated methods:
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1181586
|
||||
var givenName = [];
|
||||
if (Array.isArray(contact.name) && contact.name.length > 0) {
|
||||
givenName.push(contact.name[0]);
|
||||
} else if (contact.org && contact.org.length > 0) {
|
||||
givenName.push(contact.org[0]);
|
||||
} else if (contact.tel && contact.tel.length > 0) {
|
||||
givenName.push(contact.tel[0].value);
|
||||
} else if (contact.email && contact.email.length > 0) {
|
||||
givenName.push(contact.email[0].value);
|
||||
} else {
|
||||
givenName.push(_('noName'));
|
||||
}
|
||||
|
||||
return { givenName: givenName, modified: true };
|
||||
}
|
||||
|
||||
function doMatch(contact, callbacks) {
|
||||
LazyLoader.load(['/shared/js/text_normalizer.js',
|
||||
'/shared/js/simple_phone_matcher.js',
|
||||
'/shared/js/contacts/contacts_matcher.js'], function() {
|
||||
Matcher.match(contact, 'active', callbacks);
|
||||
});
|
||||
}
|
||||
|
||||
var mergeHandler;
|
||||
function cookMatchingCallbacks(contact) {
|
||||
return {
|
||||
onmatch: function(results) {
|
||||
MatchService.showDuplicateContacts();
|
||||
|
||||
mergeHandler = function mergeHandler(e) {
|
||||
if (e.origin !== CONTACTS_APP_ORIGIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
var data = e.data;
|
||||
switch (data.type) {
|
||||
case 'duplicate_contacts_loaded':
|
||||
// UI ready, passing duplicate contacts
|
||||
var duplicateContacts = {};
|
||||
Object.keys(results).forEach(function(id) {
|
||||
duplicateContacts[id] = {
|
||||
matchingContactId: id,
|
||||
matchings: results[id].matchings
|
||||
};
|
||||
});
|
||||
|
||||
window.postMessage({
|
||||
type: 'show_duplicate_contacts',
|
||||
data: {
|
||||
name: getCompleteName(getDisplayName(contact)),
|
||||
duplicateContacts: duplicateContacts
|
||||
}
|
||||
}, CONTACTS_APP_ORIGIN);
|
||||
|
||||
break;
|
||||
|
||||
case 'merge_duplicate_contacts':
|
||||
window.removeEventListener('message', mergeHandler);
|
||||
|
||||
// List of duplicate contacts to merge (identifiers)
|
||||
var list = [];
|
||||
var ids = data.data;
|
||||
Object.keys(ids).forEach(id => {
|
||||
list.push(results[id]);
|
||||
});
|
||||
|
||||
doMerge(contact, list, function finished() {
|
||||
window.postMessage({
|
||||
type: 'duplicate_contacts_merged',
|
||||
data: ids
|
||||
}, CONTACTS_APP_ORIGIN);
|
||||
close();
|
||||
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
case 'ready':
|
||||
// TODO Use if needed
|
||||
break;
|
||||
|
||||
case 'window_close':
|
||||
// If user igonores duplicate contacts we save the contact
|
||||
window.removeEventListener('message', mergeHandler);
|
||||
doSave(contact, true);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('message', mergeHandler);
|
||||
},
|
||||
onmismatch: function() {
|
||||
// Saving because there aren't duplicate contacts
|
||||
doSave(contact);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function doSave(contact) {
|
||||
ContactsService.addListener('contactchange',
|
||||
function oncontactchange(event) {
|
||||
sessionStorage.setItem('contactID', event.contactID);
|
||||
sessionStorage.setItem('reason', event.reason);
|
||||
ContactsService.removeListener('contactchange', oncontactchange);
|
||||
close();
|
||||
}
|
||||
);
|
||||
ContactsService.save(contact, function(error) {
|
||||
// Use if needed.
|
||||
});
|
||||
}
|
||||
|
||||
exports.FormController = {
|
||||
init: function() {
|
||||
|
||||
_ = navigator.mozL10n.get;
|
||||
window.addEventListener('close-ui', function() {
|
||||
close();
|
||||
});
|
||||
|
||||
|
||||
function saveContactHandler(event) {
|
||||
var contact = utils.misc.toMozContact(event.detail);
|
||||
LazyLoader.load(
|
||||
[
|
||||
'/contacts/style/match_service.css',
|
||||
'/contacts/js/match_service.js'
|
||||
],
|
||||
function() {
|
||||
var callbacks = cookMatchingCallbacks(contact);
|
||||
doMatch(contact, callbacks);
|
||||
}
|
||||
);
|
||||
}
|
||||
window.addEventListener(
|
||||
'save-contact',
|
||||
saveContactHandler
|
||||
);
|
||||
},
|
||||
setActivity: function(activity) {
|
||||
_activity = activity;
|
||||
}
|
||||
};
|
||||
}(window));
|
||||
803
gaia/apps/communications/contacts/views/form/js/form_ui.js
Executable file
803
gaia/apps/communications/contacts/views/form/js/form_ui.js
Executable file
|
|
@ -0,0 +1,803 @@
|
|||
/* global ContactPhotoHelper */
|
||||
/* global ContactsTag */
|
||||
/* global LazyLoader */
|
||||
/* global Normalizer */
|
||||
/* global utils */
|
||||
/* global TAG_OPTIONS */
|
||||
/* global ActionMenu */
|
||||
/* global TagSelector */
|
||||
/* global PhotoPicker */
|
||||
|
||||
|
||||
/*
|
||||
* UI *must* work alone, without any 'Controller' tied. This will
|
||||
* help us to have a well-separated model, where the UI will dispatch
|
||||
* events that a Controller could potentially handle.
|
||||
*
|
||||
* UI will be in charge of rendering the info provided by 'Boot', which
|
||||
* is the only part of the code which knows UI & Controller.
|
||||
*/
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
var counters = {
|
||||
'tel': 0,
|
||||
'email': 0,
|
||||
'adr': 0,
|
||||
'date': 0,
|
||||
'note': 0
|
||||
};
|
||||
|
||||
// Cached elements that we will keep in memory
|
||||
var contactForm,
|
||||
deleteContactButton,
|
||||
addNewDateButton,
|
||||
thumb,
|
||||
thumbAction,
|
||||
saveButton,
|
||||
formHeader,
|
||||
formTitle,
|
||||
givenName,
|
||||
company,
|
||||
familyName,
|
||||
formView;
|
||||
|
||||
|
||||
var _, currentPhoto;
|
||||
|
||||
var configs;
|
||||
function initConfigs() {
|
||||
// Keep in memory the template configuration
|
||||
configs = {
|
||||
'tel': {
|
||||
template: document.getElementById('add-phone-#i#'),
|
||||
tags: TAG_OPTIONS['phone-type'],
|
||||
fields: ['value', 'type', 'carrier'],
|
||||
container: document.getElementById('contacts-form-phones')
|
||||
},
|
||||
'email': {
|
||||
template: document.getElementById('add-email-#i#'),
|
||||
tags: TAG_OPTIONS['email-type'],
|
||||
fields: ['value', 'type'],
|
||||
container: document.getElementById('contacts-form-emails')
|
||||
},
|
||||
'adr': {
|
||||
template: document.getElementById('add-address-#i#'),
|
||||
tags: TAG_OPTIONS['address-type'],
|
||||
fields: [
|
||||
'type',
|
||||
'streetAddress',
|
||||
'postalCode',
|
||||
'locality',
|
||||
'countryName'
|
||||
],
|
||||
container: document.getElementById('contacts-form-addresses')
|
||||
},
|
||||
'date': {
|
||||
template: document.getElementById('add-date-#i#'),
|
||||
tags: TAG_OPTIONS['date-type'],
|
||||
fields: ['value', 'type'],
|
||||
container: document.getElementById('contacts-form-dates')
|
||||
},
|
||||
'note': {
|
||||
template: document.getElementById('add-note-#i#'),
|
||||
tags: TAG_OPTIONS['address-type'],
|
||||
fields: ['note'],
|
||||
container: document.getElementById('contacts-form-notes')
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Let's get the right event for the current device
|
||||
var touchstart = 'ontouchstart' in window ? 'touchstart' : 'mousedown';
|
||||
|
||||
const INVALID_CLASS = 'invalid';
|
||||
const DELETE_BUTTON_CLASS = 'img-delete-button';
|
||||
|
||||
function initContainers() {
|
||||
deleteContactButton = document.querySelector('#delete-contact');
|
||||
thumb = document.querySelector('#thumbnail-photo');
|
||||
thumbAction = document.querySelector('#thumbnail-action');
|
||||
saveButton = document.querySelector('#save-button');
|
||||
addNewDateButton = document.querySelector('#add-new-date');
|
||||
contactForm = document.getElementById('contact-form');
|
||||
formHeader = document.querySelector('#contact-form-header');
|
||||
formTitle = document.getElementById('contact-form-title');
|
||||
givenName = document.getElementById('givenName');
|
||||
company = document.getElementById('org');
|
||||
familyName = document.getElementById('familyName');
|
||||
formView = document.getElementById('view-contact-form');
|
||||
}
|
||||
|
||||
function closeHandler() {
|
||||
window.dispatchEvent(new Event('close-ui'));
|
||||
}
|
||||
|
||||
function addFieldHandler(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (e.target.dataset && e.target.dataset.action === 'delete') {
|
||||
return;
|
||||
}
|
||||
if (e.target.dataset && e.target.dataset.fieldType) {
|
||||
var type = e.target.dataset.fieldType;
|
||||
insertField(
|
||||
type,
|
||||
null,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function saveHandler() {
|
||||
var contact = {};
|
||||
var inputs = { givenName, familyName };
|
||||
|
||||
for (var field in inputs) {
|
||||
var value = inputs[field].value;
|
||||
if (value && value.length > 0) {
|
||||
contact[field] = [value];
|
||||
} else {
|
||||
contact[field] = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (company.value && company.value.length > 0) {
|
||||
contact.org = [company.value];
|
||||
}
|
||||
|
||||
contact.email = getEmails();
|
||||
contact.tel = getPhones();
|
||||
contact.adr = getAddresses();
|
||||
contact.note = getNotes();
|
||||
var dates = getDates();
|
||||
contact.bday = dates.bday;
|
||||
contact.anniversary = dates.anniversary;
|
||||
contact.name = getName(contact);
|
||||
|
||||
if (currentPhoto) {
|
||||
contact.photo = [currentPhoto];
|
||||
}
|
||||
|
||||
var customEvent = new CustomEvent('save-contact', {'detail': contact});
|
||||
window.dispatchEvent(customEvent);
|
||||
}
|
||||
|
||||
function addListeners() {
|
||||
thumbAction.querySelector('#photo-button').onclick = photoAction;
|
||||
|
||||
document.addEventListener('input', function input(event) {
|
||||
checkDisableButton();
|
||||
});
|
||||
|
||||
contactForm.addEventListener(touchstart, function click(event) {
|
||||
// Filter any other 'hit' in the DOM
|
||||
var target = event.target;
|
||||
if (target.tagName !== 'BUTTON') {
|
||||
return;
|
||||
}
|
||||
var action = target.dataset.action || target.getAttribute('type');
|
||||
switch(action) {
|
||||
case 'reset':
|
||||
event.preventDefault();
|
||||
var input = target.previousElementSibling;
|
||||
|
||||
if (input.getAttribute('name').startsWith('tel') &&
|
||||
input.dataset.field === 'value') {
|
||||
|
||||
var telId = input.id;
|
||||
var telIndex = telId.substring(telId.indexOf('_') + 1);
|
||||
var carrierField =
|
||||
document.getElementById('carrier' + '_' + telIndex);
|
||||
carrierField.classList.add(INVALID_CLASS);
|
||||
|
||||
cachedInputs = null;
|
||||
}
|
||||
input.value = '';
|
||||
checkDisableButton();
|
||||
break;
|
||||
case 'delete':
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
var type = target.dataset.type;
|
||||
var selector = target.dataset.selector;
|
||||
|
||||
var container = configs[type].container;
|
||||
var elem = document.getElementById(selector);
|
||||
|
||||
if (type !== 'photo') {
|
||||
// Remove field
|
||||
elem.parentNode.removeChild(elem);
|
||||
// Show empty if no more fields of the same type are available
|
||||
if (container.querySelectorAll('[data-field]').length === 0) {
|
||||
container.classList.add('empty');
|
||||
}
|
||||
}
|
||||
|
||||
counters[type]--;
|
||||
// In this version only two dates are allowed
|
||||
if (type === 'date') {
|
||||
checkAddDateButton();
|
||||
}
|
||||
|
||||
cachedInputs = null;
|
||||
// textFieldsCache.clear();
|
||||
checkDisableButton();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
thumbAction.addEventListener(touchstart, function click(event) {
|
||||
// Removing current photo
|
||||
if (event.target.tagName === 'BUTTON') {
|
||||
saveButton.removeAttribute('disabled');
|
||||
}
|
||||
});
|
||||
|
||||
// 'ValueModified' comes from the 'TagSelector', and will return a value
|
||||
// from a set of Tags.
|
||||
formView.addEventListener('ValueModified', function onValueModified(event) {
|
||||
if (!event.detail) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!emptyForm() && event.detail.prevValue !== event.detail.newValue) {
|
||||
saveButton.removeAttribute('disabled');
|
||||
}
|
||||
});
|
||||
|
||||
formHeader.addEventListener('action', closeHandler);
|
||||
saveButton.addEventListener('click', saveHandler);
|
||||
contactForm.addEventListener('click', addFieldHandler);
|
||||
}
|
||||
|
||||
function init() {
|
||||
// Cache l10n functionality
|
||||
_ = navigator.mozL10n.get;
|
||||
// Cache all DOM elements and reuse them
|
||||
initContainers();
|
||||
// Cache configuration for templates
|
||||
initConfigs();
|
||||
// Add listeners to DOM elements
|
||||
addListeners();
|
||||
}
|
||||
|
||||
// Renders the birthday as per the locale
|
||||
function renderDate(date, bdayInputText) {
|
||||
if (!date) {
|
||||
return;
|
||||
}
|
||||
|
||||
bdayInputText.textContent = utils.misc.formatDate(date);
|
||||
bdayInputText.classList.remove('placeholder');
|
||||
}
|
||||
|
||||
function onInputDate(bdayInputText, e) {
|
||||
renderDate(e.target.valueAsDate, bdayInputText);
|
||||
}
|
||||
|
||||
|
||||
function extractValue(value) {
|
||||
if (value) {
|
||||
if (Array.isArray(value) && value.length > 0 && value[0]) {
|
||||
return value[0].trim();
|
||||
} else if (!Array.isArray(value)) {
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
// The last statement allows to return the value in case we are getting
|
||||
// just params from an activity and not a mozContact.
|
||||
}
|
||||
|
||||
function fillDates(contact) {
|
||||
if (contact.bday) {
|
||||
contact.date = [];
|
||||
|
||||
contact.date.push({
|
||||
type: 'birthday',
|
||||
value: contact.bday
|
||||
});
|
||||
}
|
||||
|
||||
if (contact.anniversary) {
|
||||
contact.date = contact.date || [];
|
||||
contact.date.push({
|
||||
type: 'anniversary',
|
||||
value: contact.anniversary
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function renderPhoto(contact) {
|
||||
if (contact.photo && contact.photo.length > 0) {
|
||||
currentPhoto = ContactPhotoHelper.getFullResolution(contact);
|
||||
appendRemovePhotoButton();
|
||||
}
|
||||
|
||||
utils.dom.updatePhoto(currentPhoto, thumb);
|
||||
}
|
||||
|
||||
function render(params) {
|
||||
formView.classList.remove('skin-organic');
|
||||
saveButton.setAttribute('disabled', 'disabled');
|
||||
saveButton.setAttribute('data-l10n-id', 'done');
|
||||
deleteContactButton.parentNode.classList.add('hide');
|
||||
formTitle.setAttribute('data-l10n-id', 'addContact');
|
||||
|
||||
params = params || {};
|
||||
|
||||
givenName.value = extractValue(params.givenName);
|
||||
familyName.value = extractValue(params.lastName || params.familyName);
|
||||
company.value = extractValue(params.company || params.org);
|
||||
|
||||
renderPhoto(params);
|
||||
|
||||
fillDates(params);
|
||||
|
||||
['tel', 'email', 'adr', 'date', 'note'].forEach(function(field) {
|
||||
params[field] && renderTemplate(field, params[field]);
|
||||
});
|
||||
|
||||
checkDisableButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render Template
|
||||
*
|
||||
* @param {string} type Type of template, eg. 'tel'
|
||||
* @param {object[]} toRender
|
||||
*/
|
||||
function renderTemplate(type, toRender) {
|
||||
if (!Array.isArray(toRender)) {
|
||||
toRender = [{value: toRender}];
|
||||
}
|
||||
|
||||
for (var i = 0; i < toRender.length; i++) {
|
||||
insertField(type, toRender[i] || {});
|
||||
}
|
||||
}
|
||||
|
||||
function checkAddDateButton() {
|
||||
addNewDateButton.disabled = (counters.date >= 2);
|
||||
}
|
||||
|
||||
function checkCarrierTel(carrierInput, event) {
|
||||
var telInput = event.target;
|
||||
var value = telInput.value;
|
||||
|
||||
if (!value || !value.trim()) {
|
||||
// deletedTelNumber = true;
|
||||
|
||||
// If it was not previously filled then it will be disabled
|
||||
if (!telInput.dataset.wasFilled) {
|
||||
carrierInput.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
else {
|
||||
// Otherwise marked as invalid in order not to submit it
|
||||
carrierInput.classList.add(INVALID_CLASS);
|
||||
cachedInputs = null;
|
||||
// textFieldsCache.clear();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Marked as filled
|
||||
telInput.dataset.wasFilled = true;
|
||||
// Enabling and marking as valid
|
||||
carrierInput.removeAttribute('disabled');
|
||||
carrierInput.classList.remove(INVALID_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We cannot relay on the counter, but in the next id after the
|
||||
* last field.
|
||||
* See bug 1113134 for related explanation.
|
||||
*/
|
||||
function getNextTemplateId(container) {
|
||||
var nodes = container.childNodes;
|
||||
if (!nodes || nodes.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var lastNode = nodes[nodes.length - 1];
|
||||
var value = lastNode.dataset.index;
|
||||
return value ? parseInt(value) + 1 : 0;
|
||||
}
|
||||
|
||||
function isBuiltInType(type, tagList) {
|
||||
return tagList.some(function(tag) {
|
||||
return tag.type === type;
|
||||
});
|
||||
}
|
||||
|
||||
function insertField(type, object, isUserInteraction) {
|
||||
if (!type || !configs[type]) {
|
||||
console.error('Inserting field with unknown type');
|
||||
return;
|
||||
}
|
||||
|
||||
var obj = object || {};
|
||||
var config = configs[type];
|
||||
var template = config.template;
|
||||
var tags = ContactsTag.filterTags(type, null, config.tags);
|
||||
|
||||
var container = config.container;
|
||||
|
||||
var default_type = tags[0] && tags[0].type || '';
|
||||
var currField = {};
|
||||
|
||||
config.fields.forEach(function(currentElem) {
|
||||
var def = (currentElem === 'type') ? default_type : '';
|
||||
var defObj = (typeof(obj) === 'string') ? obj : obj[currentElem];
|
||||
var value = '';
|
||||
var isDate = (defObj && typeof defObj.getMonth === 'function');
|
||||
|
||||
currField[currentElem] = (defObj && typeof(defObj) === 'object' &&
|
||||
!isDate ? defObj.toString() : defObj);
|
||||
value = currField[currentElem] || def;
|
||||
if (currentElem === 'type') {
|
||||
currField.type_value = value;
|
||||
|
||||
// Do localization for built-in types
|
||||
if (isBuiltInType(value, tags)) {
|
||||
currField.type_l10n_id = value;
|
||||
value = _(value) || value;
|
||||
}
|
||||
}
|
||||
if (!isDate) {
|
||||
currField[currentElem] = Normalizer.escapeHTML(value, true);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
currField.i = getNextTemplateId(container);
|
||||
|
||||
var rendered = utils.templates.render(template, currField);
|
||||
// Controlling that if no tel phone is present carrier field is disabled
|
||||
if (type === 'tel') {
|
||||
var carrierInput = rendered.querySelector('input[data-field="carrier"]');
|
||||
var telInput = rendered.querySelector('input[data-field="value"]');
|
||||
|
||||
var cb = checkCarrierTel.bind(null, carrierInput);
|
||||
|
||||
telInput.addEventListener('input', cb, true);
|
||||
|
||||
checkCarrierTel(carrierInput, {target: telInput});
|
||||
}
|
||||
|
||||
// Adding listener to properly render dates
|
||||
if (type === 'date') {
|
||||
var dateInput = rendered.querySelector('input[type="date"]');
|
||||
|
||||
// Setting the max value as today's date
|
||||
var currentDate = new Date();
|
||||
dateInput.setAttribute('max', currentDate.getFullYear() + '-' +
|
||||
(currentDate.getMonth() + 1) + '-' +
|
||||
currentDate.getDate());
|
||||
|
||||
var dateInputText = dateInput.previousElementSibling;
|
||||
if (currField.value) {
|
||||
dateInput.valueAsDate = currField.value;
|
||||
renderDate(currField.value, dateInputText);
|
||||
}
|
||||
else {
|
||||
dateInputText.setAttribute('data-l10n-id', 'date-span-placeholder');
|
||||
}
|
||||
|
||||
dateInput.addEventListener(
|
||||
'input',
|
||||
onInputDate.bind(null, dateInputText));
|
||||
}
|
||||
|
||||
var removeEl = createDeleteButton(rendered.id, type);
|
||||
rendered.insertBefore(removeEl, rendered.firstChild);
|
||||
|
||||
// Add event listeners
|
||||
var boxTitle = rendered.querySelector('legend.action');
|
||||
if (boxTitle) {
|
||||
boxTitle.addEventListener('click', selectTag);
|
||||
}
|
||||
|
||||
// This will happen when the fields are added by the user on demand
|
||||
if (isUserInteraction) {
|
||||
rendered.classList.add('inserted');
|
||||
window.setTimeout(() => rendered.classList.add('displayed'));
|
||||
}
|
||||
|
||||
container.classList.remove('empty');
|
||||
container.appendChild(rendered);
|
||||
counters[type]++;
|
||||
cachedInputs = null;
|
||||
|
||||
// Finally we need to check the status of the add date button
|
||||
if (type === 'date') {
|
||||
checkAddDateButton();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Method for selecting a tag (i.e. "phone type"). This needs to handle
|
||||
* navigation among panels, that's why we need a 'MainNavigation' object
|
||||
*/
|
||||
function selectTag(evt) {
|
||||
evt.preventDefault();
|
||||
var target = evt.currentTarget;
|
||||
LazyLoader.load('/contacts/js/utilities/tagSelector.js', function() {
|
||||
TagSelector.show(target.children[0]);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function getPhones(contact) {
|
||||
var phones = [];
|
||||
var selector = '#view-contact-form form div.phone-template';
|
||||
var phonesDOM = document.querySelectorAll(selector);
|
||||
for (var i = 0; i < phonesDOM.length; i++) {
|
||||
var currentPhone = phonesDOM[i];
|
||||
var index = currentPhone.dataset.index;
|
||||
var numberField = document.getElementById('number_' + index);
|
||||
var value = numberField.value;
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var type =
|
||||
[document.getElementById('tel_type_' + index).dataset.value || ''];
|
||||
var carrierSelector = 'carrier_' + index;
|
||||
var carrier = document.getElementById(carrierSelector).value || '';
|
||||
phones.push({ value, type, carrier });
|
||||
}
|
||||
return phones;
|
||||
}
|
||||
|
||||
function getEmails() {
|
||||
var emails = [];
|
||||
var selector = '#view-contact-form form div.email-template';
|
||||
var emailsDOM = document.querySelectorAll(selector);
|
||||
for (var i = 0; i < emailsDOM.length; i++) {
|
||||
var currentEmail = emailsDOM[i];
|
||||
var index = currentEmail.dataset.index;
|
||||
var emailField = document.getElementById('email_' + index);
|
||||
var value = emailField.value;
|
||||
value = value && value.trim();
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var type = [
|
||||
document.getElementById('email_type_' + index).dataset.value || ''];
|
||||
emails.push({ value, type });
|
||||
}
|
||||
return emails;
|
||||
}
|
||||
|
||||
function getName(contact) {
|
||||
var givenName = '', familyName = '';
|
||||
|
||||
if (contact.givenName && Array.isArray(contact.givenName)) {
|
||||
givenName = contact.givenName[0].trim();
|
||||
}
|
||||
|
||||
if (contact.familyName && Array.isArray(contact.familyName)) {
|
||||
familyName = contact.familyName[0].trim();
|
||||
}
|
||||
|
||||
var completeName = (givenName + ' ' + familyName).trim();
|
||||
return completeName ? [completeName] : [];
|
||||
}
|
||||
|
||||
function getDates(contact) {
|
||||
var dates = {};
|
||||
var selector = '#view-contact-form form div.date-template';
|
||||
var datesDOM = document.querySelectorAll(selector);
|
||||
var bdayVal = null, anniversaryVal = null;
|
||||
|
||||
for (var i = 0; i < datesDOM.length; i++) {
|
||||
var currentDate = datesDOM[i];
|
||||
|
||||
var arrayIndex = currentDate.dataset.index;
|
||||
var dateField = document.getElementById('date_' + arrayIndex);
|
||||
var dateValue = dateField.valueAsDate;
|
||||
|
||||
selector = 'date_type_' + arrayIndex;
|
||||
var type = document.getElementById(selector).dataset.value || '';
|
||||
if (!dateValue || !type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Date value is referred to current TZ but it is not needed to normalize
|
||||
// as that will be done only when the date is presented to the user
|
||||
// by calculating the corresponding offset
|
||||
switch (type) {
|
||||
case 'birthday':
|
||||
bdayVal = dateValue;
|
||||
break;
|
||||
case 'anniversary':
|
||||
anniversaryVal = dateValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dates.bday = bdayVal;
|
||||
dates.anniversary = anniversaryVal;
|
||||
return dates;
|
||||
}
|
||||
|
||||
function getAddresses(contact) {
|
||||
var addresses = [];
|
||||
var selector =
|
||||
'#view-contact-form form div.address-template';
|
||||
var addressesDOM = document.querySelectorAll(selector);
|
||||
for (var i = 0; i < addressesDOM.length; i++) {
|
||||
var currentAddress = addressesDOM[i];
|
||||
var arrayIndex = currentAddress.dataset.index;
|
||||
var addressField = document.getElementById('streetAddress_' + arrayIndex);
|
||||
var addressValue = addressField.value || '';
|
||||
addressValue = addressValue.trim();
|
||||
selector = 'address_type_' + arrayIndex;
|
||||
var typeField = document.getElementById(selector).dataset.value || '';
|
||||
typeField = typeField.trim();
|
||||
selector = 'locality_' + arrayIndex;
|
||||
var locality = document.getElementById(selector).value || '';
|
||||
locality = locality.trim();
|
||||
selector = 'postalCode_' + arrayIndex;
|
||||
var postalCode = document.getElementById(selector).value || '';
|
||||
postalCode = postalCode.trim();
|
||||
selector = 'countryName_' + arrayIndex;
|
||||
var countryName = document.getElementById(selector).value || '';
|
||||
countryName = countryName.trim();
|
||||
|
||||
// Sanity check for pameters, check all params but the typeField
|
||||
if (addressValue === '' && locality === '' &&
|
||||
postalCode === '' && countryName === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
addresses.push({
|
||||
streetAddress: addressValue,
|
||||
postalCode: postalCode,
|
||||
locality: locality,
|
||||
countryName: countryName,
|
||||
type: [typeField]
|
||||
});
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
|
||||
function getNotes(contact) {
|
||||
var notes = [];
|
||||
var selector = '#view-contact-form form div.note-template';
|
||||
var notesDOM = document.querySelectorAll(selector);
|
||||
for (var i = 0; i < notesDOM.length; i++) {
|
||||
var currentNote = notesDOM[i];
|
||||
var arrayIndex = currentNote.dataset.index;
|
||||
var noteField = document.getElementById('note_' + arrayIndex);
|
||||
var noteValue = noteField.value;
|
||||
noteValue = noteValue && noteValue.trim();
|
||||
if (!noteValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
notes.push(noteValue);
|
||||
}
|
||||
return notes;
|
||||
}
|
||||
|
||||
|
||||
function checkDisableButton() {
|
||||
var saveButton = document.getElementById('save-button');
|
||||
if (emptyForm()) {
|
||||
saveButton.setAttribute('disabled', 'disabled');
|
||||
} else {
|
||||
saveButton.removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
var cachedInputs = null;
|
||||
function emptyForm() {
|
||||
if (!cachedInputs) {
|
||||
cachedInputs =
|
||||
contactForm.querySelectorAll('input[data-field]:not(.invalid)');
|
||||
}
|
||||
|
||||
for (var i = cachedInputs.length - 1; i >= 0; i--) {
|
||||
if ((cachedInputs[i].value && cachedInputs[i].value.trim()) ||
|
||||
(cachedInputs[i].valueAsDate)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function createDeleteButton(selector, type) {
|
||||
var delButton = document.createElement('button');
|
||||
delButton.className = DELETE_BUTTON_CLASS;
|
||||
delButton.setAttribute('data-l10n-id', 'removeField');
|
||||
delButton.dataset.type = type;
|
||||
delButton.dataset.action = 'delete';
|
||||
delButton.dataset.selector = selector;
|
||||
|
||||
return delButton;
|
||||
}
|
||||
|
||||
function appendRemovePhotoButton() {
|
||||
var button = thumbAction.querySelector('button.' + DELETE_BUTTON_CLASS);
|
||||
if (!button) {
|
||||
button = createDeleteButton(thumbAction.id, 'photo');
|
||||
thumbAction.appendChild(button);
|
||||
}
|
||||
else {
|
||||
// Ensure it is visible
|
||||
button.classList.remove('hide');
|
||||
}
|
||||
thumbAction.classList.add('with-photo');
|
||||
}
|
||||
|
||||
function appendPhoto(resized) {
|
||||
appendRemovePhotoButton();
|
||||
if (!emptyForm()) {
|
||||
saveButton.removeAttribute('disabled');
|
||||
}
|
||||
utils.dom.updatePhoto(resized, thumb);
|
||||
currentPhoto = resized;
|
||||
}
|
||||
|
||||
function photoAction() {
|
||||
if (!!currentPhoto) {
|
||||
removeOrUpdatePhoto();
|
||||
} else {
|
||||
LazyLoader.load(
|
||||
[
|
||||
'/contacts/views/form/js/photo_picker.js'
|
||||
],
|
||||
function() {
|
||||
PhotoPicker.pick(appendPhoto);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function removeOrUpdatePhoto() {
|
||||
LazyLoader.load(
|
||||
[
|
||||
'/contacts/views/form/js/photo_picker.js',
|
||||
'/contacts/js/action_menu.js'
|
||||
],
|
||||
function() {
|
||||
var prompt = new ActionMenu('photo-options');
|
||||
prompt.addToList({id: 'remove-photo'}, removePhoto);
|
||||
|
||||
prompt.addToList(
|
||||
{id: 'change-photo'},
|
||||
PhotoPicker.pick.bind(null, appendPhoto)
|
||||
);
|
||||
|
||||
prompt.show();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function removePhoto() {
|
||||
currentPhoto = null;
|
||||
thumbAction.classList.remove('with-photo');
|
||||
utils.dom.updatePhoto(null, thumb);
|
||||
|
||||
if (!emptyForm()) {
|
||||
saveButton.removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
window.FormUI = {
|
||||
'init': init,
|
||||
'render': render
|
||||
};
|
||||
}(window));
|
||||
7
gaia/apps/communications/contacts/views/form/js/main_navigation.js
Executable file
7
gaia/apps/communications/contacts/views/form/js/main_navigation.js
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
/* global navigationStack */
|
||||
/* exported MainNavigation */
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
exports.MainNavigation = new navigationStack('view-contact-form');
|
||||
}(window));
|
||||
89
gaia/apps/communications/contacts/views/form/js/photo_picker.js
Executable file
89
gaia/apps/communications/contacts/views/form/js/photo_picker.js
Executable file
|
|
@ -0,0 +1,89 @@
|
|||
/* global MozActivity */
|
||||
(function(exports) {
|
||||
|
||||
'use strict';
|
||||
|
||||
// The size we want our contact photos to be
|
||||
const PHOTO_WIDTH = 320;
|
||||
const PHOTO_HEIGHT = 320;
|
||||
// bug 1038414: ask for an image about 2MP before
|
||||
// doing the crop to save memory in both apps
|
||||
const MAX_PHOTO_SIZE = 200000;
|
||||
|
||||
function pickImage(callback) {
|
||||
callback = callback || function foo() {};
|
||||
|
||||
var activity = new MozActivity({
|
||||
name: 'pick',
|
||||
data: {
|
||||
type: 'image/jpeg',
|
||||
maxFileSizeBytes: MAX_PHOTO_SIZE
|
||||
}
|
||||
});
|
||||
|
||||
activity.onsuccess = function() {
|
||||
|
||||
// XXX
|
||||
// this.result.blob is valid now, but it won't stay valid
|
||||
// (see https://bugzilla.mozilla.org/show_bug.cgi?id=806503)
|
||||
// And it might not be the size we want, anyway, so we make
|
||||
// our own copy that is at the right size.
|
||||
resizeBlob(
|
||||
this.result.blob,
|
||||
PHOTO_WIDTH,
|
||||
PHOTO_HEIGHT,
|
||||
callback
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
activity.onerror = function() {
|
||||
window.console.error('Error in the activity', activity.error);
|
||||
};
|
||||
}
|
||||
|
||||
function resizeBlob(blob, target_width, target_height, callback) {
|
||||
var img = document.createElement('img');
|
||||
var url = URL.createObjectURL(blob);
|
||||
img.src = url;
|
||||
|
||||
function cleanupImg() {
|
||||
img.src = '';
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
img.onerror = cleanupImg;
|
||||
|
||||
img.onload = function() {
|
||||
var image_width = img.width;
|
||||
var image_height = img.height;
|
||||
var scalex = image_width / target_width;
|
||||
var scaley = image_height / target_height;
|
||||
var scale = Math.min(scalex, scaley);
|
||||
|
||||
var w = target_width * scale;
|
||||
var h = target_height * scale;
|
||||
var x = (image_width - w) / 2;
|
||||
var y = (image_height - h) / 2;
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = target_width;
|
||||
canvas.height = target_height;
|
||||
var context = canvas.getContext('2d', { willReadFrequently: true });
|
||||
|
||||
context.drawImage(img, x, y, w, h, 0, 0, target_width, target_height);
|
||||
cleanupImg();
|
||||
canvas.toBlob(function(resized) {
|
||||
context = null;
|
||||
canvas.width = canvas.height = 0;
|
||||
canvas = null;
|
||||
callback(resized);
|
||||
} , 'image/jpeg');
|
||||
};
|
||||
}
|
||||
|
||||
exports.PhotoPicker = {
|
||||
pick: pickImage
|
||||
};
|
||||
|
||||
}(window));
|
||||
22
gaia/apps/communications/contacts/views/form/js/throbber.js
Executable file
22
gaia/apps/communications/contacts/views/form/js/throbber.js
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Throbber is just a loading bar at the top of the UI for
|
||||
* showing the progress of the action requested by the user.
|
||||
*
|
||||
* This is needed for low-end devices, where 'saving' or
|
||||
* *updating* a contact could take some seconds.
|
||||
*/
|
||||
|
||||
(function(exports) {
|
||||
|
||||
'use strict';
|
||||
|
||||
var throbberDOM = document.getElementById('throbber');
|
||||
exports.Throbber = {
|
||||
show: function() {
|
||||
throbberDOM.classList.remove('hide');
|
||||
},
|
||||
hide: function() {
|
||||
throbberDOM.classList.add('hide');
|
||||
}
|
||||
};
|
||||
}(window));
|
||||
Loading…
Reference in a new issue