Comparing version 0.4.0 to 0.5.0
@@ -8,7 +8,10 @@ # Change Log | ||
~~Removed, Changed, Deprecated, Added, Fixed, Security~~ | ||
--wrap in seperate function, async only, normal callback | ||
--esm to cjs | ||
--remove parent getter. use find | ||
--setDocument | ||
## [0.5.0] - 2018-01-21 | ||
### Removed | ||
- `.find`, `.text` | ||
### Changed | ||
- reverted to an hyperscript style API | ||
## [0.4.0] - 2017-06-04 | ||
@@ -15,0 +18,0 @@ ### Removed |
@@ -1,2 +0,2 @@ | ||
var common = require('./config') | ||
var root = require('./root') | ||
@@ -23,3 +23,3 @@ var media = /^$|^all$/ //mediaTypes: all, print, screen, speach | ||
function getSheet() { | ||
var sheets = common.document.styleSheets | ||
var sheets = root.document.styleSheets | ||
// get existing sheet | ||
@@ -30,3 +30,3 @@ for (var i=0; i<sheets.length; ++i) { | ||
// or create a new one | ||
return common.document.head.appendChild(common.document.createElement('style')).sheet | ||
return root.document.head.appendChild(root.document.createElement('style')).sheet | ||
} |
37
el.js
@@ -1,11 +0,34 @@ | ||
var common = require('./config') | ||
var CElement = require('./src/_c-element') | ||
var root = require('./root'), | ||
mount = require('./mount') | ||
var htmlProps = { | ||
id: true, | ||
nodeValue: true, | ||
textContent: true, | ||
className: true, | ||
innerHTML: true, | ||
tabIndex: true, | ||
value: true | ||
} | ||
/** | ||
* @function element | ||
* @param {!string} tagName tagName | ||
* @return {!Object} Component | ||
* @param {string} tagName | ||
* @return {Element} | ||
*/ | ||
module.exports = function element(tagName) { | ||
return new CElement(common.document.createElement(tagName)) | ||
module.exports = function(tagName) { | ||
var node = root.document.createElement(tagName) | ||
for (var i=1; i<arguments.length; ++i) { | ||
var arg = arguments[i] | ||
if (arg != null) { | ||
if (arg.constructor && arg.constructor !== Object) mount(node, arg) | ||
else for (var j=0, ks=Object.keys(arg); j<ks.length; ++j) { | ||
var key = ks[j], | ||
val = arg[key] | ||
if (key === 'style') node.style.cssText = val | ||
else if (typeof val !== 'string' || htmlProps[key]) node[key] = val | ||
else node.setAttribute(key, val) | ||
} | ||
} | ||
} | ||
return node | ||
} |
// @ts-check | ||
/* eslint-disable global-require */ | ||
module.exports = { | ||
component: require('./component'), | ||
text: require('./text'), | ||
co: require('./co'), | ||
el: require('./el'), | ||
svg: require('./svg'), | ||
el: require('./el'), | ||
elNS: require('./elNS'), | ||
list: require('./list'), | ||
select: require('./list'), | ||
find: require('./find'), | ||
mount: require('./mount'), | ||
css: require('./css') | ||
} |
60
list.js
@@ -1,15 +0,8 @@ | ||
var common = require('./config'), | ||
placeItem = require('./src/place-item'), | ||
thisAssign = require('./src/this-assign'), | ||
move = require('./src/list-move'), | ||
remove = require('./src/list-remove') | ||
/** | ||
* @function | ||
* @param {!Function} factory | ||
* @param {Function} [getKey] | ||
* @return {!Object} Component | ||
* @return {List} | ||
*/ | ||
module.exports = function list(factory, getKey) { | ||
return new CKeyed(factory, getKey) | ||
module.exports = function(factory, getKey) { | ||
return new List(factory, getKey) | ||
} | ||
@@ -22,48 +15,13 @@ | ||
*/ | ||
function CKeyed(factory, getKey) { | ||
this.refs = Object.create(null) | ||
function List(factory, getKey) { | ||
this.kids = Object.create(null) //TODO | ||
this.factory = factory | ||
this.getKey = getKey || getIndex | ||
this.node = common.document.createComment('^') | ||
this.foot = common.document.createComment('$') | ||
this.node[common.key] = this | ||
this.foot[common.key] = this | ||
if (getKey) this.getKey = getKey | ||
} | ||
CKeyed.prototype = { | ||
c: thisAssign, | ||
remove: remove, | ||
moveTo: move, | ||
update: updateKeyedChildren, | ||
updateChildren: updateKeyedChildren | ||
} | ||
List.prototype.head = | ||
List.prototype.foot = null | ||
function updateKeyedChildren(arr) { | ||
var foot = this.foot, | ||
parent = foot.parentNode, | ||
spot = this.node.nextSibling, | ||
items = this.refs, | ||
refs = Object.create(null) | ||
if (!parent) throw Error('list update requires a parent node') | ||
for (var i = 0; i < arr.length; ++i) { | ||
var key = this.getKey(arr[i], i, arr), | ||
item = refs[key] = items[key] || this.factory(key, arr[i], i, arr) | ||
// place before update since lists require a parent before update | ||
spot = placeItem(parent, item, spot, foot).nextSibling | ||
if (item.update) item.update(arr[i], i, arr) | ||
} | ||
this.refs = refs | ||
while (spot !== this.foot) { | ||
item = spot[common.key] | ||
spot = (item.foot || item.node).nextSibling | ||
item.remove() | ||
} | ||
return this | ||
} | ||
function getIndex(v,i) { | ||
List.prototype.getKey = function(v,i) { | ||
return i // default: indexed | ||
} |
{ | ||
"name": "attodom", | ||
"version": "0.4.0", | ||
"main": "./dist/index.js", | ||
"module": "./module.js", | ||
"browser": "./dist/browser.js", | ||
"version": "0.5.0", | ||
"main": "./index.js", | ||
"description": "yet another small DOM component library", | ||
@@ -8,0 +6,0 @@ "keywords": [ |
137
readme.md
# attodom | ||
*yet another small DOM component library, <2kb* | ||
*yet another experimental small DOM component library, <2kb* | ||
• [Example](#example) • [Why](#why) • [API](#api) • [License](#license) | ||
• [Why](#why) • [API](#api) • [License](#license) | ||
## Examples | ||
```javascript | ||
var {el, list} = require('attodom'), | ||
Store = require('./Store'), // any user store will do | ||
{ic_remove, ic_add} = require('./icons') //any pre-defined component | ||
var store = new Store([]) | ||
var table = el('table').append( | ||
el('caption').a('class', 'f4').text('table example with...'), | ||
el('tbody').append( | ||
list(function(rowKey) { | ||
return el('tr').append( | ||
el('td') //leading column with icon | ||
.on('click', function() {store.delRow(rowKey) }) | ||
.child(ic_remove.cloneNode(true)), | ||
list(function(colKey) { | ||
return el('td') // data columns | ||
.child( | ||
el('input') | ||
.set('update', function(v) { this.node.value = v }) | ||
.on('change', function() {store.set(this.node.value, [rowKey, colKey]) } ) | ||
) | ||
}) | ||
) | ||
}), | ||
el('tr').append( | ||
el('td') | ||
.on('click', function() { store.addRow() } ) | ||
.child(ic_add) | ||
) | ||
) | ||
) | ||
.moveTo(D.body) | ||
store.onchange = function() { table.update( store.get() ) } | ||
store.set([ | ||
['icons', 'SVG icons'], | ||
['keyed', 'keyed list'], | ||
['store', 'data flow'], | ||
['event', 'event listeners'] | ||
]) | ||
``` | ||
## Why | ||
@@ -63,3 +17,3 @@ | ||
* dynamic lists and nested lists (keyed, indexed or select) | ||
* multiple dynamic lists within the same parent | ||
* svg and namespace support | ||
@@ -81,73 +35,25 @@ * ability to inject a `document API` for server use and/or testing (e.g. `jsdom`) | ||
### Components | ||
### Elements (hyperscript) | ||
Components can be created with the functions el, elNS, svg, text, list, select and component | ||
* `el(tagName [, attributes [,children]] ): HTMLElement` | ||
* `svg(tagName [, attributes [,children]] ): SVGElement` | ||
creating Element component | ||
* `el(tagName): Component` | ||
* `elNS(nsURI, tagName): Component` | ||
* `svg(tagName): Component` | ||
* `component(element): Component` | ||
creating Node components (e.g. TextNode) | ||
* `text(textContent): Component` | ||
* `component(node): Component` | ||
### Components | ||
List (component with multiple or no nodes) | ||
* `list(factory): Component` | ||
* `select(components): Component` | ||
* `co(Element, config, children): Component` | ||
* `.node` the component's associated element | ||
* `.update(value)` the function to trigger changes based on external data | ||
* `.updateChildren(array)` to update all child components | ||
Components have a number of chainable methods: | ||
* component properties: `.c(key, val)` | ||
* node properties: `.p(key, val)` | ||
* element attributes: `.a(key, val)` | ||
* element child: `.append(node | number | string | Array | null, ...)` | ||
* element event listeners: `.on(name, callback)` to add, `on(name, falsy)` to remove | ||
By default, update is set to `updateChildren` for `ParentNodes`, `setValue` form `HTMLInputElement` and `setText` for others. | ||
To change the update function, specify it in the config object `{update: myUpdateFunction}` | ||
### List and Select components | ||
### Lists | ||
`List` and `Select` are special components representing a group of multiple nodes. | ||
* `list(componentFactory, getKey)` | ||
List must be added as children to a component to be activated | ||
`List` take a single factory that will be used to generate list of varying sizes and an optional argument to derive a unique key from individual records | ||
* `list(factory)` to create dynamic indexed set of nodes based on the size of the array upon updates | ||
* `list(factory, function(v) {return v.id}})` for a keyed list | ||
* factory: `(key, val, index, array) => component` | ||
Select lists have predefined components that are used to conditionally display subsets on updates. The optional select function returns the selected keys to show on each updates | ||
* `list({a: componentA, b: componentB}, function(v) {return v ? [a] : [b]})` | ||
lists can be stacked and nested with other components or lists. | ||
#### DOM component properties and methods | ||
* `component.node`: the associated DOM node or comment node anchor node for lists | ||
* `.moveTo(parent [,before])`: to move a component | ||
* `.remove()`: to remove a component from the DOM | ||
#### Component update functions | ||
* `.update(...)` the function to trigger changes based on external data | ||
* `.updateChildren(..)` to pass update data down the tree. | ||
By default, update is set to `text` for text components and `updateChildren` for the other components. | ||
#### Lifecycle events and method wrappers | ||
For additional lifecycle behaviours, component methods can be wrapped (`moveTo`, `remove`). If the arity (number of arguments) of the custom action is greater than the that of the component method, the method will be passed as an argument for async behaviours | ||
* `co.wrap('moveTo', function() { console.log('about to move') })` | ||
* `co.wrap('remove', function(cb) { setTimeout(cb) })` | ||
Wrapper actions are launched before the native method. | ||
### Other helpers | ||
* `find(from [, test] [, until])` find a component within nodes or components and matching the test function. It parses nodes up and down following the html markup order. | ||
* eg. `find(document.body)` to get the first component in the document | ||
* eg. `find(tableComponent, function(c) { return c.key === 5 } )` | ||
* `css(ruleText)` to insert a rule in the document for cases where an exported factory relies on a specific css rule that is not convenient or practical to include in a seperate css file | ||
### Server use | ||
@@ -158,15 +64,8 @@ | ||
```javascript | ||
const config = require('attodom/config') | ||
config.document = myDocumentObject | ||
const root = require('attodom/root') | ||
root.document = myDocumentObject | ||
``` | ||
### Gotcha | ||
* Components may include items that that are not clonable like event listeners and custom properties. As such, they can only be used once. Modules should either export plain nodes (`eg svgElements icons`) or component factory functions. | ||
* `List` and `Select` can't be updated unless they have a parentNode or parent fragment to hold them together. | ||
* e.g. `list(childFactory).moveTo(D.createDocumentFragment()).update()` | ||
## License | ||
[MIT](http://www.opensource.org/licenses/MIT) © [Hugo Villeneuve](https://github.com/hville) |
27
svg.js
@@ -1,12 +0,25 @@ | ||
var common = require('./config') | ||
var CElement = require('./src/_c-element') | ||
var root = require('./root'), | ||
mount = require('./mount') | ||
var svgURI = 'http://www.w3.org/2000/svg' | ||
/** | ||
* @function svg | ||
* @param {!string} tag tagName | ||
* @return {!Object} Component | ||
* @param {string} tagName | ||
* @return {Element} | ||
*/ | ||
module.exports = function svg(tag) { | ||
return new CElement(common.document.createElementNS(svgURI, tag)) | ||
module.exports = function(tagName) { | ||
var node = root.document.createElementNS(svgURI, tagName) | ||
for (var i=1; i<arguments.length; ++i) { | ||
var arg = arguments[i] | ||
if (arg != null) { | ||
if (arg.constructor && arg.constructor !== Object) mount(node, arg) | ||
else for (var j=0, ks=Object.keys(arg); j<ks.length; ++j) { | ||
var key = ks[j], | ||
val = arg[key] | ||
if (typeof val !== 'string') node[key] = val | ||
else node.setAttribute(key, val) | ||
} | ||
} | ||
} | ||
return node | ||
} |
var ct = require('cotest'), | ||
css = require('../css'), | ||
common = require('../config'), | ||
root = require('../root'), | ||
JSDOM = require('jsdom').JSDOM | ||
common.document = (new JSDOM).window.document | ||
root.document = (new JSDOM).window.document | ||
ct('css - add rule', function() { | ||
var sheets = common.document.styleSheets, | ||
var sheets = root.document.styleSheets, | ||
sheet = null, | ||
@@ -11,0 +11,0 @@ match = /myClass/, |
155
tst/list.js
var ct = require('cotest'), | ||
el = require('../el'), | ||
list = require('../list'), | ||
select = require('../select'), | ||
text = require('../text'), | ||
common = require('../config'), | ||
co = require('../co'), | ||
ls = require('../list'), | ||
root = require('../root'), | ||
JSDOM = require('jsdom').JSDOM | ||
var window = (new JSDOM).window | ||
common.document = window.document | ||
root.document = window.document | ||
@@ -19,131 +18,69 @@ function toString(nodes) { | ||
ct('list static', function() { | ||
var childFactory = function() { return el('p').append(text('x')) }, | ||
co = el('div').append(list(childFactory)), | ||
elem = co.node | ||
function setText(t) { this.node.textContent = t } | ||
function childFactory() { | ||
return co(el('p', 'x'), {update: setText}) | ||
} | ||
var comp = co(el('div'), '^', ls(childFactory), '$'), | ||
elem = comp.node | ||
ct('===', toString(elem.childNodes), '^$') | ||
co.update([1,2,3]) | ||
comp.update([1,2,3]) | ||
ct('===', toString(elem.childNodes), '^123$') | ||
co.update([4,3,1,2]) | ||
comp.update([4,3,1,2]) | ||
ct('===', toString(elem.childNodes), '^4312$') | ||
co.update([]) | ||
comp.update([]) | ||
ct('===', toString(elem.childNodes), '^$') | ||
co.update([1,5,3]) | ||
comp.update([1,5,3]) | ||
ct('===', toString(elem.childNodes), '^153$') | ||
}) | ||
ct('list stacked', function() { | ||
var tFactory = function () { return text('') }, | ||
co = el('div').append( | ||
list(tFactory), | ||
list(tFactory), | ||
list(tFactory) | ||
) | ||
var elem = co.node | ||
ct('===', toString(elem.childNodes), '^$^$^$') | ||
co.update([1,2,3]) | ||
ct('===', toString(elem.childNodes), '^123$^123$^123$') | ||
ct('list keyed', function() { | ||
var comp = co(el('h0'), ls( | ||
function() { | ||
return co(el('p'), 'x', {update: function(v) { this.node.textContent = v.v; this.update = null }}) | ||
}, | ||
v => v.k | ||
)) | ||
var elem = comp.node | ||
co.update([4,3,1,2]) | ||
ct('===', toString(elem.childNodes), '^4312$^4312$^4312$') | ||
ct('===', toString(elem.childNodes), '') | ||
co.update([]) | ||
ct('===', toString(elem.childNodes), '^$^$^$') | ||
comp.update([{k: 1, v:1}, {k: 'b', v:'b'}]) | ||
ct('===', toString(elem.childNodes), '1b') | ||
co.update([1,5,3]) | ||
ct('===', toString(elem.childNodes), '^153$^153$^153$') | ||
}) | ||
comp.update([{ k: 'b', v: 'bb' }, { k: 1, v: 11 }]) | ||
ct('===', toString(elem.childNodes), 'b1', 'must use existing nodes') | ||
ct('list stacked and grouped', function() { | ||
var co = el('div').append( | ||
select([ | ||
list(text), | ||
list(text.bind(text,'x')), | ||
list(function() { return text('y') }) | ||
]) | ||
) | ||
comp.update([{k: 'c', v:'c'}]) | ||
ct('===', toString(elem.childNodes), 'c') | ||
var elem = co.node | ||
co.update([1,2,3]) | ||
ct('===', toString(elem.childNodes), '^^123$^123$^123$$') | ||
co.update([4,3,1,2]) | ||
ct('===', toString(elem.childNodes), '^^4312$^4312$^4312$$') | ||
co.update([]) | ||
ct('===', toString(elem.childNodes), '^^$^$^$$') | ||
co.update([1,5,3]) | ||
ct('===', toString(elem.childNodes), '^^153$^153$^153$$') | ||
comp.update([{ k: 'b', v: 'bbb' }, { k: 'c', v: 'ccc' }, { k: 1, v: 111 }]) | ||
ct('===', toString(elem.childNodes), 'bbbc111', 're-creates removed nodes') | ||
}) | ||
ct('list nested', function() { | ||
var childFactory = function() { return el('h0').append(text('')) }, | ||
co = el('div').append( | ||
list(function() { | ||
return list(childFactory) | ||
}) | ||
) | ||
var elem = co.node | ||
ct('list - multiple', function() { | ||
function childFactory() { | ||
return co(el('p')) | ||
} | ||
var comp = co(el('div'), ls(childFactory), ls(childFactory)), | ||
elem = comp.node | ||
ct('===', toString(elem.childNodes), '^$') | ||
ct('===', toString(elem.childNodes), '<>') | ||
co.update([[1,2],[3,4]]) | ||
ct('===', toString(elem.childNodes), '^^12$^34$$') | ||
comp.update([1,2,3]) | ||
ct('===', toString(elem.childNodes), '123<>123') | ||
co.update([[1],[],[2,3,4]]) | ||
ct('===', toString(elem.childNodes), '^^1$^$^234$$') | ||
comp.update([4,3,1,2]) | ||
ct('===', toString(elem.childNodes), '4312<>4312') | ||
co.update([[1,2,3,4]]) | ||
ct('===', toString(elem.childNodes), '^^1234$$') | ||
}) | ||
comp.update([]) | ||
ct('===', toString(elem.childNodes), '<>') | ||
ct('list keyed', function() { | ||
var co = el('h0').append( | ||
list(function() { | ||
return text('x').c('update', function(v) { this.node.textContent = v.v; this.update = null }) | ||
}).c('getKey', v => v.k) | ||
) | ||
var elem = co.node | ||
ct('===', toString(elem.childNodes), '^$') | ||
co.update([{k: 1, v:1}, {k: 'b', v:'b'}]) | ||
ct('===', toString(elem.childNodes), '^1b$') | ||
co.update([{ k: 'b', v: 'bb' }, { k: 1, v: 11 }]) | ||
ct('===', toString(elem.childNodes), '^b1$', 'must use existing nodes') | ||
co.update([{k: 'c', v:'c'}]) | ||
ct('===', toString(elem.childNodes), '^c$') | ||
co.update([{ k: 'b', v: 'bbb' }, { k: 'c', v: 'ccc' }, { k: 1, v: 111 }]) | ||
ct('===', toString(elem.childNodes), '^bbbc111$', 're-creates removed nodes') | ||
comp.update([1,5,3]) | ||
ct('===', toString(elem.childNodes), '153<>153') | ||
}) | ||
ct('list select', function() { | ||
var co = el('h0').append( | ||
select({ | ||
a: text('').c('update', function(v) { this.node.textContent = 'a'+v }), | ||
b: text('').c('update', function(v) { this.node.textContent = 'b'+v }) | ||
}).c('select', v => v) | ||
) | ||
var elem = co.node | ||
ct('===', toString(elem.childNodes), '^$') | ||
co.update('a') | ||
ct('===', toString(elem.childNodes), '^aa$') | ||
co.update('b') | ||
ct('===', toString(elem.childNodes), '^bb$') | ||
co.update('c') | ||
ct('===', toString(elem.childNodes), '^$') | ||
}) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
18146
17
467
69