VulpesOS/gaia/apps/email/js/element.js
OverSu b38dea0597 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.
2026-09-30 23:13:03 +02:00

195 lines
6.2 KiB
JavaScript
Executable file

/**
* element 0.0.1+
* Copyright (c) 2013-2015, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/jrburke/element for details
*/
/*jshint browser: true */
/*globals define */
define(function(require, exports, module) {
'use strict';
var slice = Array.prototype.slice,
callbackSuffix = 'Callback',
callbackSuffixLength = callbackSuffix.length,
charRegExp = /[^a-z]/g,
idToTag = function(id) {
return id.toLowerCase().replace(charRegExp, '-');
},
moduleConfig = module.config();
if (moduleConfig.hasOwnProperty('idToTag')) {
idToTag = moduleConfig.idToTag;
}
/**
* Converts an attribute like a-long-attr to aLongAttr
* @param {String} attrName The attribute name
* @return {String}
*/
function makePropName(attrName) {
var parts = attrName.split('-');
for (var i = 1; i < parts.length; i++) {
parts[i] = parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
}
return parts.join('');
}
/**
* Given an attribute name, set the corresponding property
* name on the custom element instance, if it has such a
* property.
* @param {Object} instance the custom element instance.
* @param {String} attrName the attribute name.
* @param {String} attrValue The attribute value.
*/
function setPropFromAttr(instance, attrName, attrValue) {
var proto = Object.getPrototypeOf(instance),
propName = makePropName(attrName),
descriptor = Object.getOwnPropertyDescriptor(proto, propName);
// Only check immediate prototype for a property that
// matches, to avoid calling base setters that may be
// on original HTML-based element that could cause
// bad effects. Needs more testing for those cases to
// confirm, but since element is a mixin approach, this
// approach is safe.
if (descriptor && descriptor.set) {
instance[propName] = attrValue;
}
}
function makePropFn(prop) {
return function() {
var i, ret,
args = slice.call(arguments),
fns = this._element.props[prop];
for (i = 0; i < fns.length; i++) {
ret = fns[i].apply(this, args);
}
// Last function wins on the return value.
return ret;
};
}
function mixFnProp(proto, prop, value, operation) {
if (proto.hasOwnProperty(prop)) {
var existing = proto._element.props[prop];
if (!existing) {
existing = proto._element.props[prop] = [proto[prop]];
proto[prop] = makePropFn(prop);
}
operation = operation || 'push';
existing[operation](value);
} else {
proto[prop] = value;
}
}
function mix(proto, mixin) {
// Allow a top level of a mixin to be an array of other
// mixins.
if (Array.isArray(mixin)) {
mixin.forEach(function(mixin) {
mix(proto, mixin);
});
return;
}
Object.keys(mixin).forEach(function(key) {
var suffixIndex,
descriptor = Object.getOwnPropertyDescriptor(mixin, key);
// Any property that ends in Callback, like the custom element
// lifecycle events, can be multiplexed.
suffixIndex = key.indexOf(callbackSuffix);
if (suffixIndex > 0 &&
suffixIndex === key.length - callbackSuffixLength) {
mixFnProp(proto, key, descriptor.value);
} else {
Object.defineProperty(proto, key, descriptor);
}
});
}
/**
* Main module export. These methods are visible to
* any module.
*/
var element = {
/**
* The AMD loader plugin API. Called by an AMD loader
* to handle 'element!' resources.
* @param {String} id module ID to load.
* @param {Function} req context-specific `require` function.
* @param {Function} onload function to call once loading is complete.
* @param {Object} config config from the loader. Normally just has
* config.isBuild if in a build scenario.
*/
load: function(id, req, onload, config) {
// Normal dependency request.
req([id], function(mod) {
// For builds do nothing else. Also if no module export or
// it is a function because the module already called
// document.register itself, then do not bother with the
// other work.
if (config.isBuild || !mod || typeof mod === 'function') {
return onload();
}
// Create the prototype for the custom element.
// Allow the module to be an array of mixins.
// If it is an array, then mix them all in to the
// prototype.
var proto = Object.create(HTMLElement.prototype);
// Define a property to hold all the element-specific information
Object.defineProperty(proto, '_element', {
enumerable: false,
configurable: false,
writable: false,
value: {}
});
proto._element.props = {};
mix(proto, mod);
// Wire attributes to this element's custom/getter setters.
// Because of the 'unshift' use, this will actually execute
// before the templateCreatedCallback, which is good. The
// exterior API should set up the internal state before
// other parts of createdCallback run.
mixFnProp(proto, 'createdCallback', function attrCreated() {
var i, item,
attrs = this.attributes;
for (i = 0; i < attrs.length; i++) {
item = attrs.item(i);
setPropFromAttr(this, item.nodeName, item.value);
}
}, 'unshift');
// Listen for attribute changed calls, and just trigger getter/setter
// calling if matching property. Make sure it is the first one in
// the listener set.
mixFnProp(proto, 'attributeChangedCallback',
function attrChanged(name, oldValue, newValue) {
// Only called if value has changed, so no need to check
// oldValue !== newValue
setPropFromAttr(this, name, newValue);
}, 'unshift');
// Translate any characters that are unfit for custom element
// names to dashes
var tagId = idToTag(id);
onload(document.registerElement(tagId, {
prototype: proto
}));
});
}
};
return element;
});