Open tunnckoCore opened 7 years ago
Woohoo, JSX! :rofl:
/** @jsx h */
const h = require('mich-h')
const tree = <div id="page">
<div id="header">
<h1 class="classy" style="background-color: #333; color: purple"></h1>
</div>
<nav id="menu" style="background: #2f2;">
<ul>
<li>one</li>
<li class="sec">two</li>
<li>three</li>
</ul>
</nav>
<h2 id="title" style="background-color: red;">content title</h2>
<p class="first foobie quxie">so it's just like a templating engine,
but easy to use inline with javascript</p>
<p>the intention is for this to be usedto create <strong>charlike</strong>
reusable, interactive html widgets.</p>
</div>
console.log(tree)
Very small and naive rendering of HAST to HTML string
module.exports = function toHtml (tree) {
var buf = ''
traverse([].concat(tree), function (node) {
if (node.type === 'text') {
buf += node.value
return
}
if (node.type !== 'element') return
buf = `${buf}<${node.tagName}${toAttrs(node.properties)}>`
buf = `${buf}${toHtml(node.children)}</${node.tagName}>\n`
})
return buf
}
function traverse (tree, cb) {
if (Array.isArray(tree)) {
tree.forEach(function (node) {
traverse(cb(node), cb)
})
} else if (typeof tree === 'object' && tree.hasOwnProperty('children')) {
traverse(tree.children, cb)
}
return tree
}
var transform = {
'className': 'class',
'htmlFor': 'for',
'httpEquiv': 'http-equiv'
}
function toAttrs (props) {
var attr = ''
for (var prop in props) {
var value = props[prop]
prop = prop in transform ? transform[prop] : prop
value = prop === 'class' ? value.join(' ') : value
var type = typeof value
if (value === true) {
attr += ' ' + prop
} else if (value === false) {
attr += ' ' + prop + '="false"'
} else if (type === 'string' || type === 'number') {
attr += ' ' + prop + '="' + value + '"'
} else if (prop === 'style') {
var css = ''
for (var key in value) {
css += key + ': ' + value[key] + ';'
}
attr += ' ' + prop + '="' + css + '"'
} else if (prop === 'dataset') {
for (var key in value) {
attr += ' data-' + key + '="' + value[key] + '"'
}
}
}
return attr
}
Support for data-
, class, style, for, http-equiv, truthy and falsey attributes
Example
var ast = h('div#page.foo.bar.qux', { className: 'ok' },
h('#header', // if tag name is not given, defaults to `div`
h('h1.classy', 'hi', { style: 'background-color: #333; color: purple' })),
h('nav#menu', { style: {'background': '#2f2', 'font-size': '12px' } },
h('ul',
h('li', 'one', { dataset: { foo: 'bar', 'set': 'ok' } }),
h('li.sec', 'two'),
h('li', { download: true }, 'three'))),
h('h2#title', 'content title', { style: {'background-color': 'red'} }),
h('p.first', // classes of that `p` would be `first, foobie`
{ className: 'foobie' },
"so it's just like a templating engine,\n",
"but easy to use inline with javascript\n",
{ onclick: () => {} }
),
h('p',
"the intention is for this to be used to create\n",
h('strong', 'charlike', { style: 'background: white; color: green' }),
" reusable, interactive html widgets. "))
var res = toHtml(ast)
console.log(res)
outputs a HTML string
<div id="page" class="foo bar qux ok">
<div id="header">
<h1 class="classy" style="background-color: #333; color: purple">hi</h1>
</div>
<nav id="menu" style="background: #2f2;font-size: 12px;">
<ul>
<li data-foo="bar" data-set="ok">one</li>
<li class="sec">two</li>
<li download>three</li>
</ul>
</nav>
<h2 id="title" style="background-color: red;">content title</h2>
<p class="first foobie">so it's just like a templating engine,
but easy to use inline with javascript
</p>
<p>the intention is for this to be used to create
<strong style="background: white; color: green">charlike</strong>
reusable, interactive html widgets.
</p>
</div>
Stateless Components
var Foo = function ({ name, last, children }) {
return h('user', { name, last }, children)
}
var foo = h(Foo, {
name: 'Charlike',
last: 'Reagent'
}, 'Hello')
console.log(foo)
with toHtml
it yields
<user name="Charlike" last="Reagent">Hello</user>
Chika is girlfriend Mich, the Virtual Sword guy.
They kinda have childrens - Gibon and Prince Mich Rich Router
kinda interesting signatures and stuff. For mich
and chika
.
model
argument - reducers, effects, subscriptions, state, hooks. Say app(model)
CreateView
- it accepts signle argument - a function{ state, actions, props, children, params }
- some of them may not exist - depends on useTodoItemComponent
TodoItemComponent({ props: { foo: 'bar' }, state: { hi: 'wi' } })
<TodoItemComponent foo="bar" state={{ hoho: 'hehe' }} />
- allowing to merge/change the initial state that was passed to the 1), while creating the model.opts.view
or directly view fn to the main export app(({ state, actions, props }) => <h1 />)
/** @jsx h */
const h = require('mich-h')
// const List = (model) => ({ props, children}) =>
// const CatList = app({
// subscriptions: {},
// reducers: {},
// effects: {},
// hooks: {}
// state: {}
// })
// const tree = <CatList name="charlike" />
// const tree = CatList((state, actions) => <div />)
// render(tree, document.body)
function app (options) {
let state = options.state
const actions = {}
const subscriptions = options.subscriptions || {}
const reducers = options.reducers || {}
const effects = options.effects || {}
const hooks = merge({
onAction: Function.prototype,
onUpdate: Function.prototype,
onError: (er) => { throw er }
}, options.hooks)
return function Component (ctx) {
if (typeof ctx === 'function') {
let prev = null
let viewFn = ctx
// using `var` intentionally
for (var name in merge(reducers, effects)) {
actions[name] = function action (data) {
hooks.onAction(name, data)
// double `effects[name]` for saving bytes
if (effects[name]) {
return effects[name](state, actions, data, hooks.onError)
}
const oldState = state
state = reducers[name](oldState, data)
prev = viewFn(state, actions)
hooks.onUpdate(oldState, state, data)
}
}
return viewFn(state, actions)
}
}
}
// const TodoItemComponent = Component({
// reducers: {},
// effects: {},
// state: {
// hi: 'hello',
// link: '#initial'
// }
// })
// const TodoItem = TodoItemComponent(({ state, props, actions }) => (<li>
// <a href={state.link} onclick={actions.handleClick}>some {state.hi}</a>
// </li>))
// render(<TodoItem />, document.body)
// render(TodoItem(), document.body)
const View = Component({
state: { welcome: 'Hello World', aboutme: '#hoho-about-me' }
})
// const CreateView = CreateAppModel({})
// const AnchorComponent = CreateView()
//
// // variant 1
// let tree = AnchorComponent({
// props: { foo:bar }
// })
// render(tree, document.body)
//
// // variant 2
// let ret = <AnchorComponent />
// let ret = <AnchorComponent foo="bar" state={{ hi: 'welcome' }} />
// render(ret, document.body)
// same component state, different view
const Link = View(({ state, props, children }) => <a href={props.path} title={state.welcome}>
{children}
</a>)
const Main = View(({ state, actions, props }) => <div>
<h1>{state.welcome}</h1>
<h2>Is there any props? {props}</h2>
<Link path="/about">{state.aboutme}</Link>
</div>)
render(<Main />, document.body)
// render(main(), document.body)
// const ls = <TodoItem />
// render(<TodoItem state={{
// hi: 'non def',
// link: '#new-link'
// }} />, document.body)
// using `var` intentionally
// for (var name in merge(reducers, effects)) {
// actions[name] = function action (data) {
// hooks.onAction(name, data)
// // double `effects[name]` for saving bytes
// if (effects[name]) {
// return effects[name](state, actions, data, hooks.onError)
// }
// const oldState = state
// state = reducers[name](oldState, data)
// tree = render(tree, state, actions)
// hooks.onUpdate(oldState, model, data)
// }
// }
// function render (node, root) {
// if (!node) return
// root.appendChild(createElement(node))
// }
// function createElement (node) {
// if (node && node.type === 'text') {
// return document.createTextNode(node.value)
// }
// if (node && node.type === 'element') {
// var $el = document.createElement(node.tagName)
// for (var i in node.children) {
// var el = createElement(node.children[i])
// $el.appendChild(el)
// }
// return $el
// }
// }
// function updateElement($parent, newNode, oldNode) {
// if (!oldNode) {
// $parent.appendChild(
// createElement(newNode)
// );
// }
// }
// const Item = (props) => (<li>
// {props.name}
// </li>)
// const List = (props) => (
// <ul className="list">
// {props.items.map((item) => <Item name={item}/>)}
// </ul>
// )
// const $root = document.body
// const items = [
// 'hello world',
// 'foo bar',
// 'hehehe'
// ]
// render(<List items={items}/>, $root)
// const data = {
// age: 24,
// firstName: 'Charlike',
// description: 'The `mich-h` is pretty good virtual dom builder'
// }
// const Form = (props) => <form className="form">
// <div className="form-group">
// <label>Please enter your first name:</label>
// <input type="text" placeholder="Joe" value={props.name}/>
// </div>
// <div className="form-group">
// <label>Please enter your last name:</label>
// <input type="text" placeholder="Bloggs" value="Gannaway"/>
// </div>
// <div className="form-group">
// <label>Please enter your age:</label>
// <input type="number" min="0" max="99" value={props.age}/>
// </div>
// <div className="form-group">
// <label>What is your favourite food:</label>
// <div className="inline"><input type="radio" name="food"/><span>Pizza</span></div>
// <div className="inline"><input type="radio" name="food"/><span>Pasta</span></div>
// </div>
// <div className="form-group">
// <label>Please enter your location:</label>
// <select>
// <option>United States</option>
// <option>United Kingdom</option>
// <option>France</option>
// </select>
// </div>
// <div className="form-group">
// <label>Please enter a description:</label>
// <textarea value={props.desc}>I dont know?</textarea>
// </div>
// </form>
// render(<Form
// age={data.age}
// name={data.firstName}
// desc={data.description}
// />, document.body)
Components, continued.
app()
that gets a model
- reducers, effects, hooks, initial stateapp()
returns a function CreateView
a view functionCreateView({ state, actions, props, params, children })
returns a function, Componentprops, children
signature. That function usually would be called when you use JSX syntax like <Router foo="bar"></Router>
so `Route({ props: { foo: 'bar' } }.Everything is just fine tuned to work well for usual views approach, and for components approach too.
function app (options) {
options = options || {}
let state = options.state
const actions = {}
const subscriptions = options.subscriptions || {}
const reducers = options.reducers || {}
const effects = options.effects || {}
const hooks = merge({
onAction: Function.prototype,
onUpdate: Function.prototype,
onError: (er) => { throw er }
}, options.hooks)
return function createView (viewFn) {
return function Component (props, children) {
if (Object.keys(options).length === 0) return viewFn({ props, children })
for (var name in merge(reducers, effects)) {
actions[name] = function action (data) {
hooks.onAction(name, data)
// double `effects[name]` for saving bytes
if (effects[name]) {
return effects[name]({ state, data, actions }, hooks.onError)
}
const oldState = state
state = reducers[name]({ state, data })
viewFn({ state, actions, props, children })
hooks.onUpdate(oldState, state, data)
}
}
return viewFn({ state, actions, props, children })
}
}
}
Examples
const CatListView = app({
subscriptions: {},
reducers: {
add: ({ state, data }) => ({ foo: data.foo })
},
effects: {
get: ({ state, data, actions }, done) => fetch('foobar.com', done)
},
hooks: {
onUpdate: (oldState, state, data) => {}
}
state: { some: 'initial-state' }
})
const CatList = CatListView(({ state, actions, props, params, children }) => (<p>
<a href="/cats/{state.some}">{props.name}</a>
</p>))
const tree = <CatList name="charlike"/>
// or just
// const tree = CatList({ state: { foo: 123 }, props: { name: 'charlike' } })
render(tree, document.body)
Kinda router?
var routeMatch = require('path-match')()
const Router = app()(({ children }) => children.map((route) => {
var match = routeMatch(route.properties.path)
var params = match(window.location.pathname)
if (route.children.length) {
return params ? route.children : null
}
return params ? route.properties.component({ params }) : null
}))
const tree = <Router>
<route path="/" component={CatList} />
<route path="/about">
<About name="charlike" />
</route>
<route path="/users/:user" component={User} />
</Router>
with current mich
const Router = ({ path, component, children }) => {
console.log('actual')
// component is function CatList
return component({ name: path, children })
}
const CatList = ({ name, children }) => <p>{name}/foobie</p>
const tree = <Router path="/cats" component={CatList} />
console.log(tree)
Loading Component
import loading from './raptor-loading.gif'
const Loading = app()(({ props, children }) => (<div className="Loading">
{
!props.error ? (
<img className="Loading-img" src={loading} alt="Loading..." />
) : (
<p className="alert alert-danger"><strong>Error:</strong> Could not retrieve data.</p>
)
}
</div>))
// const tree = <Loading />
export default Loading
Exporting components
/** @jsx h */
import { Component, h } from 'chika'
// var SomeClassComponent = {
// render(props) {
// return (<div>Hello {props.place || 'World'}!</div>)
// }
// }
// think for this as above
// Component(viewFn) => returns a component
export default Component(({ props }) => <div>Hello {props.place || 'World'}!</div>)
later where component is needed
/** @jsx h */
import { h, render, renderToString } from 'chika'
import HelloWorld from './HelloWorld'
render(<HelloWorld />, document.body)
render(<HelloWorld place="Charlike" />, document.body)
// server-side rendering
renderToString(<HelloWorld place="folks" />, someState)
chika for nodejs
import h from 'mich-h'
import app from './app'
import renderToString from 'mich-to-html'
const Component = (viewFn) => app()(viewFn)
export default {
h, // mich-h
app, // 37 lines of cool code
renderToString, // mich-to-html
Component // thin wrapper over `app`
}
chika for browser
import h from 'mich-h' // ~500 bytes
import app from './app' // ~300 bytes
import render from 'mich-to-dom' // ~500 bytes
const Component = (viewFn) => app()(viewFn)
export default {
h,
app,
render,
Component
}
Just for reference http://dfilatov.github.io/vidom/playground/, how similar is our approach.
a link to a svg, maybe logo for mich
data:image/svg+xml;base64,PHN2ZyBmaWxsPSIjRUM1RDU3IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMTAwIDEwMCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PGc+PHBvbHlnb24gcG9pbnRzPSIzNy44OTEsNDcuMDg2IDM1LjczOSw1MC44MTQgMzcuODkxLDU0LjU0MiA0Mi4xOTYsNTQuNTQyIDQ0LjM0OSw1MC44MTQgNDIuMTk2LDQ3LjA4NiAgIi8+PHBvbHlnb24gcG9pbnRzPSI1Ny44MDUsNDcuMDg2IDU1LjY1MSw1MC44MTQgNTcuODA1LDU0LjU0MiA2Mi4xMDksNTQuNTQyIDY0LjI2Miw1MC44MTQgNjIuMTA5LDQ3LjA4NiAgIi8+PHBhdGggZD0iTTg3Ljg0NiwzNC45NTVsLTEyLjI3OS0xLjE2N2wtMS45NjIsNC4yNjVsLTcuMjYtMTIuNTczaC03LjMxM0w0OC42NjgsMTUuMzMzbC0wLjc5LDQuMzkybC00LjI0NS03Ljk5NGwtMC44MzgsNS41MjcgICBsLTQuMzQzLTcuOTQ1bC0wLjUzMSw1LjMxN0wzMi41Myw2LjEwNGwzLjQ1NiwxOS4zNzVoLTIuMzMxbC03LjI1OSwxMi41NzNsLTEuOTYyLTQuMjY1bC0xMi4yOCwxLjE2N0w1LjAzLDQ3LjMyNmw1LjE1NSwxMS4yMDYgICBsOS4zNDktMC44ODlsOS41MDIsMTYuNDU5bC0wLjY2NywxLjA2OGwtMC43ODQsMS4yNTZsMC43ODQsMS4yNTVMMzcuOCw5Mi43ODJsMC42OTUsMS4xMTNoMS4zMTNoMjAuMzgzaDEuMzEzbDAuNjk2LTEuMTEzICAgbDkuNDMxLTE1LjEwMWwwLjc4NC0xLjI1NWwtMC43ODQtMS4yNTZsLTAuNjY4LTEuMDY4bDkuNTAzLTE2LjQ1OWw5LjM0OSwwLjg4OWw1LjE1NS0xMS4yMDZMODcuODQ2LDM0Ljk1NXogTTIwLjA3NSw1Mi4xNyAgIGwtNi41ODcsMC42MjdsLTIuNzY1LTYuMDEybDMuODIxLTYuNjM2bDYuNTg3LTAuNjI2bDIuNzY1LDYuMDExTDIwLjA3NSw1Mi4xN3ogTTU4LjQyMiw4OC45MDZINDEuNTc3bC03Ljc5NC0xMi40NzlsNy43OTQtMTIuNDgxICAgaDE2Ljg0NWw3Ljc5NSwxMi40ODFMNTguNDIyLDg4LjkwNnogTTY3LjAyNyw2Ny44MDFMNjIuMiw2MC4wNzFsLTAuNjk2LTEuMTE1aC0xLjMxM0gzOS44MDloLTEuMzEzTDM3LjgsNjAuMDcxbC00LjgyOSw3LjcyOSAgIGwtNS42NjctMTUuMjZMMzcuNjYzLDM1Ljk2aDkuMzMxTDUwLDQyLjk2N2wzLjAwNy03LjAwN2g5LjgyNGw5Ljg2NSwxNi41ODFMNjcuMDI3LDY3LjgwMXogTTg2LjUxMiw1Mi43OTdsLTYuNTg3LTAuNjI3ICAgbC0zLjgyMS02LjYzNmwyLjc2Ni02LjAxMWw2LjU4NywwLjYyNmwzLjgyLDYuNjM2TDg2LjUxMiw1Mi43OTd6Ii8+PC9nPjxwb2x5Z29uIHBvaW50cz0iNjIuMzQyLDc3LjIzNSA2MS4yNzEsNzUuMTIzIDU1Ljk3NCw3Ny44MDggNDQuMjcxLDc3LjgwOCAzOS4zNTEsNzUuMTM5IDM4LjIyMSw3Ny4yMjEgNDMuOTcsODAuMTc3IDU2LjI1Nyw4MC4xNzcgICAiLz48L3N2Zz4=
:tada: AWESOME PROGRESS ON CHIKA / MICH !! :taco:
/** @jsx h */
// const { h, app, el, renderToString } = require('./chika')
/**
* API
*/
// mich-component
// export default function Component (view, state, actions) {
// return (props, children) => view({ state, actions, props, children })
// }
// mich-mount
// import render from 'mich-render'
// export default mount
function mount (ast, dom) {
var el = ast.type && ast.tagName && ast.properties && ast.children
? render(ast)
: ast
dom.appendChild(el)
return dom
}
// mich-render
// export default render
function render (node) {
if (node && node.type === 'text') {
return document.createTextNode(node.value)
}
if (node && node.type === 'element') {
var element = document.createElement(node.tagName)
setAttributes(element, node.properties)
for (var i in node.children) {
var el = render(node.children[i])
el && element.appendChild(el)
}
return element
}
}
function setAttributes (el, name, value) {
if (arguments.length > 2) {
if (name === 'style') {
setStyle(el, value)
} else if (name.slice(0, 2) === 'on') {
el.addEventListener(name.slice(2).toLowerCase(), value)
} else if (typeof value === 'boolean') {
setBooleanProp(el, name, value)
} else {
var transform = {
htmlFor: 'for',
className: 'class',
httpEquiv: 'http-equiv'
}
name = transform[name] ? transform[name] : name
el.setAttribute(name, value)
}
} else {
for (var key in name) {
setAttributes(el, key, name[key])
}
}
}
function setStyle (el, name, value) {
if (arguments.length > 2) {
el.style = el.style || {}
el.style[name] = value
} else if (typeof name === 'string') {
el.setAttribute('style', name)
} else {
for (var key in name) {
setStyle(el, key, name[key])
}
}
}
function setBooleanProp (el, name, value) {
if (value) {
el.setAttribute(name, value)
el[name] = true
} else {
el[name] = false
}
}
// mich
// import Component from 'mich-component'
// import render from 'mich-render'
// import mount from 'mich-mount'
// import h from 'mich-h'
// export default {
// Component,
// render,
// mount,
// app: mich,
// h
// }
/** @jsx h */
// import { Component, mount, h } from 'mich'
// const HelloCharlike = Component(({ props }) => `<h1 {...props}>Hello Charlike</h1>`)
// mount(<HelloCharlike />, document.body)
/**
* Chika
*/
/**
* Example
*/
var h = require('mich-h')
function merge (foo, bar, key) {
for (key in bar) {
foo[key] = bar[key]
}
return foo
}
function michApp (view, model) {
if (!model) {
return michComponent(view)
}
var actions = {}
var subscriptions = model.subscriptions || {}
var reducers = model.reducers || {}
var effects = model.effects || {}
var state = model.state || {}
var root = model.root
var hooks = model.hooks = merge({
onStart: (context, model) => {},
onInit: (context, model) => {},
onWillUpdate: (context, model, oldEl) => {},
onDidUpdate: (context, model, oldEl) => {},
onWillMount: (context, model) => {},
onDidMount: (context, model) => {},
onAction: (name, context) => {},
onError: (er) => { throw er },
onRender: (context, model, view, el) => view(context)
}, model.hooks)
var oldEl = null
// actions start
function createAction (name, props, children) {
return function _action (data) {
var context = {
state,
data,
props,
children
}
hooks.onAction(name, context, model)
// double `effects[name]` for saving bytes
if (effects[name]) {
context.actions = actions
return effects[name](context, hooks.onError)
}
// context.state is the old state
hooks.onWillUpdate(context, model)
const oldState = merge({}, state)
const newState = reducers[name](context)
context.state = state = merge(oldState, newState)
context.actions = actions
oldEl = hooks.onRender(context, model, view, oldEl)
// context.state is the new state,
// so that's why we pass old state as 3rd arg
hooks.onDidUpdate(context, model, oldState)
}
}
// actions end
hooks.onStart({ state }, model)
return (props, children) => {
// !! NOTICE: actions should not be created here?!?!
// Because if they are here it will call the for loop,
// merge effects and reducers for each `<HelloWorld />` call
// which isn't pretty okey, because the component could be called
// more than once.
// So it will be too consuming if actions creation is here.
// But if it isn't here, the view called from action can't
// access the `props, children`, sooo.. WAT TO DO?
for (var name in merge(reducers, effects)) {
actions[name] = createAction(name, props, children)
}
var context = { state, actions, props, children }
hooks.onInit(context, model)
return (oldEl = hooks.onRender(context, model, view, root))
}
}
// the "context"
// - in all places `context` is 1st argument, except in `onAction` where is 2nd
// - if in onStart: { state }
// - if in effect: { state, data, props, children, actions }
// - if in reducer: { state, data, props, children }
//
// 1. onStart({ start }, model)
// 2. onInit({ state, actions, props, children }, model)
//
// if it is initial render, won't have `data`
// this applies for `onRender`, `onWillMount` and `onDidMount`
//
// 3. onRender({ state, actions, props, children[, data] }, model, viewFn, oldEl)
// 4. onWillMount({ state, actions, props, children[, data] }, model)
//
// 5. onAction(name, { state, actions, props, children, data }, model)
// 6. onWillUpdate({ state, props, children, data }, model)
// -- actual recall of view and again onRender
//
// 7. onDidUpdate({ state, actions, props, children, data }, model, oldState)
// 8. onDidMount({ state, actions, props, children[, data] }, model)
/**
* !!! CHIKA !!!
*/
var update = require('nanomorph/update')
function chika (view, model) {
if (!model) {
return michComponent(view)
}
var morph = null
model = merge({}, model)
model.hooks = merge({
onInit: (context, _model) => {
document.addEventListener('DOMContentLoaded', () => {
for (var sub in _model.subscriptions) {
_model.subscriptions[sub](context, _model.hooks.onError)
}
})
morph = update(_model.root)
},
onRender: (context, _model, view, oldNode) => {
_model.hooks.onWillMount(context, _model)
var el = morph(render(view(context)), oldNode)
_model.hooks.onDidMount(context, _model)
return el
}
}, model.hooks)
return michApp(view, model)
}
// export default {
// Component: chika,
// render: mich.render,
// mount: mich.mount,
// h: mich.h
// }
/**
* !!! END CHIKA !!!
*/
const model = {
root: document.body,
state: {
place: 'World',
time: new Date()
},
reducers: {
welcome: ({ state, data }) => ({ place: data.text }),
time: ({ state, data }) => ({ time: data.date })
},
subscriptions: [
(context, done) => {
setInterval(() => {
context.actions.time({ date: new Date() })
}, 1000)
},
(context, done) => {
setTimeout(() => {
context.actions.welcome({ text: 'sweety cat'})
}, 15000)
}
]
}
const HelloWorld = chika(({ state, actions, props }) => {
return h('.main', [
h('h1', {
onclick: (e) => actions.welcome({ text: 'Charlike' })
}, `Hello ${state.place}!`),
h('p', `The time is ${state.time}`)
])
}, model)
const el = HelloWorld({ id: 'hero' })
// mount(el, document.body)
// console.log(render(el))
// * @jsx h
// import { Component, h } from 'chika'
// export default Component(({ state, actions, props, children }) => <h1>Hello!</h1>)
// mount(<HelloWorld />, document.body)
// function Component (view, state, actions) {
// return function CreateComponent (props, children) {
// return view({ state, actions, props, children })
// }
// }
// // Stateful Component
// const state = { place: 'World' }
// const view = ({ state, props, actions }) => h('h1', props, `Hello ${state.place}`)
// const HelloWorld = Component(view, state, actions)
// // Stateless Component
// const HelloCharlike = Component(({ props }) => h('h1', props, 'Hello Charlike'))
// var hi = render(HelloWorld({ id: 'hero' }))
// // mount(hi, document.body)
// mount(HelloCharlike({ className: 'yea' }), document.body)
// const TodoListView = app({
// state: {
// todos: [{
// id: 1,
// done: false,
// text: 'initial todo'
// }],
// count: 0,
// input: '',
// placeholder: 'Add new todo'
// },
// reducers: {
// toggle: ({ state, data }) => ({
// todos: state.todos.map((item) => {
// return data.id === item.id
// ? Object.assign({}, item, { done: !data.done })
// : item
// })
// }),
// remove: ({ state, data }) => ({
// todos: state.todos.map((todo) => todo.id !== data.id)
// }),
// add: ({ state, data }) => ({
// todos: state.todos.concat({
// id: state.count++,
// done: false,
// text: state.input
// })
// }),
// input: ({ state, data }) => ({ input: data.input }),
// }
// })
// const TodoList = TodoListView(({ state, actions }) => <section>
// <ul>
// { state.todos.map((item) => (<li onclick={e => actions.toggle(item)}>
// {item.text}
// </li>)) }
// </ul>
// <p>
// <input
// className="add todo--item"
// type="text"
// onkeyup={e => e.keyCode === 13 ? actions.add() : ''}
// oninput={e => actions.input({ input: e.target.value })}
// placeholder={state.placeholder}
// />
// <div onclick={actions.add}>add</div>
// </p>
// </section>)
// const MainView = app({
// state: {
// title: 'Todo App',
// link: 'https://github.com/tunnckoCore/chika'
// }
// })
// const Main = MainView(({ state }) => <main id="root">
// <h1>{state.title}</h1>
// <footer>Powered by <a href={state.link}>Chika</a></footer>
// </main>)
// document.body.appendChild(el(<Main />))
monorepo
Just state management plugin for dush
so can create apps easy
var dush = require('dush')
function faz (model) {
const { state, reducers, effects } = model
return dush().use((app) => {
app.state = Object.assign({}, state)
const acts = Object.assign({}, reducers, effects)
app.actions = Object.keys(acts).reduce((actions, name) => {
actions[name] = (data) => {
app.emit('action', name, data)
const oldState = Object.assign({}, app.state)
if (effects[name]) {
const done = (er) => {
if (er) app.emit('error', er)
}
app.emit('effect', name, oldState, actions, data)
return effects[name](oldState, actions, data, done)
}
app.emit('reducer', name, oldState, data)
let newState = Object.assign({}, oldState, reducers[name](oldState, data))
app.state = newState
app.emit('update', newState, oldState, data)
}
return actions
}, {})
app.actions.emit = app.emit
})
.use((app) => {
app.Component = (view) => (props) => view(props, app.actions, app.state)
})
}
example
const app = faz({
state: {
text: 'Hello',
place: 'World'
},
effects: {
foo: (state, actions, data, done) => {},
bar: (state, actions, data) => {
actions.set({ zaz: true, txt: data.text })
}
},
reducers: {
qux: (state, data) => {},
redirect: (state, data) => (Object.assign(data, { set: true }))
}
})
const html = require('bel')
app.on('quxie', (e) => {
app.state = Object.assign({}, app.state, data)
})
// props actions
const HelloWorld = app.Component(({ place }, { redirect, emit }, state) => html`<div>
<h1 onclick=${redirect}>${state.text} ${place}</h1>
<input type="text" oninput=${(e) => emit('quxie', { text: e.target.value }} />
</div>`)
console.log(HelloWorld({ place: 'World' }).toString())
Powerful and flexible actions plugin for dush
"Small & powerful state management a la Elm, based on event system."
emitting error
(use .once), stateUpdate
and action
events
'use strict'
module.exports = function actionsPlugin (state, actions) {
return function actionsPlugis (app) {
app.actions = {}
state = Object.assign({}, state)
Object.keys(actions).forEach((name) => {
const action = createAction(app, name)
// allow calling actions explicitly, instead of "emitting" them; e.g.
// - app.actions.fooBar({ some: 'data' })
app.actions[name] = action
// and the "emitting" style
// - app.emit('fooBar', { some: 'data' })
app.on(name, action)
})
return app
function createAction (app, name) {
return function action (data) {
app.emit('action', name, data)
// merge `actions` into `emit` function
// so these two style are allowed:
// - (state, actions, data) -> actions.fooBar({ some: 'data' })
// - (state, emit, data) -> emit('fooBar', { some: 'data' })
const emit = Object.assign(app.emit, app.actions)
// call the action
const ret = actions[name](state, emit, data, done)
// allow asynchronous actions that
// returns new state, or call another action(s)
// - async (state, actions, data) => delay(2200).then(() => ({
// foo: data.foo
// }))
// - async (state, actions, data) => delay(2200).then(() => {
// actions.fooBar({ foo: data.foo })
// })
if (isPromise(ret)) {
ret.then(updateState).catch(done)
return
}
// allow synchronous actions, if they return
// non-falsey value it is merged with the state
updateState(ret)
}
}
function done (er) {
if (er) app.emit('error', er)
}
function updateState (data) {
if (!data) {
return state
}
const oldState = state
state = Object.assign({}, state, data)
app.emit('stateUpdate', state, oldState, data)
}
}
}
function isPromise (val) {
return val instanceof Promise || (
val !== null &&
typeof val === 'object' &&
typeof val.then === 'function' &&
typeof val.catch === 'function'
)
}
example
const app = dush().use(dushActions(state, actions))
app.on('stateUpdate', (state, prev, data) => {
console.log('state:', state)
console.log('prev:', prev)
})
app.emit('changeName', { foo: 'Base Emit' })
app.emit('changeName', { foo: 'Foobie Emit' })
// or
// app.actions.changeName({ foo: 'Base Fn' })
// app.actions.changeName({ foo: 'Foobie' })
with these state, actions
const state = {
text: 'Hello',
name: 'Charlike'
}
const actions = {
// effect, call `emit` argument "emit" or "actions"
changeName: (state, emit /* or actions */, data) => {
// style 1 - async action (effect) that directly update the state
// return delay(2220).then(() => ({ name: data.foo }))
// style 2
// return delay(2220).then(() => {
// actions.setName({ foo: data.foo })
// })
// style 3
return delay(1220).then(() => {
emit('setName', { foo: data.foo })
})
// style 4
// setTimeout(() => {
// emit('setName', { foo: data.foo })
// }, 2220)
// style 5
// setTimeout(() => {
// actions.setName({ foo: data.foo })
// }, 1500)
},
// reducer
setName: (state, _, data) => {
// style 6
return { name: data.foo }
}
}
sample app, probably will wrok soon
var dush = require('dush')
var delay = require('delay')
var dushRouter = require('dush-router')
var dushActions = require('./dush-actions-plugin')
var dushBrowser = require('./dush-router-browser-plugin')
/**
* EXAMPLE
*/
const state = {
text: 'Hello',
name: 'Charlike'
}
const actions = {
// effect
changeName: (state, emit, data) => {
// style 1
// return delay(2220).then(() => ({ name: data.foo }))
// style 2
// return delay(2220).then(() => {
// actions.setName({ foo: data.foo })
// })
// style 3
return delay(1220).then(() => {
emit('setName', { foo: data.foo })
})
// style 4
// setTimeout(() => {
// emit('setName', { foo: data.foo })
// }, 2220)
// style 5
// setTimeout(() => {
// actions.setName({ foo: data.foo })
// }, 1500)
},
// reducer
setName: (state, emit, data) => {
// style 6
return { name: data.foo }
}
}
/**
* APP
*/
require('undom/register')
const html = require('bel')
const nanomorph = require('nanomorph')
function chika (state, actions) {
const app = dush()
.use(dushRouter())
// .use(dushBrowser())
.use(dushActions(state, actions))
app.on('stateUpdate', (_state) => {
state = _state
})
let tree = document.createElement('div')
let data = {}
app.off('route')
app.on('route', function (view, context) {
const match = {
pathname: context.pathname,
params: context.params,
route: context.route
}
return (tree = nanomorph(tree, view(context.state, app.emit, match)))
})
app.start = function () {
tree = app.navigate(window.location.pathname, state)
app.on('render', function (node, hist) {
if (hist) {
window.history.replaceState(data, '', node.pathname)
} else {
window.history.pushState(data, '', node.pathname)
}
app.navigate(node.pathname, state)
})
app.on('historyChange', function (node) {
app.emit('render', node, true)
})
app.on('hrefChange', function (node) {
if (node.href === window.location.href) return
app.emit('render', node)
})
return tree
}
app.mount = function mount_ (selector) {
var newTree = app.start()
var main = document.querySelector(selector)
tree = nanomorph(main, newTree)
}
return app
}
/**
* USAGE CHIKA
*/
const app = chika(state, actions)
app.addRoute('/users/:id', (state, actions, match /* === { params, pathname, route }*/) => {
return html`<h1>${state.text}</h1>`
})
// console.log(app.navigate('/users/charlike'))
one awesome guide with cool ideas and signatures: https://github.com/FormidableLabs/freactal
effects / actions are just:
(effects, arg1, arg2) => (state) => Promise | newState
(effects, arg1, arg2) => Promise.resolve((state) => anotherPromise | newState)
Just don't want to open new issue or push some commits. I'll just delete that comment after some time. So temporary:
Двигaтeл нa либepaлнaтa иĸoнoмиĸa cтaнa глoбaлизaциятa. A тpитe cтълбa, въpxy ĸoитo тя ce ĸpeпи ca cвoбoднaтa тъpгoвия, cвoбoднoтo движeниe нa ĸaпитaли и cвoбoднoтo движeниe нa paбoтнa pъĸa oт бeднитe ĸъм бoгaтитe cтpaни. Днec oбaчe cвoбoднaтa тъpгoвия зaбaви cвoитe oбopoти, дaжe мoжe дa ce ĸaжe, чe cъвceм зaмpя пo peд пoлитичecĸи и дpyги пpичини. Πpoцecът нa cвoбoднo движeниe нa ĸaпитaли тъpпи вce пoвeчe oгpaничeния, cвъpзaни c пpoблeмитe нa финaнcoвaтa cигypнocт и битĸaтa c тepopизмa. Дaжe пoвeчe ce гoвopи зa „бягcтвo” нa ĸaпитaлa ĸъм oфшopнитe зoни, oтĸoлĸoтo зa cвoбoднoтo движeниe нa ĸaпитaлoвитe пoтoци в глoбaлнaтa финaнcoвa мpeжa. Cвoбoднoтo движeниe нa xopa cлeд нaxлyлитe в Eвpoпa вълни oт бeжaнци нaĸapa няĸoи oт eвpoпeйcĸитe дъpжaви дa пocтaвят физичecĸи бapиepи нa дъpжaвнитe cи гpaници, зa дa cпpaт бeжaнcĸия пoтoĸ. C тoвa дe фaĸтo ce oбeзcмиcли и пocлeднoтo пpeдимcтвo, пpипиcвaнo нa глoбaлизaциятa. Ho дopи и бeз няĸaĸъв пo-cepиoзeн aнaлиз нa peзyлтaтитe oт пpилaгaнeтo нa либepaлния мoдeл, a caмo нa бaзaтa нa иcтopичecĸия oпит oт пpeзидeнтa Peйгън нacaм, e виднo, чe тaзи иĸoнoмичecĸa cиcтeмa мoжe дa фyнĸциoниpa eдинcтвeнo нa пpинципитe нa измaмaтa и гpaбeжa. Caмa пo ceбe cи cиcтeмaтa нe мoжe дa ce възпpoизвeждa бeз пocтoяннo, външнo финaнcиpaнe. Oтнaчaлo тя пapaзитиpa въpxy гъpбa нa paзпaднaлия ce CCCP. Πo-ĸъcнo пpoблeмитe пpoличaвaт cлeд измaмaтa c „oтpoвни” дepивaти и гpaбeжa нa бaнĸитe пpeз 2008 г. Toгaвa CAЩ, зa дa пpeдoтвpaтят тoтaлния ĸoлaпc нa бaнĸитe пpиexa пoлитиĸa нa т.нap. „ĸoличecтвeни вливaния”, c цeл дa cтимyлиpaт инвecтициитe в индycтpиятa и дa извeдaт иĸoнoмиĸaтa oт дълбoĸaтa peцecия, в ĸoятo e изпaднaлa . Ceгaшният пpoдължитeлeн пepиoд бeз вoйни, пpeз ĸoйтo paзвититe дъpжaви пpaĸтичecĸи нямa oт ĸoгo дa изcмyĸвaт pecypcи, дoвeдe дo ĸpизa зa тeзи oт тяx, ĸoитo ce нaмиpaт нa въpxa нa „xpaнитeлнaтa вepигa”. Либepaлнaтa иĸoнoмичecĸa cиcтeмa бeз „пpeливaнe нa пapи” ĸoлaбиpa . Heoбxoдимocттa oт Tpeтa cвeтoвнa вoйнa (или възмoжнocттa зa чepпeнe нa cвeж pecypc oт пoбeдeнитe cтpaни) e жизнeнo нeoбxoдимa зa oцeлявaнeтo нa пoвeчeтo зaпaдни дъpжaви и пo-cпeциaлнo зa пpиĸaзнo бoгaтитe им eлити. Ho oпacнocттa зa Зaпaдa идвa oт тoвa, чe зaпoчвaнeтo нa eднa нoвa вoйнa нe caмo чe нямa дa peши, a oщe пoвeчe щe зaдълбoчи пpoблeмитe им. Днec нямa тoлĸoвa cлaби вpaгoвe, ĸoитo c лeĸoтa дa бъдaт пoбeдeни и дa cтaнaт дoнopи нa зaĸъcaлитe зaпaдни дъpжaви . Ceгa пpиблизитeлният пapитeт нa cилитe в cвeтa e тaĸъв, ĸaĸъвтo бeшe и пpeди двeтe cвeтoвни вoйни, ĸoeтo caмo пo ceбe cи пoвишaвa pиcĸa oт нoв cвeтoвeн ĸoнфлиĸт. Toй мoжe дa бъдe oт ĸлacичecĸи тип, ĸaĸъвтo бeшe в пpeдишнитe двe вoйни, нo мoжe дa бъдe и xибpидeн, зaмacĸиpaн пoд гoлямo ĸoличecтвo oт лoĸaлни ĸoнфлиĸти. Глaвнaтa цeл нa opгaнизaтopитe нa тeзи мнoгoбpoйни ĸoнфлиĸти e, нapeд c иĸoнoмичecĸaтa и инфopмaциoннa aгpecия, ĸoятo вoдят, дa ycпeят дa пapиpaт възмoжнocттa нa пpoтивниĸa дa yпoтpeби ядpeнo opъжиe . Бeлeзитe нa тaзи cтpaтeгия ca cъвceм oчeвидни. Ha пъpвo мяcтo, тoвa e пpeĸъcвaнeтo нa ycтoйчивитe иĸoнoмичecĸи вpъзĸи чpeз нaлaгaнeтo нa иĸoнoмичecĸи caнĸции и зaдълбoчaвaнeтo нa иĸoнoмичecĸия cпaд, ĸaтo ce ocтaвят нeзaceгнaти caмo oпpeдeлeни „зoни нa иĸoнoмичecĸи pacтeж”. Зa oтбeлязвaнe e, чe пpeз Πъpвaтa и Bтopaтa cвeтoвни вoйни зa тaĸaвa зoнa бeшe oпpeдeлeнa Aмepиĸa. И тъй ĸaтo тoгaвa тoвa ce xapeca нa aмepиĸaнцитe, ceгa тe oтнoвo пpaвят oпит дa пoвтapят тoзи cцeнapий. Πapaлeлнo c тoвa, ĸaĸтo e извecтнo, eднa oт цeлитe нa вcяĸa cвeтoвнa вoйнa e „зaнyлявaнe” или нaй-мaлĸo oбeзцeнявaнe нa дoвoeннитe дългoвe и pecтapтиpaнe нa cвeтoвнaтa иĸoнoмиĸa . Eдвa ли e нeoбxoдимo дa гaдaeм ĸoя cтpaнa щe бъдe нaй-oблaгoдeтeлcтвaнa oт тoвa, cлeд ĸaтo дългoвeтe нa CAЩ вeчe нaдxвъpлят 19 тpилиoнa дoлapa. Tyĸ вeднaгa възниĸвa въпpocът: Koи ca вepoятнитe зoни нa вoeнeн ĸoнфлиĸт, cлeд ĸoйтo мoжe дa бъдe pecтapтиpaнa cвeтoвнaтa иĸoнoмиĸa, пpи зaпaзвaнe нa cъщecтвyвaщия либepaлeн иĸoнoмичecĸи мoдeл и ycтaнoвeнaтa пo cтълбицaтa нa „xpaнитeлнaтa вepигa” йepapxия нa ceгaшния финaнcoв eлит? Bapиaнтитe нe ca мнoгo. Ho cпopeд мнeниeтo нa peдицa зaпaдни aнaлизaтopи, нaй-пpиeмливият oт тяx e ĸoличecтвoтo oт нaтpyпaни ĸъм мoмeнтa пpoтивopeчия, дa бъдe пoгpeбaнo въpxy pyинитe нa Pycия . Toгaвa Eвpoпa и Aзия щe ce пpeвъpнaт в тepитopии oт paзбити и дъpжaни в cъcтoяниe нa yпaдъĸ иĸoнoмичecĸи aнĸлaви, oбгpaдeни oт бyшyвaщия cлeд yнищoжaвaнeтo нa Pycия xaoc . Oт eднa cтpaнa, peaлизaциятa нa тoзи cцeнapий щe пoзвoли нa Aмepиĸa дa ce зaпaзи ĸaтo ocтpoв нa cтaбилнocттa и oтпpaвнa тoчĸa зa pecтapтиpaнe нa нoвия pacтeж нa иĸoнoмиĸитe в Eвpoпa и Aзия зa cмeтĸa нa oгpaбeнoтo oт пoбeдeнa Pycия. Oт дpyгa cтpaнa, CAЩ щe мoжe дa зaпaзи xeгeмoниятa cи и щe пpoдължи дa игpae poлятa нa cвeтoвeн жaндapм. Kaĸ oбaчe изглeждa paзпpeдeлeниeтo нa интepecитe в чeтиpиъгълниĸa oт CAЩ, Pycия, Eвpoпa и Kитaй? Яcнo e, чe CAЩ и Pycия ca aнтaгoниcти. Toвa e тaĸa, зaщoтo, ĸaĸтo oтбeлязaxмe и пo-гope, CAЩ мoжe дa зaпaзи ceгaшнaтa cи poля caмo зa cмeтĸa нa yнищoжaвaнeтo нa Pycия и oтcлaбвaнeтo нa Eвpoпa и Kитaй. Aĸo тaзи xипoтeзa e вяpнa, възмoжнocттa зa пocтигaнe нa няĸaĸъв ĸoнceнcyc мeждy Baшингтoн и Mocĸвa cтaвa cъвceм xипoтeтичнa, ocвeн aĸo тoвa нe cтaнe зa cмeтĸa нa чacтичнa зaгyбa нa нaциoнaлeн cyвepeнитeт oт cтpaнa нa Pycия или пълнaтa й ĸaпитyлaция пpeд CAЩ. Toecт, aĸo Pycия иcĸa дa oцeлee и нe ce пoяви няĸoй „нoв Гopбaчoв” тя нeизбeжнo щe тpябвa дa влeзe в cблъcъĸ cъc CAЩ. B тaзи cитyaция Eвpoпa щe бъдe ecтecтвeн cъюзниĸ нa Aмepиĸa. Днeшнoтo вacaлнo пoдчинeниe нa Eвpoпa нa зaпoвeдитe нa Baшингтoн нe ce дължи caмo нa „пoĸyпĸaтa” нa eвpoпeйcĸи пoлитици или нa „ĸoмпpoмaтитe”, c ĸoитo ЦPУ дъpжи ĸлючoви eвpoпeйcĸи лидepи.
Джон Грей: „Комунизмът е само един от западните проекти. Падането на комунизма ще бъде последвано от краха на Западния проект. Комунизмът остави след себе си пустота, която запълниха демоните, които днес господстват в света”
backup
'use strict'
function freak2ing (options) {
if (typeof options === 'function') {
options = { render: options }
}
let {
initialState,
initialProps,
effects,
reducers,
events,
render,
} = options
reducers = Object.assign({}, reducers)
effects = Object.assign({}, effects)
events = Object.assign(
{
onError: (err) => {
throw err
},
// signature: (actions)
onWillMount: Function.prototype,
// signature: ()
onDidMount: Function.prototype,
// signature: (actionName, [param1, param2, param3], actions)
onAction: Function.prototype,
// signature: (effectName, actions, [param1, param2, param3])
onEffect: Function.prototype,
// signature: (reducerName, actions, [param1, param2, param3])
onReducer: Function.prototype,
// signature: (oldState, [param1, param2, param3])
onWillUpdateState: Function.prototype,
// signature: (oldState, newState, [param1, param2, param3])
onDidUpdateState: Function.prototype,
// signature: (nextPropsWithState, nextState)
onWillUpdate: Function.prototype,
// signature: (newPropsWithState, newState)
onDidUpdate: Function.prototype,
// signature: (nextPropsWithState, oldPropsWithState)
onWillUpdateProps: Function.prototype,
// signature: (newPropsWithState, oldPropsWithState)
onDidUpdateProps: Function.prototype,
// signature: (newPropsWithState, newState)
onWillRender: Function.prototype,
// signature: (newPropsWithState, newState)
onDidRender: Function.prototype,
},
events
)
let _STATE = typeof initialState === 'function'
? initialState()
: Object.assign({}, initialState)
let _PROPS = typeof initialProps === 'function'
? initialProps()
: Object.assign({}, initialProps)
let actions = {}
let _NEXT_STATE = {}
Object.keys(Object.assign({}, reducers, effects)).forEach((name) => {
const action = createAction(actions, name)
actions[name] = action
})
function createAction (actions, name) {
return function _action_ (e) {
// get action args, without leakage
var i = arguments.length
var args = []
while (i--) {
args[i] = arguments[i]
}
// signature: (actionName, [param1, param2, param3], actions)
events.onAction(name, args, actions)
// asynchronous action, i.e. "effect"
// called on next tick
if (effects[name]) {
return Promise.resolve().then(() => {
// signature: (effectName, actions, [param1, param2, param3])
events.onEffect(name, actions, args)
// or signature: (actions, [param1, param2, param3])
// return effects[name](actions, args)
// signature: (actions, param1, param2, param3)
return effects[name].apply(null, [actions].concat(args))
}, events.onError)
}
// sync/async action, i.e. "reducer"
// called in same tick
// const promise = new Promise((resolve) => {
// try {
// signature: (reducerName, actions, [param1, param2, param3])
events.onReducer(name, actions, args)
const oldState = Object.assign({}, _STATE)
// or signature: (actions, [param1, param2, param3])
// const newState = reducers[name].apply(null, [actions].concat(args))
events.onWillUpdateState(oldState, _NEXT_STATE, args)
// or signature: (actions, param1, param2, param3)
const partialState = reducers[name].apply(null, [actions].concat(args))
// signature: (oldState, partialState, [param1, param2, param3])
// ???
// events.onStateUpdate(oldState, partialState, args)
_STATE = Object.assign({}, oldState, partialState)
// signature: (oldState, newState, [param1, param2, param3])
events.onDidUpdateState(oldState, _STATE, args)
return _STATE
// } catch (err) {
// return reject(err)
// }
// resolves changed state
// resolve(_STATE)
// })
// return promise.catch(events.onError)
}
}
let { state, ...prevProps } = Object.assign({}, _PROPS)
let oldState = _STATE
const createSimpleComponent = (_render) => {
// console.log(app.actions)
events.onWillMount(actions)
return (incomingProps) => {
let { state: nextState, ...nextProps } = Object.assign({}, incomingProps)
nextState = Object.assign({}, nextState)
let prevState = Object.assign({}, _STATE)
// signature: (nextProps, nextState)
events.onWillUpdate(incomingProps, nextState)
// signature: (prevState, nextState)
events.onWillUpdateState(prevState, nextState)
const newState = Object.assign({}, prevState, nextState)
_NEXT_STATE = _STATE = newState
// signature: (prevState, newState)
events.onDidUpdateState(prevState, newState)
let oldProps = { state: oldState, ...prevProps }
// signature: (nextPropsWithState, oldPropsWithState)
events.onWillUpdateProps({ state: nextState, ...nextProps }, oldProps)
// merge only the props, without the state
const newProps = Object.assign({ actions }, prevProps, nextProps)
newProps.state = newState
// signature: (newPropsWithState, oldPropsWithState)
events.onDidUpdateProps(newProps, oldProps)
// signature: (newPropsWithState, newState)
events.onDidUpdate(newProps, newState)
// signature: (newPropsWithState, newState)
events.onWillRender(newProps, newState)
let result = null
if (typeof newProps.children === 'function') {
// signature: (newPropsWithState, newState)
result = newProps.children(newProps, newState)
} else {
// signature: (newPropsWithState, newState)
result = _render(newProps, newState)
}
// signature: (newPropsWithState, newState)
events.onDidRender(newProps, newState)
events.onDidMount()
return result
}
}
return typeof render === 'function'
? createSimpleComponent(render)
: (_render) => createSimpleComponent(_render)
}
const wrapWithState = freak2ing({
initialState: {
some: 'initial state',
},
reducers: {
addText: (actions, text) => ({ text }),
updateInitial: (actions, param1) => ({ some: 'set on willmount' }),
},
events: {
onWillMount: (actions) => {
console.log('init', actions)
console.log('calling "updateInitial" reducer')
actions.updateInitial('he he he')
},
// signature: (oldState, nextState, [param1, param2, param3])
onWillUpdateState: (oldState, nextState) => {
console.log('will state:', oldState, nextState)
// sasa
},
onDidMount: () => {
console.log('done')
},
// signature: (oldState, newState, [param1, param2, param3])
onDidUpdateState: (oldState, newState) => {
console.log('did state:', oldState, newState)
},
// signature: (nextPropsWithState, nextState)
onWillUpdate: (nextPropsWithState, nextState) => {
// sasa
},
// signature: (newPropsWithState, newState)
onDidUpdate: (newPropsWithState, newState) => {
// sasa
},
// signature: (nextPropsWithState, oldPropsWithState)
onWillUpdateProps: (nextPropsWithState, oldPropsWithState) => {
console.log('will props', nextPropsWithState)
},
// signature: (newPropsWithState, oldPropsWithState)
onDidUpdateProps: (newPropsWithState, oldPropsWithState) => {
console.log('did props', newPropsWithState)
},
},
})
const App = wrapWithState(({ actions }) => {
console.log('some view here')
console.log('calling "addText" from view... (when render')
actions.addText('hello world')
return 'end'
})
// console.log(App())
not working "freaking" and bel
with support for components. Tools: Babel + nodemon
/** @jsx belCreateElement */
'use strict';
var headRegex = /^\n[\s]+/;
var tailRegex = /\n[\s]+$/;
var SVGNS = 'http://www.w3.org/2000/svg';
var XLINKNS = 'http://www.w3.org/1999/xlink';
var BOOL_PROPS = {
autofocus: 1,
checked: 1,
defaultchecked: 1,
disabled: 1,
formnovalidate: 1,
indeterminate: 1,
readonly: 1,
required: 1,
selected: 1,
willvalidate: 1,
};
var COMMENT_TAG = '!--';
var SVG_TAGS = [
'svg',
'altGlyph',
'altGlyphDef',
'altGlyphItem',
'animate',
'animateColor',
'animateMotion',
'animateTransform',
'circle',
'clipPath',
'color-profile',
'cursor',
'defs',
'desc',
'ellipse',
'feBlend',
'feColorMatrix',
'feComponentTransfer',
'feComposite',
'feConvolveMatrix',
'feDiffuseLighting',
'feDisplacementMap',
'feDistantLight',
'feFlood',
'feFuncA',
'feFuncB',
'feFuncG',
'feFuncR',
'feGaussianBlur',
'feImage',
'feMerge',
'feMergeNode',
'feMorphology',
'feOffset',
'fePointLight',
'feSpecularLighting',
'feSpotLight',
'feTile',
'feTurbulence',
'filter',
'font',
'font-face',
'font-face-format',
'font-face-name',
'font-face-src',
'font-face-uri',
'foreignObject',
'g',
'glyph',
'glyphRef',
'hkern',
'image',
'line',
'linearGradient',
'marker',
'mask',
'metadata',
'missing-glyph',
'mpath',
'path',
'pattern',
'polygon',
'polyline',
'radialGradient',
'rect',
'set',
'stop',
'switch',
'symbol',
'text',
'textPath',
'title',
'tref',
'tspan',
'use',
'view',
'vkern',
];
function arrayify (val) {
if (!val) return [];
if (Array.isArray(val)) return val;
return [val];
}
function isObject (val) {
return val && typeof val === 'object' && !Array.isArray(val);
}
function belCreateElement (tag, props, children) {
var el = void 0;
props = isObject(props) ? props : {};
children = children || props.children;
// If an svg tag, it needs a namespace
if (SVG_TAGS.indexOf(tag) !== -1) {
props.namespace = SVGNS;
}
props.children = children;
// if component, pass props and the children to it
if (typeof tag === 'function') {
return tag(props);
}
children = arrayify(children);
var ns = false;
if (props.namespace) {
ns = props.namespace;
delete props.namespace;
}
// Create the element
if (ns) {
el = document.createElementNS(ns, tag);
} else if (tag === COMMENT_TAG) {
return document.createComment(props.comment);
} else {
el = document.createElement(tag);
}
// Create the properties
for (var p in props) {
if (props.hasOwnProperty(p)) {
var key = p.toLowerCase();
var val = props[p];
// Normalize className
if (key === 'classname') {
key = 'class';
p = 'class';
}
// The for attribute gets transformed to htmlFor, but we just set as for
if (p === 'htmlFor') {
p = 'for';
}
// support style as object
// both `fontSize` and `font-size` work that way
if (key === 'style' && typeof val === 'object') {
for (var k in val) {
el.style[k] = val[k];
}
continue;
}
// If a property is boolean, set itself to the key
if (BOOL_PROPS[key]) {
if (val === 'true') val = key;
else if (val === 'false') continue;
}
// If a property prefers being set directly vs setAttribute
if (key.slice(0, 2) === 'on') {
el[p] = val;
} else {
if (ns) {
if (p === 'xlink:href') {
el.setAttributeNS(XLINKNS, p, val);
} else if (/^xmlns($|:)/i.test(p)) {
// skip xmlns definitions
} else {
el.setAttributeNS(null, p, val);
}
} else {
if (key !== 'children') {
el.setAttribute(p, val);
}
}
}
}
}
function appendChild (childs) {
if (!Array.isArray(childs)) return;
var hadText = false;
for (var i = 0, len = childs.length; i < len; i++) {
var node = childs[i];
if (Array.isArray(node)) {
appendChild(node);
continue;
}
if (
typeof node === 'number' ||
typeof node === 'boolean' ||
typeof node === 'function' ||
node instanceof Date ||
node instanceof RegExp
) {
node = node.toString();
}
var lastChild = el.childNodes[el.childNodes.length - 1];
if (typeof node === 'string') {
hadText = true;
if (lastChild && lastChild.nodeName === '#text') {
lastChild.nodeValue += node;
} else {
node = document.createTextNode(node);
el.appendChild(node);
lastChild = node;
}
if (i === len - 1) {
hadText = false;
var value = lastChild.nodeValue
.replace(headRegex, '')
.replace(tailRegex, '');
if (value !== '') lastChild.nodeValue = value;
else el.removeChild(lastChild);
}
} else if (node && node.nodeType) {
if (hadText) {
hadText = false;
var val = lastChild.nodeValue
.replace(headRegex, '')
.replace(tailRegex, '');
if (val !== '') lastChild.nodeValue = val;
else el.removeChild(lastChild);
}
el.appendChild(node);
}
}
}
appendChild(children);
return el;
}
/**
* EXAMPLE
*/
const Strong = (props) => (
<strong
style={{
fontSize: 22,
color: 'red',
}}
{...props}
/>
);
const freaking = require('./index');
const LinkView = freaking({
state: {
welcome: 'Hello',
},
});
const Link = LinkView(({ state, actions, path, children, ...props }) => (
<a href={path} {...props}>
{state.welcome}<Strong>{children}</Strong>
</a>
));
const el = <Link path="/hello" className="btn btn-link">Charlike</Link>;
console.log(el.outerHTML);
window.close();
More updates.
"use strict";
/** @jsx mich.h */
function mich(options) {}
const Greet = mich({
state: {
title: "Foo Bar",
greeting: "Hello",
authorized: true,
clicked: false
},
// default/initial props
props: {
foo: "webapp"
}, // used for components
effects: {
// here inside in first argument object we dont have access to "state"
// rest arguments are passed arguments we this effect action is called
authorized: ({ actions }) => {
actions.fetch(true);
},
// here we have access to the default props, same like in reducers
fetch: ({ actions }, bool) => {
return fetch("api.foobar.com/user").then(({ body }) => {
return actions.update(bool ? body : { fail: "not authorized" });
});
}
},
reducers: {
// here we have access to the default props
update: ({ state, actions }, data) => {
const { first, last } = data;
actions.add(first, last);
},
add: ({ state, actions }, firstName, lastName) => ({
realName: `${state.greeting}, ${firstName} ${lastName}`
})
// or return a promise
// add: ({ state, actions }, firstName, lastName) => {
// return Promise.resolve({
// realName: `${state.greeting}, ${firstName} ${lastName}`
// })
// }
},
lifecycle: {
beforeStateUpdateOrSuch: ({ state, actions }) => {
if (state.clicked) {
actions.authorized();
}
},
error: ({ state, actions }, err) => {
console.error(err.message || err);
}
},
render: ({ state, actions, ...props }) => {
return (
<div className="segment">
<h1>
{state.title}! This is a {props.kind || props.foo}
</h1>
<p>
{state.realName}
</p>
<button onsubmit={actions.authorized} />
</div>
);
}
});
mich.mount(
<Greet kind="our website" /*actions={mich.createActions(moreActions)}*/ />
);
morph fn - virtual dom diffing
'use strict'
const isObject = (val) => val && typeof val === 'object' && !Array.isArray(val)
const isVNode = (val) => isObject(val) && val.VNode === true
const isVNodeText = (val) => isVNode(val) && val.type === 'text'
function morph (left, right) {
if (!left) {
return right
}
if (!right) {
return left
}
if (right.isSameNode && right.isSameNode(left)) {
return left
}
if (left.tagName !== right.tagName) {
return right
}
const isLeftText = isVNodeText(left)
const isRightText = isVNodeText(right)
if (isLeftText || isRightText) {
return morphText(left, right, { isLeftText, isRightText })
}
morphProps(left, right)
morphChildren(left, right)
return left
}
function morphText (left, right, { isLeftText, isRightText }) {
if (isLeftText && isRightText) {
return left.value === right.value ? left : right
}
return isLeftText && !isRightText ? right : left
}
function morphProps (left, right) {
const props = Object.keys(right.properties)
.filter(
(propName) =>
!left.properties.hasOwnProperty(propName) ||
left.properties[propName] !== right.properties[propName]
)
.map((propName) => ({ propName, propValue: right.properties[propName] }))
.reduce((memo, { propName, propValue }) => {
memo[propName] = propValue
return memo
}, {})
Object.keys(left.properties)
.filter((k) => right.properties.hasOwnProperty(k))
.forEach((name) => {
props[name] = right.properties[name]
})
left.properties = props
return left
}
function morphChildren (left, { children }) {
left.children = children.map((newChild, idx) => {
if (same(newChild, left.children[idx])) {
return morph(left.children[idx], newChild)
}
// console.log(left.children[idx])
return morph(newChild, left.children[idx])
})
}
function same (right, left) {
if (right.isSameNode) return right.isSameNode(left)
if (right.properties && right.properties.id) {
return right.properties.id === left.properties.id
}
if (right.tagName !== left.tagName) return false
if (right.type === 'text') return right.value === left.value
return false
}
exports.morph = morph
exports.default = morph
// WORKS!
// let a = h('div', { bro: 'ok', beta: 'ss' }, 'Hello World')
// let b = h('div', { bro: 'ok', class: 'barry', zazz: 'qux' }, 'Hello Wood World')
// let c = morph(a, b)
// console.log(c)
// WORKS!
// let el1 = h(
// 'div',
// { id: 'sasa', orig: 'qux', class: 'haha' },
// 'Hello World Beta'
// )
// let el2 = h('div', { id: 'xx', class: 'zzz' }, [
// 'Hello',
// h('b', 'world'),
// 'yeah!',
// ])
// let res = morph(el1, el2)
better mich-h
:
'use strict'
const NO_SUFFIX = /acit|ex(?:s|g|n|p|$)|rph|ows|mnc|ntw|ine[ch]|zoo|^ord/i
const isString = (val) => typeof val === 'string'
const isObject = (val) => val && typeof val === 'object' && !Array.isArray(val)
const isVNode = (val) => isObject(val) && val.VNode === true
const isVNodeText = (val) => isVNode(val) && val.type === 'text'
const isVNodeElement = (val) => isVNode(val) && val.type === 'element'
const VNode = ({ tagName, properties, children }) => ({
type: 'element',
tagName,
properties,
children,
VNode: true,
})
const VText = (value) => ({
type: 'text',
value,
VNode: true,
})
function h (tagName, ...rest) {
const args = [].concat(...rest)
const node = VNode({ tagName, properties: {}, children: [] })
args
.filter((item) => item != null && typeof item !== 'boolean')
.forEach((item) => {
let child = null
if (isString(item) || typeof item === 'number') {
child = VText(item)
}
if (isVNode(item)) {
child = item
item = null
}
if (isObject(item)) {
addProps(node, item)
}
if (child) {
node.children.push(child)
}
})
return node
}
function addProps (node, props) {
node.properties = Object.keys(props).reduce((memo, name) => {
const value = props[name]
if (name === 'style') {
addStyle(memo, value)
} else if (name === 'class' || name === 'className') {
addClass(memo, value)
} else {
memo[name] = value
}
return memo
}, {})
}
function addStyle (props, value) {
if (isObject(value)) {
Object.keys(value).forEach((k) => {
const val = value[k]
const suffix = typeof val === 'number' && NO_SUFFIX.test(k) === false
props.style = props.style || {}
props.style[k] = suffix ? val + 'px' : val
})
} else {
// assume string
// add explicit check if errors come
props.style = value
}
}
function addClass (props, value) {
if (Array.isArray(value)) {
props.class = value.filter(Boolean).join(' ')
} else if (isObject(value)) {
props.class = Object.keys(value)
.filter((name) => Boolean(value[name]))
.map((name) => name)
.join(' ')
} else {
// assume string
// add explicit check if errors come
props.class = value
}
}
module.exports = {
isString,
isObject,
h,
VNode,
VText,
isVNode,
isVNodeText,
isVNodeElement,
}
render / renderToString
'use strict'
const isObject = (val) => val && typeof val === 'object' && !Array.isArray(val)
const isVNode = (val) => isObject(val) && val.VNode === true
const isVNodeText = (val) => isVNode(val) && val.type === 'text'
const isVNodeElement = (val) => isVNode(val) && val.type === 'element'
function render () {}
function renderToString (tree) {
let buf = ''
traverse([].concat(tree), (node) => {
if (isVNodeText(node)) {
buf += node.value
return
}
if (!isVNodeElement(node)) return
buf = `${buf}<${node.tagName}${toAttrs(node.properties)}>`
buf = `${buf}${renderToString(node.children)}</${node.tagName}>`
})
return buf
}
function traverse (tree, cb) {
if (Array.isArray(tree)) {
tree.forEach((node) => {
traverse(cb(node), cb)
})
} else if (isVNodeElement(tree)) {
traverse(tree.children, cb)
}
return tree
}
let transform = {
className: 'class',
htmlFor: 'for',
httpEquiv: 'http-equiv',
}
function toAttrs (props) {
return Object.keys(props).reduce((memo, propName) => {
const value = props[propName]
const name = propName in transform ? transform[propName] : propName
if (value === 'true') {
return `${memo} ${name}`
}
if (value === 'false') {
return `${memo} ${name} ="false"`
}
if (name === 'style' && isObject(value)) {
const style = Object.keys(value)
.map((key) => `${key}:${value[key]};`)
.join('')
return `${memo} style="${style}"`
}
if ((name === 'data' || name === 'dataset') && isObject(value)) {
const data = Object.keys(value)
.map((key) => `data-${key}="${value[key]}"`)
.join(' ')
return `${memo} ${data}`
}
if (name !== 'on' && !name.startsWith('on')) {
return `${memo} ${name}="${value}"`
}
return memo
}, '')
}
module.exports = { renderToString, render }
most of the nanomorph
tests pass, but swap proxy elements
fail
let nodeA = html`<li id="aa"></li>`
let placeholderA = html`<div id="aa" data-placeholder=true></div>`
placeholderA.isSameNode = function (el) {
return el === nodeA
}
let nodeB = html`<li id="bb"></li>`
let placeholderB = html`<div id="bb" data-placeholder=true></div>`
placeholderB.isSameNode = function (el) {
return el === nodeB
}
let a = html`<ul>${nodeA}${nodeB}</ul>`
console.log('A:', renderToString(a))
let b = html`<ul>${placeholderB}${placeholderA}</ul>`
console.log('B:', renderToString(b))
let c = morph(a, b)
console.log('C:', renderToString(c))
// instead of `C: <ul><li id="aa"></li><li id="bb"></li></ul>`
// should be `C: <ul><li id="bb"></li><li id="aa"></li></ul>`
Built on HAST - Hypertext Abstract Syntax Tree format.
mich-parse-selector ~250 bytes gzip+min
mich-h ~500 bytes gzip+min
mich-to-html ~420 bytes
outputs
mich-to-dom
mich-morph
- based on Facebook Reconciliation algorithmmich