Repository: KoryNunn/fastn
Branch: master
Commit: 665896739c8d
Files: 39
Total size: 132.1 KB
Directory structure:
gitextract_cj4o0y9g/
├── .gitignore
├── .npmrc
├── .travis.yml
├── CHANGELOG.md
├── README.md
├── baseComponent.js
├── binding.js
├── containerComponent.js
├── domComponents.js
├── fancyProps.js
├── firmer.js
├── genericComponent.js
├── index.js
├── is.js
├── listComponent.js
├── package.json
├── property.js
├── schedule.js
├── templaterComponent.js
├── test/
│ ├── attach.js
│ ├── binding.js
│ ├── changes.js
│ ├── component.js
│ ├── components.js
│ ├── container.js
│ ├── createFastn.js
│ ├── customBinding.js
│ ├── customModel.js
│ ├── document.js
│ ├── fancyProps.js
│ ├── firmer.js
│ ├── generic.js
│ ├── index.html
│ ├── index.js
│ ├── list.js
│ ├── property.js
│ ├── templater.js
│ └── text.js
└── textComponent.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
**/*.browser.js
node_modules
*.log
================================================
FILE: .npmrc
================================================
package-lock=false
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- '12'
addons:
apt:
packages:
- xvfb
install:
- export DISPLAY=':99.0'
- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
- npm install
script: npm run test
================================================
FILE: CHANGELOG.md
================================================
# Changelog for fastn
## v2
Version two of fastn changes the way the component constructors work, to allow for better composition of components.
In fastn v1, component constructors would create a component, modify it, then return it.
In fastn v2, component constructors are passed a component, and they may extend it with functionality.
The v1 way:
```
function myCoolComponentConstructor(fastn, type, settings, children){
var component = fastn.base(type, settings, children);
// Add properties, implement/override methods, etc...
return component;
}
```
The v2 way:
```
function myCoolComponentConstructor(fastn, component, type, settings, children){
// Add properties, implement/override methods, etc...
return component;
}
```
## Extending
In v1, if you wanted to make a component that was an extension of another component, you would do something like this:
```
function myCoolFancyList(fastn, type, settings, children){
// Create a list.
var component = fastn.createComponent('list', settings, children);
// Add properties, implement/override methods, etc...
return component;
}
```
in v2, you can just call .extend()...
```
function myCoolComponentConstructor(fastn, component, type, settings, children){
// Become a list.
component.extend('list', settings, children);
// Add properties, implement/override methods, etc...
return component;
}
```
Which is extremely handy if you want features from multiple components:
```
function myCoolComponentConstructor(fastn, component, type, settings, children){
// Become a list.
component.extend('list', settings, children);
// Also be a modal
component.extend('modal', settings, children);
// Also be a whatever
component.extend('whatever', settings, children);
// Add properties, implement/override methods, etc...
return component;
}
```
# Why
In fastn v2, you can mix components together when you create them, like so:
```
var myMapList = fastn('list:map', { ... });
```
Fastn will, under the covers, extend all the types together in the order they are listed, so the above example is equivilent to:
fastn('list', settings, children...).extend('map', settings, children...);
## Details
### API
#### Removed
- fastn.createComponent
#### Changed
- componant constructor parameters (fastn, type, settings, children) -> (fastn, component, type, settings, children)
- component.setProperty can now be passed only a key, which will use the existing property, or create a new default one for that key.
#### Added
- Mixin syntax, fastn('componantType1:componantType2')
- componant.extend(componantType, settings, children)
- componant.is(componantType) -> bool
- fastn.componants._container is now defaulted to containerComponant.
### Best Practice
#### Composition
In v1, you could add functionality to a componant arbitrarily, with no real structure
In v2, obviously, using the fastn('foo:bar') style is recommended.
#### Adding properties
In v1, properties were generally added via
```
property.addTo(componant, key);
```
In v2 this is deprecated, and it is encouraged that you instead use:
```
componant.setProperty('key', property);
```
================================================
FILE: README.md
================================================
# fastn
Create ultra-lightweight UI components
[](https://travis-ci.org/KoryNunn/fastn)
[](https://gitter.im/KoryNunn/fastn?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

## [Homepage](http://korynunn.github.io/fastn/), [Try it](http://korynunn.github.io/fastn/try/), [Example app](http://korynunn.github.io/fastn/example/)
# Usage
The absolute minimum required to make a fastn component:
initialise fastn with default DOM components:
```javascript
var fastn = require('fastn')(
// Default components for rendering DOM.
require('fastn/domComponents')(/* optional extra constructors */)
);
```
Or use your own selection of constructors:
```javascript
// Require and initialise fastn
var fastn = require('fastn')({
// component constructors.. Add what you need to use
text: require('fastn/textComponent'), // Renders text
_generic: require('fastn/genericComponent') // Renders DOM nodes
});
```
Make components:
```javascript
var something = fastn('h1', 'Hello World');
```
Put them on the screen:
```javascript
something.render();
window.addEventListener('load', function(){
document.body.appendChild(something.element);
});
```
[^ try it](http://korynunn.github.io/fastn/try/#InJldHVybiBmYXN0bignaDEnLCAnSGVsbG8gV29ybGQnKTsi)
`fastn` is a function with the signature:
```javascript
fastn(type[, settings, children...])
```
which can be used to create a UI:
```javascript
// Create some component
var someComponent = fastn('section',
fastn('h1', 'I\'m a component! :D'),
fastn('a', {href: 'http://google.com'}, 'An anchor')
);
someComponent.render();
// Append the components element to the DOM
document.body.appendChild(someComponent.element);
```
[^ try it](http://korynunn.github.io/fastn/try/#InJldHVybiBmYXN0bignc2VjdGlvbicsXG5cdGZhc3RuKCdoMScsICdJXFwnbSBhIGNvbXBvbmVudCEgOkQnKSxcblx0ZmFzdG4oJ2EnLCB7aHJlZjogJ2h0dHA6Ly9nb29nbGUuY29tJ30sICdBbiBhbmNob3InKVxuKTsi)
You can assign bindings to properties:
```javascript
var someComponent = fastn('section',
fastn('h1', 'I\'m a component! :D'),
fastn('a', {href: fastn.binding('url')},
fastn('label', 'This link points to '),
fastn('label', fastn.binding('url'))
)
);
someComponent.attach({
url: 'http://google.com'
});
```
Which can be updated via a number of methods.
```javascript
someComponent.scope().set('url', 'http://bing.com');
```
[^ try it](http://korynunn.github.io/fastn/try/#InZhciBzb21lQ29tcG9uZW50ID0gZmFzdG4oJ3NlY3Rpb24nLFxuICAgICAgICBmYXN0bignaDEnLCAnSVxcJ20gYSBjb21wb25lbnQhIDpEJyksXG4gICAgICAgIGZhc3RuKCdhJywge2hyZWY6IGZhc3RuLmJpbmRpbmcoJ3VybCcpfSxcbiAgICAgICAgICAgIGZhc3RuKCdsYWJlbCcsICdUaGlzIGxpbmsgcG9pbnRzIHRvICcpLFxuICAgICAgICAgICAgZmFzdG4oJ2xhYmVsJywgZmFzdG4uYmluZGluZygndXJsJykpXG4gICAgICAgIClcbiAgICApO1xuXG5zb21lQ29tcG9uZW50LmF0dGFjaCh7XG4gICAgdXJsOiAnaHR0cDovL2dvb2dsZS5jb20nXG59KTtcblxuc2V0VGltZW91dChmdW5jdGlvbigpe1xuXHRzb21lQ29tcG9uZW50LnNjb3BlKCkuc2V0KCd1cmwnLCAnaHR0cDovL2JpbmcuY29tJyk7XG59LCAyMDAwKTtcblxucmV0dXJuIHNvbWVDb21wb25lbnQ7Ig==)
## Special component types
There are a few special component types that are used as shorthands for some situations:
### `text`
if a string or `binding` is added as a child into a containerComponent, fastn will look for a `text` component, set it's `text` to the string or `binding`, and insert it. This is handy as you don't need to write: `fastn('text', 'foo')` all over the place.
[^ try it](http://korynunn.github.io/fastn/try/#InJldHVybiBmYXN0bignZGl2Jyxcblx0ZmFzdG4oJ3RleHQnLCB7dGV4dDogJ0V4cGxpY2l0IHRleHQsICd9KSxcblx0J0ltcGxpY2l0IHRleHQsICcsXG4gICAgZmFzdG4uYmluZGluZygnYm91bmRUZXh0JylcbikuYXR0YWNoKHtcbiAgXHRib3VuZFRleHQ6ICdCb3VuZCB0ZXh0J1xufSk7Ig==)
### `_generic`
If the type passed to fastn does not exactly match any known components, fastn will check for a `_generic` component, and pass all the settings and children through to it.
[^ try it](http://korynunn.github.io/fastn/try/#InJldHVybiBmYXN0bignZGl2JyxcbiAgICAgICAgICAgICBcblx0ZmFzdG4oJ3NwYW4nLCAnV29vIGEgc3BhbiEnKSxcbiAgICAgICAgICAgICBcblx0ZmFzdG4oJ2JyJyksIC8vIEJyIGJlY2F1c2Ugd2UgY2FuIVxuICAgICAgICAgICAgIFxuICAgIGZhc3RuKCdhJywge2hyZWY6ICdodHRwczovL2dpdGh1Yi5jb20va29yeW51bm4vZmFzdG4nfSwgJ0FuIGFuY2hvcicpLFxuICAgICAgICAgICAgIFxuXHRmYXN0bignYnInKSwgLy8gQW5vdGhlciBiciBmb3IgcmVhc29uc1xuICAgICAgICAgICAgIFxuICAgIGZhc3RuKCdpbWcnLCB7dGl0bGU6ICdBd2Vzb21lIGxvZ28nLCBzcmM6ICdodHRwOi8va29yeW51bm4uZ2l0aHViLmlvL2Zhc3RuL3RyeS9mYXN0bi1zbWwucG5nJ30pXG4pOyI=)
## Default components
fastn includes 4 extremely simple default components that render as DOM nodes. It is not necessary to use them, and you can replace them with your own, enabling you to render to anything you want to.
### textComponent
A default handler for the `text` component type that renders a textNode. e.g.:
```javascript
fastn('something', // render a thing
'Some string passed as a child' // falls into the `text` component, renders as a textNode
)
```
### genericComponent
A default handler for the `_generic` component type that renders DOM nodes based on the type passed, e.g.:
```javascript
fastn('div') // no component is assigned to 'div', fastn will search for _generic, and if this component is assigned to it, it will create a div element.
```
### listComponent
Takes a template and inserts children based on the result of its `items` property, e.g.:
```javascript
fastn('list', {
items: [1,2,3],
template: function(){
return fastn.binding('item')
}
})
```
Templated components will be attached to a model that contains `key` and `item`, where `key` is the key in the set that they correspond to, and `item` is the data of the item in the set.
#### Lazy templating
If you need to render a huge list of items, and you're noticing a UI hang, you can choose to enable
lazy templating by setting a lists' `insertionFrameTime` to some value:
```javascript
fastn('list', {
insertionFrameTime: 32, // Only render items for 32 milliseconds at a time before awaiting idle time.
items: [1,2,3],
template: function(){
return fastn.binding('item')
}
})
```
### templaterComponent
Takes a template and replaces itself with the component rendered by the template. Returning null from the template indicates that nothing should be inserted.
The template function will be passed the last component that was rendered by it as the third parameter.
Note: The template function will run immediately upon component creation. This means that if your data is a binding, the first run of the template function will always receive undefined as it's item.
```javascript
fastn('templater', {
data: 'foo',
template: function(model, scope, currentComponent){
if(model.get('item') === 'foo'){
return fastn('img');
}else{
return null;
}
}
})
```
An optional property of `attachTemplates` can be provided. When set to true (default), the children rendered from the template will be attached to a new scope that contains the templater data, under the key of `item`.
When set to false, the children rendered from the template will inherit their attachment from the templator.
```javascript
var appData = {
foo: 'bar'
};
fastn('templater', {
data: binding('foo'),
attachTemplates: false,
template: function(model, scope, currentComponent){
// model.get('item') -> is appData
}
}).attach(appData)
```
## A little deeper..
A component can be created by calling `fastn` with a `type`, like so:
```javascript
var myComponent = fastn('myComponent');
```
This will create a component registered in `components` with the key `'myComponent'`
If `'myComponent'` is not found, fastn will check for a `'_generic'` constructor, and use that if defined. The generic component will create a DOM element of the given type passed in, and is likely the most common component you will create.
```javascript
var divComponent = fastn('div', {'class':'myDiv'});
```
The above will create a `component`, that renders as a `div` with a class of `'myDiv'`
__the default genericComponent will automatically convert all keys in the settings object to properties.__
## `fastn.binding(key)`
Creates a binding with the given key.
A binding can be attached to data using `.attach(object)`.
# The Bits..
There are very few parts to fastn, they are:
`component`, `property`, and `binding`
If you are just want to render some DOM, you will probably be able to just use the default ones.
## `component`
A fastn `component` is an object that represents a chunk of UI.
```javascript
var someComponent = fastn('componentType', settings (optional), children (optional)...)
```
## `property`
A fastn `property` is a getterSetter function and EventEmitter.
```javascript
var someProperty = fastn.property(defaultValue, changes (optional), updater (optional));
// get it's value
someProperty(); // returns it's value;
// set it's value
someProperty(anything); // sets anything and returns the property.
// add a change handler
someProperty.on('change', function(value){
// value is the properties new value.
});
```
Properties can be added to components in a number of ways:
via the settings object:
```javascript
var component = fastn('div', {
property: someProperty
});
```
at a later point via property.addTo(component, key);
```javascript
someProperty.addTo(component, 'someProperty');
```
## `binding`
A fastn `binding` is a getterSetter function and `EventEmitter`.
It is used as a mapping between an object and a key or path on that object.
The path syntax is identical to that used in [enti](https://github.com/KoryNunn/enti#paths)
```javascript
var someBinding = fastn.binding('foo');
// get it's value
someBinding(); // returns it's value;
// set it's value
someBinding(anything); // sets anything and returns the binding.
// add a change handler
someBinding.on('change', function(value){
// value is the properties new value.
});
```
You can pass multiple paths or other bindings to a binding, along with a fuse function, to combine them into a single result:
```javascript
var anotherBinding = fastn.binding('bar', 'baz', someBinding, function(bar, baz, foo){
return bar + foo;
});
```
### `binding.from(value)`
- if value is a binding: return `value`,
- else: return a binding who's value is `value`.
useful when you don't know what something is, but you need it in a binding:
```javascript
var someBinding = fastn.binding('someKey', fastn.binding.from(couldBeAnything), function(someValue, valueOfAnything){
});
```
### A note on the difference between `properties` and `bindings`
On the surface, properties and bindings look very similar.
They can both be used like getter/setter functions, and they both emit change events.
They differ both in usage and implementation in that properties don't have any awareness of a model or paths,
and bindings don't have any awareness of components.
This distinction shines when you design your application with 'services' or 'controllers' that encapsulate models and how to interact with them.
Check out the example applications [search service](https://github.com/KoryNunn/fastn/blob/gh-pages/example/search.js) and
[search bar component](https://github.com/KoryNunn/fastn/blob/gh-pages/example/searchBar.js).
The service only deals with data, and the component only deals with UI.
# Browser Support
Fastn works in all the latest evergreen browsers.
Fastn *May* work in other browsers, but will almost certainly need a few polyfills like WeakMap, Map, WeakSet, Set, etc...
================================================
FILE: baseComponent.js
================================================
var is = require('./is'),
GENERIC = '_generic',
EventEmitter = require('events').EventEmitter,
slice = Array.prototype.slice;
function flatten(item){
return Array.isArray(item) ? item.reduce(function(result, element){
if(element == null){
return result;
}
return result.concat(flatten(element));
},[]) : item;
}
function attachProperties(object, firm){
for(var key in this._properties){
this._properties[key].attach(object, firm);
}
}
function onRender(){
// Ensure all bindings are somewhat attached just before rendering
this.attach(undefined, 0);
for(var key in this._properties){
this._properties[key].update();
}
}
function detachProperties(firm){
for(var key in this._properties){
this._properties[key].detach(firm);
}
}
function destroyProperties(){
for(var key in this._properties){
this._properties[key].destroy();
}
}
function clone(){
return this.fastn(this.component._type, this.component._settings, this.component._children.filter(function(child){
return !child._templated;
}).map(function(child){
return typeof child === 'object' ? child.clone() : child;
})
);
}
function getSetBinding(newBinding){
if(!arguments.length){
return this.binding;
}
if(!is.binding(newBinding)){
newBinding = this.fastn.binding(newBinding);
}
if(this.binding && this.binding !== newBinding){
this.binding.removeListener('change', this.emitAttach);
newBinding.attach(this.binding._model, this.binding._firm);
}
this.binding = newBinding;
this.binding.on('change', this.emitAttach);
this.binding.on('detach', this.emitDetach);
this.emitAttach();
return this.component;
};
function emitAttach(){
var newBound = this.binding();
if(newBound !== this.lastBound){
this.lastBound = newBound;
this.scope.attach(this.lastBound);
this.component.emit('attach', this.scope, 1);
}
}
function emitDetach(){
this.component.emit('detach', 1);
}
function getScope(){
return this.scope;
}
function destroy(){
if(this.destroyed){
return;
}
this.destroyed = true;
this.component
.removeAllListeners('render')
.removeAllListeners('attach');
this.component.emit('destroy');
this.component.element = null;
this.scope.destroy();
this.binding.destroy(true);
return this.component;
}
function attachComponent(object, firm){
this.binding.attach(object, firm);
return this.component;
}
function detachComponent(firm){
this.binding.detach(firm);
return this.component;
}
function isDestroyed(){
return this.destroyed;
}
function setProperty(key, property){
// Add a default property or use the one already there
if(!property){
property = this.component[key] || this.fastn.property();
}
this.component[key] = property;
this.component._properties[key] = property;
return this.component;
}
function bindInternalProperty(component, model, propertyName, propertyTransform){
if(!(propertyName in component)){
component.setProperty(propertyName);
}
component[propertyName].on('change', function(value){
model.set(propertyName, propertyTransform ? propertyTransform(value) : value);
});
}
function createInternalScope(data, propertyTransforms){
var componentScope = this;
var model = new componentScope.fastn.Model(data);
for(var key in data){
bindInternalProperty(componentScope.component, model, key, propertyTransforms[key]);
}
return {
binding: function(){
return componentScope.fastn.binding.apply(null, arguments).attach(model);
},
model: model
};
}
function extendComponent(type, settings, children){
var component = this.component;
if(type in this.types){
return component;
}
if(!(type in this.fastn.components)){
if(!(GENERIC in this.fastn.components)){
throw new Error('No component of type "' + type + '" is loaded');
}
component = this.fastn.components._generic(this.fastn, this.component, type, settings, children, createInternalScope.bind(this));
if(component){
this.types._generic = true;
}
}else{
component = this.fastn.components[type](this.fastn, this.component, type, settings, children, createInternalScope.bind(this));
}
if(component){
this.types[type] = true;
}
return component;
};
function isType(type){
return type in this.types;
}
function FastnComponent(fastn, type, settings, children){
var component = this;
var componentScope = {
types: {},
fastn: fastn,
component: component,
binding: fastn.binding('.'),
destroyed: false,
scope: new fastn.Model(false),
lastBound: null
};
componentScope.emitAttach = emitAttach.bind(componentScope);
componentScope.emitDetach = emitDetach.bind(componentScope);
componentScope.binding._default_binding = true;
component._type = type;
component._properties = {};
component._settings = settings || {};
component._children = children ? flatten(children) : [];
component.attach = attachComponent.bind(componentScope);
component.detach = detachComponent.bind(componentScope);
component.scope = getScope.bind(componentScope);
component.destroy = destroy.bind(componentScope);
component.destroyed = isDestroyed.bind(componentScope);
component.binding = getSetBinding.bind(componentScope);
component.setProperty = setProperty.bind(componentScope);
component.clone = clone.bind(componentScope);
component.children = slice.bind(component._children);
component.extend = extendComponent.bind(componentScope);
component.is = isType.bind(componentScope);
component.binding(componentScope.binding);
component.on('attach', attachProperties.bind(this));
component.on('render', onRender.bind(this));
component.on('detach', detachProperties.bind(this));
component.on('destroy', destroyProperties.bind(this));
if(fastn.debug){
component.on('render', function(){
if(component.element && typeof component.element === 'object'){
component.element._component = component;
}
});
}
}
FastnComponent.prototype = Object.create(EventEmitter.prototype);
FastnComponent.prototype.constructor = FastnComponent;
FastnComponent.prototype._fastn_component = true;
module.exports = FastnComponent;
================================================
FILE: binding.js
================================================
var is = require('./is'),
firmer = require('./firmer'),
functionEmitter = require('function-emitter'),
setPrototypeOf = require('setprototypeof'),
same = require('same-value');
function noop(x){
return x;
}
function fuseBinding(){
var fastn = this,
args = Array.prototype.slice.call(arguments);
var bindings = args.slice(),
transform = bindings.pop(),
updateTransform,
resultBinding = createBinding.call(fastn),
selfChanging;
resultBinding._arguments = args;
if(typeof bindings[bindings.length-1] === 'function' && !is.binding(bindings[bindings.length-1])){
updateTransform = transform;
transform = bindings.pop();
}
resultBinding._model.removeAllListeners();
resultBinding._set = function(value){
if(updateTransform){
selfChanging = true;
var newValue = updateTransform(value);
if(!same(newValue, bindings[0]())){
bindings[0](newValue);
resultBinding._change(newValue);
}
selfChanging = false;
}else{
resultBinding._change(value);
}
};
function change(){
if(selfChanging){
return;
}
resultBinding(transform.apply(null, bindings.map(function(binding){
return binding();
})));
}
resultBinding.on('detach', function(firm){
bindings.forEach(function(binding, index){
binding.detach(firm);
});
});
resultBinding.once('destroy', function(soft){
bindings.forEach(function(binding, index){
binding.removeListener('change', change);
binding.destroy(soft);
});
});
bindings.forEach(function(binding, index){
if(!is.binding(binding)){
binding = createBinding.call(fastn, binding);
bindings[index] = binding;
}
binding.on('change', change);
});
var lastAttached;
resultBinding.attach = function(object, firm){
if(firmer(resultBinding, firm)){
return resultBinding;
}
resultBinding._firm = firm;
selfChanging = true;
bindings.forEach(function(binding){
binding.attach(object, 1);
});
selfChanging = false;
if(lastAttached !== object){
change();
}
lastAttached = object;
resultBinding._model.attach(object);
resultBinding.emit('attach', object, firm);
return resultBinding;
}
return resultBinding;
}
function createValueBinding(fastn){
var valueBinding = createBinding.call(fastn, 'value');
valueBinding.attach = function(){return valueBinding;};
valueBinding.detach = function(){return valueBinding;};
return valueBinding;
}
function bindingTemplate(newValue){
if(!arguments.length){
return this.value;
}
if(this.binding._fastn_binding === '.'){
return;
}
this.binding._set(newValue);
return this.binding;
}
function modelAttachHandler(data){
var bindingScope = this;
bindingScope.binding._model.attach(data);
bindingScope.binding._change(bindingScope.binding._model.get(bindingScope.path));
bindingScope.binding.emit('attach', data, 1);
}
function modelDetachHandler(){
this.binding._model.detach();
}
function attach(object, firm){
var bindingScope = this;
var binding = bindingScope.binding;
// If the binding is being asked to attach loosly to an object,
// but it has already been defined as being firmly attached, do not attach.
if(firmer(binding, firm)){
return binding;
}
binding._firm = firm;
var isModel = bindingScope.fastn.isModel(object);
if(isModel && bindingScope.attachedModel === object){
return binding;
}
if(bindingScope.attachedModel){
bindingScope.attachedModel.removeListener('attach', bindingScope.modelAttachHandler);
bindingScope.attachedModel.removeListener('detach', bindingScope.modelDetachHandler);
bindingScope.attachedModel = null;
}
if(isModel){
bindingScope.attachedModel = object;
bindingScope.attachedModel.on('attach', bindingScope.modelAttachHandler);
bindingScope.attachedModel.on('detach', bindingScope.modelDetachHandler);
object = object._model;
}
if(binding._model._model === object){
return binding;
}
bindingScope.modelAttachHandler(object);
return binding;
};
function detach(firm){
if(firmer(this.binding, firm)){
return this.binding;
}
this.value = undefined;
if(this.binding._model.isAttached()){
this.binding._model.detach();
}
this.binding.emit('detach', 1);
return this.binding;
}
function set(newValue){
var bindingScope = this;
if(same(bindingScope.binding._model.get(bindingScope.path), newValue)){
return;
}
if(!bindingScope.binding._model.isAttached()){
bindingScope.binding._model.attach(bindingScope.binding._model.get('.'));
}
bindingScope.binding._model.set(bindingScope.path, newValue);
}
function change(newValue){
var bindingScope = this;
if(newValue === undefined && bindingScope.value === newValue && !bindingScope.binding._model._model){
return;
}
bindingScope.value = newValue;
bindingScope.binding.emit('change', bindingScope.binding());
}
function clone(keepAttachment){
var bindingScope = this;
var newBinding = createBinding.apply(bindingScope.fastn, bindingScope.binding._arguments);
if(keepAttachment){
newBinding.attach(bindingScope.attachedModel || bindingScope.binding._model._model, bindingScope.binding._firm);
}
return newBinding;
}
function destroy(soft){
var bindingScope = this;
if(bindingScope.isDestroyed){
return;
}
if(soft){
return;
}
bindingScope.isDestroyed = true;
bindingScope.binding.emit('destroy', true);
bindingScope.binding.detach();
bindingScope.binding._model.destroy();
}
function destroyed(){
return this.isDestroyed;
}
function createBinding(path, more){
var fastn = this;
if(more){ // used instead of arguments.length for performance
return fuseBinding.apply(fastn, arguments);
}
if(is.binding(path)){
return createBinding.call(this, path, noop);
}
if(arguments.length === 0){
return createValueBinding(fastn);
}
if(!(typeof path === 'string' || typeof path === 'number')){
throw new Error('Invalid path for fastn.binding(String/Number), saw: ', JSON.stringify(path))
}
var bindingScope = {
fastn: fastn,
path: path
},
binding = bindingScope.binding = bindingTemplate.bind(bindingScope);
setPrototypeOf(binding, functionEmitter);
binding.setMaxListeners(10000);
binding._arguments = [path];
binding._model = new fastn.Model(false);
binding._fastn_binding = path;
binding._firm = -Infinity;
bindingScope.modelAttachHandler = modelAttachHandler.bind(bindingScope);
bindingScope.modelDetachHandler = modelDetachHandler.bind(bindingScope);
binding.attach = attach.bind(bindingScope);
binding.detach = detach.bind(bindingScope);
binding._set = set.bind(bindingScope);
binding._change = change.bind(bindingScope);
binding.clone = clone.bind(bindingScope);
binding.destroy = destroy.bind(bindingScope);
binding.destroyed = destroyed.bind(bindingScope);
if(path !== '.'){
binding._model.on(path, binding._change);
}
return binding;
}
function from(valueOrBinding){
if(is.binding(valueOrBinding)){
return valueOrBinding;
}
var result = this();
result(valueOrBinding)
return result;
}
module.exports = function(fastn){
var binding = createBinding.bind(fastn);
binding.from = from.bind(binding);
return binding;
};
================================================
FILE: containerComponent.js
================================================
function insertChild(fastn, container, child, index){
if(child == null || child === false){
return;
}
if(child.destroyed && child.destroyed()){
throw new Error('Attempted to mount a destroyed componet. Are you re-using a componet that you stored in a variable?')
}
var currentIndex = container._children.indexOf(child),
newComponent = fastn.toComponent(child);
if(newComponent !== child && ~currentIndex){
container._children.splice(currentIndex, 1, newComponent);
}
if(!~currentIndex || newComponent !== child){
newComponent.attach(container.scope(), 1);
}
if(currentIndex !== index){
if(~currentIndex){
container._children.splice(currentIndex, 1);
}
container._children.splice(index, 0, newComponent);
}
if(container.element){
if(!newComponent.element){
newComponent.render();
}
container._insert(newComponent.element, index);
newComponent.emit('insert', container);
container.emit('childInsert', newComponent);
}
}
function getContainerElement(){
return this.containerElement || this.element;
}
function insert(child, index){
var childComponent = child,
container = this.container,
fastn = this.fastn;
if(index && typeof index === 'object'){
childComponent = Array.prototype.slice.call(arguments);
}
if(isNaN(index)){
index = container._children.length;
}
if(Array.isArray(childComponent)){
for (var i = 0; i < childComponent.length; i++) {
container.insert(childComponent[i], i + index);
}
}else{
insertChild(fastn, container, childComponent, index);
}
return container;
}
module.exports = function(fastn, component, type, settings, children){
component.insert = insert.bind({
container: component,
fastn: fastn
});
component._insert = function(element, index){
var containerElement = component.getContainerElement();
if(!containerElement){
return;
}
if(containerElement.childNodes[index] === element){
return;
}
containerElement.insertBefore(element, containerElement.childNodes[index]);
};
component.remove = function(childComponent){
var index = component._children.indexOf(childComponent);
if(~index){
component._children.splice(index,1);
}
childComponent.detach(1);
if(childComponent.element){
component._remove(childComponent.element);
childComponent.emit('remove', component);
}
component.emit('childRemove', childComponent);
};
component._remove = function(element){
var containerElement = component.getContainerElement();
if(!element || !containerElement || element.parentNode !== containerElement){
return;
}
containerElement.removeChild(element);
};
component.empty = function(){
while(component._children.length){
component.remove(component._children.pop());
}
};
component.replaceChild = function(oldChild, newChild){
var index = component._children.indexOf(oldChild);
if(!~index){
return;
}
component.remove(oldChild);
component.insert(newChild, index);
};
component.getContainerElement = getContainerElement.bind(component);
component.on('render', component.insert.bind(null, component._children, 0));
component.on('attach', function(model, firm){
for(var i = 0; i < component._children.length; i++){
if(fastn.isComponent(component._children[i])){
component._children[i].attach(model, firm);
}
}
});
component.on('destroy', function(data, firm){
for(var i = 0; i < component._children.length; i++){
if(fastn.isComponent(component._children[i])){
component._children[i].destroy(firm);
}
}
});
return component;
};
================================================
FILE: domComponents.js
================================================
module.exports = function(extra){
var components = {
// The _generic component is a catch-all for any component type that
// doesnt match any other component constructor, eg: 'div'
_generic: require('./genericComponent'),
// The text component is used to render text or bindings passed as children to other components.
text: require('./textComponent'),
// The list component is used to render items based on a set of data.
list: require('./listComponent'),
// The templater component is used to render one item based on some value.
templater: require('./templaterComponent')
};
if(extra){
Object.keys(extra).forEach(function(key){
components[key] = extra[key];
});
}
return components;
}
================================================
FILE: fancyProps.js
================================================
var setify = require('setify'),
classist = require('classist');
function updateTextProperty(generic, element, value){
if(arguments.length === 2){
return element.textContent;
}
element.textContent = (value == null ? '' : value);
}
module.exports = {
class: function(generic, element, value){
if(!generic._classist){
generic._classist = classist(element);
}
if(arguments.length < 3){
return generic._classist();
}
generic._classist(value);
},
display: function(generic, element, value){
if(arguments.length === 2){
return element.style.display !== 'none';
}
element.style.display = value ? null : 'none';
},
disabled: function(generic, element, value){
if(arguments.length === 2){
return element.hasAttribute('disabled');
}
if(value){
element.setAttribute('disabled', 'disabled');
}else{
element.removeAttribute('disabled');
}
},
innerHTML: function(generic, element, value){
if(arguments.length === 2){
return element.innerHTML;
}
element.innerHTML = (value == null ? '' : value);
},
value: function(generic, element, value){
var inputType = element.type;
if(element.nodeName === 'INPUT' && inputType === 'date'){
if(arguments.length === 2){
return element.value ? new Date(element.value.replace(/-/g,'/').replace('T',' ')) : null;
}
value = value != null ? new Date(value) : null;
if(!value || isNaN(value)){
element.value = null;
}else{
element.value = [
value.getFullYear(),
('0' + (value.getMonth() + 1)).slice(-2),
('0' + value.getDate()).slice(-2)
].join('-');
}
return;
}
if(arguments.length === 2){
return element.value;
}
if(value === undefined){
value = null;
}
if(element.nodeName === 'PROGRESS'){
value = parseFloat(value) || 0;
}
setify(element, value);
},
max: function(generic, element, value) {
if(arguments.length === 2){
return element.value;
}
if(element.nodeName === 'PROGRESS'){
value = parseFloat(value) || 0;
}
element.max = value;
},
style: function(generic, element, value){
if(arguments.length === 2){
return element.style;
}
if(typeof value === 'string'){
element.style = value;
return;
}
for(var key in value){
element.style[key] = value[key];
}
},
type: function(generic, element, value){
if(arguments.length === 2){
return element.type;
}
element.setAttribute('type', value);
}
};
================================================
FILE: firmer.js
================================================
// Is the entity firmer than the new firmness
module.exports = function(entity, firm){
if(firm != null && (entity._firm === undefined || firm < entity._firm)){
return true;
}
};
================================================
FILE: genericComponent.js
================================================
var containerComponent = require('./containerComponent'),
schedule = require('./schedule'),
fancyProps = require('./fancyProps'),
matchDomHandlerName = /^((?:el\.)?)([^. ]+)(?:\.(capture))?$/,
GENERIC = '_generic';
function createProperties(fastn, component, settings){
for(var key in settings){
var setting = settings[key];
if(typeof setting === 'function' && !fastn.isProperty(setting) && !fastn.isBinding(setting)){
continue;
}
component.addDomProperty(key);
}
}
function trackKeyEvents(component, element, event){
if('_lastStates' in component && 'charCode' in event){
component._lastStates.unshift(element.value);
component._lastStates.pop();
}
}
function addDomHandler(component, element, handlerName, eventName, capture){
var eventParts = handlerName.split('.');
if(eventParts[0] === 'on'){
eventParts.shift();
}
var handler = function(event){
trackKeyEvents(component, element, event);
component.emit(handlerName, event, component.scope());
};
element.addEventListener(eventName, handler, capture);
component.on('destroy', function(){
element.removeEventListener(eventName, handler, capture);
});
}
function addDomHandlers(component, element, eventNames){
var events = eventNames.split(' ');
for(var i = 0; i < events.length; i++){
var eventName = events[i],
match = eventName.match(matchDomHandlerName);
if(!match){
continue;
}
if(match[1] || 'on' + match[2] in element){
addDomHandler(component, element, eventNames, match[2], match[3]);
}
}
}
function addAutoHandler(component, element, key, settings){
if(!settings[key]){
return;
}
var eventName = key.slice(2);
var handler = settings[key];
delete settings[key];
if(typeof handler === 'function'){
var innerHandler = handler;
handler = function(event){
trackKeyEvents(component, element, event);
innerHandler.call(component, event, component.scope());
};
}
if (typeof handler === 'string') {
var autoEvent = handler.split(':');
handler = function(event){
var fancyProp = fancyProps[autoEvent[1]],
value = fancyProp ? fancyProp(component, element) : element[autoEvent[1]];
trackKeyEvents(component, element, event);
component[autoEvent[0]](value);
};
}
element.addEventListener(eventName, handler);
component.on('destroy', function(){
element.removeEventListener(eventName, handler);
});
}
function addDomProperty(fastn, key, property){
var component = this,
timeout;
property = property || component[key] || fastn.property();
component.setProperty(key, property);
function update(){
var element = component.getPropertyElement(key),
value = property();
if(!element || component.destroyed()){
return;
}
if(
key === 'value' &&
component._lastStates &&
~component._lastStates.indexOf(value)
){
clearTimeout(timeout);
timeout = setTimeout(update, 50);
return;
}
var isProperty = key in element.constructor.prototype || !('getAttribute' in element),
fancyProp = component._fancyProps && component._fancyProps(key) || fancyProps[key],
previous = fancyProp ? fancyProp(component, element) : isProperty ? element[key] : element.getAttribute(key);
if(!fancyProp && !isProperty && value === null){
value = '';
}
if(value !== previous){
if(fancyProp){
fancyProp(component, element, value);
return;
}
if(isProperty){
element[key] = value;
return;
}
if(typeof value !== 'function' && typeof value !== 'object'){
if(value === undefined) {
element.removeAttribute(key);
} else {
element.setAttribute(key, value);
}
}
}
}
property.updater(update);
}
function onRender(){
var component = this,
element;
for(var key in component._settings){
element = component.getEventElement(key);
if(key.slice(0,2) === 'on' && key in element){
addAutoHandler(component, element, key, component._settings);
}
}
for(var eventKey in component._events){
element = component.getEventElement(key);
addDomHandlers(component, element, eventKey);
}
}
function render(){
this.element = this.element || this.createElement(this._settings.tagName || this._tagName);
if('value' in this.element){
this._lastStates = new Array(2);
}
this.emit('render');
return this;
};
function genericComponent(fastn, component, type, settings, children){
if(component.is(type)){
return component;
}
if(global.Element && type instanceof global.Element){
component.element = type;
type = component.element.tagName;
}
if(global.Node && type instanceof global.Node){
return fastn('text', { text: type }, type.textContent);
}
if(typeof type !== 'string'){
return;
}
if(type === GENERIC){
component._tagName = component._tagName || 'div';
}else{
component._tagName = type;
}
if(component.is(GENERIC)){
return component;
}
component.extend('_container', settings, children);
component.addDomProperty = addDomProperty.bind(component, fastn);
component.getEventElement = component.getContainerElement;
component.getPropertyElement = component.getContainerElement;
component.updateProperty = genericComponent.updateProperty;
component.createElement = genericComponent.createElement;
createProperties(fastn, component, settings);
component.render = render.bind(component);
component.on('render', onRender);
return component;
}
genericComponent.updateProperty = function(component, property, update){
if(typeof document !== 'undefined' && document.contains(component.element)){
schedule(property, update);
}else{
update();
}
};
genericComponent.createElement = function(tagName){
if(tagName instanceof Node){
return tagName;
}
return document.createElement(tagName);
};
module.exports = genericComponent;
================================================
FILE: index.js
================================================
var createProperty = require('./property'),
createBinding = require('./binding'),
BaseComponent = require('./baseComponent'),
Enti = require('enti'),
objectAssign = require('object-assign'),
is = require('./is');
function inflateProperties(component, settings){
for(var key in settings){
var setting = settings[key],
property = component[key];
if(is.property(settings[key])){
if(is.property(property)){
property.destroy();
}
setting.addTo(component, key);
}else if(is.property(property)){
if(is.binding(setting)){
property.binding(setting);
}else{
property(setting);
}
property.addTo(component, key);
}
}
}
function validateExpectedComponents(components, componentName, expectedComponents){
expectedComponents = expectedComponents.filter(function(componentName){
return !(componentName in components);
});
if(expectedComponents.length){
console.warn([
'fastn("' + componentName + '") uses some components that have not been registered with fastn',
'Expected component constructors: ' + expectedComponents.join(', ')
].join('\n\n'));
}
}
module.exports = function(components, debug){
if(!components || typeof components !== 'object'){
throw new Error('fastn must be initialised with a components object');
}
components._container = components._container || require('./containerComponent');
function fastn(type){
var args = Array.prototype.slice.call(arguments);
var settings = args[1],
childrenIndex = 2,
settingsChild = fastn.toComponent(args[1]);
if(Array.isArray(args[1]) || settingsChild || !args[1]){
if(args.length > 1){
args[1] = settingsChild || args[1];
}
childrenIndex--;
settings = null;
}
settings = objectAssign({}, settings || {});
var types = typeof type === 'string' ? type.split(':') : Array.isArray(type) ? type : [type],
baseType,
children = args.slice(childrenIndex),
component = fastn.base(type, settings, children);
while(component && (baseType = types.shift())){
component = component.extend(baseType, settings, children);
}
if(!component){
// Type was not a component.
return;
}
component._properties = {};
inflateProperties(component, settings);
return component;
}
fastn.toComponent = function(component){
if(component == null || Array.isArray(component)){
return;
}
if(is.component(component)){
return component;
}
if(typeof component !== 'object' || component instanceof Date){
return fastn('text', { text: component }, component);
}
return fastn(component)
};
fastn.debug = debug;
fastn.property = createProperty.bind(fastn);
fastn.binding = createBinding(fastn);
fastn.isComponent = is.component;
fastn.isBinding = is.binding;
fastn.isDefaultBinding = is.defaultBinding;
fastn.isBindingObject = is.bindingObject;
fastn.isProperty = is.property;
fastn.components = components;
fastn.Model = Enti;
fastn.isModel = Enti.isEnti.bind(Enti);
fastn.base = function(type, settings, children){
return new BaseComponent(fastn, type, settings, children);
};
for(var key in components){
var componentConstructor = components[key];
if(componentConstructor.expectedComponents){
validateExpectedComponents(components, key, componentConstructor.expectedComponents);
}
}
return fastn;
};
================================================
FILE: is.js
================================================
var FUNCTION = 'function',
OBJECT = 'object',
FASTNBINDING = '_fastn_binding',
FASTNPROPERTY = '_fastn_property',
FASTNCOMPONENT = '_fastn_component',
DEFAULTBINDING = '_default_binding';
function isComponent(thing){
return thing && typeof thing === OBJECT && FASTNCOMPONENT in thing;
}
function isBindingObject(thing){
return thing && typeof thing === OBJECT && FASTNBINDING in thing;
}
function isBinding(thing){
return typeof thing === FUNCTION && FASTNBINDING in thing;
}
function isProperty(thing){
return typeof thing === FUNCTION && FASTNPROPERTY in thing;
}
function isDefaultBinding(thing){
return typeof thing === FUNCTION && FASTNBINDING in thing && DEFAULTBINDING in thing;
}
module.exports = {
component: isComponent,
bindingObject: isBindingObject,
binding: isBinding,
defaultBinding: isDefaultBinding,
property: isProperty
};
================================================
FILE: listComponent.js
================================================
var MultiMap = require('multimap'),
merge = require('flat-merge');
var requestIdleCallback = global.requestIdleCallback || global.requestAnimationFrame || global.setTimeout;
MultiMap.Map = Map;
function each(value, fn){
if(!value || typeof value !== 'object'){
return;
}
if(Array.isArray(value)){
for(var i = 0; i < value.length; i++){
fn(value[i], i)
}
}else{
for(var key in value){
fn(value[key], key);
}
}
}
function keyFor(object, value){
if(!object || typeof object !== 'object'){
return false;
}
if(Array.isArray(object)){
var index = object.indexOf(value);
return index >=0 ? index : false;
}
for(var key in object){
if(object[key] === value){
return key;
}
}
return false;
}
module.exports = function(fastn, component, type, settings, children){
if(fastn.components._generic){
component.extend('_generic', settings, children);
}else{
component.extend('_container', settings, children);
}
if(!('template' in settings)){
console.warn('No "template" function was set for this templater component');
}
var itemsMap = new MultiMap(),
dataMap = new WeakMap(),
lastTemplate,
existingItem = {};
var insertQueue = [];
var inserting;
function updateOrCreateChild(template, item, key){
var child,
existing;
if(Array.isArray(item) && item[0] === existingItem){
existing = true;
child = item[2];
item = item[1];
}
var childModel;
if(!existing){
childModel = new fastn.Model({
item: item,
key: key
});
child = fastn.toComponent(template(childModel, component.scope()));
if(!child){
child = fastn('template');
}
child._listItem = item;
child._templated = true;
dataMap.set(child, childModel);
itemsMap.set(item, child);
}else{
childModel = dataMap.get(child);
childModel.set('key', key);
}
if(fastn.isComponent(child) && component._settings.attachTemplates !== false){
child.attach(childModel, 2);
}
return child;
}
function insertNextItems(template, insertionFrameTime){
if(inserting){
return;
}
inserting = true;
component.emit('insertionStart', insertQueue.length);
insertQueue.sort(function(a, b){
return a[2] - b[2];
});
function insertNext(){
var startTime = Date.now();
while(insertQueue.length && Date.now() - startTime < insertionFrameTime) {
var nextInsersion = insertQueue.shift();
var child = updateOrCreateChild(template, nextInsersion[0], nextInsersion[1]);
component.insert(child, nextInsersion[2]);
}
if(!insertQueue.length || component.destroyed()){
inserting = false;
if(!component.destroyed()){
component.emit('insertionComplete');
}
return;
}
requestIdleCallback(insertNext);
}
insertNext();
}
function updateItems(){
insertQueue = [];
var value = component.items(),
template = component.template(),
emptyTemplate = component.emptyTemplate(),
insertionFrameTime = component.insertionFrameTime() || Infinity,
newTemplate = lastTemplate !== template;
var currentItems = merge(template ? value : []);
itemsMap.forEach(function(childComponent, item){
var currentKey = keyFor(currentItems, item);
if(!newTemplate && currentKey !== false){
currentItems[currentKey] = [existingItem, item, childComponent];
}else{
removeComponent(childComponent);
itemsMap.delete(item, childComponent);
}
});
var index = 0;
var templateIndex = 0;
function updateItem(item, key){
while(index < component._children.length && !component._children[index]._templated){
index++;
}
insertQueue.push([item, key, index + templateIndex]);
templateIndex++;
}
each(currentItems, updateItem);
template && insertNextItems(template, insertionFrameTime);
lastTemplate = template;
if(templateIndex === 0 && emptyTemplate){
var child = fastn.toComponent(emptyTemplate(component.scope()));
if(!child){
child = fastn('template');
}
child._templated = true;
itemsMap.set({}, child);
component.insert(child);
}
}
function removeComponent(childComponent){
component.remove(childComponent);
childComponent.destroy();
}
component.setProperty('insertionFrameTime');
component.setProperty('items',
fastn.property([], settings.itemChanges || 'type keys shallowStructure')
.on('change', updateItems)
);
component.setProperty('template',
fastn.property().on('change', updateItems)
);
component.setProperty('emptyTemplate',
fastn.property().on('change', updateItems)
);
return component;
};
================================================
FILE: package.json
================================================
{
"name": "fastn",
"version": "2.14.5",
"description": "",
"main": "index.js",
"scripts": {
"test": "node test",
"watch": "watchify test/index.js -o test/index.browser.js -d"
},
"author": "",
"license": "ISC",
"repository": {
"type": "git",
"url": "git+https://github.com/KoryNunn/fastn.git"
},
"dependencies": {
"classist": "^1.1.1",
"enti": "^6.1.3",
"flat-merge": "^1.0.0",
"function-emitter": "^1.0.0",
"multimap": "^1.0.2",
"object-assign": "^4.1.1",
"same-value": "^1.0.2",
"setify": "^1.0.3",
"setprototypeof": "^1.1.0",
"what-changed": "^2.3.0"
},
"devDependencies": {
"browserify": "^14.5.0",
"console-watch": "^1.0.2",
"crel": "^4.0.1",
"dom-lightning": "^1.0.2",
"dom-lite": "^0.5.1",
"tape": "^5.0.1",
"tape-run": "^6.0.1",
"watchify": "^3.11.0"
}
}
================================================
FILE: property.js
================================================
var WhatChanged = require('what-changed'),
same = require('same-value'),
firmer = require('./firmer'),
functionEmitter = require('function-emitter'),
setPrototypeOf = require('setprototypeof');
var propertyProto = Object.create(functionEmitter);
propertyProto._fastn_property = true;
propertyProto._firm = 1;
function propertyTemplate(value){
if(!arguments.length){
return this.observable && typeof this.observable === 'function' && this.observable() || this.property._value;
}
if(!this.destroyed){
if(this.observable){
typeof this.observable === 'function' && this.observable(value);
return this.property;
}
this.valueUpdate(value);
}
return this.property;
}
function changeChecker(current, changes){
if(changes){
var changes = new WhatChanged(current, changes);
return function(value){
return changes.update(value).any;
};
}else{
var lastValue = current;
return function(newValue){
if(!same(lastValue, newValue)){
lastValue = newValue;
return true;
}
};
}
}
function propertyBinding(newBinding){
if(!arguments.length){
return this.observable;
}
if(typeof newBinding === 'string' || typeof newBinding === 'number'){
newBinding = this.fastn.binding(newBinding);
}
if(newBinding === this.observable){
return this.property;
}
if(this.observable){
this.observable.removeListener('change', this.valueUpdate);
}
this.observable = newBinding;
if(this.model){
this.property.attach(this.model, this.property._firm);
}
this.observable.on('change', this.valueUpdate);
if(typeof this.observable === 'function'){
this.valueUpdate(this.observable());
} else {
this.valueUpdate(undefined);
}
return this.property;
}
function attachProperty(object, firm){
if(firmer(this.property, firm)){
return this.property;
}
this.property._firm = firm;
if(!(object instanceof Object)){
object = {};
}
if(this.observable){
this.model = object;
this.observable.attach && this.observable.attach(object, 1);
}
if(this.property._events && 'attach' in this.property._events){
this.property.emit('attach', object, 1);
}
return this.property;
};
function detachProperty(firm){
if(firmer(this.property, firm)){
return this.property;
}
if(this.observable){
this.observable.removeListener('change', this.valueUpdate);
this.observable.detach && this.observable.detach(1);
this.model = null;
}
if(this.property._events && 'detach' in this.property._events){
this.property.emit('detach', 1);
}
return this.property;
};
function updateProperty(){
if(!this.destroyed){
if(this.property._update){
this.property._update(this.property._value, this.property);
}
this.property.emit('update', this.property._value);
}
return this.property;
};
function propertyUpdater(fn){
if(!arguments.length){
return this.property._update;
}
this.property._update = fn;
return this.property;
};
function destroyProperty(){
if(!this.destroyed){
this.destroyed = true;
this.property
.removeAllListeners('change')
.removeAllListeners('update')
.removeAllListeners('attach');
this.property.emit('destroy');
this.property.detach();
if(this.observable){
this.observable.destroy && this.observable.destroy(true);
}
}
return this.property;
};
function propertyDestroyed(){
return this.destroyed;
};
function addPropertyTo(component, key){
component.setProperty(key, this.property);
return this.property;
};
function createProperty(currentValue, changes, updater){
if(typeof changes === 'function'){
updater = changes;
changes = null;
}
var propertyScope = {
fastn: this,
hasChanged: changeChecker(currentValue, changes)
},
property = propertyTemplate.bind(propertyScope);
propertyScope.valueUpdate = function(value){
property._value = value;
if(!propertyScope.hasChanged(value)){
return;
}
property.emit('change', property._value);
property.update();
};
var property = propertyScope.property = propertyTemplate.bind(propertyScope);
property._value = currentValue;
property._update = updater;
setPrototypeOf(property, propertyProto);
property.binding = propertyBinding.bind(propertyScope);
property.attach = attachProperty.bind(propertyScope);
property.detach = detachProperty.bind(propertyScope);
property.update = updateProperty.bind(propertyScope);
property.updater = propertyUpdater.bind(propertyScope);
property.destroy = destroyProperty.bind(propertyScope);
property.destroyed = propertyDestroyed.bind(propertyScope);
property.addTo = addPropertyTo.bind(propertyScope);
return property;
};
module.exports = createProperty;
================================================
FILE: schedule.js
================================================
var todo = [],
todoKeys = [],
scheduled,
updates = 0;
function run(){
var startTime = Date.now();
while(todo.length && Date.now() - startTime < 16){
todoKeys.shift();
todo.shift()();
}
if(todo.length){
requestAnimationFrame(run);
}else{
scheduled = false;
}
}
function schedule(key, fn){
if(~todoKeys.indexOf(key)){
return;
}
todo.push(fn);
todoKeys.push(key);
if(!scheduled){
scheduled = true;
requestAnimationFrame(run);
}
}
module.exports = schedule;
================================================
FILE: templaterComponent.js
================================================
module.exports = function(fastn, component, type, settings, children){
var itemModel = new fastn.Model({});
if(!('template' in settings)){
console.warn('No "template" function was set for this templater component');
}
function replaceElement(element){
if(component.element && component.element.parentNode){
component.element.parentNode.replaceChild(element, component.element);
}
component.element = element;
}
function update(){
var value = component.data(),
template = component.template();
itemModel.set('item', value);
var newComponent;
if(template){
newComponent = fastn.toComponent(template(itemModel, component.scope(), component._currentComponent));
}
if(component._currentComponent && component._currentComponent !== newComponent){
if(fastn.isComponent(component._currentComponent)){
component._currentComponent.destroy();
}
}
component._currentComponent = newComponent;
if(!newComponent){
replaceElement(component.emptyElement);
return;
}
if(fastn.isComponent(newComponent)){
if(component._settings.attachTemplates !== false){
newComponent.attach(itemModel, 2);
}else{
newComponent.attach(component.scope(), 1);
}
if(component.element && component.element !== newComponent.element){
if(newComponent.element == null){
newComponent.render();
}
replaceElement(component._currentComponent.element);
}
}
}
component.render = function(){
var element;
component.emptyElement = document.createTextNode('');
if(component._currentComponent){
component._currentComponent.render();
element = component._currentComponent.element;
}
component.element = element || component.emptyElement;
component.emit('render');
return component;
};
component.setProperty('data',
fastn.property(undefined, settings.dataChanges || 'value structure')
.on('change', update)
);
component.setProperty('template',
fastn.property(undefined, 'value reference')
.on('change', update)
);
component.on('destroy', function(){
if(fastn.isComponent(component._currentComponent)){
component._currentComponent.destroy();
}
});
component.on('attach', function(data){
if(fastn.isComponent(component._currentComponent)){
component._currentComponent.attach(component.scope(), 1);
}
});
return component;
};
================================================
FILE: test/attach.js
================================================
var test = require('tape'),
Enti = require('enti'),
createFastn = require('./createFastn');
test('manual attach', function(t){
t.plan(3);
var fastn = createFastn();
var child,
parent = fastn('div',
child = fastn('span')
);
parent.attach({
foo:'bar'
});
t.deepEqual(parent.scope().get('.'), {
foo:'bar'
});
t.deepEqual(child.scope().get('.'), {
foo:'bar'
});
t.equal(parent.scope().get('.'), child.scope().get('.'));
});
test('weak attach attempt', function(t){
t.plan(3);
var fastn = createFastn();
var child,
parent = fastn('div',
child = fastn('span')
);
parent.attach({
foo:'bar'
});
child.attach({
baz: 'inga'
}, 0);
t.deepEqual(parent.scope().get('.'), {
foo:'bar'
});
t.deepEqual(child.scope().get('.'), {
foo:'bar'
});
t.equal(parent.scope().get('.'), child.scope().get('.'));
});
test('firmer attach attempt', function(t){
t.plan(3);
var fastn = createFastn();
var child,
parent = fastn('div',
child = fastn('span')
);
parent.attach({
foo:'bar'
});
child.attach({
baz: 'inga'
}, 1);
t.deepEqual(parent.scope().get('.'), {
foo:'bar'
});
t.deepEqual(child.scope().get('.'), {
baz:'inga'
});
t.notEqual(parent.scope().get('.'), child.scope().get('.'));
});
test('firmest attach', function(t){
t.plan(3);
var fastn = createFastn();
var child,
parent = fastn('div',
child = fastn('span')
);
parent.attach({
foo:'bar'
});
child.attach({
baz: 'inga'
});
t.deepEqual(parent.scope().get('.'), {
foo:'bar'
});
t.deepEqual(child.scope().get('.'), {
baz:'inga'
});
t.notEqual(parent.scope().get('.'), child.scope().get('.'));
});
================================================
FILE: test/binding.js
================================================
var test = require('tape'),
createBinding = require('../index')({}).binding,
Enti = require('enti');
test('invalid path', function(t){
t.plan(5);
t.throws(function(){
createBinding(true);
});
t.throws(function(){
createBinding({});
});
t.throws(function(){
createBinding(function(){});
});
t.throws(function(){
createBinding(null);
});
t.throws(function(){
createBinding(undefined);
});
});
test('simple binding initialisation', function(t){
t.plan(3);
var binding = createBinding('foo');
var model = {},
enti = new Enti(model);
t.equal(binding(), undefined);
enti.set('foo', 'bar');
t.equal(binding(), undefined);
binding.attach(model);
t.equal(binding(), 'bar');
});
test('initial attach doesnt cause emit', function(t){
t.plan(1);
var binding = createBinding('foo');
binding.on('change', () => t.pass('Recieved change'));
binding.attach();
binding.attach({});
});
test('simple binding set', function(t){
t.plan(2);
var binding = createBinding('foo');
binding.attach({});
t.equal(binding(), undefined);
binding('bazinga');
t.equal(binding(), 'bazinga');
});
test('simple binding event', function(t){
t.plan(3);
var binding = createBinding('foo');
var model = {},
enti = new Enti(model);
binding.attach(model);
binding.once('change', function(value){
t.equal(value, 'bar');
t.equal(binding(), 'bar');
});
enti.set('foo', 'bar');
binding.once('detach', function(){
t.equal(binding(), undefined);
});
binding.detach();
enti.set('foo', 'baz');
});
test('no model', function(t){
t.plan(3);
var binding = createBinding('foo');
t.equal(binding(), undefined);
binding.on('change', function(value){
t.equal(value, 'bar');
console.log(value)
});
binding('bar');
console.log(binding())
t.equal(binding(), 'bar');
});
test('drill get', function(t){
t.plan(2);
var data = {
foo: {
bar: 123
}
},
model = new Enti(data),
binding = createBinding('foo.bar');
binding.attach(data);
t.equal(binding(), 123);
model.set('foo', {
bar: 456
});
t.equal(binding(), 456);
});
test('drill change', function(t){
t.plan(1);
var data = {
foo: {
bar: 123
}
},
model = new Enti(data),
binding = createBinding('foo.bar');
binding.attach(data);
binding.on('change', function(){
t.pass('target changed');
});
model.set('foo', {
bar: 456
});
});
test('drill attach', function(t){
t.plan(2);
var data = {
foo: {
bar: 123
}
},
model = new Enti(data),
binding = createBinding('foo.bar');
binding.once('change', function(value){
t.equal(value, 123);
});
binding.attach(data);
binding.once('change', function(value){
t.equal(value, 456);
});
model.set('foo', {
bar: 456
});
});
test('drill set', function(t){
t.plan(1);
var data = {
foo: {
bar: 123
}
},
model = new Enti(data),
fooModel = new Enti(data.foo),
binding = createBinding('foo.bar');
fooModel.on('bar', function(value){
t.equal(value, 456);
});
binding.attach(data);
binding(456);
});
test('drill multiple', function(t){
t.plan(3);
var data = {
foo: {
bar: 123
}
},
model = new Enti(data),
fooModel = new Enti(data.foo),
binding = createBinding('foo.bar');
fooModel.once('bar', function(value){
t.equal(value, 456);
});
binding.attach(data);
binding(456);
binding.once('change', function(value){
t.equal(value, 789);
});
fooModel.set('bar', 789);
binding.once('change', function(value){
t.equal(value, 987);
});
binding(987);
});
test('fuse', function(t){
t.plan(2);
var data = {
foo: 1,
bar: 2,
baz: 3
},
model = new Enti(data),
binding = createBinding('foo', 'bar', 'baz', function(foo, bar, baz){
return foo + bar + baz;
});
binding.attach(data);
binding(2);
binding.once('change', function(value){
t.equal(value, 7);
});
model.set('bar', 3);
binding.once('change', function(value){
t.equal(value, 3);
});
binding(3);
});
test('fuse attached to object with result', function(t){
t.plan(1);
var data = {
foo: 1,
bar: 2,
result: 'result'
},
binding = createBinding('foo', 'bar', function(foo, bar){
return foo + bar;
});
binding.on('change', value => t.equal(value, 3));
binding.attach(data);
});
test('fuse firmness', function(t){
t.plan(1);
var data1 = {
foo: 1
},
binding = createBinding('foo', function(foo){
return foo;
});
binding.on('change', value => t.equal(value, 1));
binding.attach({ foo: 1 });
binding.attach({ foo: 2 }, 1);
});
test('fuse destroy inner used', function(t){
t.plan(4);
var data1 = {
foo: 1
},
data2 = {
bar: 1
},
innerBinding = createBinding('foo').attach(data1),
binding = createBinding(innerBinding, 'bar', function(foo, bar){
return foo + bar;
});
// Add a listener to the inner binding
// This should prevent destruction.
innerBinding.on('change', function(){
t.pass('Inner binding changed');
});
binding.attach(data2);
binding.once('change', function(value){
t.equal(value, 3);
});
Enti.set(data1, 'foo', 2);
binding.once('change', function(value){
t.fail('No event should occur since the binding is detached');
});
binding.destroy();
t.notOk(innerBinding.destroyed(), 'inner binding should not be destroyed');
Enti.set(data1, 'foo', 3);
});
test('fuse set', function(t){
t.plan(1);
var data = {
a: 1,
b: 2,
c: 3
};
var binding = createBinding('a', 'b', 'c', function(a, b, c){
return a + b + c;
}, x => x).attach(data);
binding(2);
t.equal(data.a, 2);
});
test('filter', function(t){
t.plan(2);
var data = {},
model = new Enti(data),
binding = createBinding('foo|*');
binding.attach(data);
binding.on('change', function(value){
t.pass();
});
model.set('foo', []);
Enti.set(data.foo, 0, {});
});
test('things', function(t){
t.plan(2);
var data = {},
model = new Enti(data),
binding = createBinding('foo|*.bar');
binding.attach(data);
binding.on('change', function(value){
t.pass();
});
model.set('foo', [{}]);
Enti.set(data.foo[0], 'bar', true);
});
test('clone', function(t){
t.plan(4);
var data1 = {foo:1},
data2 = {foo:2},
binding = createBinding('foo');
binding.attach(data1);
t.equal(binding(), 1, 'Original binding has correct data');
var newBinding = binding.clone();
t.equal(newBinding(), undefined, 'New binding has no data');
newBinding.attach(data2);
t.equal(newBinding(), 2, 'New binding has new data');
t.equal(binding(), 1, 'Original binding still has original data');
});
test('clone with attachment', function(t){
t.plan(2);
var data1 = {foo:1},
binding = createBinding('foo');
binding.attach(data1);
t.equal(binding(), 1, 'Original binding has correct data');
var newBinding = binding.clone(true);
t.equal(newBinding(), 1, 'New binding has same data');
});
test('clone fuse', function(t){
t.plan(2);
var data1 = {foo:1, bar:2},
binding = createBinding('foo', 'bar', function(foo, bar){
return foo + bar;
});
binding.attach(data1);
t.equal(binding(), 3, 'Original binding has correct data');
var newBinding = binding.clone(true);
t.equal(newBinding(), 3, 'New binding has same data');
});
test('binding as a bindings target', function(t){
t.plan(1);
var binding1 = createBinding('foo'),
binding2 = createBinding('bar');
binding1(binding2);
t.equal(binding1(), binding2, 'binding1 value correctly set to binding2');
});
test('binding as own target', function(t){
t.plan(1);
var binding = createBinding('foo');
binding(binding);
t.equal(binding(), binding, 'binding value correctly set to self');
});
test('value-only binding', function(t){
t.plan(1);
var binding = createBinding();
binding('foo');
t.equal(binding(), 'foo', 'binding value correctly set to foo');
});
test('value-only binding cannot be attached', function(t){
t.plan(1);
var binding = createBinding();
binding('foo');
binding.attach({
value: 'bar'
});
t.equal(binding(), 'foo', 'binding value correctly set to foo');
});
test('destroy', function(t){
t.plan(1);
var binding = createBinding().on('change', function(){
t.pass('binding changed');
});
binding('foo');
binding.destroy();
binding('bar');
});
test('soft destroy', function(t){
t.plan(2);
var binding = createBinding().on('change', function(){
t.pass('binding changed');
});
binding('foo');
binding.destroy(true);
binding('bar');
});
test('soft destroy 2', function(t){
t.plan(1);
function changeHandler(){
t.pass('binding changed');
}
var binding = createBinding().on('change', changeHandler);
binding('foo');
binding.removeListener('change', changeHandler);
binding.destroy(true);
binding('bar');
});
test('model attach', function(t){
t.plan(2);
var model = new Enti();
var binding = createBinding('a');
binding.attach(model);
t.equal(binding(), undefined);
model.attach({
a: 2
});
t.equal(binding(), 2);
});
test('from', function(t){
t.plan(3);
var binding = createBinding(),
value = 5;
binding(10);
var from1 = createBinding.from(binding);
var from2 = createBinding.from(value);
t.equal(from1(), 10);
t.equal(from1, binding);
t.equal(from2(), 5);
});
test('binding as path', function(t){
t.plan(1);
var binding1 = createBinding(),
binding2 = createBinding(binding1);
binding1(10);
t.equal(binding2(), 10);
});
test('detach memory usage', function(t){
t.plan(1);
var data = {
foo: null
};
var runs = 0;
function run(){
var bindings = [];
for(var i = 0; i < 1000; i++){
bindings.push(createBinding('foo').attach(data));
}
Enti.set(data, 'foo', runs);
bindings.map(function(binding){
binding.detach();
});
setTimeout(function(){
if(runs++ < 10){
run();
} else {
t.pass();
}
});
}
run();
});
================================================
FILE: test/changes.js
================================================
const righto = require('righto');
const outputOnError = error => { error && console.log(error); };
function runTest (fn) {
if (fn.constructor.name === 'GeneratorFunction') {
return function () {
const generator = righto.iterate(fn);
const result = righto.apply(null, [generator].concat(Array.from(arguments)));
result(outputOnError);
};
}
return fn;
}
function rightoTest (name, fn) {
test(name, runTest(fn));
}
module.exports = rightoTest;
================================================
FILE: test/component.js
================================================
var test = require('tape'),
Enti = require('enti'),
createFastn = require('./createFastn');
test('binding', function(t){
t.plan(2);
var fastn = createFastn();
var data = {
foo:{
bar:1
}
},
component = fastn('div');
component.attach(data);
t.equal(component.scope().get('.'), data);
component.binding('foo');
t.equal(component.scope().get('.'), data.foo);
});
test('pre-created component', function(t){
t.plan(3);
var fastn = createFastn({
custom: function(fastn, component, type, settings, children){
t.pass('Used custom constructor');
return component;
}
});
var data = {
foo:{
bar:1
}
},
component = fastn('custom');
component.attach(data);
t.equal(component.scope().get('.'), data);
component.binding('foo');
t.equal(component.scope().get('.'), data.foo);
});
test('auto extend component', function(t){
t.plan(6);
var fastn = createFastn({
foo: function(fastn, component, type, settings, children){
t.pass('Used foo constructor');
return component;
},
bar: function(fastn, component, type, settings, children){
t.pass('Used bar constructor');
return component;
},
baz: function(fastn, component, type, settings, children){
t.pass('Used baz constructor');
return component;
}
});
var component = fastn('foo:bar:baz');
t.ok(component.is('foo'), 'componant is foo');
t.ok(component.is('bar'), 'componant is bar');
t.ok(component.is('baz'), 'componant is baz');
});
test('manual extend component', function(t){
t.plan(6);
var fastn = createFastn({
foo: function(fastn, component, type, settings, children){
t.pass('Used foo constructor');
return component;
},
bar: function(fastn, component, type, settings, children){
t.pass('Used bar constructor');
return component;
},
baz: function(fastn, component, type, settings, children){
t.pass('Used baz constructor');
return component;
}
});
var component = fastn('foo');
component.extend('bar', {});
component.extend('baz', {});
t.ok(component.is('foo'), 'componant is foo');
t.ok(component.is('bar'), 'componant is bar');
t.ok(component.is('baz'), 'componant is baz');
});
test('cannot double-extend component', function(t){
t.plan(4);
var fastn = createFastn({
foo: function(fastn, component, type, settings, children){
t.pass('Used foo constructor');
return component;
},
bar: function(fastn, component, type, settings, children){
t.pass('Used bar constructor');
return component;
}
});
var component = fastn('foo');
component.extend('bar', {});
// Shouldn't cause another call to bar constructor.
component.extend('bar', {});
t.ok(component.is('foo'), 'componant is foo');
t.ok(component.is('bar'), 'componant is bar');
});
================================================
FILE: test/components.js
================================================
module.exports = function(components){
if(!components){
components = {};
}
var genericComponent = require('../genericComponent'),
textComponent = require('../textComponent');
// dont do fancy requestAnimationFrame scheduling that is hard to test.
genericComponent.updateProperty = function(generic, property, update){
update();
};
genericComponent.createElement = function(tagName){
if(tagName instanceof Node){
return tagName;
}
return document.createElement(tagName);
};
textComponent.createTextNode = document.createTextNode.bind(document);
components._generic = genericComponent;
components.list = require('../listComponent');
components.templater = require('../templaterComponent');
components.text = textComponent;
return components;
};
================================================
FILE: test/container.js
================================================
var test = require('tape'),
createFastn = require('./createFastn');
test('children are added', function(t){
t.plan(2);
var fastn = createFastn();
var child,
parent = fastn('div',
child = fastn('span')
);
parent.render();
document.body.appendChild(parent.element);
t.equal(document.body.childNodes.length, 1);
t.equal(parent.element.childNodes.length, 1);
parent.element.remove();
parent.destroy();
});
test('undefined or null children are ignored', function(t){
t.plan(1);
var fastn = createFastn();
var child,
parent = fastn('div',
child = fastn('span'),
undefined,
null
);
parent.render();
document.body.appendChild(parent.element);
t.equal(parent.element.childNodes.length, 1);
parent.element.remove();
parent.destroy();
});
test('flatten children', function(t){
t.plan(1);
var fastn = createFastn();
var parent = fastn('div',
[fastn('span'), fastn('span')],
fastn('span')
);
parent.render();
document.body.appendChild(parent.element);
t.equal(parent.element.childNodes.length, 3);
parent.element.remove();
parent.destroy();
});
test('insert many after current', function(t){
t.plan(1);
var fastn = createFastn();
var parent = fastn('div',
fastn('span', '1'),
fastn('span', '2')
);
parent.insert(
fastn('span', '3'),
fastn('span', '4')
);
parent.render();
document.body.appendChild(parent.element);
t.equal(document.body.textContent, '1234');
parent.element.remove();
parent.destroy();
});
test('insert returns container', function(t){
t.plan(1);
var fastn = createFastn();
var container = fastn('div');
t.equal(container.insert(fastn('span')), container);
container.destroy();
});
test('children passed attachment', function(t){
t.plan(2);
var fastn = createFastn();
var container = fastn('div', fastn.binding('foo'));
container.render();
container.attach({foo: 'bar'});
t.equal(container.element.textContent, 'bar');
container.attach({foo: 'baz'});
t.equal(container.element.textContent, 'baz');
container.destroy();
});
test('children passed model change attachment', function(t){
t.plan(2);
var fastn = createFastn();
var container = fastn('div', fastn.binding('foo')),
model = new fastn.Model({foo: 'bar'});
container.render();
container.attach(model);
t.equal(container.element.textContent, 'bar');
model.attach({foo: 'baz'});
t.equal(container.element.textContent, 'baz');
container.destroy();
});
test('insert undefined', function(t){
t.plan(1);
var fastn = createFastn();
var container = fastn('div');
container.insert(undefined);
t.equal(container.children().length, 0, 'Nothing was added');
});
test('insert undefined in array', function(t){
t.plan(1);
var fastn = createFastn();
var container = fastn('div');
container.insert([1, undefined, 2]);
t.equal(container.children().length, 2, 'Only values added');
});
test('insert mixed array', function(t){
t.plan(1);
var fastn = createFastn();
var container = fastn('div');
container.insert([
undefined,
null,
false,
1,
'2',
NaN
]);
t.equal(container.children().length, 3, 'Only values added');
});
test('insert destroyed component throws', function(t){
t.plan(1);
var fastn = createFastn();
var container = fastn('div');
var child = fastn('div');
child.destroy();
t.throws(function(){
container.insert(child);
});
});
================================================
FILE: test/createFastn.js
================================================
var merge = require('flat-merge');
module.exports = function createFastn(components){
return require('../')(require('./components')(components));
};
================================================
FILE: test/customBinding.js
================================================
var test = require('tape'),
createBinding = require('../binding'),
Enti = require('enti');
test('simple binding initialisation', function(t){
t.plan(3);
var binding = createBinding('foo');
var model = {},
enti = new Enti(model);
t.equal(binding(), undefined);
enti.set('foo', 'bar');
t.equal(binding(), undefined);
binding.attach(model);
t.equal(binding(), 'bar');
});
test('simple binding set', function(t){
t.plan(2);
var binding = createBinding('foo');
binding.attach({});
t.equal(binding(), undefined);
binding('bazinga');
t.equal(binding(), 'bazinga');
});
test('simple binding event', function(t){
t.plan(3);
var binding = createBinding('foo');
var model = {},
enti = new Enti(model);
binding.attach(model);
binding.once('change', function(value){
t.equal(value, 'bar');
t.equal(binding(), 'bar');
});
enti.set('foo', 'bar');
binding.once('detach', function(){
t.equal(binding(), undefined);
});
binding.detach();
enti.set('foo', 'baz');
});
test('no model', function(t){
t.plan(3);
var binding = createBinding('foo');
t.equal(binding(), undefined);
binding.on('change', function(value){
t.equal(value, 'bar');
console.log(value)
});
binding('bar');
console.log(binding())
t.equal(binding(), 'bar');
});
test('drill get', function(t){
t.plan(2);
var data = {
foo: {
bar: 123
}
},
model = new Enti(data),
binding = createBinding('foo.bar');
binding.attach(data);
t.equal(binding(), 123);
model.set('foo', {
bar: 456
});
t.equal(binding(), 456);
});
test('drill change', function(t){
t.plan(1);
var data = {
foo: {
bar: 123
}
},
model = new Enti(data),
binding = createBinding('foo.bar');
binding.attach(data);
binding.on('change', function(){
t.pass('target changed');
});
model.set('foo', {
bar: 456
});
});
test('drill attach', function(t){
t.plan(2);
var data = {
foo: {
bar: 123
}
},
model = new Enti(data),
binding = createBinding('foo.bar');
binding.once('change', function(value){
t.equal(value, 123);
});
binding.attach(data);
binding.once('change', function(value){
t.equal(value, 456);
});
model.set('foo', {
bar: 456
});
});
test('drill set', function(t){
t.plan(1);
var data = {
foo: {
bar: 123
}
},
model = new Enti(data),
fooModel = new Enti(data.foo),
binding = createBinding('foo.bar');
fooModel.on('bar', function(value){
t.equal(value, 456);
});
binding.attach(data);
binding(456);
});
test('drill multiple', function(t){
t.plan(3);
var data = {
foo: {
bar: 123
}
},
model = new Enti(data),
fooModel = new Enti(data.foo),
binding = createBinding('foo.bar');
fooModel.once('bar', function(value){
t.equal(value, 456);
});
binding.attach(data);
binding(456);
binding.once('change', function(value){
t.equal(value, 789);
});
fooModel.set('bar', 789);
binding.once('change', function(value){
t.equal(value, 987);
});
binding(987);
});
test('fuse', function(t){
t.plan(2);
var data = {
foo: 1,
bar: 2,
baz: 3
},
model = new Enti(data),
binding = createBinding('foo', 'bar', 'baz', function(foo, bar, baz){
return foo + bar + baz;
});
binding.attach(data);
binding(2);
binding.once('change', function(value){
t.equal(value, 7);
});
model.set('bar', 3);
binding.once('change', function(value){
t.equal(value, 3);
});
binding(3);
});
test('filter', function(t){
t.plan(2);
var data = {},
model = new Enti(data),
binding = createBinding('foo|*');
binding.attach(data);
binding.on('change', function(value){
t.pass();
});
model.set('foo', []);
Enti.set(data.foo, 0, {});
});
test('things', function(t){
t.plan(2);
var data = {},
model = new Enti(data),
binding = createBinding('foo|*.bar');
binding.attach(data);
binding.on('change', function(value){
t.pass();
});
model.set('foo', [{}]);
Enti.set(data.foo[0], 'bar', true);
});
test('clone', function(t){
t.plan(4);
var data1 = {foo:1},
data2 = {foo:2},
binding = createBinding('foo');
binding.attach(data1);
t.equal(binding(), 1, 'Original binding has correct data');
var newBinding = binding.clone();
t.equal(newBinding(), undefined, 'New binding has no data');
newBinding.attach(data2);
t.equal(newBinding(), 2, 'New binding has new data');
t.equal(binding(), 1, 'Original binding still has original data');
});
test('clone with attachment', function(t){
t.plan(2);
var data1 = {foo:1},
binding = createBinding('foo');
binding.attach(data1);
t.equal(binding(), 1, 'Original binding has correct data');
var newBinding = binding.clone(true);
t.equal(newBinding(), 1, 'New binding has same data');
});
test('clone fuse', function(t){
t.plan(2);
var data1 = {foo:1, bar:2},
binding = createBinding('foo', 'bar', function(foo, bar){
return foo + bar;
});
binding.attach(data1);
t.equal(binding(), 3, 'Original binding has correct data');
var newBinding = binding.clone(true);
t.equal(newBinding(), 3, 'New binding has same data');
});
test('binding as a bindings target', function(t){
t.plan(1);
var binding1 = createBinding('foo'),
binding2 = createBinding('bar');
binding1(binding2);
t.equal(binding1(), binding2, 'binding1 value correctly set to binding2');
});
test('binding as own target', function(t){
t.plan(1);
var binding = createBinding('foo');
binding(binding);
t.equal(binding(), binding, 'binding value correctly set to self');
});
test('value-only binding', function(t){
t.plan(1);
var binding = createBinding();
binding('foo');
t.equal(binding(), 'foo', 'binding value correctly set to foo');
});
test('value-only binding cannot be attached', function(t){
t.plan(1);
var binding = createBinding();
binding('foo');
binding.attach({
value: 'bar'
});
t.equal(binding(), 'foo', 'binding value correctly set to foo');
});
test('destroy', function(t){
t.plan(1);
var binding = createBinding().on('change', function(){
t.pass('binding changed');
});
binding('foo');
binding.destroy();
binding('bar');
});
test('soft destroy', function(t){
t.plan(2);
var binding = createBinding().on('change', function(){
t.pass('binding changed');
});
binding('foo');
binding.destroy(true);
binding('bar');
});
test('soft destroy 2', function(t){
t.plan(1);
function changeHandler(){
t.pass('binding changed');
}
var binding = createBinding().on('change', changeHandler);
binding('foo');
binding.removeListener('change', changeHandler);
binding.destroy(true);
binding('bar');
});
test('model attach', function(t){
t.plan(2);
var model = new Enti();
var binding = createBinding('a');
binding.attach(model);
t.equal(binding(), undefined);
model.attach({
a: 2
});
t.equal(binding(), 2);
});
================================================
FILE: test/customModel.js
================================================
var test = require('tape'),
EventEmitter = require('events'),
createFastn = require('../index');
var allModels = new Set();
function CustomModel(instance){
allModels.add(this);
this._model = instance;
this;
return this;
}
CustomModel.get = function(target, key){
var match = key.match(matchKeys);
if(!match){
return;
}
while(match[2]){
if(!target){
return;
}
target = target[match[1]];
match = match[2].match(matchKeys);
}
if(!target){
return;
}
return target[match[1]];
};
CustomModel.set = function(target, key, value){
var instance = target,
match = key.match(matchKeys);
if(!match){
return;
}
while(match[2]){
if(!target){
return;
}
target = target[match[1]];
match = match[2].match(matchKeys);
}
if(!target){
return;
}
target[match[1]] = value;
allModels.forEach(function(model){
if(model.isAttached() && model._model === instance){
model._events && Object.keys(model._events).forEach(function(key){
if(model.get(key.match(/(.*?)\./)[1]) === target){
model.emit(key, value);
}
});
}
});
};
CustomModel.remove = function(target, key){
var instance = target,
match = key.match(matchKeys);
if(!match){
return;
}
while(match[2]){
if(!target){
return;
}
target = target[match[1]];
match = match[2].match(matchKeys);
}
if(!target){
return;
}
delete target[match[1]];
allModels.forEach(function(model){
if(model.isAttached() && model._model === instance){
model._events && Object.keys(model._events).forEach(function(key){
if(model.get(key.match(/(.*?)\./)[1]) === target){
model.emit(key);
}
});
}
});
};
CustomModel.prototype = Object.create(EventEmitter.prototype);
CustomModel.prototype.constructor = CustomModel;
CustomModel.prototype._maxListeners = 100;
CustomModel.prototype.constructor = CustomModel;
CustomModel.prototype.attach = function(instance){
if(this._model !== instance){
this.detach();
}
allModels.add(this);
this._attached = true;
this._model = instance;
this.emit('attach', instance);
};
CustomModel.prototype.detach = function(){
allModels.delete(this);
this._model = {};
this._attached = false;
this.emit('detach');
};
CustomModel.prototype.destroy = function(){
this.detach();
this._events = null;
this.emit('destroy');
};
var matchKeys = /(.*?)(?:\.(.*)|$)/;
CustomModel.prototype.get = function(key){
return CustomModel.get(this._model, key);
};
CustomModel.prototype.set = function(key, value){
return CustomModel.set(this._model, key, value);
};
CustomModel.prototype.remove = function(key){
return CustomModel.remove(this._model, key);
};
CustomModel.prototype.isAttached = function(){
return !!this._model;
};
CustomModel.isModel = function(target){
return target && target instanceof CustomModel;
};
test('binding with custom model', function(t){
t.plan(4);
var fastn = createFastn({});
fastn.Model = CustomModel;
fastn.isModel = CustomModel.isModel;
var binding = fastn.binding('foo');
var model = {},
enti = new CustomModel(model);
t.equal(binding(), undefined);
enti.set('foo', 'bar');
t.equal(binding(), undefined);
binding.attach(model);
t.equal(binding(), 'bar');
binding.detach();
t.equal(binding(), undefined);
});
================================================
FILE: test/document.js
================================================
module.exports = function(){
var domLite = require('dom-lightning');
document = domLite.document;
document.body = document.createElement('body');
global.Node = domLite.Node;
global.document = document;
global.Element = domLite.Element;
global.HTMLElement = domLite.HTMLElement;
};
================================================
FILE: test/fancyProps.js
================================================
var test = require('tape'),
crel = require('crel'),
fancyProps = require('../fancyProps');
test('date input', function(t){
t.plan(2);
var input = crel('input', {type: 'date'});
t.equal(fancyProps.value({}, input), null);
fancyProps.value({}, input, new Date('2000-1-1'));
t.equal(fancyProps.value({}, input).toString(), new Date('2000-1-1').toString());
});
test('class', function(t){
t.plan(3);
var component = {},
span = crel('span');
t.equal(fancyProps.class(component, span), '');
fancyProps.class(component, span, 'foo');
t.equal(fancyProps.class(component, span), 'foo');
fancyProps.class(component, span, ['bar']);
t.equal(fancyProps.class(component, span), 'bar');
});
test('class 2', function(t){
t.plan(6);
var component = {},
span = crel('span', {class: 'majigger'});
t.equal(fancyProps.class(component, span), '');
t.equal(span.className, 'majigger');
fancyProps.class(component, span, 'foo');
t.equal(fancyProps.class(component, span), 'foo');
t.equal(span.className, 'majigger foo');
span.className += ' whatsits';
fancyProps.class(component, span, ['bar']);
t.equal(fancyProps.class(component, span), 'bar');
t.equal(span.className, 'majigger whatsits bar');
});
test('style string', function(t){
t.plan(4);
var component = {},
span = crel('span');
t.equal(fancyProps.style(component, span).background, '');
t.equal(span.style.background, '');
fancyProps.style(component, span, 'background: red');
t.equal(fancyProps.style(component, span).background, 'red');
t.equal(span.style.background, 'red');
});
test('style object', function(t){
t.plan(4);
var component = {},
span = crel('span');
t.equal(fancyProps.style(component, span).background, '');
t.equal(span.style.background, '');
fancyProps.style(component, span, { background: 'red' });
t.equal(fancyProps.style(component, span).background, 'red');
t.equal(span.style.background, 'red');
});
================================================
FILE: test/firmer.js
================================================
var test = require('tape'),
firmer = require('../firmer');
test('default (0) firmness', function(t){
t.plan(2);
var entitiy = {_firm:0};
t.notOk(firmer(entitiy, 1));
t.notOk(firmer(entitiy, 0));
});
test('template (1) firmness', function(t){
t.plan(2);
var entitiy = {_firm:1};
t.notOk(firmer(entitiy, 1));
t.ok(firmer(entitiy, 0));
});
test('custom (2) firmness', function(t){
t.plan(2);
var entitiy = {_firm:2};
t.ok(firmer(entitiy, 1));
t.ok(firmer(entitiy, 0));
});
test('attach() (undefined) firmness', function(t){
t.plan(3);
var entitiy = {_firm:undefined};
t.ok(firmer(entitiy, 0));
t.ok(firmer(entitiy, 1));
t.ok(firmer(entitiy, Infinity));
});
================================================
FILE: test/generic.js
================================================
var test = require('tape'),
crel = require('crel'),
createFastn = require('./createFastn');
test('div', function(t){
t.plan(2);
var fastn = createFastn();
var div = fastn('div');
div.render();
document.body.appendChild(div.element);
t.equal(document.body.childNodes.length, 1);
t.equal(document.body.childNodes[0].tagName, 'DIV');
div.element.remove();
div.destroy();
});
test('undefined attribute is removed', function(t){
t.plan(2);
var fastn = createFastn();
var div = fastn('div', { someattr: true });
div.render();
t.equal(div.element.hasAttribute('someattr'), true);
div.someattr(undefined);
t.equal(div.element.hasAttribute('someattr'), false, 'undefined attribute is removed');
div.destroy();
});
test('special properties - input value - undefined', function(t){
t.plan(3);
var fastn = createFastn();
var input = fastn('input', {value: undefined});
input.render();
document.body.appendChild(input.element);
t.equal(document.body.childNodes.length, 1);
t.equal(document.body.childNodes[0].tagName, 'INPUT');
t.equal(document.body.childNodes[0].value, '');
input.element.remove();
input.destroy();
});
test('special properties - input value - dates', function(t){
t.plan(8);
var fastn = createFastn();
var input = fastn('input', {
type: 'date',
value: new Date('2015/01/01'),
onchange: 'value:value',
onclick: 'value:value' // so I can trigger events..
});
input.render();
document.body.appendChild(input.element);
t.equal(document.body.childNodes.length, 1, 'node added');
t.equal(document.body.childNodes[0].tagName, 'INPUT', 'correct tagName');
t.equal(document.body.childNodes[0].value, '2015-01-01', 'correct initial input.value');
t.deepEqual(input.value(), new Date('2015/01/01'), 'correct initial property()');
input.value(new Date('2015/02/02'));
t.equal(document.body.childNodes[0].value, '2015-02-02', 'correctly set new input.value');
t.deepEqual(input.value(), new Date('2015/02/02'), 'correctly set new property()');
input.element.value = '2016-02-02';
input.element.click();
t.equal(document.body.childNodes[0].value, '2016-02-02', 'correctly set new input.value 2');
t.deepEqual(input.value(), new Date('2016/02/02'), 'correctly set new property() 2');
input.element.remove();
input.destroy();
});
test('special properties - disabled', function(t){
t.plan(4);
var fastn = createFastn();
var button = fastn('button', {
type: 'button',
disabled: false
});
button.render();
document.body.appendChild(button.element);
t.equal(document.body.childNodes.length, 1);
t.equal(document.body.childNodes[0].tagName, 'BUTTON');
t.equal(document.body.childNodes[0].getAttribute('disabled'), null);
button.disabled(true);
t.equal(document.body.childNodes[0].getAttribute('disabled'), 'disabled');
button.element.remove();
button.destroy();
});
test('special properties - textContent', function(t){
t.plan(4);
var fastn = createFastn();
var label = fastn('label', {
textContent: 'foo'
});
label.render();
document.body.appendChild(label.element);
t.equal(document.body.childNodes.length, 1);
t.equal(document.body.childNodes[0].tagName, 'LABEL');
t.equal(document.body.childNodes[0].textContent, 'foo');
label.textContent(null);
t.equal(document.body.childNodes[0].textContent, '');
label.element.remove();
label.destroy();
});
test('special properties - innerHTML', function(t){
t.plan(4);
var fastn = createFastn();
var label = fastn('label', {
innerHTML: 'foo'
});
label.render();
document.body.appendChild(label.element);
t.equal(document.body.childNodes.length, 1);
t.equal(document.body.childNodes[0].tagName, 'LABEL');
t.equal(document.body.childNodes[0].innerHTML, 'foo');
label.innerHTML(null);
t.equal(document.body.childNodes[0].innerHTML, '');
label.element.remove();
label.destroy();
});
test('preexisting element', function(t){
t.plan(4);
var fastn = createFastn();
var element = crel('label'),
label = fastn(element, {
textContent: 'foo'
});
label.render();
document.body.appendChild(label.element);
t.equal(document.body.childNodes.length, 1);
t.equal(document.body.childNodes[0].tagName, 'LABEL');
t.equal(document.body.childNodes[0].textContent, 'foo');
label.textContent(null);
t.equal(document.body.childNodes[0].textContent, '');
label.element.remove();
label.destroy();
});
test('DOM children', function(t){
t.plan(3);
var fastn = createFastn();
var label = fastn('div',
crel('h1', 'DOM Child')
);
label.render();
document.body.appendChild(label.element);
t.equal(document.body.childNodes.length, 1);
t.equal(document.body.childNodes[0].tagName, 'DIV');
t.equal(document.body.childNodes[0].textContent, 'DOM Child');
label.element.remove();
label.destroy();
});
test('same scope', function(t){
t.plan(4);
var fastn = createFastn();
var thing = fastn('label', {}, fastn.binding('x'));
thing.render();
document.body.appendChild(thing.element);
t.equal(document.body.childNodes.length, 1);
t.equal(document.body.childNodes[0].tagName, 'LABEL');
thing.attach({
x: 10
});
t.equal(document.body.childNodes[0].textContent, '10');
thing.attach({
x: 20
});
t.equal(document.body.childNodes[0].textContent, '20');
thing.element.remove();
thing.destroy();
});
test('default type', function(t){
t.plan(1);
var fastn = createFastn();
var thing = fastn('_generic').render();
t.equal(thing.element.tagName, 'DIV');
thing.destroy();
});
test('override type', function(t){
t.plan(1);
var fastn = createFastn();
var thing = fastn('span:div:section').render();
t.equal(thing.element.tagName, 'SECTION');
thing.destroy();
});
test('custom fancyProps', function(t){
t.plan(3);
var fastn = createFastn({
custom: function(fastn, component, type, settings, children){
// Map all settings to data-{name} as an example
component.extend('_generic', settings, children);
component._fancyProps = function(attribute){
if(attribute === 'ignore'){
return;
}
return function(component, element, value){
if(arguments.length < 3){
return element.getAttribute('data-' + attribute);
}
return element.setAttribute('data-' + attribute, value);
}
}
return component;
}
});
var thing = fastn('div:custom', { property: 'foo', ignore: 'bar' }).render();
t.equal(thing.element.tagName, 'DIV');
t.equal(thing.element.getAttribute('data-property'), 'foo');
t.equal(thing.element.getAttribute('ignore'), 'bar');
thing.destroy();
});
test('event handling - auto handler', function(t){
t.plan(1);
var fastn = createFastn();
var input = fastn('input', {
value: 'a',
onclick: 'value:value',
});
input.render();
document.body.appendChild(input.element);
input.element.value = 'b';
input.element.click();
t.equal(input.value(), 'b')
input.element.remove();
input.destroy();
});
test('event handling - function handler', function(t){
t.plan(1);
var fastn = createFastn();
var button = fastn('button', {
onclick: (event, scope) => t.pass('recieved click')
});
button.render();
document.body.appendChild(button.element);
button.element.click();
button.element.remove();
button.destroy();
});
test('event handling - function handler - this', function(t){
t.plan(1);
var fastn = createFastn();
var input = fastn('input', {
value: 'a',
onclick: function(event, scope){ this.value('b') }
});
input.render();
document.body.appendChild(input.element);
input.element.value = 'b';
input.element.click();
t.equal(input.value(), 'b')
input.element.remove();
input.destroy();
});
test('event handling - component handler', function(t){
t.plan(1);
var fastn = createFastn();
var button = fastn('button')
.on('click', (event, scope) => t.pass('recieved click'))
button.render();
document.body.appendChild(button.element);
button.element.click();
button.element.remove();
button.destroy();
});
test('event handling - function handler - this', function(t){
t.plan(1);
var fastn = createFastn();
var input = fastn('input', {
value: 'a'
})
.on('click', function(event, scope){ this.value('b') })
input.render();
document.body.appendChild(input.element);
input.element.value = 'b';
input.element.click();
t.equal(input.value(), 'b')
input.element.remove();
input.destroy();
});
================================================
FILE: test/index.html
================================================
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height">
<script>
var oldLog = console.log,
logs = [];
function printResults(){
logs.forEach(function(log){
document.write(log + '<br>');
});
document.body.scrollTop = Number.MAX_VALUE;
}
console.log = function(line){
logs.push(line);
oldLog.apply(this, arguments);
if(line.match(/^# ok$/)){
printResults();
}
if(line.match(/^# fail .*?$/)){
printResults();
}
};
window.onerror = printResults;
</script>
<body>
<script src="index.browser.js"></script>
</body>
================================================
FILE: test/index.js
================================================
"use strict"
function run(){
document.body.innerHTML = '';
require('./firmer.js');
require('./binding.js');
require('./property.js');
require('./component.js');
require('./text.js');
require('./list.js');
require('./templater.js');
require('./container.js');
require('./generic.js');
require('./attach.js');
require('./fancyProps.js');
require('./customModel.js');
}
if(typeof document !== 'undefined'){
window.onload = run;
}else{
require('./document')();
run();
}
================================================
FILE: test/list.js
================================================
var test = require('tape'),
consoleWatch = require('console-watch'),
Enti = require('enti'),
createFastn = require('./createFastn');
test('value items', function(t){
t.plan(1);
var fastn = createFastn();
var list = fastn('list', {
items: [1,2,3,4],
template: function(model){
return fastn.binding('item');
}
});
list.render();
document.body.appendChild(list.element);
t.equal(document.body.textContent, '1234');
list.element.remove();
list.destroy();
});
test('value items duplicate values', function(t){
t.plan(1);
var fastn = createFastn();
var list = fastn('list', {
items: [1,1,2,2],
template: function(model){
return fastn.binding('item');
}
});
list.render();
document.body.appendChild(list.element);
t.equal(document.body.textContent, '1122');
list.element.remove();
list.destroy();
});
test('bound items', function(t){
t.plan(1);
var fastn = createFastn();
var list = fastn('list', {
items: fastn.binding('items|*'),
template: function(model){
return fastn.binding('item');
}
});
list.attach({
items: [1,2,3,4]
});
list.render();
document.body.appendChild(list.element);
t.equal(document.body.textContent, '1234');
list.element.remove();
list.destroy();
});
test('bound items changing', function(t){
t.plan(2);
var fastn = createFastn();
var list = fastn('list', {
items: fastn.binding('items|*'),
template: function(model){
return fastn.binding('item');
}
}),
model = new Enti({
items: [1,2,3,4]
});
list.attach(model);
list.render();
document.body.appendChild(list.element);
t.equal(document.body.textContent, '1234');
model.set('items.1', 5);
t.equal(document.body.textContent, '1534');
list.element.remove();
list.destroy();
});
test('bound items add', function(t){
t.plan(2);
var fastn = createFastn();
var list = fastn('list', {
items: fastn.binding('items|*'),
template: function(model){
return fastn.binding('item');
}
}),
model = new Enti({
items: [1,2,3,4]
});
list.attach(model);
list.render();
document.body.appendChild(list.element);
t.equal(document.body.textContent, '1234');
model.set('items.4', 5);
t.equal(document.body.textContent, '12345');
list.element.remove();
list.destroy();
});
test('bound items remove', function(t){
t.plan(2);
var fastn = createFastn();
var list = fastn('list', {
items: fastn.binding('items|*'),
template: function(model){
return fastn.binding('item');
}
}),
model = new Enti({
items: [1,2,3,4]
});
list.attach(model);
list.render();
document.body.appendChild(list.element);
t.equal(document.body.textContent, '1234');
model.remove('items.3');
t.equal(document.body.textContent, '123');
list.element.remove();
list.destroy();
});
test('bound items remove same instance', function(t){
t.plan(3);
var fastn = createFastn();
var list = fastn('list', {
items: fastn.binding('items|*'),
template: function(model){
return fastn.binding('item.name');
}
}),
model = new Enti({
items: []
});
var testItem = {
name: 'foo'
};
model.push('items', testItem);
model.push('items', testItem);
list.attach(model);
list.render();
document.body.appendChild(list.element);
t.equal(document.body.textContent, 'foofoo');
model.remove('items.0');
t.equal(document.body.textContent, 'foo');
model.remove('items.0');
t.equal(document.body.textContent, '');
list.element.remove();
list.destroy();
});
test('null items', function(t){
t.plan(1);
var fastn = createFastn();
var list = fastn('list', {
items: null,
template: function(model){
return fastn.binding('item');
}
});
list.render();
document.body.appendChild(list.element);
t.equal(document.body.textContent, '');
list.element.remove();
list.destroy();
});
test('null template', function(t){
t.plan(1);
var fastn = createFastn();
var list = fastn('list', {
items: [1,2,3,4],
template: function(model){}
});
list.render();
document.body.appendChild(list.element);
t.equal(document.body.textContent, '');
list.element.remove();
list.destroy();
});
test('array to undefined', function(t){
t.plan(2);
var fastn = createFastn();
var list = fastn('list', {
items: fastn.binding('items|*'),
template: function(model){
return fastn.binding('item');
}
}),
model = new Enti({
items: [1,2,3,4]
});
list.attach(model);
list.render();
document.body.appendChild(list.element);
t.equal(document.body.textContent, '1234');
model.remove('items');
t.equal(document.body.textContent, '');
list.element.remove();
list.destroy();
});
test('array to null', function(t){
t.plan(2);
var fastn = createFastn();
var list = fastn('list', {
items: fastn.binding('items|*'),
template: function(model){
return fastn.binding('item');
}
}),
model = new Enti({
items: [1,2,3,4]
});
list.attach(model);
list.render();
document.body.appendChild(list.element);
t.equal(document.body.textContent, '1234');
model.set('items', null);
t.equal(document.body.textContent, '');
list.element.remove();
list.destroy();
});
test('reattach list with templates', function(t){
t.plan(3);
var fastn = createFastn();
var data = {foo: [
{a:1}
]},
list = fastn('list', {
items: fastn.binding('.|*'),
template: function(model, scope, lastTemplate){
return fastn.binding('item.a');
}
})
.attach(data)
.binding('foo');
list.render();
document.body.appendChild(list.element);
t.equal(document.body.textContent, '1');
fastn.Model.set(data, 'foo', [{
a: 2
}]);
t.equal(document.body.textContent, '2');
fastn.Model.set(data, 'foo', [{
a: 3
}]);
t.equal(document.body.textContent, '3');
list.element.remove();
list.destroy();
});
test('dynamic template removed', function(t){
t.plan(2);
var fastn = createFastn();
var templateBinding = fastn.binding();
templateBinding(function(model){
return fastn.binding('item');
});
var list = fastn('list', {
items: [1,2,3,4],
template: templateBinding
});
list.render();
document.body.appendChild(list.element);
t.equal(document.body.textContent, '1234');
templateBinding(null);
t.equal(document.body.textContent, '');
list.element.remove();
list.destroy();
});
test('dynamic template', function(t){
t.plan(2);
var fastn = createFastn();
var templateBinding = fastn.binding();
templateBinding(function(model){
return fastn.binding('item');
});
var list = fastn('list', {
items: [1,2,3,4],
template: templateBinding
});
list.render();
document.body.appendChild(list.element);
t.equal(document.body.textContent, '1234');
templateBinding(function(model){
return '*';
});
t.equal(document.body.textContent, '****');
list.element.remove();
list.destroy();
});
test('object item keys', function(t){
t.plan(2);
var fastn = createFastn();
var list = fastn('list', {
items: {foo:'bar'},
template: function(model){
t.equal(model.get('item'), 'bar');
t.equal(model.get('key'), 'foo');
}
});
list.attach();
list.destroy();
});
test('warns on no template', function(t){
t.plan(1);
var fastn = createFastn();
consoleWatch(function(getResults) {
var list = fastn('list');
t.deepEqual(getResults(), {warn: ['No "template" function was set for this templater component']})
});
});
test('Lazy item templating', function(t){
t.plan(3);
var fastn = createFastn();
var items = [];
while(items.length < 100){
items.push(items.length);
}
var list = fastn('list', {
insertionFrameTime: 100,
items: items,
template: function(model){
if(model.get('item') < 10){
var start = new Date();
while(Date.now() - start < 10){}
}
return fastn.binding('item');
}
});
list.render();
document.body.appendChild(list.element);
var expectedEventualText = items.join('');
t.equal(expectedEventualText.indexOf(document.body.textContent), 0);
t.notEqual(document.body.textContent, expectedEventualText);
setTimeout(function(){
t.equal(document.body.textContent, expectedEventualText);
list.element.remove();
list.destroy();
}, 100);
});
================================================
FILE: test/property.js
================================================
var test = require('tape'),
fastn = require('../index')({}),
createBinding = fastn.binding,
createProperty = fastn.property,
Enti = require('enti'),
EventEmitter = require('events');
test('simple property initialisation', function(t){
t.plan(3);
var property = createProperty();
t.equal(property(), undefined);
property('bar');
t.equal(property(), 'bar');
property.on('change', function(value){
t.equal(value, 'foo');
});
property('foo');
});
test('bound property', function(t){
t.plan(5);
var property = createProperty();
var binding = createBinding('foo');
t.equal(property(), undefined, 'No initial value');
property('bar');
t.equal(property(), 'bar', 'bar set');
property.binding(binding);
t.equal(property(), undefined, 'bar overridden by binding');
binding('baz');
t.equal(property(), 'baz', 'baz set via binding');
property.on('change', function(value){
t.equal(value, 'foo', 'property changed');
});
binding('foo');
});
test('bound property with model', function(t){
t.plan(3);
var data = {
foo: 'bar'
},
model = new Enti(data),
currentValue;
var property = createProperty();
property.on('change', function(value){
t.equal(value, currentValue);
});
var binding = createBinding('foo');
binding('baz');
currentValue = 'baz';
property.binding(binding);
currentValue = 'bar';
property.attach(model);
currentValue = 'foo';
model.set('foo', 'foo');
});
test('bound property with model and drill', function(t){
t.plan(1);
var data = {},
model = new Enti(data);
var property = createProperty();
var binding = createBinding('foo.bar');
binding.attach(model);
property.binding(binding);
property.on('change', function(value){
t.equal(value, 123);
});
model.set('foo', {bar: 123});
});
test('cyclic value', function(t){
t.plan(1);
var model = new Enti();
var property = createProperty(null, 'keys');
var binding = createBinding('.|*');
binding.attach(model);
property.binding(binding);
property.on('change', function(value){
t.equal(value, model.get('.'));
});
model.set('self', model.get('.'));
});
test('cyclic value with structure changes', function(t){
t.plan(1);
var model = new Enti();
var property = createProperty(null, 'structure');
var binding = createBinding('.|*');
binding.attach(model);
property.binding(binding);
property.on('change', function(value){
t.equal(value, model.get('.'));
});
model.set('self', model.get('.'));
});
test('bound property to EventEmitter', function(t){
t.plan(5);
var property = createProperty();
var observable = new EventEmitter();
t.equal(property(), undefined, 'No initial value');
property('bar');
t.equal(property(), 'bar', 'bar set');
property.binding(observable);
t.equal(property(), undefined, 'bar overridden by observable');
observable.emit('change', 'baz');
t.equal(property(), 'baz', 'baz set via observable');
property.on('change', function(value){
t.equal(value, 'foo', 'property changed');
});
observable.emit('change', 'foo');
});
test('bound property to EventEmitter with custom attach', function(t){
t.plan(2);
var property = createProperty();
function customObservable(path) {
var observable = new EventEmitter();
observable.attach = function(data){
this.emit('change', data[path])
};
return observable;
}
var observable = customObservable('foo')
property.binding(observable);
t.equal(property(), undefined, 'no value');
property.attach({
foo: 'bar'
})
t.equal(property(), 'bar', 'bar set via observable attach');
});
================================================
FILE: test/templater.js
================================================
var test = require('tape'),
consoleWatch = require('console-watch'),
Enti = require('enti'),
createFastn = require('./createFastn');
test('value data', function(t){
t.plan(1);
var fastn = createFastn();
var template = fastn('templater', {
data: {foo:'bar'},
template: function(model){
return fastn.binding('item.foo');
}
});
template.render();
document.body.appendChild(template.element);
t.equal(document.body.textContent, 'bar');
template.element.remove();
template.destroy();
});
test('bound data', function(t){
t.plan(1);
var fastn = createFastn();
var template = fastn('templater', {
data: fastn.binding('data|*'),
template: function(model){
return fastn.binding('item.foo');
}
});
template.attach({
data: {
foo: 'bar'
}
});
template.render();
document.body.appendChild(template.element);
t.equal(document.body.textContent, 'bar');
template.element.remove();
template.destroy();
});
test('bound data changing', function(t){
t.plan(2);
var fastn = createFastn();
var template = fastn('templater', {
data: fastn.binding('data|*'),
template: function(model){
return fastn.binding('item.foo');
}
}),
model = new Enti({
data: {
foo: 'bar'
}
});
template.attach(model);
template.render();
document.body.appendChild(template.element);
t.equal(document.body.textContent, 'bar');
model.set('data.foo', 'baz');
t.equal(document.body.textContent, 'baz');
template.element.remove();
template.destroy();
});
test('null data', function(t){
t.plan(1);
var fastn = createFastn();
var template = fastn('templater', {
data: null,
template: function(model){}
});
template.render();
document.body.appendChild(template.element);
t.equal(document.body.textContent, '');
template.element.remove();
template.destroy();
});
test('undefined template', function(t){
t.plan(1);
var fastn = createFastn();
var template = fastn('templater', {
data: null,
template: function(model){}
});
template.render();
document.body.appendChild(template.element);
t.equal(document.body.textContent, '');
template.element.remove();
template.destroy();
});
test('reuse template', function(t){
t.plan(1);
var fastn = createFastn();
var template = fastn('templater', {
data: 'foo',
template: function(model, scope, lastTemplate){
if(lastTemplate){
return lastTemplate;
}
t.pass();
return fastn('text');
}
});
template.render();
template.data('bar');
});
test('reuse template same element', function(t){
t.plan(3);
var fastn = createFastn();
var template = fastn('templater', {
data: 'foo',
template: function(model, scope, lastTemplate){
if(lastTemplate){
return lastTemplate;
}
return fastn.binding('item');
}
});
template.render();
document.body.appendChild(template.element);
t.equal(document.body.textContent, 'foo');
var lastNode = document.body.childNodes[1];
// Don't re-render or re-insert the template if it is already rendered or inserted
document.body.replaceChild = function(){
debugger
t.fail();
};
template.data('bar');
t.equal(document.body.textContent, 'bar');
t.equal(lastNode, document.body.childNodes[1]);
template.element.remove();
template.destroy();
});
test('reattach templater with attachTemplates = false', function(t){
t.plan(3);
var fastn = createFastn();
var data = {foo: {bar: 1}},
template = fastn('templater', {
data: fastn.binding('nothing'),
attachTemplates: false,
template: function(model, scope, lastTemplate){
return fastn.binding('bar');
}
})
.attach(data)
.binding('foo');
template.render();
document.body.appendChild(template.element);
t.equal(document.body.textContent, '1');
fastn.Model.set(data, 'foo', {
bar: 2
});
t.equal(document.body.textContent, '2');
fastn.Model.set(data, 'foo', {
bar: 3
});
t.equal(document.body.textContent, '3');
template.element.remove();
template.destroy();
});
test('warns on no template', function(t){
t.plan(1);
var fastn = createFastn();
consoleWatch(function(getResults) {
var list = fastn('templater');
t.deepEqual(getResults(), {warn: ['No "template" function was set for this templater component']})
});
});
================================================
FILE: test/text.js
================================================
var test = require('tape'),
Enti = require('enti'),
createFastn = require('./createFastn');
test('value text', function(t){
t.plan(1);
var fastn = createFastn();
var text = fastn('text', {text: 'foo'});
text.render();
document.body.appendChild(text.element);
t.equal(document.body.textContent, 'foo');
text.element.remove();
text.destroy();
});
test('bound text', function(t){
t.plan(1);
var fastn = createFastn();
var text = fastn('text', {text: fastn.binding('value')});
text.attach({
value: 'foo'
});
text.render();
document.body.appendChild(text.element);
t.equal(document.body.textContent, 'foo');
text.element.remove();
text.destroy();
});
test('bound text changing', function(t){
t.plan(2);
var fastn = createFastn();
var text = fastn('text', {text: fastn.binding('value')}),
model = new Enti({
value: 'foo'
});
text.attach(model);
text.render();
document.body.appendChild(text.element);
t.equal(document.body.textContent, 'foo');
model.set('value', 'bar');
t.equal(document.body.textContent, 'bar');
text.element.remove();
text.destroy();
});
test('auto binding text', function(t){
t.plan(2);
var fastn = createFastn();
var parent = fastn('span', fastn.binding('value')),
model = new Enti({
value: 'foo'
});
parent.attach(model);
parent.render();
document.body.appendChild(parent.element);
t.equal(document.body.textContent, 'foo');
model.set('value', 'bar');
t.equal(document.body.textContent, 'bar');
parent.element.remove();
parent.destroy();
});
test('undefined text', function(t){
t.plan(1);
var fastn = createFastn();
var text = fastn('text', {text: undefined});
text.render();
document.body.appendChild(text.element);
t.equal(document.body.textContent, '');
text.element.remove();
text.destroy();
});
test('auto text Date', function(t){
t.plan(1);
var fastn = createFastn();
var date = new Date(),
parent = fastn('span', date);
parent.render();
document.body.appendChild(parent.element);
t.equal(document.body.textContent, date.toString());
parent.element.remove();
parent.destroy();
});
test('clone text', function(t){
t.plan(2);
var fastn = createFastn();
var parent = fastn('span', 'text');
parent.render();
document.body.appendChild(parent.element);
t.equal(document.body.textContent, 'text');
parent.element.remove();
var newParent = parent.clone();
parent.destroy();
newParent.render();
document.body.appendChild(newParent.element);
t.equal(document.body.textContent, 'text');
newParent.element.remove();
newParent.destroy();
});
test('clone text binding', function(t){
t.plan(2);
var data = {
foo: 'bar'
};
var fastn = createFastn();
var binding = fastn.binding('foo').attach(data);
var parent = fastn('span', binding);
parent.render();
document.body.appendChild(parent.element);
t.equal(document.body.textContent, 'bar');
parent.element.remove();
var newParent = parent.clone();
parent.destroy();
newParent.render();
document.body.appendChild(newParent.element);
t.equal(document.body.textContent, 'bar');
newParent.element.remove();
newParent.destroy();
});
================================================
FILE: textComponent.js
================================================
function updateText(){
if(!this.element){
return;
}
var value = this.text();
this.element.data = (value == null ? '' : value);
}
function autoRender(content){
this.element = document.createTextNode(content);
}
function autoText(text, fastn, content) {
text.render = autoRender.bind(text, content);
return text;
}
function render(){
this.element = this.createTextNode(this.text());
this.emit('render');
};
function textComponent(fastn, component, type, settings, children){
component.createTextNode = textComponent.createTextNode;
component.render = render.bind(component);
component.setProperty('text', fastn.property('', updateText.bind(component)));
return component;
}
textComponent.createTextNode = function(text){
return document.createTextNode(text);
};
module.exports = textComponent;
gitextract_cj4o0y9g/ ├── .gitignore ├── .npmrc ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── baseComponent.js ├── binding.js ├── containerComponent.js ├── domComponents.js ├── fancyProps.js ├── firmer.js ├── genericComponent.js ├── index.js ├── is.js ├── listComponent.js ├── package.json ├── property.js ├── schedule.js ├── templaterComponent.js ├── test/ │ ├── attach.js │ ├── binding.js │ ├── changes.js │ ├── component.js │ ├── components.js │ ├── container.js │ ├── createFastn.js │ ├── customBinding.js │ ├── customModel.js │ ├── document.js │ ├── fancyProps.js │ ├── firmer.js │ ├── generic.js │ ├── index.html │ ├── index.js │ ├── list.js │ ├── property.js │ ├── templater.js │ └── text.js └── textComponent.js
SYMBOL INDEX (90 symbols across 18 files)
FILE: baseComponent.js
function flatten (line 6) | function flatten(item){
function attachProperties (line 15) | function attachProperties(object, firm){
function onRender (line 21) | function onRender(){
function detachProperties (line 31) | function detachProperties(firm){
function destroyProperties (line 37) | function destroyProperties(){
function clone (line 43) | function clone(){
function getSetBinding (line 52) | function getSetBinding(newBinding){
function emitAttach (line 76) | function emitAttach(){
function emitDetach (line 85) | function emitDetach(){
function getScope (line 89) | function getScope(){
function destroy (line 93) | function destroy(){
function attachComponent (line 111) | function attachComponent(object, firm){
function detachComponent (line 116) | function detachComponent(firm){
function isDestroyed (line 121) | function isDestroyed(){
function setProperty (line 125) | function setProperty(key, property){
function bindInternalProperty (line 138) | function bindInternalProperty(component, model, propertyName, propertyTr...
function createInternalScope (line 147) | function createInternalScope(data, propertyTransforms){
function extendComponent (line 163) | function extendComponent(type, settings, children){
function isType (line 193) | function isType(type){
function FastnComponent (line 197) | function FastnComponent(fastn, type, settings, children){
FILE: binding.js
function noop (line 7) | function noop(x){
function fuseBinding (line 11) | function fuseBinding(){
function createValueBinding (line 98) | function createValueBinding(fastn){
function bindingTemplate (line 105) | function bindingTemplate(newValue){
function modelAttachHandler (line 118) | function modelAttachHandler(data){
function modelDetachHandler (line 125) | function modelDetachHandler(){
function attach (line 129) | function attach(object, firm){
function detach (line 168) | function detach(firm){
function set (line 181) | function set(newValue){
function change (line 192) | function change(newValue){
function clone (line 201) | function clone(keepAttachment){
function destroy (line 212) | function destroy(soft){
function destroyed (line 226) | function destroyed(){
function createBinding (line 230) | function createBinding(path, more){
function from (line 280) | function from(valueOrBinding){
FILE: containerComponent.js
function insertChild (line 1) | function insertChild(fastn, container, child, index){
function getContainerElement (line 38) | function getContainerElement(){
function insert (line 42) | function insert(child, index){
FILE: fancyProps.js
function updateTextProperty (line 4) | function updateTextProperty(generic, element, value){
FILE: genericComponent.js
function createProperties (line 7) | function createProperties(fastn, component, settings){
function trackKeyEvents (line 19) | function trackKeyEvents(component, element, event){
function addDomHandler (line 26) | function addDomHandler(component, element, handlerName, eventName, captu...
function addDomHandlers (line 45) | function addDomHandlers(component, element, eventNames){
function addAutoHandler (line 62) | function addAutoHandler(component, element, key, settings){
function addDomProperty (line 99) | function addDomProperty(fastn, key, property){
function onRender (line 156) | function onRender(){
function render (line 173) | function render(){
function genericComponent (line 185) | function genericComponent(fastn, component, type, settings, children){
FILE: index.js
function inflateProperties (line 8) | function inflateProperties(component, settings){
function validateExpectedComponents (line 34) | function validateExpectedComponents(components, componentName, expectedC...
function fastn (line 55) | function fastn(type){
FILE: is.js
function isComponent (line 8) | function isComponent(thing){
function isBindingObject (line 12) | function isBindingObject(thing){
function isBinding (line 16) | function isBinding(thing){
function isProperty (line 20) | function isProperty(thing){
function isDefaultBinding (line 24) | function isDefaultBinding(thing){
FILE: listComponent.js
function each (line 8) | function each(value, fn){
function keyFor (line 24) | function keyFor(object, value){
function updateOrCreateChild (line 63) | function updateOrCreateChild(template, item, key){
function insertNextItems (line 102) | function insertNextItems(template, insertionFrameTime){
function updateItems (line 137) | function updateItems(){
function removeComponent (line 190) | function removeComponent(childComponent){
FILE: property.js
function propertyTemplate (line 12) | function propertyTemplate(value){
function changeChecker (line 29) | function changeChecker(current, changes){
function propertyBinding (line 47) | function propertyBinding(newBinding){
function attachProperty (line 80) | function attachProperty(object, firm){
function detachProperty (line 103) | function detachProperty(firm){
function updateProperty (line 121) | function updateProperty(){
function propertyUpdater (line 133) | function propertyUpdater(fn){
function destroyProperty (line 141) | function destroyProperty(){
function propertyDestroyed (line 159) | function propertyDestroyed(){
function addPropertyTo (line 163) | function addPropertyTo(component, key){
function createProperty (line 169) | function createProperty(currentValue, changes, updater){
FILE: schedule.js
function run (line 6) | function run(){
function schedule (line 21) | function schedule(key, fn){
FILE: templaterComponent.js
function replaceElement (line 8) | function replaceElement(element){
function update (line 15) | function update(){
FILE: test/binding.js
function changeHandler (line 526) | function changeHandler(){
function run (line 597) | function run(){
FILE: test/changes.js
function runTest (line 5) | function runTest (fn) {
function rightoTest (line 17) | function rightoTest (name, fn) {
FILE: test/customBinding.js
function changeHandler (line 405) | function changeHandler(){
FILE: test/customModel.js
function CustomModel (line 7) | function CustomModel(instance){
FILE: test/index.js
function run (line 3) | function run(){
FILE: test/property.js
function customObservable (line 177) | function customObservable(path) {
FILE: textComponent.js
function updateText (line 1) | function updateText(){
function autoRender (line 11) | function autoRender(content){
function autoText (line 15) | function autoText(text, fastn, content) {
function render (line 21) | function render(){
function textComponent (line 26) | function textComponent(fastn, component, type, settings, children){
Condensed preview — 39 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (143K chars).
[
{
"path": ".gitignore",
"chars": 34,
"preview": "**/*.browser.js\nnode_modules\n*.log"
},
{
"path": ".npmrc",
"chars": 19,
"preview": "package-lock=false\n"
},
{
"path": ".travis.yml",
"chars": 202,
"preview": "language: node_js\nnode_js:\n- '12'\naddons:\n apt:\n packages:\n - xvfb\ninstall:\n - export DISPLAY=':99.0'\n - Xvfb"
},
{
"path": "CHANGELOG.md",
"chars": 3242,
"preview": "# Changelog for fastn\n\n\n## v2\n\nVersion two of fastn changes the way the component constructors work, to allow for better"
},
{
"path": "README.md",
"chars": 11859,
"preview": "# fastn\n\nCreate ultra-lightweight UI components\n\n[,\n GENERIC = '_generic',\n EventEmitter = require('events').EventEmitter,\n slice = Array"
},
{
"path": "binding.js",
"chars": 7998,
"preview": "var is = require('./is'),\n firmer = require('./firmer'),\n functionEmitter = require('function-emitter'),\n setPr"
},
{
"path": "containerComponent.js",
"chars": 4142,
"preview": "function insertChild(fastn, container, child, index){\n if(child == null || child === false){\n return;\n }\n\n "
},
{
"path": "domComponents.js",
"chars": 813,
"preview": "module.exports = function(extra){\n var components = {\n // The _generic component is a catch-all for any compon"
},
{
"path": "fancyProps.js",
"chars": 3043,
"preview": "var setify = require('setify'),\n classist = require('classist');\n\nfunction updateTextProperty(generic, element, value"
},
{
"path": "firmer.js",
"chars": 193,
"preview": "// Is the entity firmer than the new firmness\nmodule.exports = function(entity, firm){\n if(firm != null && (entity._f"
},
{
"path": "genericComponent.js",
"chars": 6718,
"preview": "var containerComponent = require('./containerComponent'),\n schedule = require('./schedule'),\n fancyProps = require"
},
{
"path": "index.js",
"chars": 3899,
"preview": "var createProperty = require('./property'),\n createBinding = require('./binding'),\n BaseComponent = require('./bas"
},
{
"path": "is.js",
"chars": 906,
"preview": "var FUNCTION = 'function',\n OBJECT = 'object',\n FASTNBINDING = '_fastn_binding',\n FASTNPROPERTY = '_fastn_prope"
},
{
"path": "listComponent.js",
"chars": 5588,
"preview": "var MultiMap = require('multimap'),\n merge = require('flat-merge');\n\nvar requestIdleCallback = global.requestIdleCall"
},
{
"path": "package.json",
"chars": 878,
"preview": "{\n \"name\": \"fastn\",\n \"version\": \"2.14.5\",\n \"description\": \"\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"test\": \"node "
},
{
"path": "property.js",
"chars": 5261,
"preview": "var WhatChanged = require('what-changed'),\n same = require('same-value'),\n firmer = require('./firmer'),\n funct"
},
{
"path": "schedule.js",
"chars": 576,
"preview": "var todo = [],\n todoKeys = [],\n scheduled,\n updates = 0;\n\nfunction run(){\n var startTime = Date.now();\n\n "
},
{
"path": "templaterComponent.js",
"chars": 2820,
"preview": "module.exports = function(fastn, component, type, settings, children){\n var itemModel = new fastn.Model({});\n\n if("
},
{
"path": "test/attach.js",
"chars": 1986,
"preview": "var test = require('tape'),\n Enti = require('enti'),\n createFastn = require('./createFastn');\n\ntest('manual attach"
},
{
"path": "test/binding.js",
"chars": 11409,
"preview": "var test = require('tape'),\n createBinding = require('../index')({}).binding,\n Enti = require('enti');\n\ntest('inva"
},
{
"path": "test/changes.js",
"chars": 499,
"preview": "const righto = require('righto');\r\n\r\nconst outputOnError = error => { error && console.log(error); };\r\n\r\nfunction runTes"
},
{
"path": "test/component.js",
"chars": 3247,
"preview": "var test = require('tape'),\n Enti = require('enti'),\n createFastn = require('./createFastn');\n\ntest('binding', fun"
},
{
"path": "test/components.js",
"chars": 864,
"preview": "module.exports = function(components){\n if(!components){\n components = {};\n }\n\n var genericComponent = r"
},
{
"path": "test/container.js",
"chars": 3824,
"preview": "var test = require('tape'),\n createFastn = require('./createFastn');\n\ntest('children are added', function(t){\n\n t."
},
{
"path": "test/createFastn.js",
"chars": 153,
"preview": "var merge = require('flat-merge');\n\nmodule.exports = function createFastn(components){\n return require('../')(require"
},
{
"path": "test/customBinding.js",
"chars": 7870,
"preview": "var test = require('tape'),\n createBinding = require('../binding'),\n Enti = require('enti');\n\ntest('simple binding"
},
{
"path": "test/customModel.js",
"chars": 3742,
"preview": "var test = require('tape'),\n EventEmitter = require('events'),\n createFastn = require('../index');\n\nvar allModels "
},
{
"path": "test/document.js",
"chars": 314,
"preview": "module.exports = function(){\n var domLite = require('dom-lightning');\n\n document = domLite.document;\n document."
},
{
"path": "test/fancyProps.js",
"chars": 2084,
"preview": "var test = require('tape'),\n crel = require('crel'),\n fancyProps = require('../fancyProps');\n\ntest('date input', f"
},
{
"path": "test/firmer.js",
"chars": 741,
"preview": "var test = require('tape'),\n firmer = require('../firmer');\n\ntest('default (0) firmness', function(t){\n\n t.plan(2)"
},
{
"path": "test/generic.js",
"chars": 9309,
"preview": "var test = require('tape'),\n crel = require('crel'),\n createFastn = require('./createFastn');\n\ntest('div', functio"
},
{
"path": "test/index.html",
"chars": 738,
"preview": "<meta name=\"viewport\" content=\"user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, "
},
{
"path": "test/index.js",
"chars": 532,
"preview": "\"use strict\"\n\nfunction run(){\n document.body.innerHTML = '';\n\n require('./firmer.js');\n require('./binding.js')"
},
{
"path": "test/list.js",
"chars": 9705,
"preview": "var test = require('tape'),\n consoleWatch = require('console-watch'),\n Enti = require('enti'),\n createFastn = r"
},
{
"path": "test/property.js",
"chars": 3955,
"preview": "var test = require('tape'),\n fastn = require('../index')({}),\n createBinding = fastn.binding,\n createProperty ="
},
{
"path": "test/templater.js",
"chars": 5090,
"preview": "var test = require('tape'),\n consoleWatch = require('console-watch'),\n Enti = require('enti'),\n createFastn = r"
},
{
"path": "test/text.js",
"chars": 3510,
"preview": "var test = require('tape'),\n Enti = require('enti'),\n createFastn = require('./createFastn');\n\ntest('value text', "
},
{
"path": "textComponent.js",
"chars": 867,
"preview": "function updateText(){\n if(!this.element){\n return;\n }\n\n var value = this.text();\n\n this.element.data"
}
]
About this extraction
This page contains the full source code of the KoryNunn/fastn GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 39 files (132.1 KB), approximately 31.7k tokens, and a symbol index with 90 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.