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
49
gaia/shared/js/contacts/utilities/dom.js
Executable file
49
gaia/shared/js/contacts/utilities/dom.js
Executable file
|
|
@ -0,0 +1,49 @@
|
|||
'use strict';
|
||||
|
||||
var utils = window.utils || {};
|
||||
|
||||
if (!utils.dom) {
|
||||
(function(document) {
|
||||
var Dom = utils.dom = {};
|
||||
|
||||
// remove all children of a given parent node
|
||||
Dom.removeChildNodes = function(node) {
|
||||
while (node.hasChildNodes()) {
|
||||
node.removeChild(node.firstChild);
|
||||
}
|
||||
};
|
||||
|
||||
// Given a container, applys the class passed to all the
|
||||
// nodes matching a selector.
|
||||
// CAUTION: Please be careful using this function as it could
|
||||
// be quite expensive depending on the container and selector.
|
||||
Dom.addClassToNodes = function(container, selector, clazz) {
|
||||
var nodes = container.querySelectorAll(selector);
|
||||
for (var i = 0, n = nodes.length; i < n; ++i) {
|
||||
nodes[i].classList.add(clazz);
|
||||
}
|
||||
};
|
||||
|
||||
// Given a container, removes the class passed to all the
|
||||
// nodes matching a selector.
|
||||
// CAUTION: Please be careful using this function as it could
|
||||
// be quite expensive depending on the container and selector.
|
||||
Dom.removeClassFromNodes = function(container, selector, clazz) {
|
||||
var nodes = container.querySelectorAll(selector);
|
||||
for (var i = 0, n = nodes.length; i < n; ++i) {
|
||||
nodes[i].classList.remove(clazz);
|
||||
}
|
||||
};
|
||||
|
||||
Dom.updatePhoto = function(photo, dest) {
|
||||
var background = '';
|
||||
if (photo != null) {
|
||||
background = 'url(' + URL.createObjectURL(photo) + ')';
|
||||
}
|
||||
dest.style.backgroundImage = background;
|
||||
// Only for testing purposes
|
||||
dest.dataset.photoReady = 'true';
|
||||
};
|
||||
|
||||
})(document);
|
||||
}
|
||||
37
gaia/shared/js/contacts/utilities/event_listeners.js
Executable file
37
gaia/shared/js/contacts/utilities/event_listeners.js
Executable file
|
|
@ -0,0 +1,37 @@
|
|||
/*jshint loopfunc: true */
|
||||
'use strict';
|
||||
|
||||
var utils = window.utils || {};
|
||||
|
||||
if (!utils.listeners) {
|
||||
(function(document) {
|
||||
var Listeners = utils.listeners = {};
|
||||
|
||||
Listeners.add = function(config) {
|
||||
try {
|
||||
for (var id in config) {
|
||||
var handler = config[id];
|
||||
var nodes = document.querySelectorAll(id);
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
if (Array.isArray(handler)) {
|
||||
handler.forEach(function handle(item) {
|
||||
if (!item.hasOwnProperty('event') &&
|
||||
!item.hasOwnProperty('handler')) {
|
||||
return;
|
||||
}
|
||||
node.addEventListener(item.event, item.handler);
|
||||
});
|
||||
} else {
|
||||
node.addEventListener('click', handler);
|
||||
}
|
||||
} // nodes
|
||||
} // Handlers
|
||||
}
|
||||
catch (e) {
|
||||
window.console.error('Error while registering listener for: ', id, e);
|
||||
}
|
||||
}; // Add function
|
||||
|
||||
})(document);
|
||||
}
|
||||
92
gaia/shared/js/contacts/utilities/http_rest.js
Executable file
92
gaia/shared/js/contacts/utilities/http_rest.js
Executable file
|
|
@ -0,0 +1,92 @@
|
|||
'use strict';
|
||||
|
||||
if (!window.Rest) {
|
||||
window.Rest = (function() {
|
||||
|
||||
function RestRequest(xhr) {
|
||||
var cancelled = false;
|
||||
this.cancel = function oncancel() {
|
||||
cancelled = true;
|
||||
window.setTimeout(xhr.abort.bind(xhr), 0);
|
||||
};
|
||||
this.isCancelled = function isCancelled() {
|
||||
return cancelled;
|
||||
};
|
||||
}
|
||||
|
||||
function Rest() { }
|
||||
|
||||
Rest.prototype = {
|
||||
get: function(uri, callback, pOptions) {
|
||||
var DEFAULT_TIMEOUT = 30000;
|
||||
var options = pOptions || {};
|
||||
|
||||
var xhr = new XMLHttpRequest({
|
||||
mozSystem: true
|
||||
});
|
||||
var outReq = new RestRequest(xhr);
|
||||
|
||||
xhr.open('GET', uri, true);
|
||||
var responseType = options.responseType || 'json';
|
||||
xhr.responseType = responseType;
|
||||
var responseProperty = responseType === 'xml' ?
|
||||
'responseXML' : 'response';
|
||||
|
||||
xhr.timeout = options.operationsTimeout || DEFAULT_TIMEOUT;
|
||||
if (!xhr.timeout || xhr.timeout === DEFAULT_TIMEOUT &&
|
||||
(parent && parent.config && parent.config.operationsTimeout)) {
|
||||
xhr.timeout = parent.config.operationsTimeout;
|
||||
}
|
||||
|
||||
if (options.requestHeaders) {
|
||||
for (var header in options.requestHeaders) {
|
||||
xhr.setRequestHeader(header, options.requestHeaders[header]);
|
||||
}
|
||||
}
|
||||
|
||||
xhr.onload = function(e) {
|
||||
if (xhr.status === 200 || xhr.status === 400 || xhr.status === 0) {
|
||||
if (callback && typeof callback.success === 'function') {
|
||||
setTimeout(function() {
|
||||
callback.success(xhr[responseProperty]);
|
||||
},0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.error('HTTP error executing GET. ',
|
||||
uri, ' Status: ', xhr.status);
|
||||
if (callback && typeof callback.error === 'function') {
|
||||
setTimeout(function errorHandler() {
|
||||
callback.error({ status: xhr.status });
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
}; // onload
|
||||
|
||||
xhr.ontimeout = function(e) {
|
||||
console.error('Timeout!!! while HTTP GET: ', uri);
|
||||
if (callback && typeof callback.timeout === 'function') {
|
||||
setTimeout(callback.timeout, 0);
|
||||
}
|
||||
}; // ontimeout
|
||||
|
||||
xhr.onerror = function(e) {
|
||||
console.error('Error while executing HTTP GET: ', uri,
|
||||
': ', e);
|
||||
if (callback && typeof callback.error === 'function' &&
|
||||
!outReq.isCancelled()) {
|
||||
setTimeout(function() {
|
||||
callback.error(e);
|
||||
},0);
|
||||
}
|
||||
}; // onerror
|
||||
|
||||
xhr.send();
|
||||
|
||||
return outReq;
|
||||
} // get
|
||||
}; // prototype
|
||||
|
||||
return new Rest();
|
||||
})();
|
||||
}
|
||||
103
gaia/shared/js/contacts/utilities/ice_store.js
Executable file
103
gaia/shared/js/contacts/utilities/ice_store.js
Executable file
|
|
@ -0,0 +1,103 @@
|
|||
/* global Promise, Event */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Helper library to deal with in case of emergency (ICE)
|
||||
* Contacts.
|
||||
* Those contacts are suppose to be first responders to
|
||||
* an emergency. More info:
|
||||
* https://wiki.mozilla.org/Gaia/Comms/2.1/Ice_Contacts
|
||||
*
|
||||
* The information will be stored in a DS for sharing
|
||||
* between contacts and dialer.
|
||||
* The content will be an array of two contacts ids.
|
||||
* This helper will be used for fetching:
|
||||
* ICEStore.getContacts().then(function(iceContacts) {
|
||||
* ...
|
||||
* });
|
||||
* and setting the data:
|
||||
* ICEStore.setContacts(ids).then(function() {
|
||||
* ...
|
||||
* });
|
||||
*/
|
||||
(function(exports) {
|
||||
var STORE_NAME = 'ice_contacts';
|
||||
var store = null;
|
||||
var status = 'not_initialized';
|
||||
var FIELD = 'ICE_CONTACTS';
|
||||
|
||||
// Get a reference to the store, or returns it if
|
||||
// we already have.
|
||||
function init() {
|
||||
if (store !== null) {
|
||||
return Promise.resolve(store);
|
||||
}
|
||||
|
||||
if (!navigator.getDataStores) {
|
||||
return Promise.reject('No DS supported');
|
||||
}
|
||||
|
||||
if (status === 'initialising') {
|
||||
document.addEventListener('ice_ready', function ready() {
|
||||
document.removeEventListener('ice_ready', ready);
|
||||
if (store === null) {
|
||||
return Promise.reject('Error getting store');
|
||||
} else {
|
||||
return Promise.resolve(store);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
status = 'initialising';
|
||||
return new Promise(function(resolve, reject) {
|
||||
navigator.getDataStores(STORE_NAME).then(function(stores) {
|
||||
if (!stores || stores.length < 0) {
|
||||
document.dispatchEvent(new Event('on_ice_ready'));
|
||||
return reject('No DS found');
|
||||
} else {
|
||||
store = stores[0];
|
||||
status = 'initialised';
|
||||
document.dispatchEvent(new Event('ice_ready'));
|
||||
return resolve(store);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var ICEStore = {
|
||||
/**
|
||||
* Set the in case of emergency contacts.
|
||||
* @param {Array} iceContacts array of contacts id.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
setContacts: function(iceContacts) {
|
||||
return init().then(function() {
|
||||
return store.put(iceContacts, FIELD).then(function() {
|
||||
return Promise.resolve(iceContacts);
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Retrieves the in case of emergency contacts
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getContacts: function() {
|
||||
return init().then(function() {
|
||||
return store.get(FIELD);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Subscribe to changes in the store
|
||||
* @param {Function} Callback when a change happens
|
||||
*/
|
||||
onChange: function(cb) {
|
||||
return init().then(function() {
|
||||
store.addEventListener('change', function() {
|
||||
store.get(FIELD).then(cb);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.ICEStore = ICEStore;
|
||||
})(window);
|
||||
207
gaia/shared/js/contacts/utilities/image_loader.js
Executable file
207
gaia/shared/js/contacts/utilities/image_loader.js
Executable file
|
|
@ -0,0 +1,207 @@
|
|||
/* exported ImageLoader */
|
||||
'use strict';
|
||||
|
||||
if (!window.ImageLoader) {
|
||||
var ImageLoader = function ImageLoader(pContainer, pItems) {
|
||||
var container, items, itemsSelector, lastScrollTime, scrollLatency = 100,
|
||||
scrollTimer, itemHeight, total, imgsLoading = 0,
|
||||
loadImage = defaultLoadImage, self = this;
|
||||
|
||||
init(pContainer, pItems);
|
||||
|
||||
/**
|
||||
* Initializer
|
||||
*
|
||||
*/
|
||||
function init(pContainer, pItems) {
|
||||
itemsSelector = pItems;
|
||||
container = document.querySelector(pContainer);
|
||||
|
||||
attachHandlers();
|
||||
window.addEventListener('image-loader-resume', resuming);
|
||||
window.addEventListener('image-loader-pause', unload);
|
||||
load();
|
||||
}
|
||||
|
||||
function resuming() {
|
||||
window.clearTimeout(scrollTimer);
|
||||
attachHandlers();
|
||||
update();
|
||||
}
|
||||
|
||||
function onUpdate(evt) {
|
||||
evt.stopPropagation();
|
||||
onScroll();
|
||||
}
|
||||
|
||||
function load() {
|
||||
window.clearTimeout(scrollTimer);
|
||||
scrollTimer = null;
|
||||
items = container.querySelectorAll(itemsSelector);
|
||||
// All items have the same height
|
||||
itemHeight = items[0] ? items[0].offsetHeight : 1;
|
||||
total = items.length;
|
||||
// Initial check if items should appear
|
||||
window.setTimeout(update, 0);
|
||||
}
|
||||
|
||||
function attachHandlers() {
|
||||
container.addEventListener('scroll', onScroll);
|
||||
document.addEventListener('onupdate', onUpdate);
|
||||
}
|
||||
|
||||
function unload() {
|
||||
container.removeEventListener('scroll', onScroll);
|
||||
document.removeEventListener('onupdate', onUpdate);
|
||||
window.clearTimeout(scrollTimer);
|
||||
scrollTimer = null;
|
||||
}
|
||||
|
||||
function setResolver(pResolver) {
|
||||
loadImage = pResolver;
|
||||
}
|
||||
|
||||
function onScroll() {
|
||||
if (imgsLoading > 0) {
|
||||
// Stop the pending images load
|
||||
window.stop();
|
||||
imgsLoading = 0;
|
||||
}
|
||||
// Clearing and setting a timer on every scroll event is too slow on
|
||||
// some mobile devices. Therefore, set a timer once here and then
|
||||
// check how long it has been since the last scroll event in the
|
||||
// timer handler to determine what to do.
|
||||
lastScrollTime = Date.now();
|
||||
if (!scrollTimer) {
|
||||
scrollTimer = window.setTimeout(updateFromScroll, scrollLatency);
|
||||
}
|
||||
}
|
||||
|
||||
function updateFromScroll() {
|
||||
scrollTimer = null;
|
||||
// If we scrolled more since the timer was set, then we need to
|
||||
// delay again. Otherwise, go ahead and update now.
|
||||
var deltaLatency = lastScrollTime - Date.now() + scrollLatency;
|
||||
if (deltaLatency > 0) {
|
||||
scrollTimer = window.setTimeout(updateFromScroll, deltaLatency);
|
||||
} else {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the image contained in a DOM Element.
|
||||
*/
|
||||
function defaultLoadImage(item) {
|
||||
var image = item.querySelector('span[data-type=img][data-src]');
|
||||
if (!image) {
|
||||
image = item.querySelector('img[data-src]');
|
||||
if (!image) {
|
||||
// Image by default
|
||||
image = item.querySelector('span[data-type=img][data-group]');
|
||||
if (image) {
|
||||
item.dataset.visited = 'true';
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
++imgsLoading;
|
||||
var tmp = new Image();
|
||||
var src = tmp.src = image.dataset.src;
|
||||
tmp.onload = function onload() {
|
||||
--imgsLoading;
|
||||
image.style.backgroundImage = 'url(' + src + ')';
|
||||
if (tmp.complete) {
|
||||
item.dataset.visited = 'true';
|
||||
}
|
||||
tmp = null;
|
||||
};
|
||||
|
||||
tmp.onabort = tmp.onerror = function onerror() {
|
||||
--imgsLoading;
|
||||
item.dataset.visited = 'false';
|
||||
tmp = null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the set of items are in the current viewport
|
||||
*
|
||||
*/
|
||||
function update() {
|
||||
if (total === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var viewTop = container.scrollTop;
|
||||
// Index is always inside or below viewport
|
||||
var index = Math.floor(viewTop / itemHeight);
|
||||
var containerHeight = container.offsetHeight;
|
||||
|
||||
// Goes backward
|
||||
for (var i = index; i >= 0; i--) {
|
||||
var item = items[i];
|
||||
if (item) {
|
||||
if (item.offsetTop + itemHeight < viewTop) {
|
||||
break; // Over
|
||||
}
|
||||
|
||||
if (item.dataset.visited !== 'true' &&
|
||||
item.offsetTop <= viewTop + containerHeight) {
|
||||
loadImage(item, self); // Inside
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Goes forward
|
||||
for (var j = index + 1; j < total; j++) {
|
||||
var theItem = items[j];
|
||||
if (!theItem) {
|
||||
// Returning because of index out of bound
|
||||
return;
|
||||
}
|
||||
|
||||
if (theItem.offsetTop > viewTop + containerHeight) {
|
||||
return; // Below
|
||||
}
|
||||
|
||||
if (theItem.dataset.visited !== 'true') {
|
||||
loadImage(theItem, self);
|
||||
}
|
||||
}
|
||||
} // update
|
||||
|
||||
function releaseImage(item) {
|
||||
var image = item.querySelector('span[data-type=img][data-src]');
|
||||
if (!image) {
|
||||
// Image by default
|
||||
image = item.querySelector('span[data-type=img][data-group]');
|
||||
if (image) {
|
||||
item.dataset.visited = 'false';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
image.style.backgroundImage = 'none';
|
||||
item.dataset.visited = 'false';
|
||||
return image;
|
||||
}
|
||||
|
||||
// It's necessary to prevent unit test errors
|
||||
function destroy() {
|
||||
unload();
|
||||
window.removeEventListener('image-loader-pause', unload);
|
||||
window.removeEventListener('image-loader-resume', resuming);
|
||||
container = items = itemsSelector = lastScrollTime = scrollLatency = null;
|
||||
scrollTimer = itemHeight = total = imgsLoading = loadImage = null;
|
||||
}
|
||||
|
||||
this.reload = load;
|
||||
this.unload = unload;
|
||||
this.setResolver = setResolver;
|
||||
this.defaultLoad = defaultLoadImage;
|
||||
this.releaseImage = releaseImage;
|
||||
this.destroy = destroy;
|
||||
};
|
||||
|
||||
}
|
||||
41
gaia/shared/js/contacts/utilities/image_square.js
Executable file
41
gaia/shared/js/contacts/utilities/image_square.js
Executable file
|
|
@ -0,0 +1,41 @@
|
|||
'use strict';
|
||||
|
||||
var utils = window.utils || {};
|
||||
|
||||
if (typeof utils.squareImage === 'undefined') {
|
||||
utils.squareImage = function(blob, 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 onBlobLoad() {
|
||||
var width = img.width;
|
||||
var height = img.height;
|
||||
|
||||
if (width === height) {
|
||||
cleanupImg();
|
||||
callback(blob);
|
||||
} else {
|
||||
var canvas = document.createElement('canvas');
|
||||
var min = canvas.width = canvas.height = Math.min(width, height);
|
||||
var context = canvas.getContext('2d', { willReadFrequently: true });
|
||||
context.drawImage(img, (width - min) / 2, (height - min) / 2, min, min,
|
||||
0, 0, min, min);
|
||||
cleanupImg();
|
||||
canvas.toBlob(function onCanvasToBlob(canvasBlob) {
|
||||
context = null;
|
||||
canvas.width = canvas.height = 0;
|
||||
canvas = null;
|
||||
callback(canvasBlob);
|
||||
}, 'image/jpeg', 0.95);
|
||||
}
|
||||
};
|
||||
}; // utils.squareImage
|
||||
} // if
|
||||
77
gaia/shared/js/contacts/utilities/image_thumbnail.js
Executable file
77
gaia/shared/js/contacts/utilities/image_thumbnail.js
Executable file
|
|
@ -0,0 +1,77 @@
|
|||
'use strict';
|
||||
|
||||
/* global ImageUtils, LazyLoader */
|
||||
|
||||
/*
|
||||
* WARNING: This module LazyLoads
|
||||
*
|
||||
* '/shared/js/contacts/import/utilities/config.js',
|
||||
* '/shared/js/image_utils.js'
|
||||
*
|
||||
*/
|
||||
|
||||
var utils = window.utils || {};
|
||||
|
||||
(function(utils) {
|
||||
var DEPENDENCIES = [
|
||||
'/shared/js/contacts/import/utilities/config.js',
|
||||
'/shared/js/image_utils.js'
|
||||
];
|
||||
|
||||
var dpr = window.devicePixelRatio || 1;
|
||||
|
||||
var CONFIG_FILE = '/config-images.json';
|
||||
|
||||
// Default data for creating a thumbnail. This will be used if a config file
|
||||
// is not found
|
||||
var DEFAULT_CONFIG = {
|
||||
'thumbnail' : {
|
||||
'format': 'image/jpeg',
|
||||
'size': 65,
|
||||
'quality': 1.0
|
||||
}
|
||||
};
|
||||
var THUMB_CONFIG = DEFAULT_CONFIG.thumbnail;
|
||||
|
||||
if (typeof utils.thumbnailImage !== 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Obtains the thumbnail configuration by looking for the config file
|
||||
// If no config file is found, then default values are taken
|
||||
function getThumbsConfig() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
utils.config.load(CONFIG_FILE).then(function resolved(config) {
|
||||
resolve(config);
|
||||
}, function rejected() {
|
||||
resolve(DEFAULT_CONFIG);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function scaleImage(blob, configData) {
|
||||
var thumbConfig = configData && configData.thumbnail || {};
|
||||
|
||||
var thumbnailEdge = (thumbConfig.size || THUMB_CONFIG.size) * dpr;
|
||||
var format = thumbConfig.format || THUMB_CONFIG.format;
|
||||
var encodingQuality = thumbConfig.quality || THUMB_CONFIG.quality;
|
||||
|
||||
return ImageUtils.resizeAndCropToCover(blob, thumbnailEdge,
|
||||
thumbnailEdge, format,
|
||||
encodingQuality);
|
||||
}
|
||||
|
||||
// We keep the aspect ratio and make the smallest edge be
|
||||
// |thumbnailEdge| long.
|
||||
utils.thumbnailImage = function(blob, callback) {
|
||||
LazyLoader.load(DEPENDENCIES, function() {
|
||||
getThumbsConfig().then(function(config) {
|
||||
return scaleImage(blob, config);
|
||||
}).then(callback).catch(function(err) {
|
||||
console.error('Error while converting image to thumbnail:', err.name);
|
||||
callback(blob);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
})(utils);
|
||||
261
gaia/shared/js/contacts/utilities/templates.js
Executable file
261
gaia/shared/js/contacts/utilities/templates.js
Executable file
|
|
@ -0,0 +1,261 @@
|
|||
'use strict';
|
||||
|
||||
var utils = window.utils || {};
|
||||
|
||||
if (!utils.templates) {
|
||||
(function() {
|
||||
var Templates = utils.templates = {};
|
||||
|
||||
/**
|
||||
* Returns a target HTMLElement from a selector or HTMLElement itself
|
||||
*
|
||||
* @param {HTMLElement or Selector} element target element.
|
||||
*
|
||||
* @return {HTMLElement} HTMLElement according to the selector or itself.
|
||||
*
|
||||
*
|
||||
*/
|
||||
function getTarget(element) {
|
||||
var target = element;
|
||||
if (!element.tagName) {
|
||||
target = document.querySelector(element);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a target HTML element which contains a template set
|
||||
* returns the template that will have to be applied over the data
|
||||
*
|
||||
* @param {HTMLElement} target which contains the template.
|
||||
* @param {Object} data to be used on the template.
|
||||
*
|
||||
* @return {HTMLElement} HTMLElement with the template.
|
||||
*
|
||||
*/
|
||||
function getTemplate(target, data) {
|
||||
// Multi templates temporarily disabled to avoid eval usage
|
||||
// TODO: Implement an alternative for eval
|
||||
var templates = target.querySelectorAll('*[data-template]');
|
||||
if (templates.length === 0) {
|
||||
throw new Error('No template declared');
|
||||
}
|
||||
if (templates.length > 1 && templates.item(0).dataset.condition) {
|
||||
throw new Error('Only one template supported in this version');
|
||||
}
|
||||
|
||||
return {
|
||||
template: templates.item(0),
|
||||
isMulti: false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function used to replace data on a template
|
||||
*
|
||||
* @param {Object} data the data to be used on the template.
|
||||
*
|
||||
* @return {function} to be used.
|
||||
*
|
||||
*/
|
||||
function templateReplace(data) {
|
||||
return function(text, property) {
|
||||
var out;
|
||||
if (property.indexOf('.') === -1) {
|
||||
out = data[property];
|
||||
if (Array.isArray(out) && out.length > 0) {
|
||||
out = out[0];
|
||||
}
|
||||
} else {
|
||||
throw new Error('Dotted expressions not supported');
|
||||
}
|
||||
|
||||
if (typeof out === 'undefined') {
|
||||
out = text;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds (append or prepend) a new instance HTMLElement (or array of)
|
||||
* of a template
|
||||
* The template is assumed to be a child of the element
|
||||
* passed as parameter
|
||||
* The new element will be appended as a child
|
||||
*
|
||||
* @param {HTMLElement} ele container lement that contains the template
|
||||
* and which will contain the new instance. Can be an HTMLElement
|
||||
* or a CSS selector.
|
||||
*
|
||||
* @param {object or array} data with the data displayed by the template.
|
||||
*
|
||||
* @param {String} mode oneOf ('A','P').
|
||||
*
|
||||
* @return {HTMLElement} (or last element if data is an array).
|
||||
*
|
||||
*
|
||||
*/
|
||||
function add(element, data, mode, targetNode) {
|
||||
// It is supported both the element itself or a selector
|
||||
var target = getTarget(element);
|
||||
var newElem;
|
||||
|
||||
var theData = [data];
|
||||
if (data instanceof Array) {
|
||||
theData = data;
|
||||
}
|
||||
|
||||
// Optimization to avoid trying to find a template when
|
||||
// only one is needed
|
||||
var multiTemplate = true;
|
||||
var template;
|
||||
var idx = 0;
|
||||
theData.forEach(function(oneData) {
|
||||
// Pseudo-field with the index
|
||||
oneData._idx_ = idx++;
|
||||
// A suitable template for the data is firstly found
|
||||
if (multiTemplate === true) {
|
||||
var tresult = getTemplate(target, oneData);
|
||||
template = tresult.template;
|
||||
if (tresult.isMulti === false) {
|
||||
multiTemplate = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (template) {
|
||||
newElem = this.render(template, oneData);
|
||||
target = targetNode || target;
|
||||
if (mode === 'A') {
|
||||
target.appendChild(newElem);
|
||||
} else if (mode === 'P') { // Append mode
|
||||
if (target.firstChild) {
|
||||
target.insertBefore(newElem, element.firstChild);
|
||||
} else {
|
||||
target.appendChild(newElem);
|
||||
}
|
||||
} // prepend mode
|
||||
|
||||
} // if template
|
||||
|
||||
}.bind(Templates)); // forEach data
|
||||
|
||||
return newElem;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Appends a new instance HTMLElement (or array of) of a template
|
||||
*
|
||||
* The template is assumed to be a child of the element passed
|
||||
* as parameter
|
||||
* The new element will be appended as a child
|
||||
*
|
||||
* @param {HTMLElement or String} ele container element that
|
||||
* contains the template and which will contain the new instance.
|
||||
* Can be an HTMLElement or a CSS selector.
|
||||
*
|
||||
* @param {object or array} data with the data displayed by the template.
|
||||
*
|
||||
* @return {HTMLelement} (or last element if data is an array).
|
||||
*
|
||||
*
|
||||
*/
|
||||
Templates.append = function(element, data, targetNode) {
|
||||
var f = add.bind(this);
|
||||
|
||||
return f(element, data, 'A', targetNode);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Prepends a new instance (or array of) of a template
|
||||
*
|
||||
* The template is assumed to be a child of the element passed
|
||||
* as parameter
|
||||
*
|
||||
* @param {HTMLElement or String} ele container element that
|
||||
* contains the template and which will contain the new instance.
|
||||
* Can be an HTMLElement or a CSS selector.
|
||||
*
|
||||
* @param {Object or Array} data with the data displayed.
|
||||
*
|
||||
* @return {HTMLElement} added.
|
||||
*
|
||||
*
|
||||
*/
|
||||
Templates.prepend = function(element, data, targetNode) {
|
||||
var f = add.bind(this);
|
||||
|
||||
return f(element, data, 'P', targetNode);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Renders the content specified by a template with object data
|
||||
*
|
||||
* @param {HTMLElement} eleTemplate the template itself.
|
||||
* @param {Object} data the data to be used.
|
||||
*
|
||||
* @return {HTMLElement} according to the template and with the data.
|
||||
*
|
||||
*
|
||||
*/
|
||||
Templates.render = function(eleTemplate, data) {
|
||||
var newElem = eleTemplate.cloneNode(true);
|
||||
newElem.removeAttribute('data-template');
|
||||
newElem.removeAttribute('data-condition');
|
||||
|
||||
/* var pattern = /#(\w+)#/g; */
|
||||
var pattern = /#(\w+[\w.]*)#/g;
|
||||
|
||||
var inner = newElem.innerHTML;
|
||||
|
||||
// Replace function
|
||||
var replaceFunction = templateReplace(data);
|
||||
var ninner = inner.replace(pattern, replaceFunction);
|
||||
|
||||
newElem.innerHTML = ninner;
|
||||
|
||||
var attrs = newElem.attributes;
|
||||
|
||||
var total = attrs.length;
|
||||
for (var c = 0; c < total; c++) {
|
||||
var val = attrs[c].value;
|
||||
var nval = val.replace(pattern, replaceFunction);
|
||||
|
||||
newElem.setAttribute(attrs[c].name, nval);
|
||||
}
|
||||
|
||||
if (!newElem.id) {
|
||||
if (data.id) {
|
||||
newElem.id = data.id;
|
||||
}
|
||||
}
|
||||
|
||||
return newElem;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears a container element
|
||||
*
|
||||
* @param {HTMLElement or String} element (selector or HTML element).
|
||||
*
|
||||
*
|
||||
*/
|
||||
Templates.clear = function(element) {
|
||||
var target = getTarget(element);
|
||||
|
||||
var templates = target.querySelectorAll('*[data-template]');
|
||||
|
||||
target.innerHTML = '';
|
||||
|
||||
var total = templates.length;
|
||||
|
||||
for (var c = 0; c < total; c++) {
|
||||
target.appendChild(templates.item(c));
|
||||
}
|
||||
};
|
||||
}) ();
|
||||
} // window.templates
|
||||
Loading…
Reference in a new issue