UNPKG

treant

Version:

Dependency free component library for the browser

1,060 lines (907 loc) 29.6 kB
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.treant = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ var hook = require("./src/hook") var register = require("./src/register") var component = require("./src/create") var storage = require("./src/storage") var Component = require("./src/Component") var delegate = require("./src/delegate") var fragment = require("./src/fragment") var treant = {} module.exports = treant treant.register = register treant.component = component treant.storage = storage treant.Component = Component treant.delegate = delegate treant.fragment = fragment treant.hook = hook var util = {} treant.util = util util.extend = require("./util/extend") util.merge = require("./util/merge") util.object = require("./util/object") },{"./src/Component":3,"./src/create":5,"./src/delegate":6,"./src/fragment":7,"./src/hook":8,"./src/register":9,"./src/storage":11,"./util/extend":12,"./util/merge":13,"./util/object":14}],2:[function(require,module,exports){ 'use strict'; module.exports = function (str) { str = str.trim(); if (str.length === 1 || !(/[_.\- ]+/).test(str) ) { if (str[0] === str[0].toLowerCase() && str.slice(1) !== str.slice(1).toLowerCase()) { return str; } return str.toLowerCase(); } return str .replace(/^[_.\- ]+/, '') .toLowerCase() .replace(/[_.\- ]+(\w|$)/g, function (m, p1) { return p1.toUpperCase(); }); }; },{}],3:[function(require,module,exports){ var hook = require("./hook") var registry = require("./registry") var storage = require("./storage") var delegate = require("./delegate") module.exports = Component function Component (element, options) { if (element && !(element instanceof Element)) { throw new Error("element should be an Element instance or null") } if (!(this instanceof Component)) { return new Component(element, options) } this._element = null this._id = null this.components = {} this.element = element || null } Component.create = function (name, element, options) { var ComponentConstructor = null if (registry.exists(name)) { ComponentConstructor = registry.get(name) } else { console.warn("Missing component definition: ", name) return null } return new ComponentConstructor(element, options) } Component.prototype = { constructor: Component, destroy: function () { storage.remove(this) this.element = null var components = this.components var component for (var name in components) { if (components.hasOwnProperty(name)) { component = components[name] if (component.destroy) { component.destroy() } } } this.components = null }, delegate: function (options) { options.element = this.element options.context = options.context || this return delegate(options) }, action: function (event) { return this.constructor.createAction(event) }, dispatch: function (type, detail) { var definition = this.constructor.getEventDefinition(type, detail) return this.element.dispatchEvent(new window.CustomEvent(type, definition)) }, findComponent: function (name) { return hook.findComponent(name, this.element) }, findAllComponents: function (name) { return hook.findAllComponents(name, this.element) }, findSubComponents: function () { //var subComponents = [] //var element = this.element //this.constructor.parents.forEach(function (ParentComponent) { // var components = hook.findSubComponents(ParentComponent.componentName, element) // subComponents = subComponents.concat(components) //}) //subComponents = subComponents.concat(hook.findSubComponents(this.getMainComponentName(false), element)) //return subComponents return hook.findSubComponents(this.getMainComponentName(false), this.element) }, getComponentName: function (cc) { return hook.getComponentName(this.constructor.componentName, cc) }, getMainComponentName: function (cc) { return hook.getMainComponentName(this.constructor.componentName, cc) }, getSubComponentName: function (cc) { return hook.getSubComponentName(this.constructor.componentName, cc) }, clearSubComponents: function () { var components = this.constructor.components for (var name in components) { if (components.hasOwnProperty(name)) { if (Array.isArray(components[name])) { this.components[name] = [] } else { this.components[name] = components[name] } } } }, assignSubComponents: function (transform) { if (!this.element) return var hostComponent = this var subComponents = this.findSubComponents() var constructor = this.constructor this.clearSubComponents() if (!subComponents.length) { return } if (typeof transform == "undefined" || transform === true) { transform = function (element, name) { // TODO: subclass subcomponents should be handled properly (B extends A that has a subcomponent A:a becomes B:a that's not in the registry) return registry.exists(name) ? Component.create(name, element, hostComponent) : element } } hook.assignSubComponents(this.components, subComponents, transform, function (components, name, element) { if (Array.isArray(constructor.components[name])) { components[name] = components[name] || [] components[name].push(element) } else { components[name] = element } }) } } Object.defineProperty(Component.prototype, "element", { get: function () { return this._element }, set: function (element) { this._element = element if (element && this.constructor.componentName) { if (this.constructor.autoSave) { storage.save(this) } if (this.constructor.autoAssign) { this.assignSubComponents() } this.constructor.resetAttributes(this) } } }) },{"./delegate":6,"./hook":8,"./registry":10,"./storage":11}],4:[function(require,module,exports){ var camelcase = require("camelcase") var extend = require("../util/extend") var merge = require("../util/merge") var object = require("../util/object") var delegate = require("./delegate") var storage = require("./storage") var registry = require("./registry") var hook = require("./hook") var defaultEventDefinition = { detail: null, view: window, bubbles: true, cancelable: true } module.exports = function (CustomComponent, componentName) { CustomComponent.componentName = componentName CustomComponent.autoAssign = true CustomComponent.autoSave = true CustomComponent.components = {} CustomComponent.parents = [] var prototype = CustomComponent.prototype var _events = CustomComponent._events = {} var _constructors = CustomComponent._constructors = [] var _attributes = CustomComponent._attributes = {} var _actions = CustomComponent._actions = [] CustomComponent.extend = function (BaseComponent) { prototype = CustomComponent.prototype = Object.create(BaseComponent.prototype) CustomComponent.prototype.constructor = CustomComponent if (BaseComponent.componentName) { CustomComponent.parents = CustomComponent.parents.concat(BaseComponent.parents) CustomComponent.parents.push(BaseComponent) CustomComponent.autoAssign = BaseComponent.autoAssign extend(CustomComponent.components, BaseComponent.components) extend(_events, BaseComponent._events) _constructors = _constructors.concat(BaseComponent._constructors) extend(_attributes, BaseComponent._attributes) BaseComponent._actions.forEach(function (args) { var event = args[0] var matches = args[1] var matcher = CustomComponent.action(event) matches.forEach(function (args) { matcher.match.apply(matcher, args) }) }) } } CustomComponent.onCreate = function (constructor) { _constructors.push(constructor) return CustomComponent } CustomComponent.create = function (instance, args) { _constructors.forEach(function (constructor) { constructor.apply(instance, args) }) } CustomComponent.method = function (name, fn) { object.method(prototype, name, fn) return CustomComponent } CustomComponent.property = function (name, fn) { object.property(prototype, name, fn) return CustomComponent } CustomComponent.get = function (name, fn) { object.defineGetter(prototype, name, fn) return CustomComponent } CustomComponent.set = function (name, fn) { object.defineSetter(prototype, name, fn) return CustomComponent } CustomComponent.accessor = function (name, get, set) { object.accessor(prototype, name, get, set) return CustomComponent } CustomComponent.proto = function (prototype) { for (var prop in prototype) { if (prototype.hasOwnProperty(prop)) { if (typeof prototype[prop] == "function") { if (prop === "onCreate") { CustomComponent.onCreate(prototype[prop]) } else { CustomComponent.method(prop, prototype[prop]) } } else { CustomComponent.property(prop, prototype[prop]) } } } return CustomComponent } CustomComponent.shortcut = function (name, componentName, extra) { CustomComponent.get(name, function () { var element = this.element.querySelector(hook.selector(componentName, "~=", extra)) return registry.exists(componentName) ? storage.get(element, componentName) : element }) } CustomComponent.action = function action(event) { var matches = [] var action = CustomComponent.createAction(event) var match = action.match _actions.push([event, matches]) action.match = function (components, cb) { matches.push([components, cb]) return match(components, cb) } return action } CustomComponent.createAction = function (event) { var delegator = delegate({element: window, event: event}) var action = {} action.match = function (components, cb) { if (!cb) { cb = components components = [] } if (typeof components == "string") { components = [components] } var selectors = components.map(function (component) { if (component[0] == ":") { component = componentName+component } return hook.selector(component, "~=") }) selectors.unshift(hook.selector(componentName, "~=")) delegator.match(selectors, function (e, main) { var instance = storage.get(main, componentName) || main var instanceComponents = instance.components var args = [e]; [].slice.call(arguments, 2).forEach(function (element, i) { var name = components[i] name = name[0] == ":" ? name.substr(1) : name var propertyName = camelcase(name) var arg if (instanceComponents && instanceComponents.hasOwnProperty(propertyName)) { arg = instance.components[propertyName] if (Array.isArray(arg)) { arg.some(function (member) { if (member == element || member.element == element) { arg = member return true } return false }) } } else { arg = storage.get(element, name) || element } args.push(arg) }) return cb.apply(instance, args) }) return action } return action } CustomComponent.event = function (type, definition) { _events[type] = definition return CustomComponent } CustomComponent.getEventDefinition = function (type, detail) { var definition = merge(defaultEventDefinition, _events[type]) definition.detail = typeof detail == "undefined" ? definition.detail : detail return definition } CustomComponent.resetAttributes = function (instance) { if (!instance.element) return var attribute var value for (var name in _attributes) { if (_attributes.hasOwnProperty(name)) { attribute = _attributes[name] value = attribute.get.call(instance, false) if (attribute.hasDefault && !attribute.has.call(instance, value)) { attribute.set.call(instance, attribute.defaultValue, false) } } } } CustomComponent.attribute = function (name, def) { if (def == null) { def = {} } var typeOfDef = typeof def var type var defaultValue var getter var setter var onchange var property = camelcase(name) switch (typeOfDef) { case "boolean": case "number": case "string": // the definition is a primitive value type = typeOfDef defaultValue = def break case "object": default: // or a definition object defaultValue = typeof def["default"] == "undefined" ? null : def["default"] if (typeof def["type"] == "undefined") { if (defaultValue == null) { type = "string" } else { type = typeof defaultValue } } else { type = def["type"] } getter = def["get"] setter = def["set"] onchange = def["onchange"] } var parseValue var stringifyValue var has has = function (value) { return value != null } switch (type) { case "boolean": has = function (value) { return value !== false } parseValue = function (value) { return value != null } stringifyValue = function () { return "" } break case "number": parseValue = function (value) { return value == null ? null : parseInt(value, 10) } break case "float": parseValue = function (value) { return value == null ? null : parseFloat(value) } break case "string": default: stringifyValue = function (value) { return value == null ? null : value ? ""+value : "" } } _attributes[property] = { get: get, set: set, has: has, defaultValue: defaultValue, hasDefault: defaultValue != null } function get(useDefault) { var value = this.element.getAttribute(name) if (value == null && useDefault == true) { return defaultValue } return parseValue ? parseValue(value) : value } function set(value, callOnchange) { var old = get.call(this, false) if (!has(value)) { this.element.removeAttribute(name) } else if (old === value) { return } else { var newValue = stringifyValue ? stringifyValue(value) : value this.element.setAttribute(name, newValue) } onchange && callOnchange != false && onchange.call(this, old, value) } Object.defineProperty(prototype, property, { get: getter || get, set: setter || set }) return CustomComponent } return CustomComponent } },{"../util/extend":12,"../util/merge":13,"../util/object":14,"./delegate":6,"./hook":8,"./registry":10,"./storage":11,"camelcase":2}],5:[function(require,module,exports){ var Component = require("./Component") var hook = require("./hook") module.exports = component function component (name, root, options) { // component("string"[, {}]) if (!(root instanceof Element)) { options = root root = null } var element = hook.findComponent(name, root) return Component.create(name, element, options) } component.all = function (name, root, options) { // component("string"[, {}]) if (!(root instanceof Element)) { options = root root = null } // component("string"[, Element]) var elements = hook.findAllComponents(name, root) return [].map.call(elements, function (element) { return Component.create(name, element, options) }) } },{"./Component":3,"./hook":8}],6:[function(require,module,exports){ /** * Registers an event listener on an element * and returns a delegator. * A delegated event runs matches to find an event target, * then executes the handler paired with the matcher. * Matchers can check if an event target matches a given selector, * or see if an of its parents do. * */ module.exports = function delegate( options ){ var element = options.element , event = options.event , capture = !!options.capture||false , context = options.context||element if( !element ){ console.log("Can't delegate undefined element") return null } if( !event ){ console.log("Can't delegate undefined event") return null } var delegator = createDelegator(context) element.addEventListener(event, delegator, capture) return delegator } /** * Returns a delegator that can be used as an event listener. * The delegator has static methods which can be used to register handlers. * */ function createDelegator( context ){ var matchers = [] function delegator( e ){ var l = matchers.length if( !l ){ return true } var el = this , i = -1 , handler , selector , delegateElement , stopPropagation , args while( ++i < l ){ args = matchers[i] handler = args[0] selector = args[1] delegateElement = matchCapturePath(selector, el, e) if( delegateElement && delegateElement.length ) { stopPropagation = false === handler.apply(context, [e].concat(delegateElement)) if( stopPropagation ) { return false } } } return true } /** * Registers a handler with a target finder logic * */ delegator.match = function( selector, handler ){ matchers.push([handler, selector]) return delegator } return delegator } function matchCapturePath( selector, el, e ){ var delegateElements = [] var delegateElement = null if( Array.isArray(selector) ){ var i = -1 var l = selector.length while( ++i < l ){ delegateElement = findParent(selector[i], el, e) if( !delegateElement ) return null delegateElements.push(delegateElement) } } else { delegateElement = findParent(selector, el, e) if( !delegateElement ) return null delegateElements.push(delegateElement) } return delegateElements } /** * Check if the target or any of its parent matches a selector * */ function findParent( selector, el, e ){ var target = e.target switch( typeof selector ){ case "string": while( target && target != el ){ if( target.matches && target.matches(selector) ) return target target = target.parentNode } break case "function": while( target && target != el ){ if( selector.call(el, target) ) return target target = target.parentNode } break default: return null } return null } },{}],7:[function(require,module,exports){ var merge = require("../util/merge") module.exports = fragment fragment.options = { variable: "f" } function fragment( html, compiler, compilerOptions ){ compilerOptions = merge(fragment.options, compilerOptions) var render = null return function( templateData ){ var temp = window.document.createElement("div") if( typeof compiler == "function" && !render ){ render = compiler(html, compilerOptions) } if( render ){ try{ html = render(templateData) } catch( e ){ console.error("Error rendering fragment with context:", templateData) console.error(render.toString()) console.error(e) throw e } } temp.innerHTML = html var fragment = window.document.createDocumentFragment() while( temp.childNodes.length ){ fragment.appendChild(temp.firstChild) } return fragment } } fragment.render = function( html, templateData ){ return fragment(html)(templateData) } },{"../util/merge":13}],8:[function(require,module,exports){ var camelcase = require("camelcase") var COMPONENT_ATTRIBUTE = "data-component" var hook = module.exports = {} hook.setHookAttribute = setHookAttribute hook.selector = selector hook.findComponent = findComponent hook.findAllComponents = findAllComponents hook.findSubComponents = findSubComponents hook.getComponentName = getComponentName hook.getMainComponentName = getMainComponentName hook.getSubComponentName = getSubComponentName hook.assignSubComponents = assignSubComponents hook.filter = filter function setHookAttribute (hook) { COMPONENT_ATTRIBUTE = hook } function selector (name, operator, extra) { name = name && '"' + name + '"' operator = name ? operator || "=" : "" extra = extra || "" return "[" + COMPONENT_ATTRIBUTE + operator + name + "]" + extra } function find (selector, root) { return (root || document).querySelector(selector) } function findAll (selector, root) { return (root || document).querySelectorAll(selector) } function findComponent (name, root) { return find(selector(name), root) } function findAllComponents (name, root) { return [].slice.call(findAll(selector(name), root)) } function getComponentName (element, cc) { if (!element) return "" cc = cc == undefined || cc var value = typeof element == "string" ? element : element.getAttribute(COMPONENT_ATTRIBUTE) || "" return cc ? camelcase(value) : value } function getMainComponentName (element, cc) { cc = cc == undefined || cc var value = getComponentName(element, false).split(":") value = value[0] || "" return cc && value ? camelcase(value) : value } function getSubComponentName (element, cc) { cc = cc == undefined || cc var value = getComponentName(element, false).split(":") value = value[1] || "" return cc && value ? camelcase(value) : value } function getComponentNameList (element, cc) { return getComponentName(element, cc).split(/\s+/) } function findSubComponents (mainName, root) { var elements = findAll(selector(mainName+":", "*="), root) return filter(elements, function (element, componentName) { return getComponentNameList(componentName, false).some(function (name) { return getMainComponentName(name, false) == mainName && getSubComponentName(name) }) }) } function assignSubComponents (obj, subComponents, transform, assign) { return subComponents.reduce(function (obj, element) { getComponentNameList(element, false).forEach(function (name) { var subName = getSubComponentName(name, true) element = typeof transform == "function" ? transform(element, name) : element if (typeof assign == "function") { assign(obj, subName, element) } else if (Array.isArray(obj[subName])) { obj[subName].push(element) } else { obj[subName] = element } }) return obj }, obj) } function filter (elements, filter) { switch (typeof filter) { case "function": return [].slice.call(elements).filter(function (element) { return filter(element, getComponentName(element, false)) }) break case "string": return [].slice.call(elements).filter(function (element) { return getComponentName(element) === filter }) break default: return null } } },{"camelcase":2}],9:[function(require,module,exports){ var registry = require("./registry") var Component = require("./Component") var Internals = require("./Internals") module.exports = function register (name, mixin) { mixin = [].slice.call(arguments, 1) function CustomComponent (element, options) { if (!(this instanceof CustomComponent)) { return new CustomComponent(element, options) } var instance = this this.name = name Component.call(instance, element, options) // at this point custom constructors can already access the element and sub components // so they only receive the options object for convenience CustomComponent.create(instance, [options]) } Internals(CustomComponent, name) CustomComponent.extend(Component) CustomComponent.autoAssign = true mixin.forEach(function (mixin) { if (typeof mixin == "function") { if (mixin.componentName) { CustomComponent.extend(mixin) } else { mixin.call(CustomComponent.prototype, CustomComponent) } } else { CustomComponent.proto(mixin) } }) return registry.set(name, CustomComponent) } },{"./Component":3,"./Internals":4,"./registry":10}],10:[function(require,module,exports){ var registry = module.exports = {} var components = {} registry.get = function exists (name) { return components[name] } registry.exists = function exists (name) { return !!components[name] } registry.set = function exists (name, ComponentConstructor) { return components[name] = ComponentConstructor } },{}],11:[function(require,module,exports){ var hook = require("./hook") var camelcase = require("camelcase") var storage = module.exports = {} var components = [] var elements = [] var counter = 0 function createProperty (componentName) { return camelcase(componentName+"-id") } function getId (element, componentName) { return element.dataset[createProperty(componentName)] } function setId (element, componentName, id) { element.dataset[createProperty(componentName)] = id } function hasId (element, componentName) { return !!(element.dataset[createProperty(componentName)]) } function removeId (element, componentName) { if (hasId(element, componentName)) { delete element.dataset[createProperty(componentName)] } } storage.get = function (element, componentName) { var store = components[getId(element, componentName)] return store ? store[componentName] : null } storage.save = function (component) { if (component.element) { var id = component._id var componentName = component.name var store if (!id) { id = ++counter setId(component.element, componentName, id) component._id = id } store = components[id] if (!store) { store = components[id] = {length: 0} } if (store[componentName] !== component) { ++store.length store[componentName] = component } var existingElement = elements[id] if (existingElement) { removeId(existingElement, componentName) setId(component.element, componentName, id) } elements[id] = component.element } } storage.remove = function (component, onlyComponent) { var element = component instanceof Element ? component : component.element var componentName = component.name var id = getId(element, componentName) var store = components[id] if (component instanceof Element) { if (onlyComponent) { if (delete store[onlyComponent]) --store.length } else { for (var prop in store) { if (store.hasOwnProperty(id)) { store[prop]._id = null //--store.length } } delete components[id] } } else { var existing = store[componentName] if (existing == component) { existing._id = null delete store[componentName] --store.length } } if (store && !store.length) { removeId(elements[id], componentName) delete elements[id] } } },{"./hook":8,"camelcase":2}],12:[function(require,module,exports){ module.exports = function extend( obj, extension ){ for( var name in extension ){ if( extension.hasOwnProperty(name) ) obj[name] = extension[name] } return obj } },{}],13:[function(require,module,exports){ var extend = require("./extend") module.exports = function( obj, extension ){ return extend(extend({}, obj), extension) } },{"./extend":12}],14:[function(require,module,exports){ var object = module.exports = {} object.accessor = function (obj, name, get, set) { Object.defineProperty(obj, name, { get: get, set: set }) } object.defineGetter = function (obj, name, fn) { Object.defineProperty(obj, name, { get: fn }) } object.defineSetter = function (obj, name, fn) { Object.defineProperty(obj, name, { set: fn }) } object.method = function (obj, name, fn) { Object.defineProperty(obj, name, { value: fn }) } object.property = function (obj, name, fn) { Object.defineProperty(obj, name, { value: fn, configurable: true }) } },{}]},{},[1])(1) });