treant
Version:
Dependency free component library for the browser
322 lines (277 loc) • 9.09 kB
JavaScript
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
}