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
242
gaia/apps/bluetooth/js/modules/mvvm/list_view.js
Executable file
242
gaia/apps/bluetooth/js/modules/mvvm/list_view.js
Executable file
|
|
@ -0,0 +1,242 @@
|
|||
'use strict';
|
||||
|
||||
define(function(require) {
|
||||
var elements = new WeakMap();
|
||||
var ObservableArray = require('modules/mvvm/observable_array');
|
||||
|
||||
/*
|
||||
* A ListView takes an ObservableArray or an ordinary array, and generate/
|
||||
* manipulate the corresponding DOM elements of the content in the array using
|
||||
* the given template function. If the array is an ObservableArray, ListView
|
||||
* updates the DOM elements accordingly when the array is manipulated.
|
||||
*/
|
||||
var ListView = function(root, observableArray, templateFunc) {
|
||||
var _observableArray = null;
|
||||
var _root = root;
|
||||
var _templateFunc = templateFunc;
|
||||
var _enabled = true;
|
||||
|
||||
// keep track of observeables so we can unobserve
|
||||
var observerMap = new WeakMap();
|
||||
|
||||
function releaseObservers(element) {
|
||||
var observable = observerMap.get(element);
|
||||
if (!observable || typeof observable !== 'object') {
|
||||
return;
|
||||
}
|
||||
var observers = observerMap.get(observable);
|
||||
if (observers) {
|
||||
observers.forEach(function eachObserver(observer) {
|
||||
observable.unobserve(observer);
|
||||
});
|
||||
observerMap.delete(observable);
|
||||
observerMap.delete(element);
|
||||
}
|
||||
}
|
||||
|
||||
// helper to observe something and automatically remove old observers
|
||||
function observeAndCall(observable, map) {
|
||||
var observers = observerMap.get(observable) || [];
|
||||
if (observerMap.has(observable)) {
|
||||
observers = observerMap.get(observable);
|
||||
}
|
||||
observers = Object.keys(map).map(function eachProperty(property) {
|
||||
observable.observe(property, map[property]);
|
||||
map[property]();
|
||||
return map[property];
|
||||
});
|
||||
observerMap.set(observable, observers);
|
||||
}
|
||||
|
||||
var _helper = {
|
||||
observeAndCall: observeAndCall
|
||||
};
|
||||
|
||||
var _handleEvent = function(event) {
|
||||
// Ignore the change event when the list view is not enabled.
|
||||
if (!_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
var data = event.data;
|
||||
switch (event.type) {
|
||||
case 'insert':
|
||||
_insert(data.index, data.items);
|
||||
break;
|
||||
case 'remove':
|
||||
_remove(data.index, data.count);
|
||||
break;
|
||||
case 'replace':
|
||||
_replace(data.index, data.newValue);
|
||||
break;
|
||||
case 'reset':
|
||||
_reset(data.items || []);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
var _insert = function(index, items) {
|
||||
if (items.length <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// add DOM elements
|
||||
var referenceElement = _root.children[index];
|
||||
for (var i = items.length - 1; i >= 0; i--) {
|
||||
var curElement = _templateFunc(items[i], undefined, _helper);
|
||||
observerMap.set(curElement, items[i]);
|
||||
_root.insertBefore(curElement, referenceElement);
|
||||
referenceElement = curElement;
|
||||
}
|
||||
};
|
||||
|
||||
var _remove = function(index, count) {
|
||||
if (count === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove DOM elements
|
||||
if (count >= _root.childElementCount) {
|
||||
// clear all
|
||||
while (_root.firstElementChild) {
|
||||
releaseObservers(_root.firstElementChild);
|
||||
_root.removeChild(_root.firstElementChild);
|
||||
}
|
||||
} else {
|
||||
var nextElement = _root.children[index];
|
||||
for (var i = 0; i < count; i++) {
|
||||
if (nextElement) {
|
||||
var temp = nextElement.nextElementSibling;
|
||||
releaseObservers(nextElement);
|
||||
_root.removeChild(nextElement);
|
||||
nextElement = temp;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var _replace = function(index, value) {
|
||||
var element = _root.children[index];
|
||||
if (element) {
|
||||
releaseObservers(element);
|
||||
var newElement = _templateFunc(value, element, _helper);
|
||||
observerMap.set(element, value);
|
||||
if (newElement !== element) {
|
||||
_root.insertBefore(newElement, element);
|
||||
_root.removeChild(element);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var _reset = function(items) {
|
||||
var itemCount = items.length;
|
||||
var elementCount = _root.childElementCount;
|
||||
|
||||
if (itemCount === 0) {
|
||||
_remove(0, elementCount);
|
||||
} else if (itemCount <= elementCount) {
|
||||
items.forEach(function(item, index) {
|
||||
_replace(index, item);
|
||||
});
|
||||
// remove extra elements
|
||||
_remove(itemCount, elementCount - itemCount);
|
||||
} else {
|
||||
var slicedItems = items.slice(0, elementCount);
|
||||
var remainingItems = items.slice(elementCount);
|
||||
|
||||
slicedItems.forEach(function(item, index) {
|
||||
_replace(index, item);
|
||||
});
|
||||
// add extra elements
|
||||
_insert(elementCount, remainingItems);
|
||||
}
|
||||
};
|
||||
|
||||
var _enabledChanged = function() {
|
||||
if (_enabled) {
|
||||
_reset(_observableArray.array);
|
||||
}
|
||||
};
|
||||
|
||||
var view = {
|
||||
set: function lv_set(newArray) {
|
||||
if (_observableArray) {
|
||||
_observableArray.unobserve(_handleEvent);
|
||||
}
|
||||
|
||||
if (!newArray) {
|
||||
// clear all existing items
|
||||
if (_observableArray) {
|
||||
_remove(0, _observableArray.length);
|
||||
_observableArray = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If new array is an ordinary array, convert it to an ObservableArray.
|
||||
if (Array.isArray(newArray)) {
|
||||
newArray = ObservableArray(newArray);
|
||||
}
|
||||
_observableArray = newArray;
|
||||
|
||||
// An ObservableArray fires the four events for notifying its changes.
|
||||
// Register to these events for updating UI.
|
||||
_observableArray.observe('insert', _handleEvent);
|
||||
_observableArray.observe('remove', _handleEvent);
|
||||
_observableArray.observe('replace', _handleEvent);
|
||||
_observableArray.observe('reset', _handleEvent);
|
||||
|
||||
if (this.enabled) {
|
||||
_reset(_observableArray.array);
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
// unobserve from array and null everything out
|
||||
if (_observableArray) {
|
||||
_observableArray.unobserve(_handleEvent);
|
||||
}
|
||||
_remove(0, _observableArray.length);
|
||||
elements.delete(_root);
|
||||
_root = null;
|
||||
_observableArray = null;
|
||||
_templateFunc = null;
|
||||
_enabled = false;
|
||||
},
|
||||
|
||||
get element() {
|
||||
return _root;
|
||||
},
|
||||
|
||||
set enabled(value) {
|
||||
if (_enabled !== value) {
|
||||
_enabled = value;
|
||||
_enabledChanged();
|
||||
}
|
||||
},
|
||||
|
||||
get enabled() {
|
||||
return _enabled;
|
||||
}
|
||||
};
|
||||
|
||||
if (elements.has(_root)) {
|
||||
// destroy old ListView if we setup a second one
|
||||
elements.get(_root).destroy();
|
||||
}
|
||||
|
||||
elements.set(_root, view);
|
||||
|
||||
// emtpy element at creation time
|
||||
_root.innerHTML = '';
|
||||
|
||||
view.set(observableArray);
|
||||
return view;
|
||||
};
|
||||
|
||||
return ListView;
|
||||
});
|
||||
102
gaia/apps/bluetooth/js/modules/mvvm/observable.js
Executable file
102
gaia/apps/bluetooth/js/modules/mvvm/observable.js
Executable file
|
|
@ -0,0 +1,102 @@
|
|||
'use strict';
|
||||
|
||||
define(function() {
|
||||
function unobserve(_eventHandlers, prop, handler) {
|
||||
// arguments in reverse order to support .bind(handler) for the
|
||||
// unbind from all case
|
||||
function removeHandler(handler, prop) {
|
||||
var handlers = _eventHandlers[prop];
|
||||
if (!handlers) {
|
||||
return;
|
||||
}
|
||||
var index = handlers.indexOf(handler);
|
||||
if (index >= 0) {
|
||||
handlers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof prop === 'function') {
|
||||
// (handler) -- remove from every key in _eventHandlers
|
||||
Object.keys(_eventHandlers).forEach(removeHandler.bind(null, prop));
|
||||
} else if (handler) {
|
||||
// (prop, handler) -- remove handler from the specific prop
|
||||
removeHandler(handler, prop);
|
||||
} else if (prop in _eventHandlers) {
|
||||
// (prop) -- otherwise remove all handlers for property
|
||||
_eventHandlers[prop] = [];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* An Observable is able to notify its property change. It is initialized by
|
||||
* an ordinary object.
|
||||
*/
|
||||
function Observable(obj) {
|
||||
var _eventHandlers = {};
|
||||
var _observable = {
|
||||
observe: function o_observe(p, handler) {
|
||||
/*
|
||||
* We should check if _observable[_p] exists. Since _observable[_p] is
|
||||
* created along with _eventHandlers[p], here we simply check
|
||||
* _eventHandlers[p].
|
||||
*/
|
||||
var handlers = _eventHandlers[p];
|
||||
if (handlers) {
|
||||
handlers.push(handler);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* unobserve([prop], handler) - remove handler from observeable callbacks
|
||||
*/
|
||||
unobserve: unobserve.bind(null, _eventHandlers)
|
||||
};
|
||||
|
||||
var _getFunctionTemplate = function(p) {
|
||||
return function() {
|
||||
return _observable['_' + p];
|
||||
};
|
||||
};
|
||||
|
||||
var _setFunctionTemplate = function(p) {
|
||||
return function(value) {
|
||||
var oldValue = _observable['_' + p];
|
||||
if (oldValue !== value) {
|
||||
_observable['_' + p] = value;
|
||||
var handlers = _eventHandlers[p];
|
||||
handlers.forEach(function(handler) {
|
||||
handler(value, oldValue);
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* Iterate all properties in the object and create corresponding getter and
|
||||
* setter for them.
|
||||
*/
|
||||
for (var p in obj) {
|
||||
// If p is a function, simply add it to the observable.
|
||||
if (typeof obj[p] === 'function') {
|
||||
_observable[p] = obj[p];
|
||||
continue;
|
||||
}
|
||||
|
||||
_eventHandlers[p] = [];
|
||||
|
||||
Object.defineProperty(_observable, '_' + p, {
|
||||
value: obj[p],
|
||||
writable: true
|
||||
});
|
||||
|
||||
Object.defineProperty(_observable, p, {
|
||||
enumerable: true,
|
||||
get: _getFunctionTemplate(p),
|
||||
set: _setFunctionTemplate(p)
|
||||
});
|
||||
}
|
||||
|
||||
return _observable;
|
||||
}
|
||||
|
||||
return Observable;
|
||||
});
|
||||
153
gaia/apps/bluetooth/js/modules/mvvm/observable_array.js
Executable file
153
gaia/apps/bluetooth/js/modules/mvvm/observable_array.js
Executable file
|
|
@ -0,0 +1,153 @@
|
|||
'use strict';
|
||||
|
||||
define(function() {
|
||||
function unobserve(_eventHandlers, prop, handler) {
|
||||
// arguments in reverse order to support .bind(handler) for the
|
||||
// unbind from all case
|
||||
function removeHandler(handler, prop) {
|
||||
var handlers = _eventHandlers[prop];
|
||||
if (!handlers) {
|
||||
return;
|
||||
}
|
||||
var index = handlers.indexOf(handler);
|
||||
if (index >= 0) {
|
||||
handlers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof prop === 'function') {
|
||||
// (handler) -- remove from every key in _eventHandlers
|
||||
Object.keys(_eventHandlers).forEach(removeHandler.bind(null, prop));
|
||||
} else if (handler) {
|
||||
// (prop, handler) -- remove handler from the specific prop
|
||||
removeHandler(handler, prop);
|
||||
} else if (prop in _eventHandlers) {
|
||||
// (prop) -- otherwise remove all handlers for property
|
||||
_eventHandlers[prop] = [];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* An ObservableArray is able to notify its change through four basic
|
||||
* operations including 'insert', 'remove', 'replace', 'reset'. It is
|
||||
* initialized by an ordinary array.
|
||||
*/
|
||||
function ObservableArray(array) {
|
||||
var _array = array || [];
|
||||
var _eventHandlers = {
|
||||
'insert': [],
|
||||
'remove': [],
|
||||
'replace': [],
|
||||
'reset': []
|
||||
};
|
||||
|
||||
var _notify = function(eventType, data) {
|
||||
var handlers = _eventHandlers[eventType];
|
||||
handlers.forEach(function(handler) {
|
||||
handler({
|
||||
type: eventType,
|
||||
data: data
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
get length() {
|
||||
return _array.length;
|
||||
},
|
||||
|
||||
get array() {
|
||||
return _array;
|
||||
},
|
||||
|
||||
forEach: function oa_foreach(func) {
|
||||
_array.forEach(func);
|
||||
},
|
||||
|
||||
observe: function oa_observe(eventType, handler) {
|
||||
var handlers = _eventHandlers[eventType];
|
||||
if (handlers) {
|
||||
handlers.push(handler);
|
||||
}
|
||||
},
|
||||
|
||||
unobserve: unobserve.bind(null, _eventHandlers),
|
||||
|
||||
push: function oa_push(item) {
|
||||
_array.push(item);
|
||||
|
||||
_notify('insert', {
|
||||
index: _array.length - 1,
|
||||
count: 1,
|
||||
items: [item]
|
||||
});
|
||||
},
|
||||
|
||||
pop: function oa_pop() {
|
||||
if (!_array.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var item = _array.pop();
|
||||
|
||||
_notify('remove', {
|
||||
index: _array.length,
|
||||
count: 1
|
||||
});
|
||||
|
||||
return item;
|
||||
},
|
||||
|
||||
splice: function oa_splice(index, count) {
|
||||
if (arguments.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
var addedItems = Array.prototype.slice.call(arguments, 2);
|
||||
_array.splice.apply(_array, arguments);
|
||||
|
||||
if (count) {
|
||||
_notify('remove', {
|
||||
index: index,
|
||||
count: count
|
||||
});
|
||||
}
|
||||
|
||||
if (addedItems.length) {
|
||||
_notify('insert', {
|
||||
index: index,
|
||||
count: addedItems.length,
|
||||
items: addedItems
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
set: function oa_set(index, value) {
|
||||
if (index < 0 || index >= _array.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var oldValue = _array[index];
|
||||
_array[index] = value;
|
||||
_notify('replace', {
|
||||
index: index,
|
||||
oldValue: oldValue,
|
||||
newValue: value
|
||||
});
|
||||
},
|
||||
|
||||
get: function oa_get(index) {
|
||||
return _array[index];
|
||||
},
|
||||
|
||||
reset: function oa_reset(array) {
|
||||
_array = array;
|
||||
_notify('reset', {
|
||||
items: _array
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return ObservableArray;
|
||||
});
|
||||
Loading…
Reference in a new issue