├── .gitignore ├── assets └── images │ ├── logo.png │ ├── ninja.jpg │ ├── ninja.png │ ├── qrcode.jpg │ ├── 91songli.png │ └── 91pintuan.png ├── src ├── ninja-redux │ ├── util │ │ ├── isPlainObject.js │ │ ├── invariant.js │ │ ├── warning.js │ │ └── shallowEqual.js │ ├── index.js │ └── components │ │ ├── provider.js │ │ └── connect.js ├── ninja-redux-form │ ├── index.js │ ├── validator.js │ ├── reducer.js │ └── hoc.js ├── ninja-router │ ├── invariant.js │ ├── decorators.js │ └── util.js ├── ninja-core │ ├── util.js │ ├── vnode.js │ ├── dom │ │ ├── recycler.js │ │ └── index.js │ ├── render-queue.js │ ├── render.js │ ├── component.js │ ├── constants.js │ ├── vdom │ │ ├── component-recycler.js │ │ ├── index.js │ │ ├── component.js │ │ └── diff.js │ └── h.js ├── store.js ├── ninja-tag │ └── index.js ├── index.js ├── ninja-router-redux │ ├── index.js │ ├── ninja.router.reducers.js │ └── hoc.js ├── application.js └── util.js ├── .babelrc ├── dist ├── riot-redux │ ├── util │ │ ├── invariant.js │ │ ├── warning.js │ │ ├── isPlainObject.js │ │ └── shallowEqual.js │ ├── index.js │ └── components │ │ ├── provider.js │ │ └── connect.js ├── riot-redux-form │ ├── index.js │ ├── validator.js │ └── reducer.js ├── riot-router │ ├── invariant.js │ ├── decorators.js │ └── util.js ├── riot-tag │ └── index.js ├── store.js ├── index.js ├── riot-router-redux │ ├── index.js │ ├── riot.router.reducers.js │ └── hoc.js ├── view.js ├── util.js └── application.js ├── example ├── build.js ├── server.js ├── index.html ├── test.js └── npm-debug.log ├── package.json ├── webpack.config.js ├── npm-debug.log └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .idea 3 | /.DS_Store 4 | **/.DS_Store 5 | .DS_Store? 6 | .DS_Store -------------------------------------------------------------------------------- /assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leekangtaqi/ninjajs/HEAD/assets/images/logo.png -------------------------------------------------------------------------------- /assets/images/ninja.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leekangtaqi/ninjajs/HEAD/assets/images/ninja.jpg -------------------------------------------------------------------------------- /assets/images/ninja.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leekangtaqi/ninjajs/HEAD/assets/images/ninja.png -------------------------------------------------------------------------------- /assets/images/qrcode.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leekangtaqi/ninjajs/HEAD/assets/images/qrcode.jpg -------------------------------------------------------------------------------- /assets/images/91songli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leekangtaqi/ninjajs/HEAD/assets/images/91songli.png -------------------------------------------------------------------------------- /assets/images/91pintuan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leekangtaqi/ninjajs/HEAD/assets/images/91pintuan.png -------------------------------------------------------------------------------- /src/ninja-redux/util/isPlainObject.js: -------------------------------------------------------------------------------- 1 | export default function isPlainObject(o) { 2 | return typeof o === 'object' && !Array.isArray(o) 3 | } -------------------------------------------------------------------------------- /src/ninja-redux/index.js: -------------------------------------------------------------------------------- 1 | import Connect from './components/connect'; 2 | import provider from './components/provider'; 3 | 4 | export { Connect, provider }; -------------------------------------------------------------------------------- /src/ninja-redux-form/index.js: -------------------------------------------------------------------------------- 1 | import Form from './hoc'; 2 | import { registerValidators } from './validator' 3 | 4 | export default Form; 5 | export { registerValidators }; -------------------------------------------------------------------------------- /src/ninja-redux/util/invariant.js: -------------------------------------------------------------------------------- 1 | export default function invariant(o, msg) { 2 | if(typeof o === 'undefined' || !o){ 3 | throw new Error(msg); 4 | } 5 | } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0"], 3 | "plugins": [ 4 | "syntax-async-functions", 5 | "transform-decorators-legacy", 6 | "transform-regenerator", 7 | "transform-decorators", 8 | ["transform-react-jsx", { "pragma":"h" }] 9 | ] 10 | } -------------------------------------------------------------------------------- /src/ninja-redux/util/warning.js: -------------------------------------------------------------------------------- 1 | export default function warning(message) { 2 | if(typeof console !== 'undefined' && typeof console.error === 'function'){ 3 | console.error(message); 4 | } 5 | try { 6 | throw new Error(message); 7 | } catch (e) {} 8 | } -------------------------------------------------------------------------------- /dist/riot-redux/util/invariant.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = invariant; 7 | function invariant(o, msg) { 8 | if (typeof o === 'undefined' || !o) { 9 | throw new Error(msg); 10 | } 11 | } -------------------------------------------------------------------------------- /src/ninja-router/invariant.js: -------------------------------------------------------------------------------- 1 | export default function invariant (expect, message, level = 'warn') { 2 | if (!message) { 3 | message = expect 4 | expect = undefined 5 | } 6 | if (typeof expect != 'undefined') { 7 | if (!expect) { 8 | console.error(message) 9 | } 10 | return 11 | } 12 | console[level](message) 13 | } -------------------------------------------------------------------------------- /example/build.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _h = require("../src/ninja-core/h"); 4 | 5 | var a = (0, _h.h)( 6 | "ul", 7 | { "class": "list", xx: "fff", yy: "xxx" }, 8 | (0, _h.h)( 9 | "li", 10 | null, 11 | "item 1" 12 | ), 13 | (0, _h.h)( 14 | "li", 15 | null, 16 | "item 2" 17 | ) 18 | ); 19 | 20 | console.log(a); 21 | -------------------------------------------------------------------------------- /dist/riot-redux/util/warning.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = warning; 7 | function warning(message) { 8 | if (typeof console !== 'undefined' && typeof console.error === 'function') { 9 | console.error(message); 10 | } 11 | try { 12 | throw new Error(message); 13 | } catch (e) {} 14 | } -------------------------------------------------------------------------------- /src/ninja-core/util.js: -------------------------------------------------------------------------------- 1 | export let isBoolean = o => o === true || o === false 2 | export let isNumber = o => typeof o === 'number' 3 | export let isString = o => typeof o === 'string' 4 | export let isFunction = o => typeof o === 'function' 5 | 6 | /** Just a memoized String#toLowerCase */ 7 | let lcCache = {}; 8 | export const toLowerCase = s => lcCache[s] || (lcCache[s] = s.toLowerCase()); 9 | 10 | export const defer = fn => setTimeout(fn, 0) -------------------------------------------------------------------------------- /src/ninja-core/vnode.js: -------------------------------------------------------------------------------- 1 | /** Virtual DOM Node */ 2 | export function VNode(nodeName, attributes, children) { 3 | /** @type {string|function} */ 4 | this.nodeName = nodeName; 5 | 6 | /** @type {object|undefined} */ 7 | this.attributes = attributes; 8 | 9 | /** @type {array|undefined} */ 10 | this.children = children; 11 | 12 | /** Reference to the given key. */ 13 | this.key = attributes && attributes.key; 14 | } 15 | -------------------------------------------------------------------------------- /example/server.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa'); 2 | const fs = require('fs'); 3 | const serve = require('koa-static'); 4 | const send = require('koa-send') 5 | const sendfile = require('koa-sendfile') 6 | const path = require('path') 7 | const app = new Koa(); 8 | 9 | app.use(serve(path.join(__dirname, '../dist'))); 10 | 11 | app.use(async (ctx, next) => { 12 | await sendfile(ctx, path.join(__dirname, './index.html')); 13 | }) 14 | 15 | app.listen(3000); -------------------------------------------------------------------------------- /src/ninja-core/dom/recycler.js: -------------------------------------------------------------------------------- 1 | import { toLowerCase } from '../util' 2 | 3 | /** DOM node pool, keyed on nodeName. */ 4 | 5 | const nodes = {}; 6 | 7 | export function createNode(nodeName, isSvg) { 8 | let name = toLowerCase(nodeName), 9 | node = nodes[name] && nodes[name].pop() || (isSvg ? document.createElementNS('http://www.w3.org/2000/svg', nodeName) : document.createElement(nodeName)); 10 | node.normalizedNodeName = name; 11 | return node; 12 | } -------------------------------------------------------------------------------- /dist/riot-redux-form/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.registerValidators = undefined; 7 | 8 | var _hoc = require('./hoc'); 9 | 10 | var _hoc2 = _interopRequireDefault(_hoc); 11 | 12 | var _validator = require('./validator'); 13 | 14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 15 | 16 | exports.default = _hoc2.default; 17 | exports.registerValidators = _validator.registerValidators; -------------------------------------------------------------------------------- /dist/riot-router/invariant.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = invariant; 7 | function invariant(expect, message) { 8 | var level = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'warn'; 9 | 10 | if (!message) { 11 | message = expect; 12 | expect = undefined; 13 | } 14 | if (typeof expect != 'undefined') { 15 | if (!expect) { 16 | console.error(message); 17 | } 18 | return; 19 | } 20 | console[level](message); 21 | } -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import _ from './util'; 2 | import { compose, createStore, combineReducers, applyMiddleware} from 'redux'; 3 | import ninjaRouterRedux from './ninja-router-redux'; 4 | 5 | export const configureStore = (initialState = {}, reducers, middlewares, HISTORY_MODE = 'browser') => { 6 | const reducer = combineReducers({ 7 | ...reducers 8 | }); 9 | const routeMw = ninjaRouterRedux.routerMiddlewareCreator(HISTORY_MODE); 10 | return compose(applyMiddleware(routeMw, ...(_.values(middlewares))))(createStore)(reducer, initialState); 11 | }; -------------------------------------------------------------------------------- /src/ninja-core/render-queue.js: -------------------------------------------------------------------------------- 1 | import { defer } from './util'; 2 | import { renderComponent } from './vdom/component'; 3 | 4 | /** Managed queue of dirty components to be re-rendered */ 5 | 6 | let items = []; 7 | 8 | export function enqueueRender(component) { 9 | if (!component._dirty && (component._dirty = true) && items.push(component)==1) { 10 | (defer)(rerender); 11 | } 12 | } 13 | 14 | 15 | export function rerender() { 16 | let p, list = items; 17 | items = []; 18 | while ( (p = list.pop()) ) { 19 | if (p._dirty) renderComponent(p); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /dist/riot-redux/util/isPlainObject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 8 | 9 | exports.default = isPlainObject; 10 | function isPlainObject(o) { 11 | return (typeof o === 'undefined' ? 'undefined' : _typeof(o)) === 'object' && !Array.isArray(o); 12 | } -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /dist/riot-redux/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.provider = exports.Connect = undefined; 7 | 8 | var _connect = require('./components/connect'); 9 | 10 | var _connect2 = _interopRequireDefault(_connect); 11 | 12 | var _provider = require('./components/provider'); 13 | 14 | var _provider2 = _interopRequireDefault(_provider); 15 | 16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 17 | 18 | exports.Connect = _connect2.default; 19 | exports.provider = _provider2.default; -------------------------------------------------------------------------------- /example/test.js: -------------------------------------------------------------------------------- 1 | import { render } from '../src/ninja-core/render'; 2 | import Component from '../src/ninja-core/component'; 3 | import { h } from '../src/ninja-core/h'; 4 | 5 | class Test extends Component{ 6 | constructor() { 7 | super() 8 | this.state = { 9 | ok: '33333' 10 | } 11 | } 12 | componentWillMount() { 13 | setTimeout(() => { 14 | this.setState({ok: '33333dsafdsafs'}) 15 | }, 1000) 16 | } 17 | render() { 18 | return () 22 | } 23 | } 24 | 25 | render(, document.body) 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/ninja-redux/util/shallowEqual.js: -------------------------------------------------------------------------------- 1 | export default function shallowEqual(objA, objB) { 2 | if (objA === objB) { 3 | return true 4 | } 5 | 6 | const keysA = Object.keys(objA) 7 | const keysB = Object.keys(objB) 8 | 9 | if (keysA.length !== keysB.length) { 10 | return false 11 | } 12 | 13 | // Test for A's keys different from B. 14 | const hasOwn = Object.prototype.hasOwnProperty 15 | for (let i = 0; i < keysA.length; i++) { 16 | if (!hasOwn.call(objB, keysA[i]) || 17 | objA[keysA[i]] !== objB[keysA[i]]) { 18 | return false 19 | } 20 | } 21 | 22 | return true 23 | } -------------------------------------------------------------------------------- /dist/riot-redux/util/shallowEqual.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = shallowEqual; 7 | function shallowEqual(objA, objB) { 8 | if (objA === objB) { 9 | return true; 10 | } 11 | 12 | var keysA = Object.keys(objA); 13 | var keysB = Object.keys(objB); 14 | 15 | if (keysA.length !== keysB.length) { 16 | return false; 17 | } 18 | 19 | // Test for A's keys different from B. 20 | var hasOwn = Object.prototype.hasOwnProperty; 21 | for (var i = 0; i < keysA.length; i++) { 22 | if (!hasOwn.call(objB, keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) { 23 | return false; 24 | } 25 | } 26 | 27 | return true; 28 | } -------------------------------------------------------------------------------- /src/ninja-redux/components/provider.js: -------------------------------------------------------------------------------- 1 | import invariant from '../util/invariant' 2 | import riot from 'riot' 3 | 4 | let provider = null; 5 | 6 | function recurFindProvider(tag) { 7 | if(!tag.parent) return tag; 8 | return recurFindProvider(tag.parent); 9 | } 10 | 11 | export default function provide(store) { 12 | 13 | invariant(store, `provider expect a state store`); 14 | 15 | return function(entry) { 16 | invariant((typeof entry === 'object' && entry instanceof riot.Tag), ` 17 | provider expect a tag instead of ${entry}`); 18 | entry.opts.store = store; 19 | provider = entry; 20 | return provider; 21 | } 22 | } 23 | 24 | export function getProvider(tag) { 25 | return provider || recurFindProvider(tag); 26 | } -------------------------------------------------------------------------------- /src/ninja-core/render.js: -------------------------------------------------------------------------------- 1 | import { diff } from './vdom/diff'; 2 | 3 | /** Render JSX into a `parent` Element. 4 | * @param {VNode} vnode A (JSX) VNode to render 5 | * @param {Element} parent DOM element to render into 6 | * @param {Element} [merge] Attempt to re-use an existing DOM tree rooted at `merge` 7 | * @public 8 | * 9 | * @example 10 | * // render a div into : 11 | * render(
hello!
, document.body); 12 | * 13 | * @example 14 | * // render a "Thing" component into #foo: 15 | * const Thing = ({ name }) => { name }; 16 | * render(, document.querySelector('#foo')); 17 | */ 18 | export function render(vnode, parent, merge) { 19 | return diff(merge, vnode, {}, false, parent); 20 | } -------------------------------------------------------------------------------- /src/ninja-core/component.js: -------------------------------------------------------------------------------- 1 | import { enqueueRender } from './render-queue'; 2 | 3 | export default class Component { 4 | constructor(props, context) { 5 | this.context = context 6 | this.props = props 7 | if (!this.state) this.state = {} 8 | } 9 | 10 | linkState(key, eventPath) { 11 | let c = this._linkedStates || (this._linkedStates = {}); 12 | return c[key+eventPath] || (c[key+eventPath] = createLinkedState(this, key, eventPath)); 13 | } 14 | 15 | setState(state, callback) { 16 | let s = this.state 17 | if (!this.prevState) this.prevState = Object.assign({}, s) 18 | if (callback) (this._renderCallbacks = (this._renderCallbacks || [])) 19 | enqueueRender(this) 20 | } 21 | 22 | forceUpdate() { 23 | renderComponent(this, FORCE_RENDER) 24 | } 25 | 26 | render() { 27 | throw new Error(`this component expected a render function.`) 28 | } 29 | } -------------------------------------------------------------------------------- /src/ninja-core/constants.js: -------------------------------------------------------------------------------- 1 | export const EMPTY_CHILDREN = []; 2 | // render modes 3 | 4 | export const NO_RENDER = 0; 5 | export const SYNC_RENDER = 1; 6 | export const FORCE_RENDER = 2; 7 | export const ASYNC_RENDER = 3; 8 | 9 | export const EMPTY = {}; 10 | 11 | export const ATTR_KEY = typeof Symbol!=='undefined' ? Symbol.for('preactattr') : '__preactattr_'; 12 | 13 | // DOM properties that should NOT have "px" added when numeric 14 | export const NON_DIMENSION_PROPS = { 15 | boxFlex:1, boxFlexGroup:1, columnCount:1, fillOpacity:1, flex:1, flexGrow:1, 16 | flexPositive:1, flexShrink:1, flexNegative:1, fontWeight:1, lineClamp:1, lineHeight:1, 17 | opacity:1, order:1, orphans:1, strokeOpacity:1, widows:1, zIndex:1, zoom:1 18 | }; 19 | 20 | // DOM event types that do not bubble and should be attached via useCapture 21 | export const NON_BUBBLING_EVENTS = { blur:1, error:1, focus:1, load:1, resize:1, scroll:1 }; 22 | -------------------------------------------------------------------------------- /src/ninja-core/vdom/component-recycler.js: -------------------------------------------------------------------------------- 1 | import Component from '../component'; 2 | 3 | /** Retains a pool of Components for re-use, keyed on component name. 4 | * Note: since component names are not unique or even necessarily available, these are primarily a form of sharding. 5 | * @private 6 | */ 7 | const components = {}; 8 | 9 | 10 | export function collectComponent(component) { 11 | let name = component.constructor.name, 12 | list = components[name]; 13 | if (list) list.push(component); 14 | else components[name] = [component]; 15 | } 16 | 17 | export function createComponent(Ctor, props, context) { 18 | let inst = new Ctor(props, context), 19 | list = components[Ctor.name]; 20 | Component.call(inst, props, context); 21 | if (list) { 22 | for (let i=list.length; i--; ) { 23 | if (list[i].constructor===Ctor) { 24 | inst.nextBase = list[i].nextBase; 25 | list.splice(i, 1); 26 | break; 27 | } 28 | } 29 | } 30 | return inst; 31 | } 32 | -------------------------------------------------------------------------------- /src/ninja-core/h.js: -------------------------------------------------------------------------------- 1 | import { VNode } from './vnode' 2 | import { isBoolean, isNumber, isString } from './util' 3 | 4 | const stack = []; 5 | const EMPTY_CHILDREN = []; 6 | 7 | /** @jsx h */ 8 | export function h(nodeName, attributes, ...oChildren) { 9 | let children = [], child = null, vnode = null, i, lastString 10 | !attributes && (attributes = {}) 11 | oChildren && oChildren.length && stack.push(oChildren) 12 | // functional component contains children in props 13 | attributes.children && stack.push(attributes.children) && delete attributes['children'] 14 | 15 | while (stack.length) { 16 | if (Array.isArray(child = stack.pop())) { 17 | for (i = child.length; i--; ) stack.push(child[i]) 18 | } 19 | else if (child != null && !isBoolean(child)) { 20 | if (isNumber(child)) child = String(child) 21 | if (isString(child) && lastString) { 22 | children[children.length-1] += child; 23 | } 24 | else { 25 | children.push(child); 26 | lastString = isString(child) 27 | } 28 | } 29 | } 30 | 31 | vnode = new VNode(nodeName, attributes, children) 32 | return vnode 33 | } 34 | -------------------------------------------------------------------------------- /dist/riot-tag/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = Component; 7 | 8 | var _riot = require('riot'); 9 | 10 | var _riot2 = _interopRequireDefault(_riot); 11 | 12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 13 | 14 | function extractPrototypes(s) { 15 | var o = {}; 16 | var excludes = ['tmpl', 'css', 'attrs']; 17 | for (var p in s) { 18 | if (excludes.indexOf(s[p]) < 0) { 19 | o[p] = s[p]; 20 | } 21 | } 22 | return o; 23 | } 24 | 25 | function Component(WrappedComponent) { 26 | if (!WrappedComponent.originName) { 27 | throw new Error('register decorator expected a origin name.'); 28 | } 29 | 30 | if (!WrappedComponent.prototype.tmpl) { 31 | throw new Error('register decorator expected a template.'); 32 | } 33 | _riot2.default.tag(WrappedComponent.originName, WrappedComponent.prototype.tmpl, WrappedComponent.prototype.css || '', WrappedComponent.prototype.attrs || '', function componentConstructor(opts) { 34 | this.mixin(extractPrototypes(WrappedComponent.prototype)); 35 | this.onCreate.apply(this, [opts]); 36 | }); 37 | 38 | return WrappedComponent; 39 | } -------------------------------------------------------------------------------- /src/ninja-tag/index.js: -------------------------------------------------------------------------------- 1 | import riot from 'riot'; 2 | 3 | function extractPrototypes(s) { 4 | let o = {}; 5 | let excludes = ['tmpl', 'css', 'attrs']; 6 | for (let p in s) { 7 | if (excludes.indexOf(s[p]) < 0) { 8 | o[p] = s[p] 9 | } 10 | } 11 | return o; 12 | } 13 | 14 | export default function Component(WrappedComponent) { 15 | if (!WrappedComponent.name) { 16 | throw new Error(`register decorator expected a origin name.`) 17 | } 18 | 19 | if (!WrappedComponent.prototype.tmpl) { 20 | throw new Error(`register decorator expected a template.`) 21 | } 22 | if ( !WrappedComponent.prototype.name ) { 23 | WrappedComponent.prototype.name = WrappedComponent.name.toLowerCase() 24 | } 25 | 26 | riot.tag( 27 | WrappedComponent.name.toLowerCase(), 28 | WrappedComponent.prototype.tmpl, 29 | WrappedComponent.prototype.css || '', 30 | WrappedComponent.prototype.attrs || '', 31 | function componentConstructor(opts) { 32 | this.mixin(extractPrototypes(WrappedComponent.prototype)) 33 | function noop(){} 34 | (this.onCreate || WrappedComponent.prototype.onCreate).apply(this, [opts]) 35 | } 36 | ) 37 | 38 | return WrappedComponent; 39 | } 40 | 41 | function mixin(source){ 42 | for (let p in source) { 43 | this[p] = source[p] 44 | } 45 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import router, { onUse } from './ninja-router/router' 2 | import Component from './ninja-tag' 3 | import Application, { uiLib } from './application' 4 | import { provider, Connect } from './ninja-redux' 5 | import { View } from './ninja-router-redux' 6 | import Form from './ninja-redux-form' 7 | import route from 'riot-route' 8 | 9 | let { hub } = router; 10 | 11 | hub.subscribe('history-pending', (from, to, location, ctx, next) => { 12 | if(from && from.tag){ 13 | from.tag.trigger('before-leave'); 14 | } 15 | next(); 16 | }); 17 | 18 | hub.subscribe('history-resolve', (from, to, ctx, hints, index, next) => { 19 | next(); 20 | }); 21 | 22 | hub.on('history-success', (from, to) => { 23 | // to && to.tag && to.tag.trigger('entered'); 24 | }); 25 | 26 | hub.setHandler(function handler(direction, tag){ 27 | if (!router.app) { 28 | console.warn(` 29 | router hub expected a handler. 30 | plz invoke [app.router] to set the handler to hub. 31 | `); 32 | return; 33 | } 34 | let actionType = direction === 'enter' ? '$enter' : '$leave'; 35 | router.app.store.dispatch({type: actionType, payload: tag}) 36 | }) 37 | 38 | export { 39 | Component, 40 | router, 41 | Application as Ninja, 42 | provider, 43 | Connect, 44 | View, 45 | Form, 46 | onUse, 47 | route, 48 | uiLib 49 | } -------------------------------------------------------------------------------- /src/ninja-redux-form/validator.js: -------------------------------------------------------------------------------- 1 | let validators = {}; 2 | 3 | let buildinValidators = [ 4 | { 5 | name: "required", 6 | fn: function(val){ 7 | return val.trim() === "" 8 | } 9 | }, 10 | { 11 | name: 'max', 12 | fn: function(val, expected){ 13 | return parseInt(val.trim(), 10) > expected; 14 | } 15 | }, 16 | { 17 | name: 'min', 18 | fn: function(val, expected){ 19 | return parseInt(val.trim(), 10) < expected; 20 | } 21 | }, 22 | { 23 | name: 'maxlength', 24 | fn: function(val, expected){ 25 | return val.length > expected; 26 | } 27 | }, 28 | { 29 | name: 'minlength', 30 | fn: function(val, expected){ 31 | return val.length < expected; 32 | } 33 | }, 34 | { 35 | name: 'pattern', 36 | fn: function(val, expected){ 37 | return !(new RegExp(expected)).test(val.trim()); 38 | } 39 | } 40 | ]; 41 | 42 | /** 43 | * register build-in validators 44 | */ 45 | buildinValidators.map(validator => { 46 | registerValidators(validator.name, validator.fn); 47 | }); 48 | 49 | function registerValidators(name, fn){ 50 | 51 | if (!name || typeof name != 'string') { 52 | throw new Error(`invalid validator name [name]=${name}`) 53 | } 54 | 55 | if (!fn || typeof fn != 'function') { 56 | throw new Error(`invalid validator name [fn]=${fn}`) 57 | } 58 | 59 | validators[name] = fn; 60 | } 61 | 62 | export { validators, registerValidators } -------------------------------------------------------------------------------- /dist/riot-redux/components/provider.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 8 | 9 | exports.default = provide; 10 | exports.getProvider = getProvider; 11 | 12 | var _invariant = require('../util/invariant'); 13 | 14 | var _invariant2 = _interopRequireDefault(_invariant); 15 | 16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 17 | 18 | var provider = null; 19 | 20 | function recurFindProvider(tag) { 21 | if (!tag.parent) return tag; 22 | return recurFindProvider(tag.parent); 23 | } 24 | 25 | function provide(store) { 26 | 27 | (0, _invariant2.default)(store, 'provider expect a state store'); 28 | 29 | return function (entry) { 30 | (0, _invariant2.default)((typeof entry === 'undefined' ? 'undefined' : _typeof(entry)) === 'object' && entry instanceof riot.Tag, '\n\t\t\tprovider expect a riot tag instead of ' + entry); 31 | entry.opts.store = store; 32 | provider = entry; 33 | return provider; 34 | }; 35 | } 36 | 37 | function getProvider(tag) { 38 | return provider || recurFindProvider(tag); 39 | } -------------------------------------------------------------------------------- /src/ninja-router/decorators.js: -------------------------------------------------------------------------------- 1 | function onUse(fnArray) { 2 | return function(target, key, descriptor) { 3 | let originOnCreate = descriptor.value; 4 | if (!descriptor.$originOnCreate) { 5 | descriptor.$originOnCreate = originOnCreate; 6 | } 7 | descriptor.value = function(opts) { 8 | if ( 9 | descriptor.$originOnCreate && 10 | !descriptor.$originOnCreateInvocated 11 | ) { 12 | descriptor.$originOnCreate.apply(this, [opts]) 13 | descriptor.$originOnCreateInvocated = true; 14 | } 15 | if (!this.$mws || !this.$mws.length) { 16 | this.mixin('router'); 17 | } 18 | this.one('leave', () => { 19 | descriptor.$originOnCreateInvocated = false; 20 | }) 21 | this.one('unmount', () => { 22 | descriptor.$originOnCreateInvocated = false; 23 | }) 24 | if (!Array.isArray(fnArray)) { 25 | fnArray = [fnArray] 26 | } 27 | fnArray.forEach(fn => { 28 | this.$use((next, ctx) => { 29 | if (typeof fn === 'string') { 30 | fn = this.opts[fn]; 31 | if (!fnArray) { 32 | invariant(`[onUse]: Error not such a ${fnArray} in opts`) 33 | } 34 | } 35 | return fn.apply(this, [next, ctx, this]) 36 | }); 37 | }) 38 | } 39 | return descriptor; 40 | } 41 | } 42 | 43 | export default { 44 | onUse 45 | } -------------------------------------------------------------------------------- /src/ninja-core/vdom/index.js: -------------------------------------------------------------------------------- 1 | import { isString } from '../util'; 2 | 3 | /** 4 | * Reconstruct Component-style `props` from a VNode. 5 | * Ensures default/fallback values from `defaultProps`: 6 | * Own-properties of `defaultProps` not present in `vnode.attributes` are added. 7 | * @param {VNode} vnode 8 | * @returns {Object} props 9 | */ 10 | export function getNodeProps(vnode) { 11 | let props = Object.assign({}, vnode.attributes); 12 | props.children = vnode.children; 13 | 14 | let defaultProps = vnode.nodeName.defaultProps; 15 | if (defaultProps) { 16 | for (let i in defaultProps) { 17 | if (props[i]===undefined) { 18 | props[i] = defaultProps[i]; 19 | } 20 | } 21 | } 22 | 23 | return props; 24 | } 25 | 26 | export function isNamedNode(node, nodeName) { 27 | return node.normalizedNodeName===nodeName || toLowerCase(node.nodeName)===toLowerCase(nodeName); 28 | } 29 | 30 | /** Check if two nodes are equivalent. 31 | * @param {Element} node 32 | * @param {VNode} vnode 33 | * @private 34 | */ 35 | export function isSameNodeType(node, vnode) { 36 | if (isString(vnode)) { 37 | return node instanceof Text; 38 | } 39 | if (isString(vnode.nodeName)) { 40 | return !node._componentConstructor && isNamedNode(node, vnode.nodeName); 41 | } 42 | if (isFunction(vnode.nodeName)) { 43 | return (node._componentConstructor ? node._componentConstructor===vnode.nodeName : true) || isFunctionalComponent(vnode); 44 | } 45 | } -------------------------------------------------------------------------------- /dist/riot-redux-form/validator.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | var validators = {}; 7 | 8 | var buildinValidators = [{ 9 | name: "required", 10 | fn: function fn(val) { 11 | return val.trim() === ""; 12 | } 13 | }, { 14 | name: 'max', 15 | fn: function fn(val, expected) { 16 | return parseInt(val.trim(), 10) > expected; 17 | } 18 | }, { 19 | name: 'min', 20 | fn: function fn(val, expected) { 21 | return parseInt(val.trim(), 10) < expected; 22 | } 23 | }, { 24 | name: 'maxlength', 25 | fn: function fn(val, expected) { 26 | return val.length > expected; 27 | } 28 | }, { 29 | name: 'minlength', 30 | fn: function fn(val, expected) { 31 | return val.length < expected; 32 | } 33 | }, { 34 | name: 'pattern', 35 | fn: function fn(val, expected) { 36 | return !new RegExp(expected).test(val.trim()); 37 | } 38 | }]; 39 | 40 | /** 41 | * register build-in validators 42 | */ 43 | buildinValidators.map(function (validator) { 44 | registerValidators(validator.name, validator.fn); 45 | }); 46 | 47 | function registerValidators(name, fn) { 48 | 49 | if (!name || typeof name != 'string') { 50 | throw new Error("invalid validator name [name]=" + name); 51 | } 52 | 53 | if (!fn || typeof fn != 'function') { 54 | throw new Error("invalid validator name [fn]=" + fn); 55 | } 56 | 57 | validators[name] = fn; 58 | } 59 | 60 | exports.validators = validators; 61 | exports.registerValidators = registerValidators; -------------------------------------------------------------------------------- /dist/riot-router/decorators.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | function onUse(fnArray) { 7 | return function (target, key, descriptor) { 8 | var originOnCreate = descriptor.value; 9 | if (!descriptor.$originOnCreate) { 10 | descriptor.$originOnCreate = originOnCreate; 11 | } 12 | descriptor.value = function (opts) { 13 | var _this = this; 14 | 15 | if (descriptor.$originOnCreate && !descriptor.$originOnCreateInvocated) { 16 | descriptor.$originOnCreate.apply(this, [opts]); 17 | descriptor.$originOnCreateInvocated = true; 18 | } 19 | if (!this.$mws || !this.$mws.length) { 20 | this.mixin('router'); 21 | } 22 | this.one('leave', function () { 23 | descriptor.$originOnCreateInvocated = false; 24 | }); 25 | this.one('unmount', function () { 26 | descriptor.$originOnCreateInvocated = false; 27 | }); 28 | if (!Array.isArray(fnArray)) { 29 | fnArray = [fnArray]; 30 | } 31 | fnArray.forEach(function (fn) { 32 | _this.$use(function (next, ctx) { 33 | if (typeof fn === 'string') { 34 | fn = _this.opts[fn]; 35 | if (!fnArray) { 36 | invariant('[onUse]: Error not such a ' + fnArray + ' in opts'); 37 | } 38 | } 39 | return fn.apply(_this, [next, ctx, _this]); 40 | }); 41 | }); 42 | }; 43 | return descriptor; 44 | }; 45 | } 46 | 47 | exports.default = { 48 | onUse: onUse 49 | }; -------------------------------------------------------------------------------- /dist/store.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.configureStore = undefined; 7 | 8 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 9 | 10 | var _util = require('./util'); 11 | 12 | var _util2 = _interopRequireDefault(_util); 13 | 14 | var _redux = require('redux'); 15 | 16 | var _riotRouterRedux = require('./riot-router-redux'); 17 | 18 | var _riotRouterRedux2 = _interopRequireDefault(_riotRouterRedux); 19 | 20 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 21 | 22 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } 23 | 24 | var configureStore = exports.configureStore = function configureStore() { 25 | var initialState = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 26 | var reducers = arguments[1]; 27 | var middlewares = arguments[2]; 28 | var HISTORY_MODE = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'browser'; 29 | 30 | var reducer = (0, _redux.combineReducers)(_extends({}, reducers)); 31 | var routeMw = _riotRouterRedux2.default.routerMiddlewareCreator(HISTORY_MODE); 32 | return (0, _redux.compose)(_redux.applyMiddleware.apply(undefined, [routeMw].concat(_toConsumableArray(_util2.default.values(middlewares)))))(_redux.createStore)(reducer, initialState); 33 | }; -------------------------------------------------------------------------------- /src/ninja-router-redux/index.js: -------------------------------------------------------------------------------- 1 | import route from 'riot-route'; 2 | import View from './hoc'; 3 | 4 | function routerMiddlewareCreator(historyMode){ 5 | return store => next => action => { 6 | if(action.type === 'route'){ 7 | } 8 | return next(action); 9 | } 10 | } 11 | function syncHistoryWithStore(hub, store){ 12 | hub.off('state-search').on('state-search', ({param, value}) => { 13 | let route = store.getState().route; 14 | let query = route.data.req.query; 15 | // if(query[param] && (query[param] = value)){ 16 | // return; 17 | // } 18 | query[param] = value; 19 | route(route.$location + '?' + $.util.querystring.stringify(query), null, true); 20 | }); 21 | hub.off('state-change').on('state-change', route => { 22 | store.dispatch({ 23 | type: '$route', 24 | payload: { 25 | route: route 26 | } 27 | }) 28 | }); 29 | hub.off('sync-state-query').on('sync-state-query', query => { 30 | store.dispatch({ 31 | type: '$query', 32 | payload: query 33 | }) 34 | }) 35 | hub.off('busy-pending').on('busy-pending', () => { 36 | // store.dispatch({ 37 | // type: 'maskShow' 38 | // }); 39 | // store.dispatch({ 40 | // type: '$routeBusy' 41 | // }) 42 | }); 43 | hub.off('busy-resolve').on('busy-resolve', () => { 44 | // store.dispatch({ 45 | // type: 'maskHidden' 46 | // }); 47 | // store.dispatch({ 48 | // type: '$routeUnBusy' 49 | // }) 50 | }) 51 | } 52 | 53 | export default { routerMiddlewareCreator, syncHistoryWithStore, View } 54 | export { routerMiddlewareCreator, syncHistoryWithStore, View } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ninjajs", 3 | "version": "3.4.3", 4 | "description": "frontend framework base on riot and redux", 5 | "main": "./dist/bundle.js", 6 | "author": "leekangtaqi", 7 | "keywords": [ 8 | "riot", 9 | "redux", 10 | "frontend", 11 | "framework" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/leekangtaqi/ninjajs.git" 16 | }, 17 | "dependencies": { 18 | "preact": "^7.2.1", 19 | "redux": "*", 20 | "riot": "*", 21 | "riot-route": "*" 22 | }, 23 | "devDependencies": { 24 | "babel": "^6.23.0", 25 | "babel-cli": "^6.24.1", 26 | "babel-core": "^6.9.1", 27 | "babel-loader": "^6.4.1", 28 | "babel-plugin-external-helpers-2": "^6.3.13", 29 | "babel-plugin-syntax-async-functions": "^6.8.0", 30 | "babel-plugin-transform-class-properties": "^6.9.1", 31 | "babel-plugin-transform-decorators": "^6.22.0", 32 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 33 | "babel-plugin-transform-es2015-arrow-functions": "^6.8.0", 34 | "babel-plugin-transform-react-jsx": "^6.24.1", 35 | "babel-plugin-transform-runtime": "^6.9.0", 36 | "babel-polyfill": "^6.9.1", 37 | "babel-preset-env": "^1.2.2", 38 | "babel-preset-es2015": "^6.9.0", 39 | "babel-preset-es2015-node5": "^1.2.0", 40 | "babel-preset-stage-0": "^6.5.0", 41 | "babel-preset-stage-3": "^6.5.0", 42 | "babel-regenerator-runtime": "^6.5.0", 43 | "cross-env": "^3.2.4", 44 | "koa": "^2.2.0", 45 | "koa-send": "^3.3.0", 46 | "koa-sendfile": "^2.0.0", 47 | "koa-static": "^3.0.0", 48 | "webpack": "^2.3.0", 49 | "webpack-dev-server": "^3.1.11" 50 | }, 51 | "engines": { 52 | "node": ">=6.2.1" 53 | }, 54 | "scripts": { 55 | "dev": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js", 56 | "build": "cross-env NODE_ENV=production webpack --config webpack.config.js", 57 | "start": "node ./example/server.js" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.route = exports.onUse = exports.Form = exports.View = exports.Connect = exports.provider = exports.Ninjia = exports.router = exports.Component = undefined; 7 | 8 | var _router = require('./riot-router/router'); 9 | 10 | var _router2 = _interopRequireDefault(_router); 11 | 12 | var _riotTag = require('./riot-tag'); 13 | 14 | var _riotTag2 = _interopRequireDefault(_riotTag); 15 | 16 | var _application = require('./application'); 17 | 18 | var _application2 = _interopRequireDefault(_application); 19 | 20 | var _riotRedux = require('./riot-redux'); 21 | 22 | var _riotRouterRedux = require('./riot-router-redux'); 23 | 24 | var _riotReduxForm = require('./riot-redux-form'); 25 | 26 | var _riotReduxForm2 = _interopRequireDefault(_riotReduxForm); 27 | 28 | var _riotRoute = require('riot-route'); 29 | 30 | var _riotRoute2 = _interopRequireDefault(_riotRoute); 31 | 32 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 33 | 34 | var hub = _router2.default.hub; 35 | 36 | 37 | hub.subscribe('history-pending', function (from, to, location, ctx, next) { 38 | if (from && from.tag) { 39 | from.tag.trigger('before-leave'); 40 | } 41 | next(); 42 | }); 43 | 44 | hub.subscribe('history-resolve', function (from, to, ctx, hints, index, next) { 45 | next(); 46 | }); 47 | 48 | hub.on('history-success', function (from, to) { 49 | // to && to.tag && to.tag.trigger('entered'); 50 | }); 51 | 52 | hub.setHandler(function handler(direction, tag) { 53 | if (!_router2.default.app) { 54 | console.warn('\n\t\t\trouter hub expected a handler.\n\t\t\tplz invoke [app.router] to set the handler to hub.\n\t\t'); 55 | return; 56 | } 57 | var actionType = direction === 'enter' ? '$enter' : '$leave'; 58 | _router2.default.app.store.dispatch({ type: actionType, payload: tag }); 59 | }); 60 | 61 | exports.Component = _riotTag2.default; 62 | exports.router = _router2.default; 63 | exports.Ninjia = _application2.default; 64 | exports.provider = _riotRedux.provider; 65 | exports.Connect = _riotRedux.Connect; 66 | exports.View = _riotRouterRedux.View; 67 | exports.Form = _riotReduxForm2.default; 68 | exports.onUse = _router.onUse; 69 | exports.route = _riotRoute2.default; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var path = require('path'); 3 | let env = process.env.NODE_ENV; 4 | 5 | let baseConfig = { 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.tag$/, 10 | use: [ 11 | 'html-loader' 12 | ] 13 | }, 14 | { 15 | test: /\.jsx|js$/, 16 | exclude: [/node_modules/], 17 | use: { 18 | loader: 'babel-loader', 19 | options: { 20 | compact: true, 21 | presets: ["es2015", "stage-0"], 22 | plugins: ['transform-decorators-legacy', 'transform-decorators', ["transform-react-jsx", { "pragma":"h" }]] 23 | } 24 | }, 25 | } 26 | ] 27 | }, 28 | node: { 29 | net: 'mock', 30 | dns: 'mock', 31 | fs: 'empty' 32 | } 33 | }; 34 | 35 | let proConfig = { 36 | entry: { 37 | bundle: ['babel-regenerator-runtime', './src/index.js'], 38 | testbundle: ['babel-regenerator-runtime', './example/test.js'] 39 | }, 40 | plugins: [ 41 | new webpack.optimize.UglifyJsPlugin({ 42 | compress: { warnings: false }, 43 | comments: false, 44 | sourceMap: false, 45 | mangle: true, 46 | minimize: true 47 | }), 48 | ], 49 | output: { 50 | path: path.resolve(__dirname, './dist/'), 51 | filename: '[name].js' 52 | } 53 | } 54 | 55 | let devConfig = { 56 | entry: { 57 | bundle: [ 58 | 'babel-regenerator-runtime', 59 | 'webpack-dev-server/client?http:localhost:8080', 60 | './src/index.js' 61 | ], 62 | testbundle: [ 63 | 'babel-regenerator-runtime', 64 | './example/test.js' 65 | ] 66 | }, 67 | devtool: 'eval', 68 | output: { 69 | path: path.resolve(__dirname, './dist'), 70 | filename: '[name].js', 71 | publicPath: '/dist/' 72 | }, 73 | devServer: { 74 | contentBase: path.join(__dirname, "example/"), 75 | compress: true, 76 | port: 8080 77 | } 78 | } 79 | 80 | let envMap = { 81 | production: proConfig, 82 | development: devConfig 83 | } 84 | 85 | module.exports = merge(baseConfig, envMap[env]); 86 | /** 87 | * Helper functions 88 | */ 89 | function merge(t, s) { 90 | for (let p in s) { 91 | if (t.hasOwnProperty(p)) { 92 | if (Array.isArray(t[p])) { 93 | t[p] = [...s[p], ...t[p]]; 94 | } else if(typeof t[p] === 'object') { 95 | merge(t[p], s[p]) 96 | } 97 | } else { 98 | t[p] = s[p]; 99 | } 100 | } 101 | return t; 102 | } 103 | -------------------------------------------------------------------------------- /dist/riot-router-redux/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.View = exports.syncHistoryWithStore = exports.routerMiddlewareCreator = undefined; 7 | 8 | var _riotRoute = require('riot-route'); 9 | 10 | var _riotRoute2 = _interopRequireDefault(_riotRoute); 11 | 12 | var _hoc = require('./hoc'); 13 | 14 | var _hoc2 = _interopRequireDefault(_hoc); 15 | 16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 17 | 18 | function routerMiddlewareCreator(historyMode) { 19 | return function (store) { 20 | return function (next) { 21 | return function (action) { 22 | if (action.type === 'route') {} 23 | return next(action); 24 | }; 25 | }; 26 | }; 27 | } 28 | function syncHistoryWithStore(hub, store) { 29 | hub.off('state-search').on('state-search', function (_ref) { 30 | var param = _ref.param, 31 | value = _ref.value; 32 | 33 | var route = store.getState().route; 34 | var query = route.data.req.query; 35 | // if(query[param] && (query[param] = value)){ 36 | // return; 37 | // } 38 | query[param] = value; 39 | route(route.$location + '?' + $.util.querystring.stringify(query), null, true); 40 | }); 41 | hub.off('state-change').on('state-change', function (route) { 42 | store.dispatch({ 43 | type: '$route', 44 | payload: { 45 | route: route 46 | } 47 | }); 48 | }); 49 | hub.off('sync-state-query').on('sync-state-query', function (query) { 50 | store.dispatch({ 51 | type: '$query', 52 | payload: query 53 | }); 54 | }); 55 | hub.off('busy-pending').on('busy-pending', function () { 56 | // store.dispatch({ 57 | // type: 'maskShow' 58 | // }); 59 | // store.dispatch({ 60 | // type: '$routeBusy' 61 | // }) 62 | }); 63 | hub.off('busy-resolve').on('busy-resolve', function () { 64 | // store.dispatch({ 65 | // type: 'maskHidden' 66 | // }); 67 | // store.dispatch({ 68 | // type: '$routeUnBusy' 69 | // }) 70 | }); 71 | } 72 | 73 | exports.default = { routerMiddlewareCreator: routerMiddlewareCreator, syncHistoryWithStore: syncHistoryWithStore, View: _hoc2.default }; 74 | exports.routerMiddlewareCreator = routerMiddlewareCreator; 75 | exports.syncHistoryWithStore = syncHistoryWithStore; 76 | exports.View = _hoc2.default; -------------------------------------------------------------------------------- /src/ninja-router-redux/ninja.router.reducers.js: -------------------------------------------------------------------------------- 1 | let initialRouteData = { 2 | $prev_state: '/', 3 | $prev_location: '/', 4 | $state: '/', 5 | $location: '/', 6 | $protocol: 'http://', 7 | $host: typeof window != 'undefined' && location && location.host || '', 8 | data: {}, 9 | stack: [], 10 | $views: {} 11 | }; 12 | const route = (route = initialRouteData, action) => { 13 | switch (action.type) { 14 | 15 | case '$enter': 16 | var tagName = action.payload.$routePath;; 17 | var newOne = { 18 | [tagName]: true 19 | }; 20 | return Object.assign({}, route, { 21 | $views: {...route.$views, ...newOne} 22 | }) 23 | 24 | case '$leave': 25 | var tagName = action.payload.$routePath;; 26 | return Object.assign({}, route, { 27 | $views: Object.keys(route.$views) 28 | .filter(v => v != tagName) 29 | .reduce((o, k) => { 30 | o[k] = true; 31 | return o; 32 | }, {}) 33 | }) 34 | 35 | case '$route': 36 | return Object.assign({}, route, { 37 | $prev_state: route.$state, 38 | $prev_location: route.$location, 39 | $state: action.payload.route.$state, 40 | $location: action.payload.route.$location, 41 | $protocol: action.payload.route.$protocol || route.$protocol, 42 | $host: route.$host, 43 | data: action.payload.route.ctx 44 | }); 45 | 46 | case '$query': 47 | return Object.assign({}, route, { 48 | $prev_state: route.$state, 49 | $prev_location: route.$location, 50 | $state: route.$state, 51 | $location: route.$location, 52 | $protocol: route.$protocol, 53 | $host: route.$host, 54 | data: Object.assign({}, 55 | { 56 | request: { 57 | body: route.data.req.body, 58 | params: route.data.req.params, 59 | query: action.payload, 60 | } 61 | } 62 | ) 63 | }) 64 | 65 | case '$routeBusy': 66 | return Object.assign({}, route, {busy: true}); 67 | 68 | case '$routeUnBusy': 69 | return Object.assign({}, route, {busy: false}); 70 | 71 | default: 72 | return route; 73 | } 74 | }; 75 | export default {route} -------------------------------------------------------------------------------- /dist/riot-router-redux/riot.router.reducers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 10 | 11 | var initialRouteData = { 12 | $prev_state: '/', 13 | $prev_location: '/', 14 | $state: '/', 15 | $location: '/', 16 | $protocol: 'http://', 17 | $host: typeof window != 'undefined' && location && location.host || '', 18 | data: {}, 19 | stack: [], 20 | $views: {} 21 | }; 22 | var route = function route() { 23 | var route = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initialRouteData; 24 | var action = arguments[1]; 25 | 26 | switch (action.type) { 27 | 28 | case '$enter': 29 | var tagName = action.payload.$routePath;; 30 | var newOne = _defineProperty({}, tagName, true); 31 | return Object.assign({}, route, { 32 | $views: _extends({}, route.$views, newOne) 33 | }); 34 | 35 | case '$leave': 36 | var tagName = action.payload.$routePath;; 37 | return Object.assign({}, route, { 38 | $views: Object.keys(route.$views).filter(function (v) { 39 | return v != tagName; 40 | }).reduce(function (o, k) { 41 | o[k] = true; 42 | return o; 43 | }, {}) 44 | }); 45 | 46 | case '$route': 47 | return Object.assign({}, route, { 48 | $prev_state: route.$state, 49 | $prev_location: route.$location, 50 | $state: action.payload.route.$state, 51 | $location: action.payload.route.$location, 52 | $protocol: action.payload.route.$protocol || route.$protocol, 53 | $host: route.$host, 54 | data: action.payload.route.ctx 55 | }); 56 | 57 | case '$query': 58 | return Object.assign({}, route, { 59 | $prev_state: route.$state, 60 | $prev_location: route.$location, 61 | $state: route.$state, 62 | $location: route.$location, 63 | $protocol: route.$protocol, 64 | $host: route.$host, 65 | data: Object.assign({}, { 66 | request: { 67 | body: route.data.req.body, 68 | params: route.data.req.params, 69 | query: action.payload 70 | } 71 | }) 72 | }); 73 | 74 | case '$routeBusy': 75 | return Object.assign({}, route, { busy: true }); 76 | 77 | case '$routeUnBusy': 78 | return Object.assign({}, route, { busy: false }); 79 | 80 | default: 81 | return route; 82 | } 83 | }; 84 | exports.default = { route: route }; -------------------------------------------------------------------------------- /npm-debug.log: -------------------------------------------------------------------------------- 1 | 0 info it worked if it ends with ok 2 | 1 verbose cli [ '/Users/ricklee/.nvm/versions/node/v7.7.4/bin/node', 3 | 1 verbose cli '/Users/ricklee/.nvm/versions/node/v7.7.4/bin/npm', 4 | 1 verbose cli 'run', 5 | 1 verbose cli 'babel2' ] 6 | 2 info using npm@4.1.2 7 | 3 info using node@v7.7.4 8 | 4 verbose run-script [ 'prebabel2', 'babel2', 'postbabel2' ] 9 | 5 info lifecycle ninjajs@3.4.3~prebabel2: ninjajs@3.4.3 10 | 6 silly lifecycle ninjajs@3.4.3~prebabel2: no script for prebabel2, continuing 11 | 7 info lifecycle ninjajs@3.4.3~babel2: ninjajs@3.4.3 12 | 8 verbose lifecycle ninjajs@3.4.3~babel2: unsafe-perm in lifecycle true 13 | 9 verbose lifecycle ninjajs@3.4.3~babel2: PATH: /Users/ricklee/.nvm/versions/node/v7.7.4/lib/node_modules/npm/bin/node-gyp-bin:/Users/ricklee/dev/codebase/ninjajs/node_modules/.bin:/Users/ricklee/.nvm/versions/node/v7.7.4/bin:/Users/ricklee/.rvm/gems/ruby-2.2.0/bin:/Users/ricklee/.rvm/gems/ruby-2.2.0@global/bin:/Users/ricklee/.rvm/rubies/ruby-2.2.0/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/ricklee/.rvm/bin:/Users/ricklee/Library/Android/sdk/tools:/Users/ricklee/Library/Android/sdk/platform-tools 14 | 10 verbose lifecycle ninjajs@3.4.3~babel2: CWD: /Users/ricklee/dev/codebase/ninjajs 15 | 11 silly lifecycle ninjajs@3.4.3~babel2: Args: [ '-c', 'node ./example/build.js' ] 16 | 12 silly lifecycle ninjajs@3.4.3~babel2: Returned: code: 1 signal: null 17 | 13 info lifecycle ninjajs@3.4.3~babel2: Failed to exec babel2 script 18 | 14 verbose stack Error: ninjajs@3.4.3 babel2: `node ./example/build.js` 19 | 14 verbose stack Exit status 1 20 | 14 verbose stack at EventEmitter. (/Users/ricklee/.nvm/versions/node/v7.7.4/lib/node_modules/npm/lib/utils/lifecycle.js:279:16) 21 | 14 verbose stack at emitTwo (events.js:106:13) 22 | 14 verbose stack at EventEmitter.emit (events.js:194:7) 23 | 14 verbose stack at ChildProcess. (/Users/ricklee/.nvm/versions/node/v7.7.4/lib/node_modules/npm/lib/utils/spawn.js:40:14) 24 | 14 verbose stack at emitTwo (events.js:106:13) 25 | 14 verbose stack at ChildProcess.emit (events.js:194:7) 26 | 14 verbose stack at maybeClose (internal/child_process.js:899:16) 27 | 14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:226:5) 28 | 15 verbose pkgid ninjajs@3.4.3 29 | 16 verbose cwd /Users/ricklee/dev/codebase/ninjajs 30 | 17 error Darwin 15.3.0 31 | 18 error argv "/Users/ricklee/.nvm/versions/node/v7.7.4/bin/node" "/Users/ricklee/.nvm/versions/node/v7.7.4/bin/npm" "run" "babel2" 32 | 19 error node v7.7.4 33 | 20 error npm v4.1.2 34 | 21 error code ELIFECYCLE 35 | 22 error ninjajs@3.4.3 babel2: `node ./example/build.js` 36 | 22 error Exit status 1 37 | 23 error Failed at the ninjajs@3.4.3 babel2 script 'node ./example/build.js'. 38 | 23 error Make sure you have the latest version of node.js and npm installed. 39 | 23 error If you do, this is most likely a problem with the ninjajs package, 40 | 23 error not with npm itself. 41 | 23 error Tell the author that this fails on your system: 42 | 23 error node ./example/build.js 43 | 23 error You can get information on how to open an issue for this project with: 44 | 23 error npm bugs ninjajs 45 | 23 error Or if that isn't available, you can get their info via: 46 | 23 error npm owner ls ninjajs 47 | 23 error There is likely additional logging output above. 48 | 24 verbose exit [ 1, true ] 49 | -------------------------------------------------------------------------------- /example/npm-debug.log: -------------------------------------------------------------------------------- 1 | 0 info it worked if it ends with ok 2 | 1 verbose cli [ '/Users/ricklee/.nvm/versions/node/v7.7.4/bin/node', 3 | 1 verbose cli '/Users/ricklee/.nvm/versions/node/v7.7.4/bin/npm', 4 | 1 verbose cli 'run', 5 | 1 verbose cli 'babel2' ] 6 | 2 info using npm@4.1.2 7 | 3 info using node@v7.7.4 8 | 4 verbose run-script [ 'prebabel2', 'babel2', 'postbabel2' ] 9 | 5 info lifecycle ninjajs@3.4.3~prebabel2: ninjajs@3.4.3 10 | 6 silly lifecycle ninjajs@3.4.3~prebabel2: no script for prebabel2, continuing 11 | 7 info lifecycle ninjajs@3.4.3~babel2: ninjajs@3.4.3 12 | 8 verbose lifecycle ninjajs@3.4.3~babel2: unsafe-perm in lifecycle true 13 | 9 verbose lifecycle ninjajs@3.4.3~babel2: PATH: /Users/ricklee/.nvm/versions/node/v7.7.4/lib/node_modules/npm/bin/node-gyp-bin:/Users/ricklee/dev/codebase/ninjajs/node_modules/.bin:/Users/ricklee/.nvm/versions/node/v7.7.4/bin:/Users/ricklee/.rvm/gems/ruby-2.2.0/bin:/Users/ricklee/.rvm/gems/ruby-2.2.0@global/bin:/Users/ricklee/.rvm/rubies/ruby-2.2.0/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/ricklee/.rvm/bin:/Users/ricklee/Library/Android/sdk/tools:/Users/ricklee/Library/Android/sdk/platform-tools 14 | 10 verbose lifecycle ninjajs@3.4.3~babel2: CWD: /Users/ricklee/dev/codebase/ninjajs 15 | 11 silly lifecycle ninjajs@3.4.3~babel2: Args: [ '-c', 'node ./example/build.js' ] 16 | 12 silly lifecycle ninjajs@3.4.3~babel2: Returned: code: 1 signal: null 17 | 13 info lifecycle ninjajs@3.4.3~babel2: Failed to exec babel2 script 18 | 14 verbose stack Error: ninjajs@3.4.3 babel2: `node ./example/build.js` 19 | 14 verbose stack Exit status 1 20 | 14 verbose stack at EventEmitter. (/Users/ricklee/.nvm/versions/node/v7.7.4/lib/node_modules/npm/lib/utils/lifecycle.js:279:16) 21 | 14 verbose stack at emitTwo (events.js:106:13) 22 | 14 verbose stack at EventEmitter.emit (events.js:194:7) 23 | 14 verbose stack at ChildProcess. (/Users/ricklee/.nvm/versions/node/v7.7.4/lib/node_modules/npm/lib/utils/spawn.js:40:14) 24 | 14 verbose stack at emitTwo (events.js:106:13) 25 | 14 verbose stack at ChildProcess.emit (events.js:194:7) 26 | 14 verbose stack at maybeClose (internal/child_process.js:899:16) 27 | 14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:226:5) 28 | 15 verbose pkgid ninjajs@3.4.3 29 | 16 verbose cwd /Users/ricklee/dev/codebase/ninjajs/example 30 | 17 error Darwin 15.3.0 31 | 18 error argv "/Users/ricklee/.nvm/versions/node/v7.7.4/bin/node" "/Users/ricklee/.nvm/versions/node/v7.7.4/bin/npm" "run" "babel2" 32 | 19 error node v7.7.4 33 | 20 error npm v4.1.2 34 | 21 error code ELIFECYCLE 35 | 22 error ninjajs@3.4.3 babel2: `node ./example/build.js` 36 | 22 error Exit status 1 37 | 23 error Failed at the ninjajs@3.4.3 babel2 script 'node ./example/build.js'. 38 | 23 error Make sure you have the latest version of node.js and npm installed. 39 | 23 error If you do, this is most likely a problem with the ninjajs package, 40 | 23 error not with npm itself. 41 | 23 error Tell the author that this fails on your system: 42 | 23 error node ./example/build.js 43 | 23 error You can get information on how to open an issue for this project with: 44 | 23 error npm bugs ninjajs 45 | 23 error Or if that isn't available, you can get their info via: 46 | 23 error npm owner ls ninjajs 47 | 23 error There is likely additional logging output above. 48 | 24 verbose exit [ 1, true ] 49 | -------------------------------------------------------------------------------- /src/ninja-core/dom/index.js: -------------------------------------------------------------------------------- 1 | import { NON_DIMENSION_PROPS, NON_BUBBLING_EVENTS } from '../constants'; 2 | import options from '../options'; 3 | import { toLowerCase, isString, isFunction, hashToClassName } from '../util'; 4 | 5 | 6 | 7 | 8 | /** Removes a given DOM Node from its parent. */ 9 | export function removeNode(node) { 10 | let p = node.parentNode; 11 | if (p) p.removeChild(node); 12 | } 13 | 14 | 15 | /** Set a named attribute on the given Node, with special behavior for some names and event handlers. 16 | * If `value` is `null`, the attribute/handler will be removed. 17 | * @param {Element} node An element to mutate 18 | * @param {string} name The name/key to set, such as an event or attribute name 19 | * @param {any} old The last value that was set for this name/node pair 20 | * @param {any} value An attribute value, such as a function to be used as an event handler 21 | * @param {Boolean} isSvg Are we currently diffing inside an svg? 22 | * @private 23 | */ 24 | export function setAccessor(node, name, old, value, isSvg) { 25 | 26 | if (name==='className') name = 'class'; 27 | 28 | if (name==='class' && value && typeof value==='object') { 29 | value = hashToClassName(value); 30 | } 31 | 32 | if (name==='key') { 33 | // ignore 34 | } 35 | else if (name==='class' && !isSvg) { 36 | node.className = value || ''; 37 | } 38 | else if (name==='style') { 39 | if (!value || isString(value) || isString(old)) { 40 | node.style.cssText = value || ''; 41 | } 42 | if (value && typeof value==='object') { 43 | if (!isString(old)) { 44 | for (let i in old) if (!(i in value)) node.style[i] = ''; 45 | } 46 | for (let i in value) { 47 | node.style[i] = typeof value[i]==='number' && !NON_DIMENSION_PROPS[i] ? (value[i]+'px') : value[i]; 48 | } 49 | } 50 | } 51 | else if (name==='dangerouslySetInnerHTML') { 52 | if (value) node.innerHTML = value.__html || ''; 53 | } 54 | else if (name[0]=='o' && name[1]=='n') { 55 | let l = node._listeners || (node._listeners = {}); 56 | name = toLowerCase(name.substring(2)); 57 | // @TODO: this might be worth it later, un-breaks focus/blur bubbling in IE9: 58 | // if (node.attachEvent) name = name=='focus'?'focusin':name=='blur'?'focusout':name; 59 | if (value) { 60 | if (!l[name]) node.addEventListener(name, eventProxy, !!NON_BUBBLING_EVENTS[name]); 61 | } 62 | else if (l[name]) { 63 | node.removeEventListener(name, eventProxy, !!NON_BUBBLING_EVENTS[name]); 64 | } 65 | l[name] = value; 66 | } 67 | else if (name!=='list' && name!=='type' && !isSvg && name in node) { 68 | setProperty(node, name, value==null ? '' : value); 69 | if (value==null || value===false) node.removeAttribute(name); 70 | } 71 | else { 72 | let ns = isSvg && name.match(/^xlink\:?(.+)/); 73 | if (value==null || value===false) { 74 | if (ns) node.removeAttributeNS('http://www.w3.org/1999/xlink', toLowerCase(ns[1])); 75 | else node.removeAttribute(name); 76 | } 77 | else if (typeof value!=='object' && !isFunction(value)) { 78 | if (ns) node.setAttributeNS('http://www.w3.org/1999/xlink', toLowerCase(ns[1]), value); 79 | else node.setAttribute(name, value); 80 | } 81 | } 82 | } 83 | 84 | 85 | /** Attempt to set a DOM property to the given value. 86 | * IE & FF throw for certain property-value combinations. 87 | */ 88 | function setProperty(node, name, value) { 89 | try { 90 | node[name] = value; 91 | } catch (e) { } 92 | } 93 | 94 | 95 | /** Proxy an event to hooked event handlers 96 | * @private 97 | */ 98 | function eventProxy(e) { 99 | return this._listeners[e.type](options.event && options.event(e) || e); 100 | } 101 | -------------------------------------------------------------------------------- /src/ninja-router-redux/hoc.js: -------------------------------------------------------------------------------- 1 | import { getProvider } from '../ninja-redux/components/provider'; 2 | import _ from '../util'; 3 | 4 | const isShow = (views, tag) => { 5 | if(tag.$routePath){ 6 | return views[tag.$routePath] 7 | } 8 | return false; 9 | } 10 | 11 | function hoistStatics(targetComponent, sourceComponent, customStatics) { 12 | const RIOT_STATICS = { 13 | displayName: true, 14 | mixins: true, 15 | type: true 16 | }; 17 | 18 | const KNOWN_STATICS = { 19 | name: true, 20 | length: true, 21 | prototype: true, 22 | caller: true, 23 | arguments: true, 24 | arity: true 25 | }; 26 | 27 | var isGetOwnPropertySymbolsAvailable = typeof Object.getOwnPropertySymbols === 'function'; 28 | 29 | if (typeof sourceComponent !== 'string') { // don't hoist over string (html) components 30 | var keys = Object.getOwnPropertyNames(sourceComponent); 31 | 32 | /* istanbul ignore else */ 33 | if (isGetOwnPropertySymbolsAvailable) { 34 | keys = keys.concat(Object.getOwnPropertySymbols(sourceComponent)); 35 | } 36 | 37 | for (var i = 0; i < keys.length; ++i) { 38 | if (!RIOT_STATICS[keys[i]] && !KNOWN_STATICS[keys[i]] && (!customStatics || !customStatics[keys[i]])) { 39 | try { 40 | targetComponent[keys[i]] = sourceComponent[keys[i]]; 41 | } catch (error) { 42 | console.warn("hoistStatics failed."); 43 | } 44 | } 45 | } 46 | } 47 | 48 | return targetComponent; 49 | } 50 | 51 | const getDisplayName = WrappedComponent => WrappedComponent.displayName || _.lineToCamel(WrappedComponent.name) || 'Component'; 52 | 53 | export default function View(WrappedComponent) { 54 | const connectDisplayName = `View(${getDisplayName(WrappedComponent)})`; 55 | class View extends WrappedComponent { 56 | get name() { 57 | return _.camelToLine(super.name.toLowerCase()); 58 | } 59 | 60 | get tmpl() { 61 | return ` 62 |
${super.tmpl}
63 | ` 64 | } 65 | 66 | onCreate(opts) { 67 | this.displayName = connectDisplayName; 68 | let provider = getProvider(this) 69 | let state = provider.opts.store.getState() 70 | super.onCreate(opts); 71 | if (this.opts.force) { 72 | Object.defineProperties(this.opts, { 73 | $show: { 74 | get: () => { 75 | return true; 76 | } 77 | } 78 | }) 79 | } 80 | this.onInit(); 81 | } 82 | 83 | enter(from, to) { 84 | this.trigger('enter', from); 85 | this.opts.show = true; 86 | this.opts.hidden = false; 87 | } 88 | 89 | getStore() { 90 | let provider = getProvider(this) 91 | return provider.opts.store 92 | } 93 | onInit() { 94 | let store = this.getStore() 95 | let state = store.getState() 96 | store.subscribe(this.onSubscribe.bind(this)) 97 | } 98 | onSubscribe(s) { 99 | let state = this.getStore().getState() 100 | let action = state.lastAction; 101 | if( 102 | View.concerns.some(a => action.type === a) && 103 | // todo 104 | // Object.keys(state.route.$views).length > 0 && 105 | action.payload === this 106 | ){ 107 | this.renderForView(state) 108 | } 109 | 110 | } 111 | 112 | renderForView(state){ 113 | this.opts.$show = isShow(state.route.$views, this) || false 114 | this.update(); 115 | } 116 | } 117 | 118 | View.concerns = ['$enter', '$leave']; 119 | View.displayName = connectDisplayName; 120 | View.WrappedComponent = WrappedComponent; 121 | hoistStatics(View, WrappedComponent) 122 | return View; 123 | } -------------------------------------------------------------------------------- /src/ninja-router/util.js: -------------------------------------------------------------------------------- 1 | export default class Util { 2 | static distinct(arr){ 3 | let res = []; 4 | for(let i=0, len=arr.length; i { 14 | var arr = node.children; 15 | if(!arr){ 16 | return; 17 | } 18 | for (var i=0, len=arr.length; i { 100 | o[k] = vs[index] 101 | }); 102 | return o; 103 | } 104 | 105 | static getParams(fn){ 106 | if(typeof fn != 'function') throw new Error('Failed to get Param on ' + typeof fn); 107 | var argO = fn.toString().match(/\(.*\)/).toString(); 108 | if(argO.length<=2) return null; 109 | var argArr = argO.substr(1, argO.length-2).split(','); 110 | return argArr.map(function(a){ 111 | return a.trim(); 112 | }); 113 | }; 114 | 115 | static extractParams(path){ 116 | return path.match(/_[a-zA-Z0-9:]+/g) 117 | } 118 | 119 | static toPattern(route){ 120 | return route.replace(/_[a-zA-Z0-9:]+/g, "*"); 121 | } 122 | 123 | static nextTick(fn){ 124 | return setTimeout(fn, 0); 125 | } 126 | 127 | } -------------------------------------------------------------------------------- /src/ninja-redux-form/reducer.js: -------------------------------------------------------------------------------- 1 | import _ from '../util'; 2 | 3 | const initForm = {} 4 | 5 | const forms = (forms = initForm , action) => { 6 | let form = null; 7 | let formName = null; 8 | switch (action.type) { 9 | 10 | case 'form/input': 11 | 12 | let {form, input} = action.payload; 13 | formName = form.$name; 14 | forms[formName][input.name] = input.value; 15 | return Object.assign({}, forms, {[form.$name]: form}) 16 | 17 | case 'form/add': 18 | 19 | form = action.payload; 20 | return Object.assign({}, forms, {[form.$name]: form}) 21 | 22 | case 'form/update': 23 | 24 | form = action.payload; 25 | return Object.assign({}, forms, {[form.$name]: form}) 26 | 27 | case 'form/reset': 28 | 29 | form = action.payload; 30 | delete forms[form.$name]; 31 | return forms; 32 | 33 | case 'forms/inputs/add': 34 | 35 | let formsInAction = action.payload; 36 | let formsMap = formsInAction.reduce((acc, curr) => { 37 | let formJson = forms[curr['form']]; 38 | let inputs = curr.inputs; 39 | inputs.forEach(i => { 40 | formJson[i.$name] = i; 41 | }) 42 | return { [curr['form']]: formJson } 43 | }, {}) 44 | return Object.assign({}, forms, { ...formsMap }) 45 | 46 | case 'forms/remove': 47 | 48 | return Object.assign({}, _.omit(forms, action.payload.map(f => f.form))) 49 | 50 | case 'forms/inputs/remove': 51 | 52 | let objArrToRemove = action.payload; 53 | 54 | let formUpdated = objArrToRemove.map(o => { 55 | return Object.keys(forms).filter(formName => formName === o.form).map(formName => ({inputs: o.inputs, form: forms[formName]}))[0] 56 | }).reduce((acc, curr) => { 57 | let key = curr.form.$name 58 | let val = _.omit(curr.form, curr.inputs) 59 | acc[key] = val; 60 | return acc; 61 | }, {}) 62 | 63 | 64 | return Object.assign({}, forms, formUpdated) 65 | 66 | case 'forms/inputs/update': 67 | 68 | let objArrToUpdate = action.payload; 69 | 70 | let inputsMap = objArrToUpdate.inputs.map(input => { 71 | return { 72 | name: input.$name, 73 | val: input 74 | } 75 | }).reduce((acc, curr) => { 76 | acc[curr.name] = curr.val; 77 | return acc; 78 | }, {}) 79 | 80 | let isDirty = inputsMap[Object.keys(inputsMap)[0]].$dirty; 81 | let formMeta = Object.assign({}, ...forms[objArrToUpdate.form], { ...{ 82 | $dirty: isDirty, 83 | $pristine: !isDirty 84 | }, 85 | ...inputsMap }) 86 | 87 | const isInvalid = () => { 88 | let inputs = _.extractField(formMeta); 89 | return Object.keys(inputs).filter(i => { 90 | let errors = inputs[i].$error 91 | return Object.keys(errors).length > 0 92 | }).length > 0 93 | } 94 | 95 | let inputsKeys = Object.keys(inputsMap); 96 | 97 | if (inputsKeys && inputsKeys.length <= 0) { 98 | formMeta.$invalid = false; 99 | formMeta.$valid = true; 100 | } else { 101 | if (isInvalid()) { 102 | formMeta.$invalid = true; 103 | formMeta.$valid = false; 104 | } else { 105 | formMeta.$invalid = false; 106 | formMeta.$valid = true; 107 | } 108 | } 109 | 110 | let o = Object.assign({}, {[objArrToUpdate.form]: {}}, {[objArrToUpdate.form]: {...formMeta}}) 111 | return Object.assign({}, forms, o) 112 | 113 | case 'form/submit': 114 | formName = action.payload; 115 | 116 | return Object.assign({}, forms, { [formName]: {...forms[formName], ...{ $submitted: true }}}) 117 | 118 | case 'form/valid': 119 | formName = action.payload; 120 | 121 | return Object.assign({}, forms, { [formName]: {...forms[formName], ...{ $invalid: false, $valid: true }}}) 122 | 123 | case 'form/unsubmit': 124 | formName = action.payload; 125 | 126 | return Object.assign({}, forms, { [formName]: {...forms[formName], ...{ $submitted: false }}}) 127 | 128 | default: 129 | return forms; 130 | } 131 | } 132 | 133 | export default { forms } -------------------------------------------------------------------------------- /dist/view.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 10 | 11 | var View = function () { 12 | function View() { 13 | _classCallCheck(this, View); 14 | } 15 | 16 | _createClass(View, [{ 17 | key: 'setHandler', 18 | value: function setHandler(callback) { 19 | this.handler = callback; 20 | } 21 | }, { 22 | key: 'enter', 23 | value: function enter(tag, from, callback) { 24 | if (!tag) { 25 | return; 26 | } 27 | tag.trigger('enter', from); 28 | tag.opts.show = true; 29 | tag.opts.hidden = false; 30 | if (this.handler) { 31 | return this.handler('enter', tag); 32 | } 33 | tag.update(); 34 | } 35 | }, { 36 | key: 'leaveUpstream', 37 | value: function leaveUpstream(tag) { 38 | var _this = this; 39 | 40 | if (!tag || !tag.parent || !tag.parent.tags || !tag.parent.tags['router-outlet']) { 41 | return; 42 | } 43 | var routes = tag.parent.tags['router-outlet'].routes; 44 | var siblings = tag.parent.tags['router-outlet'].routes.filter(function (r) { 45 | return r.tag && r.tag != tag; 46 | }); 47 | if (!siblings || !siblings.length) { 48 | return false; 49 | } 50 | siblings.map(function (t) { 51 | return t.tag; 52 | }).forEach(function (t) { 53 | if (t && (t.opts.show || t.opts.$show)) { 54 | _this.leaveDownstream(t, tag); 55 | } 56 | }); 57 | return this.leaveUpstream(tag.parent); 58 | } 59 | }, { 60 | key: 'leaveDownstream', 61 | value: function leaveDownstream(tag, parent) { 62 | var _this2 = this; 63 | 64 | if (!tag) { 65 | return; 66 | } 67 | this.leave(tag, parent); 68 | var outlet = tag.tags['router-outlet']; 69 | if (!outlet) { 70 | return; 71 | } 72 | var routes = outlet.routes; 73 | if (!routes) { 74 | return; 75 | } 76 | routes.map(function (r) { 77 | return r.tag; 78 | }).forEach(function (t) { 79 | if (t && t.opts.$show && !t.cache) { 80 | _this2.leave(t, tag); 81 | return _this2.leaveDownstream(t, tag); 82 | } 83 | }); 84 | } 85 | }, { 86 | key: 'leave', 87 | value: function leave(tag, to, callback) { 88 | if (!tag) { 89 | return; 90 | } 91 | tag.trigger('leave', to); 92 | if (tag.opts.show || tag.opts.$show) { 93 | tag.opts.show = false; 94 | tag.opts.hidden = true; 95 | if (this.handler) { 96 | return this.handler('leave', tag); 97 | } 98 | tag.update(); 99 | } 100 | } 101 | }, { 102 | key: 'init', 103 | value: function init(tag, name) { 104 | tag.opts.hidden = true; 105 | tag.opts.show = false; 106 | } 107 | }]); 108 | 109 | return View; 110 | }(); 111 | 112 | var rendererCreator = function rendererCreator(router) { 113 | return new View(router); 114 | }; 115 | 116 | exports.default = rendererCreator; -------------------------------------------------------------------------------- /src/application.js: -------------------------------------------------------------------------------- 1 | import riot from 'riot' 2 | import { configureStore } from './store' 3 | import ninjaRouterRedux from './ninja-router-redux' 4 | import router from './ninja-router/router' 5 | import { provider, connect } from './ninja-redux' 6 | import formReducer from './ninja-redux-form/reducer' 7 | import _ from './util' 8 | 9 | class Ninja { 10 | /** 11 | * @param container {Object} window in browser, or global in server. 12 | */ 13 | constructor({ container, reducer = {}, middlewares = {}, state = {} }){ 14 | if(!container){ 15 | throw new Error(`a container expected.`); 16 | } 17 | this.framework = riot; 18 | this.container = container; 19 | let finalReducer = { ...reducer, ...formReducer }; 20 | this.reducer = finalReducer; 21 | this.middlewares = middlewares; 22 | this.buildInProps = ['env', 'entry', 'context', 'mode', 'routes']; 23 | this._mode = 'hash'; 24 | this._store = configureStore(state, this.reducer, middlewares, this._mode); 25 | this.router(router); 26 | this._context = { 27 | store: this._store, 28 | hub: router.hub, 29 | tags: {} 30 | }; 31 | riot.util.tmpl.errorHandler = e => {} 32 | this.emitter = riot.observable({}); 33 | container.widgets = this._widgets = {}; 34 | } 35 | 36 | set(prop, val){ 37 | switch(this.accseptSet(prop)){ 38 | 39 | case 'mode': 40 | this[`_${prop}`] = val; 41 | var initialState = {}; 42 | if(this.store){ 43 | initialState = this.store.getState(); 44 | } 45 | this._store = configureStore(initialState, this.reducer, this.middlewares, this._mode); 46 | if(this._router){ 47 | ninjaRouterRedux.syncHistoryWithStore(this._router.hub, this._store); 48 | this.mixin('router', this._router); 49 | } 50 | break; 51 | 52 | case 'context': 53 | _.mixin(this._context, val) 54 | break; 55 | 56 | case 'routes': 57 | if (!this._router || !this._router.hub) { 58 | throw new Error(`ninja compose routes expected a router hub.`) 59 | } 60 | this._router.hub.routes = val 61 | this.router(this._router) 62 | break; 63 | 64 | case 'entry': 65 | this[`_${prop}`] = val; 66 | this.hub.root = val 67 | // set provider for redux. 68 | provider(this.store)(val) 69 | break; 70 | 71 | default: 72 | this[`_${prop}`] = val; 73 | } 74 | } 75 | 76 | accseptSet(val){ 77 | if(this.buildInProps.indexOf(val) >= 0){ 78 | return val; 79 | } 80 | return null; 81 | } 82 | 83 | router(router){ 84 | this._router = router; 85 | router.app = this; 86 | ninjaRouterRedux.syncHistoryWithStore(this._router.hub, this._store); 87 | return this; 88 | } 89 | 90 | registerWidget({name, methods}){ 91 | let components = riot.mount(name); 92 | let component = components[0] 93 | this._context.tags[name] = component; 94 | let upperName = name.replace(/(\w)/, v => v.toUpperCase()); 95 | this._widgets[upperName] = {}; 96 | methods.forEach( method => { 97 | this._widgets[upperName][method] = (...args) => { 98 | component[method].apply(component, args) 99 | } 100 | }) 101 | } 102 | 103 | async start(bootstrap){ 104 | if (!bootstrap || typeof bootstrap != 'function') { 105 | throw new Error(`application start expected a callback`); 106 | } 107 | if (!this._router) { 108 | throw new Error(`application start expected routes`); 109 | } 110 | await bootstrap(); 111 | if (!this.entry) { 112 | throw new Error(`application expected a entry component`); 113 | } 114 | this._router.hub.startup(); 115 | } 116 | 117 | mixin(...args){ 118 | return this.framework.mixin.apply(this.framework, args); 119 | } 120 | 121 | on(...args){ 122 | return this.emitter.on.apply(this.emitter, args) 123 | } 124 | 125 | one(...args){ 126 | return this.emitter.one.apply(this.emitter, args) 127 | } 128 | 129 | off(...args){ 130 | return this.emitter.off.apply(this.emitter, args) 131 | } 132 | 133 | trigger(...args) { 134 | return this.emitter.trigger.apply(this.emitter, args) 135 | } 136 | 137 | get container() { 138 | return this._container; 139 | } 140 | 141 | set container(val) { 142 | this._container = val; 143 | } 144 | 145 | get hub() { 146 | return this._router.hub; 147 | } 148 | get context(){ 149 | return this._context; 150 | } 151 | get store(){ 152 | return this._store; 153 | } 154 | set store(val){ 155 | this._store = val; 156 | } 157 | get mode(){ 158 | return this._mode; 159 | } 160 | get entry(){ 161 | return this._entry; 162 | } 163 | get env(){ 164 | return this._env; 165 | } 166 | } 167 | 168 | const appCreator = params => new Ninja(params) 169 | const uiLib = riot 170 | 171 | appCreator.Component = riot.Tag 172 | 173 | export default appCreator 174 | export { connect, provider, uiLib } -------------------------------------------------------------------------------- /dist/riot-redux-form/reducer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _util = require('../util'); 10 | 11 | var _util2 = _interopRequireDefault(_util); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } 16 | 17 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 18 | 19 | var initForm = {}; 20 | 21 | var forms = function forms() { 22 | var forms = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : initForm; 23 | var action = arguments[1]; 24 | 25 | var form = null; 26 | var formName = null; 27 | switch (action.type) { 28 | 29 | case 'form/input': 30 | var _action$payload = action.payload, 31 | _form = _action$payload.form, 32 | input = _action$payload.input; 33 | 34 | formName = _form.$name; 35 | forms[formName][input.name] = input.value; 36 | return Object.assign({}, forms, _defineProperty({}, _form.$name, _form)); 37 | 38 | case 'form/add': 39 | 40 | _form = action.payload; 41 | return Object.assign({}, forms, _defineProperty({}, _form.$name, _form)); 42 | 43 | case 'form/update': 44 | 45 | _form = action.payload; 46 | return Object.assign({}, forms, _defineProperty({}, _form.$name, _form)); 47 | 48 | case 'form/reset': 49 | 50 | _form = action.payload; 51 | delete forms[_form.$name]; 52 | return forms; 53 | 54 | case 'forms/inputs/add': 55 | 56 | var formsInAction = action.payload; 57 | var formsMap = formsInAction.reduce(function (acc, curr) { 58 | var formJson = forms[curr['form']]; 59 | var inputs = curr.inputs; 60 | inputs.forEach(function (i) { 61 | formJson[i.$name] = i; 62 | }); 63 | return _defineProperty({}, curr['form'], formJson); 64 | }, {}); 65 | return Object.assign({}, forms, _extends({}, formsMap)); 66 | 67 | case 'forms/remove': 68 | 69 | return Object.assign({}, _util2.default.omit(forms, action.payload.map(function (f) { 70 | return f.form; 71 | }))); 72 | 73 | case 'forms/inputs/remove': 74 | 75 | var objArrToRemove = action.payload; 76 | 77 | var formUpdated = objArrToRemove.map(function (o) { 78 | return Object.keys(forms).filter(function (formName) { 79 | return formName === o.form; 80 | }).map(function (formName) { 81 | return { inputs: o.inputs, form: forms[formName] }; 82 | })[0]; 83 | }).reduce(function (acc, curr) { 84 | var key = curr.form.$name; 85 | var val = _util2.default.omit(curr.form, curr.inputs); 86 | acc[key] = val; 87 | return acc; 88 | }, {}); 89 | 90 | return Object.assign({}, forms, formUpdated); 91 | 92 | case 'forms/inputs/update': 93 | 94 | var objArrToUpdate = action.payload; 95 | 96 | var inputsMap = objArrToUpdate.inputs.map(function (input) { 97 | return { 98 | name: input.$name, 99 | val: input 100 | }; 101 | }).reduce(function (acc, curr) { 102 | acc[curr.name] = curr.val; 103 | return acc; 104 | }, {}); 105 | 106 | var isDirty = inputsMap[Object.keys(inputsMap)[0]].$dirty; 107 | var formMeta = Object.assign.apply(Object, [{}].concat(_toConsumableArray(forms[objArrToUpdate.form]), [_extends({ 108 | $dirty: isDirty, 109 | $pristine: !isDirty 110 | }, inputsMap)])); 111 | 112 | var isInvalid = function isInvalid() { 113 | var inputs = _util2.default.extractField(formMeta); 114 | return Object.keys(inputs).filter(function (i) { 115 | var errors = inputs[i].$error; 116 | return Object.keys(errors).length > 0; 117 | }).length > 0; 118 | }; 119 | 120 | var inputsKeys = Object.keys(inputsMap); 121 | 122 | if (inputsKeys && inputsKeys.length <= 0) { 123 | formMeta.$invalid = false; 124 | formMeta.$valid = true; 125 | } else { 126 | if (isInvalid()) { 127 | formMeta.$invalid = true; 128 | formMeta.$valid = false; 129 | } else { 130 | formMeta.$invalid = false; 131 | formMeta.$valid = true; 132 | } 133 | } 134 | 135 | var o = Object.assign({}, _defineProperty({}, objArrToUpdate.form, {}), _defineProperty({}, objArrToUpdate.form, _extends({}, formMeta))); 136 | return Object.assign({}, forms, o); 137 | 138 | case 'form/submit': 139 | formName = action.payload; 140 | 141 | return Object.assign({}, forms, _defineProperty({}, formName, _extends({}, forms[formName], { $submitted: true }))); 142 | 143 | case 'form/valid': 144 | formName = action.payload; 145 | 146 | return Object.assign({}, forms, _defineProperty({}, formName, _extends({}, forms[formName], { $invalid: false, $valid: true }))); 147 | 148 | case 'form/unsubmit': 149 | formName = action.payload; 150 | 151 | return Object.assign({}, forms, _defineProperty({}, formName, _extends({}, forms[formName], { $submitted: false }))); 152 | 153 | default: 154 | return forms; 155 | } 156 | }; 157 | 158 | exports.default = { forms: forms }; -------------------------------------------------------------------------------- /dist/riot-router/util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 8 | 9 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 10 | 11 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 12 | 13 | var Util = function () { 14 | function Util() { 15 | _classCallCheck(this, Util); 16 | } 17 | 18 | _createClass(Util, null, [{ 19 | key: 'distinct', 20 | value: function distinct(arr) { 21 | var res = []; 22 | for (var i = 0, len = arr.length; i < len; i++) { 23 | var o = arr[i]; 24 | if (res.indexOf(o) < 0) { 25 | res.push(o); 26 | } 27 | } 28 | return res; 29 | } 30 | }, { 31 | key: 'genId', 32 | value: function genId(n) { 33 | var chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; 34 | var res = ""; 35 | for (var i = 0; i < n; i++) { 36 | var id = Math.ceil(Math.random() * 35); 37 | res += chars[id]; 38 | } 39 | return res; 40 | } 41 | }, { 42 | key: 'completePart', 43 | value: function completePart(uri) { 44 | return uri.startsWith('/') ? uri : '/' + uri; 45 | } 46 | }, { 47 | key: 'assert', 48 | value: function assert(val, msg) { 49 | if (!val) { 50 | throw new Error(msg); 51 | } 52 | } 53 | }, { 54 | key: 'omit', 55 | value: function omit(o) { 56 | var res = {}; 57 | 58 | for (var _len = arguments.length, params = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 59 | params[_key - 1] = arguments[_key]; 60 | } 61 | 62 | for (var p in o) { 63 | if (params.indexOf(p) < 0) { 64 | res[p] = o[p]; 65 | } 66 | } 67 | return res; 68 | } 69 | }, { 70 | key: 'compareUrl', 71 | value: function compareUrl(u1, u2) { 72 | var r = []; 73 | var arr1 = u1.split('/'); 74 | var arr2 = u2.split('/'); 75 | for (var i = 0, len = arr1.length; i < len; i++) { 76 | if (arr1[i] === arr2[i]) { 77 | r.push(arr1[i]); 78 | } else { 79 | break; 80 | } 81 | } 82 | return r.join('/'); 83 | } 84 | }, { 85 | key: 'isEqual', 86 | value: function isEqual(o1, o2) { 87 | var len = Object.keys(o1).length; 88 | var res = 0; 89 | if (len != Object.keys(o2).length) { 90 | return false; 91 | } 92 | for (var prop in o1) { 93 | if (o1[prop] === o2[prop]) { 94 | res++; 95 | } 96 | } 97 | return res === len; 98 | } 99 | }, { 100 | key: 'combineUriParts', 101 | value: function combineUriParts(parts, i, combined) { 102 | if (!parts.length || i <= 0) { 103 | return combined; 104 | } 105 | var uri = parts[i - 1] + '/' + combined; 106 | return Util.combineUriParts(parts, --i, uri); 107 | } 108 | }, { 109 | key: 'composeObject', 110 | value: function composeObject(ks, vs) { 111 | var o = {}; 112 | if (!Array.isArray(ks) || !Array.isArray(vs) || ks.length != vs.length) { 113 | return o; 114 | } 115 | ks.forEach(function (k, index) { 116 | o[k] = vs[index]; 117 | }); 118 | return o; 119 | } 120 | }, { 121 | key: 'getParams', 122 | value: function getParams(fn) { 123 | if (typeof fn != 'function') throw new Error('Failed to get Param on ' + (typeof fn === 'undefined' ? 'undefined' : _typeof(fn))); 124 | var argO = fn.toString().match(/\(.*\)/).toString(); 125 | if (argO.length <= 2) return null; 126 | var argArr = argO.substr(1, argO.length - 2).split(','); 127 | return argArr.map(function (a) { 128 | return a.trim(); 129 | }); 130 | } 131 | }, { 132 | key: 'extractParams', 133 | value: function extractParams(path) { 134 | return path.match(/_[a-zA-Z0-9:]+/g); 135 | } 136 | }, { 137 | key: 'toPattern', 138 | value: function toPattern(route) { 139 | return route.replace(/_[a-zA-Z0-9:]+/g, "*"); 140 | } 141 | }, { 142 | key: 'nextTick', 143 | value: function nextTick(fn) { 144 | return setTimeout(fn, 0); 145 | } 146 | }]); 147 | 148 | return Util; 149 | }(); 150 | 151 | Util.flatAndComposePrefix = function (node, res) { 152 | var arr = node.children; 153 | if (!arr) { 154 | return; 155 | } 156 | for (var i = 0, len = arr.length; i < len; i++) { 157 | var route = arr[i]; 158 | route.path = (node.path || '') + route.path; 159 | route.parent = node.id || ''; 160 | route.id = Util.genId(8); 161 | res.push(route); 162 | Util.flatAndComposePrefix(route, res); 163 | } 164 | }; 165 | 166 | exports.default = Util; -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | const values = (o) => { 2 | return Object.keys(o).map(k=>o[k]) 3 | } 4 | 5 | const mixin = (...args) => { 6 | return Object.assign(...args) 7 | } 8 | 9 | const intersect = (arr1, arr2) => { 10 | let res = []; 11 | for (let i=0, len=arr1.length; i= 0) { 13 | res.push(arr1[i]) 14 | } 15 | } 16 | return res; 17 | } 18 | 19 | const pick = (o, ...fs) => 20 | Object.keys(o) 21 | .filter(f => fs.indexOf(f) >= 0) 22 | .map(f=>({key: f, val: o[f]})) 23 | .reduce((acc, pair)=>{ 24 | acc[pair.key] = pair.val; 25 | return acc; 26 | }, {}) 27 | 28 | const omit = (o, ...fs) => { 29 | let args = fs; 30 | let p1 = fs[0]; 31 | if (Array.isArray(p1)) { 32 | fs = p1; 33 | } 34 | return Object.keys(o) 35 | .filter(f => fs.indexOf(f) < 0) 36 | .map(f=>({key: f, val: o[f]})) 37 | .reduce((acc, pair) => { 38 | acc[pair.key] = pair.val; 39 | return acc; 40 | }, {}) 41 | } 42 | 43 | const clone = item => { 44 | if (!item) { return item; } // null, undefined values check 45 | 46 | var types = [ Number, String, Boolean ], 47 | result; 48 | 49 | // normalizing primitives if someone did new String('aaa'), or new Number('444'); 50 | types.forEach(function(type) { 51 | if (item instanceof type) { 52 | result = type( item ); 53 | } 54 | }); 55 | 56 | if (typeof result == "undefined") { 57 | if (Object.prototype.toString.call( item ) === "[object Array]") { 58 | result = []; 59 | item.forEach(function(child, index, array) { 60 | result[index] = clone( child ); 61 | }); 62 | } else if (typeof item == "object") { 63 | // testing that this is DOM 64 | if (item.nodeType && typeof item.cloneNode == "function") { 65 | var result = item.cloneNode( true ); 66 | } else if (!item.prototype) { // check that this is a literal 67 | if (item instanceof Date) { 68 | result = new Date(item); 69 | } else { 70 | // it is an object literal 71 | result = {}; 72 | for (var i in item) { 73 | result[i] = clone( item[i] ); 74 | } 75 | } 76 | } else { 77 | // depending what you would like here, 78 | // just keep the reference, or create new object 79 | if (false && item.constructor) { 80 | // would not advice to do that, reason? Read below 81 | result = new item.constructor(); 82 | } else { 83 | result = item; 84 | } 85 | } 86 | } else { 87 | result = item; 88 | } 89 | } 90 | return result; 91 | } 92 | 93 | const querystring = { 94 | stringify: json => Object.keys(json).map(k=>k+'='+json[k]).join('&'), 95 | parse: str => str.split('&').reduce((acc, curr)=>{ 96 | let parts = curr.split('='); 97 | acc[parts[0]] = parts[1]; 98 | return acc; 99 | }, {}) 100 | } 101 | 102 | let timer = null; 103 | const throttle = (fn, wait) => { 104 | clearTimeout(timer); 105 | timer = setTimeout(()=>{ 106 | fn.apply(context); 107 | timer = null; 108 | }, wait) 109 | } 110 | 111 | const deepEqual = (x, y) => { 112 | return (x && y && typeof x === 'object' && typeof y === 'object') ? 113 | (Object.keys(x).length === Object.keys(y).length) && 114 | Object.keys(x).reduce(function(isEqual, key) { 115 | return isEqual && deepEqual(x[key], y[key]); 116 | }, true) : (x === y); 117 | } 118 | 119 | const lineToCamel = str => { 120 | if (!str || typeof str !== 'string') { 121 | throw new Error(`[util-line to camel]: expected a string`); 122 | } 123 | return str.split('-').reduce((acc, curr) => { 124 | let first = curr.slice(0, 1); 125 | let last = curr.slice(1); 126 | return `${acc}${first.toUpperCase()}${last}` 127 | }, ''); 128 | } 129 | 130 | const camelToLine = str => { 131 | let res = ""; 132 | for (let i=0, len=str.length; i 0 && 139 | action.payload === this) { 140 | this.renderForView(state); 141 | } 142 | } 143 | }, { 144 | key: 'renderForView', 145 | value: function renderForView(state) { 146 | this.opts.$show = isShow(state.route.$views, this) || false; 147 | this.update(); 148 | } 149 | }, { 150 | key: 'name', 151 | get: function get() { 152 | return _util2.default.camelToLine(_get(View.prototype.__proto__ || Object.getPrototypeOf(View.prototype), 'name', this)); 153 | } 154 | }, { 155 | key: 'tmpl', 156 | get: function get() { 157 | return '\n\t\t\t\t\t
' + _get(View.prototype.__proto__ || Object.getPrototypeOf(View.prototype), 'tmpl', this) + '
\n\t\t\t\t'; 158 | } 159 | }]); 160 | 161 | return View; 162 | }(WrappedComponent); 163 | 164 | View.concerns = ['$enter', '$leave']; 165 | View.displayName = connectDisplayName; 166 | View.WrappedComponent = WrappedComponent; 167 | hoistStatics(View, WrappedComponent); 168 | return View; 169 | } -------------------------------------------------------------------------------- /dist/util.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; 8 | 9 | var values = function values(o) { 10 | return Object.keys(o).map(function (k) { 11 | return o[k]; 12 | }); 13 | }; 14 | 15 | var mixin = function mixin() { 16 | return Object.assign.apply(Object, arguments); 17 | }; 18 | 19 | var intersect = function intersect(arr1, arr2) { 20 | var res = []; 21 | for (var i = 0, len = arr1.length; i < len; i++) { 22 | if (arr2.indexOf(arr1[i]) >= 0) { 23 | res.push(arr1[i]); 24 | } 25 | } 26 | return res; 27 | }; 28 | 29 | var pick = function pick(o) { 30 | for (var _len = arguments.length, fs = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 31 | fs[_key - 1] = arguments[_key]; 32 | } 33 | 34 | return Object.keys(o).filter(function (f) { 35 | return fs.indexOf(f) >= 0; 36 | }).map(function (f) { 37 | return { key: f, val: o[f] }; 38 | }).reduce(function (acc, pair) { 39 | acc[pair.key] = pair.val; 40 | return acc; 41 | }, {}); 42 | }; 43 | 44 | var omit = function omit(o) { 45 | for (var _len2 = arguments.length, fs = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { 46 | fs[_key2 - 1] = arguments[_key2]; 47 | } 48 | 49 | var args = fs; 50 | var p1 = fs[0]; 51 | if (Array.isArray(p1)) { 52 | fs = p1; 53 | } 54 | return Object.keys(o).filter(function (f) { 55 | return fs.indexOf(f) < 0; 56 | }).map(function (f) { 57 | return { key: f, val: o[f] }; 58 | }).reduce(function (acc, pair) { 59 | acc[pair.key] = pair.val; 60 | return acc; 61 | }, {}); 62 | }; 63 | 64 | var clone = function clone(item) { 65 | if (!item) { 66 | return item; 67 | } // null, undefined values check 68 | 69 | var types = [Number, String, Boolean], 70 | result; 71 | 72 | // normalizing primitives if someone did new String('aaa'), or new Number('444'); 73 | types.forEach(function (type) { 74 | if (item instanceof type) { 75 | result = type(item); 76 | } 77 | }); 78 | 79 | if (typeof result == "undefined") { 80 | if (Object.prototype.toString.call(item) === "[object Array]") { 81 | result = []; 82 | item.forEach(function (child, index, array) { 83 | result[index] = clone(child); 84 | }); 85 | } else if ((typeof item === "undefined" ? "undefined" : _typeof(item)) == "object") { 86 | // testing that this is DOM 87 | if (item.nodeType && typeof item.cloneNode == "function") { 88 | var result = item.cloneNode(true); 89 | } else if (!item.prototype) { 90 | // check that this is a literal 91 | if (item instanceof Date) { 92 | result = new Date(item); 93 | } else { 94 | // it is an object literal 95 | result = {}; 96 | for (var i in item) { 97 | result[i] = clone(item[i]); 98 | } 99 | } 100 | } else { 101 | // depending what you would like here, 102 | // just keep the reference, or create new object 103 | if (false && item.constructor) { 104 | // would not advice to do that, reason? Read below 105 | result = new item.constructor(); 106 | } else { 107 | result = item; 108 | } 109 | } 110 | } else { 111 | result = item; 112 | } 113 | } 114 | return result; 115 | }; 116 | 117 | var querystring = { 118 | stringify: function stringify(json) { 119 | return Object.keys(json).map(function (k) { 120 | return k + '=' + json[k]; 121 | }).join('&'); 122 | }, 123 | parse: function parse(str) { 124 | return str.split('&').reduce(function (acc, curr) { 125 | var parts = curr.split('='); 126 | acc[parts[0]] = parts[1]; 127 | return acc; 128 | }, {}); 129 | } 130 | }; 131 | 132 | var timer = null; 133 | var throttle = function throttle(fn, wait) { 134 | clearTimeout(timer); 135 | timer = setTimeout(function () { 136 | fn.apply(context); 137 | timer = null; 138 | }, wait); 139 | }; 140 | 141 | var deepEqual = function deepEqual(x, y) { 142 | return x && y && (typeof x === "undefined" ? "undefined" : _typeof(x)) === 'object' && (typeof y === "undefined" ? "undefined" : _typeof(y)) === 'object' ? Object.keys(x).length === Object.keys(y).length && Object.keys(x).reduce(function (isEqual, key) { 143 | return isEqual && deepEqual(x[key], y[key]); 144 | }, true) : x === y; 145 | }; 146 | 147 | var lineToCamel = function lineToCamel(str) { 148 | if (!str || typeof str !== 'string') { 149 | throw new Error("[util-line to camel]: expected a string"); 150 | } 151 | return str.split('-').reduce(function (acc, curr) { 152 | var first = curr.slice(0, 1); 153 | var last = curr.slice(1); 154 | return "" + acc + first.toUpperCase() + last; 155 | }, ''); 156 | }; 157 | 158 | var camelToLine = function camelToLine(str) { 159 | var res = ""; 160 | for (var i = 0, len = str.length; i < len; i++) { 161 | var c = str.charAt(i); 162 | if (/^[A-Z]+$/.test(c)) { 163 | if (i === 0) { 164 | res += "" + c.toLowerCase(); 165 | } else { 166 | res += "-" + c.toLowerCase(); 167 | } 168 | } else { 169 | res += c; 170 | } 171 | } 172 | return res; 173 | }; 174 | 175 | function extractField(o) { 176 | return exclude(o, "$name", "$dirty", "$pristine", "$valid", "$invalid", "$submitted", "$error", "$ok", "$allPristine", "$allDirty", "$validate", "$meta"); 177 | } 178 | 179 | function exclude() { 180 | var args = [].slice.apply(arguments); 181 | var o = args[0]; 182 | var props = args.slice(1); 183 | var res = {}; 184 | for (var p in o) { 185 | if (props.indexOf(p) < 0) { 186 | res[p] = o[p]; 187 | } 188 | } 189 | return res; 190 | } 191 | 192 | function hasClass(el, className) { 193 | if (el.classList) return el.classList.contains(className);else return !!el.className.match(new RegExp('(\\s|^)' + className + '(\\s|$)')); 194 | } 195 | 196 | function addClass(el, className) { 197 | if (el.classList) el.classList.add(className);else if (!hasClass(el, className)) el.className += " " + className; 198 | } 199 | 200 | function removeClass(el, className) { 201 | if (el.classList) el.classList.remove(className);else if (hasClass(el, className)) { 202 | var reg = new RegExp('(\\s|^)' + className + '(\\s|$)'); 203 | el.className = el.className.replace(reg, ' '); 204 | } 205 | } 206 | 207 | exports.default = { 208 | values: values, 209 | mixin: mixin, 210 | querystring: querystring, 211 | throttle: throttle, 212 | clone: clone, 213 | deepEqual: deepEqual, 214 | omit: omit, 215 | pick: pick, 216 | camelToLine: camelToLine, 217 | lineToCamel: lineToCamel, 218 | exclude: exclude, 219 | extractField: extractField, 220 | hasClass: hasClass, 221 | addClass: addClass, 222 | removeClass: removeClass, 223 | intersect: intersect 224 | }; -------------------------------------------------------------------------------- /src/ninja-core/vdom/component.js: -------------------------------------------------------------------------------- 1 | import { getNodeProps } from './index' 2 | import { createComponent } from './component-recycler' 3 | import { isFunction } from '../util' 4 | import { SYNC_RENDER, NO_RENDER, FORCE_RENDER, ASYNC_RENDER, ATTR_KEY } from '../constants'; 5 | import { diff, diffLevel, mounts } from './diff' 6 | import { isSameNodeType, isNamedNode } from './index'; 7 | 8 | /** Render a Component, triggering necessary lifecycle events and taking High-Order Components into account. 9 | * @param {Component} component 10 | * @param {Object} [opts] 11 | * @param {boolean} [opts.build=false] If `true`, component will build and store a DOM node if not already associated with one. 12 | * @private 13 | */ 14 | export function renderComponent(component, opts, mountAll, isChild) { 15 | if (component._disable) return; 16 | let skip, rendered, 17 | props = component.props, 18 | state = component.state, 19 | context = component.context, 20 | previousProps = component.prevProps || props, 21 | previousState = component.prevState || state, 22 | previousContext = component.prevContext || context, 23 | isUpdate = component.base, 24 | nextBase = component.nextBase, 25 | initialBase = isUpdate || nextBase, 26 | initialChildComponent = component._component, 27 | inst, cbase; 28 | 29 | // if updating 30 | if (isUpdate) { 31 | component.props = previousProps; 32 | component.state = previousState; 33 | component.context = previousContext; 34 | if (opts!==FORCE_RENDER 35 | && component.shouldComponentUpdate 36 | && component.shouldComponentUpdate(props, state, context) === false) { 37 | skip = true; 38 | } 39 | else if (component.componentWillUpdate) { 40 | component.componentWillUpdate(props, state, context); 41 | } 42 | component.props = props; 43 | component.state = state; 44 | component.context = context; 45 | } 46 | 47 | component.prevProps = component.prevState = component.prevContext = component.nextBase = null; 48 | component._dirty = false; 49 | 50 | if (!skip) { 51 | if (component.render) rendered = component.render(props, state, context); 52 | 53 | // context to pass to the child, can be updated via (grand-)parent component 54 | if (component.getChildContext) { 55 | context = extend(Object.assign({}, context), component.getChildContext()); 56 | } 57 | 58 | let childComponent = rendered && rendered.nodeName, 59 | toUnmount, base; 60 | 61 | if (isFunction(childComponent)) { 62 | // set up high order component link 63 | 64 | let childProps = getNodeProps(rendered); 65 | inst = initialChildComponent; 66 | 67 | if (inst && inst.constructor===childComponent && childProps.key==inst.__key) { 68 | setComponentProps(inst, childProps, SYNC_RENDER, context); 69 | } 70 | else { 71 | toUnmount = inst; 72 | 73 | inst = createComponent(childComponent, childProps, context); 74 | inst.nextBase = inst.nextBase || nextBase; 75 | inst._parentComponent = component; 76 | component._component = inst; 77 | setComponentProps(inst, childProps, NO_RENDER, context); 78 | renderComponent(inst, SYNC_RENDER, mountAll, true); 79 | } 80 | 81 | base = inst.base; 82 | } 83 | else { 84 | cbase = initialBase; 85 | 86 | // destroy high order component link 87 | toUnmount = initialChildComponent; 88 | if (toUnmount) { 89 | cbase = component._component = null; 90 | } 91 | 92 | if (initialBase || opts===SYNC_RENDER) { 93 | if (cbase) cbase._component = null; 94 | base = diff(cbase, rendered, context, mountAll || !isUpdate, initialBase && initialBase.parentNode, true); 95 | } 96 | } 97 | 98 | if (initialBase && base!==initialBase && inst!==initialChildComponent) { 99 | let baseParent = initialBase.parentNode; 100 | if (baseParent && base!==baseParent) { 101 | baseParent.replaceChild(base, initialBase); 102 | 103 | if (!toUnmount) { 104 | initialBase._component = null; 105 | recollectNodeTree(initialBase); 106 | } 107 | } 108 | } 109 | 110 | if (toUnmount) { 111 | unmountComponent(toUnmount, base!==initialBase); 112 | } 113 | 114 | component.base = base; 115 | if (base && !isChild) { 116 | let componentRef = component, 117 | t = component; 118 | while ((t=t._parentComponent)) { 119 | (componentRef = t).base = base; 120 | } 121 | base._component = componentRef; 122 | base._componentConstructor = componentRef.constructor; 123 | } 124 | } 125 | 126 | if (!isUpdate || mountAll) { 127 | mounts.unshift(component); 128 | } 129 | else if (!skip) { 130 | if (component.componentDidUpdate) { 131 | component.componentDidUpdate(previousProps, previousState, previousContext); 132 | } 133 | // if (options.afterUpdate) options.afterUpdate(component); 134 | } 135 | 136 | let cb = component._renderCallbacks, fn; 137 | if (cb) while ( (fn = cb.pop()) ) fn.call(component); 138 | 139 | // if (!diffLevel && !isChild) flushMounts(); 140 | } 141 | 142 | /** Set a component's `props` (generally derived from JSX attributes). 143 | * @param {Object} props 144 | * @param {Object} [opts] 145 | * @param {boolean} [opts.renderSync=false] If `true` and {@link options.syncComponentUpdates} is `true`, triggers synchronous rendering. 146 | * @param {boolean} [opts.render=true] If `false`, no render will be triggered. 147 | */ 148 | export function setComponentProps(component, props, opts, context, mountAll) { 149 | if (component._disable) return; 150 | component._disable = true; 151 | 152 | if ((component.__ref = props.ref)) delete props.ref; 153 | if ((component.__key = props.key)) delete props.key; 154 | 155 | if (!component.base || mountAll) { 156 | if (component.componentWillMount) component.componentWillMount(); 157 | } 158 | else if (component.componentWillReceiveProps) { 159 | component.componentWillReceiveProps(props, context); 160 | } 161 | 162 | if (context && context!==component.context) { 163 | if (!component.prevContext) component.prevContext = component.context; 164 | component.context = context; 165 | } 166 | 167 | if (!component.prevProps) component.prevProps = component.props; 168 | component.props = props; 169 | 170 | component._disable = false; 171 | 172 | if (opts!==NO_RENDER) { 173 | if (opts===SYNC_RENDER || options.syncComponentUpdates!==false || !component.base) { 174 | renderComponent(component, SYNC_RENDER, mountAll); 175 | } 176 | else { 177 | enqueueRender(component); 178 | } 179 | } 180 | 181 | if (component.__ref) component.__ref(component); 182 | } 183 | 184 | /** Apply the Component referenced by a VNode to the DOM. 185 | * @param {Element} dom The DOM node to mutate 186 | * @param {VNode} vnode A Component-referencing VNode 187 | * @returns {Element} dom The created/mutated element 188 | * @private 189 | */ 190 | export function buildComponentFromVNode(dom, vnode, context, mountAll) { 191 | let c = dom && dom._component, 192 | originalComponent = c, 193 | oldDom = dom, 194 | isDirectOwner = c && dom._componentConstructor===vnode.nodeName, 195 | isOwner = isDirectOwner, 196 | props = getNodeProps(vnode); 197 | 198 | while (c && !isOwner && (c=c._parentComponent)) { 199 | isOwner = c.constructor===vnode.nodeName; 200 | } 201 | 202 | if (c && isOwner && (!mountAll || c._component)) { 203 | setComponentProps(c, props, ASYNC_RENDER, context, mountAll); 204 | dom = c.base; 205 | } 206 | else { 207 | if (originalComponent && !isDirectOwner) { 208 | unmountComponent(originalComponent, true); 209 | dom = oldDom = null; 210 | } 211 | 212 | c = createComponent(vnode.nodeName, props, context); 213 | if (dom && !c.nextBase) { 214 | c.nextBase = dom; 215 | // passing dom/oldDom as nextBase will recycle it if unused, so bypass recycling on L241: 216 | oldDom = null; 217 | } 218 | setComponentProps(c, props, SYNC_RENDER, context, mountAll); 219 | dom = c.base; 220 | 221 | if (oldDom && dom!==oldDom) { 222 | oldDom._component = null; 223 | recollectNodeTree(oldDom); 224 | } 225 | } 226 | 227 | return dom; 228 | } -------------------------------------------------------------------------------- /src/ninja-core/vdom/diff.js: -------------------------------------------------------------------------------- 1 | import { isFunction, isString } from '../util' 2 | import { buildComponentFromVNode } from './component' 3 | import { createNode } from '../dom/recycler' 4 | import { isNamedNode, isSameNodeType } from './index' 5 | import { ATTR_KEY } from '../constants'; 6 | 7 | /** Queue of components that have been mounted and are awaiting componentDidMount */ 8 | export const mounts = []; 9 | 10 | /** Diff recursion count, used to track the end of the diff cycle. */ 11 | export let diffLevel = 0; 12 | 13 | /** Global flag indicating if the diff is currently within an SVG */ 14 | let isSvgMode = false; 15 | 16 | /** Global flag indicating if the diff is performing hydration */ 17 | let hydrating = false; 18 | 19 | export function diff(dom, vnode, context, mountAll, parent, componentRoot) { 20 | // diffLevel having been 0 here indicates initial entry into the diff (not a subdiff) 21 | if (!diffLevel++) { 22 | // when first starting the diff, check if we're diffing an SVG or within an SVG 23 | isSvgMode = parent && typeof parent.ownerSVGElement!=='undefined'; 24 | 25 | // hydration is inidicated by the existing element to be diffed not having a prop cache 26 | hydrating = dom && !(ATTR_KEY in dom); 27 | } 28 | 29 | let ret = idiff(dom, vnode, context, mountAll); 30 | 31 | // append the element if its a new parent 32 | if (parent && ret.parentNode!==parent) parent.appendChild(ret); 33 | 34 | // diffLevel being reduced to 0 means we're exiting the diff 35 | if (!--diffLevel) { 36 | hydrating = false; 37 | // invoke queued componentDidMount lifecycle methods 38 | // if (!componentRoot) flushMounts(); 39 | } 40 | 41 | return ret; 42 | } 43 | 44 | function idiff(dom, vnode, context, mountAll) { 45 | let ref = vnode && vnode.attributes && vnode.attributes.ref; 46 | 47 | // empty values (null & undefined) render as empty Text nodes 48 | if (vnode==null) vnode = ''; 49 | 50 | // Fast case: Strings create/update Text nodes. 51 | if (isString(vnode)) { 52 | // update if it's already a Text node 53 | if (dom && dom instanceof Text && dom.parentNode) { 54 | if (dom.nodeValue!=vnode) { 55 | dom.nodeValue = vnode; 56 | } 57 | } 58 | else { 59 | // it wasn't a Text node: replace it with one and recycle the old Element 60 | if (dom) recollectNodeTree(dom); 61 | dom = document.createTextNode(vnode); 62 | } 63 | 64 | return dom; 65 | } 66 | 67 | // If the VNode represents a Component, perform a component diff. 68 | if (isFunction(vnode.nodeName)) { 69 | return buildComponentFromVNode(dom, vnode, context, mountAll); 70 | } 71 | 72 | let out = dom, 73 | nodeName = String(vnode.nodeName), // @TODO this masks undefined component errors as `` 74 | prevSvgMode = isSvgMode, 75 | vchildren = vnode.children; 76 | 77 | // SVGs have special namespace stuff. 78 | // This tracks entering and exiting that namespace when descending through the tree. 79 | isSvgMode = nodeName==='svg' ? true : nodeName==='foreignObject' ? false : isSvgMode; 80 | 81 | if (!dom) { 82 | // case: we had no element to begin with 83 | // - create an element with the nodeName from VNode 84 | out = createNode(nodeName, isSvgMode); 85 | } 86 | 87 | else if (!isNamedNode(dom, nodeName)) { 88 | // case: Element and VNode had different nodeNames 89 | // - need to create the correct Element to match VNode 90 | // - then migrate children from old to new 91 | 92 | out = createNode(nodeName, isSvgMode); 93 | 94 | // move children into the replacement node 95 | while (dom.firstChild) out.appendChild(dom.firstChild); 96 | 97 | // if the previous Element was mounted into the DOM, replace it inline 98 | if (dom.parentNode) dom.parentNode.replaceChild(out, dom); 99 | 100 | // recycle the old element (skips non-Element node types) 101 | recollectNodeTree(dom); 102 | } 103 | let fc = out.firstChild, 104 | props = out[ATTR_KEY]; 105 | 106 | // Attribute Hydration: if there is no prop cache on the element, 107 | // ...create it and populate it with the element's attributes. 108 | if (!props) { 109 | out[ATTR_KEY] = props = {}; 110 | for (let a=out.attributes, i=a.length; i--; ) props[a[i].name] = a[i].value; 111 | } 112 | 113 | // Optimization: fast-path for elements containing a single TextNode: 114 | if (!hydrating && vchildren && vchildren.length===1 && typeof vchildren[0]==='string' && fc && fc instanceof Text && !fc.nextSibling) { 115 | if (fc.nodeValue!=vchildren[0]) { 116 | fc.nodeValue = vchildren[0]; 117 | } 118 | } 119 | 120 | // otherwise, if there are existing or new children, diff them: 121 | else if (vchildren && vchildren.length || fc) { 122 | innerDiffNode(out, vchildren, context, mountAll, !!props.dangerouslySetInnerHTML); 123 | } 124 | 125 | // Apply attributes/props from VNode to the DOM Element: 126 | diffAttributes(out, vnode.attributes, props); 127 | 128 | 129 | // invoke original ref (from before resolving Pure Functional Components): 130 | if (ref) { 131 | (props.ref = ref)(out); 132 | } 133 | 134 | isSvgMode = prevSvgMode; 135 | 136 | return out; 137 | } 138 | 139 | /** Apply child and attribute changes between a VNode and a DOM Node to the DOM. 140 | * @param {Element} dom Element whose children should be compared & mutated 141 | * @param {Array} vchildren Array of VNodes to compare to `dom.childNodes` 142 | * @param {Object} context Implicitly descendant context object (from most recent `getChildContext()`) 143 | * @param {Boolean} mountAll 144 | * @param {Boolean} absorb If `true`, consumes externally created elements similar to hydration 145 | */ 146 | function innerDiffNode(dom, vchildren, context, mountAll, absorb) { 147 | let originalChildren = dom.childNodes, 148 | children = [], 149 | keyed = {}, 150 | keyedLen = 0, 151 | min = 0, 152 | len = originalChildren.length, 153 | childrenLen = 0, 154 | vlen = vchildren && vchildren.length, 155 | j, c, vchild, child; 156 | 157 | if (len) { 158 | for (let i=0; i=len) { 210 | dom.appendChild(child); 211 | } 212 | else if (child!==originalChildren[i]) { 213 | if (child===originalChildren[i+1]) { 214 | removeNode(originalChildren[i]); 215 | } 216 | dom.insertBefore(child, originalChildren[i] || null); 217 | } 218 | } 219 | } 220 | } 221 | 222 | 223 | if (keyedLen) { 224 | for (let i in keyed) if (keyed[i]) recollectNodeTree(keyed[i]); 225 | } 226 | 227 | // remove orphaned children 228 | while (min<=childrenLen) { 229 | child = children[childrenLen--]; 230 | if (child) recollectNodeTree(child); 231 | } 232 | } 233 | 234 | /** Recursively recycle (or just unmount) a node an its descendants. 235 | * @param {Node} node DOM node to start unmount/removal from 236 | * @param {Boolean} [unmountOnly=false] If `true`, only triggers unmount lifecycle, skips removal 237 | */ 238 | export function recollectNodeTree(node, unmountOnly) { 239 | let component = node._component; 240 | if (component) { 241 | // if node is owned by a Component, unmount that component (ends up recursing back here) 242 | unmountComponent(component, !unmountOnly); 243 | } 244 | else { 245 | // If the node's VNode had a ref function, invoke it with null here. 246 | // (this is part of the React spec, and smart for unsetting references) 247 | if (node[ATTR_KEY] && node[ATTR_KEY].ref) node[ATTR_KEY].ref(null); 248 | 249 | if (!unmountOnly) { 250 | collectNode(node); 251 | } 252 | 253 | // Recollect/unmount all children. 254 | // - we use .lastChild here because it causes less reflow than .firstChild 255 | // - it's also cheaper than accessing the .childNodes Live NodeList 256 | let c; 257 | while ((c=node.lastChild)) recollectNodeTree(c, unmountOnly); 258 | } 259 | 260 | } 261 | 262 | /** Apply differences in attributes from a VNode to the given DOM Element. 263 | * @param {Element} dom Element with attributes to diff `attrs` against 264 | * @param {Object} attrs The desired end-state key-value attribute pairs 265 | * @param {Object} old Current/previous attributes (from previous VNode or element's prop cache) 266 | */ 267 | function diffAttributes(dom, attrs, old) { 268 | // remove attributes no longer present on the vnode by setting them to undefined 269 | let name; 270 | for (name in old) { 271 | if (!(attrs && name in attrs) && old[name]!=null) { 272 | setAccessor(dom, name, old[name], old[name] = undefined, isSvgMode); 273 | } 274 | } 275 | 276 | // add new & update changed attributes 277 | if (attrs) { 278 | for (name in attrs) { 279 | if ( 280 | name!=='children' 281 | && name!=='innerHTML' 282 | && (!(name in old) || attrs[name]!==(name==='value' || name==='checked' ? dom[name] : old[name])) 283 | ) { 284 | setAccessor(dom, name, old[name], old[name] = attrs[name], isSvgMode); 285 | } 286 | } 287 | } 288 | } -------------------------------------------------------------------------------- /dist/application.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.provider = exports.connect = undefined; 7 | 8 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 9 | 10 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 11 | 12 | var _riot = require('riot'); 13 | 14 | var _riot2 = _interopRequireDefault(_riot); 15 | 16 | var _store = require('./store'); 17 | 18 | var _riotRouterRedux = require('./riot-router-redux'); 19 | 20 | var _riotRouterRedux2 = _interopRequireDefault(_riotRouterRedux); 21 | 22 | var _router2 = require('./riot-router/router'); 23 | 24 | var _router3 = _interopRequireDefault(_router2); 25 | 26 | var _riotRedux = require('./riot-redux'); 27 | 28 | var _reducer = require('./riot-redux-form/reducer'); 29 | 30 | var _reducer2 = _interopRequireDefault(_reducer); 31 | 32 | var _util = require('./util'); 33 | 34 | var _util2 = _interopRequireDefault(_util); 35 | 36 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 37 | 38 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } 39 | 40 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 41 | 42 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 43 | 44 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 45 | 46 | var Component = function (_riot$Tag) { 47 | _inherits(Component, _riot$Tag); 48 | 49 | function Component() { 50 | _classCallCheck(this, Component); 51 | 52 | var _this = _possibleConstructorReturn(this, (Component.__proto__ || Object.getPrototypeOf(Component)).call(this)); 53 | 54 | _this.constructor.originName = _this.name; 55 | return _this; 56 | } 57 | 58 | return Component; 59 | }(_riot2.default.Tag); 60 | 61 | var Ninjia = function () { 62 | /** 63 | * @param container {Object} window in browser, or global in server. 64 | */ 65 | function Ninjia(_ref) { 66 | var container = _ref.container, 67 | reducer = _ref.reducer, 68 | middlewares = _ref.middlewares, 69 | _ref$state = _ref.state, 70 | state = _ref$state === undefined ? {} : _ref$state; 71 | 72 | _classCallCheck(this, Ninjia); 73 | 74 | if (!container) { 75 | throw new Error('a container expected.'); 76 | } 77 | this.framework = _riot2.default; 78 | this.container = container; 79 | var finalReducer = _extends({}, reducer, _reducer2.default); 80 | this.reducer = finalReducer; 81 | this.middlewares = middlewares; 82 | this.buildInProps = ['env', 'entry', 'context', 'mode', 'routes']; 83 | this._mode = 'hash'; 84 | this._store = (0, _store.configureStore)(state, this.reducer, middlewares, this._mode); 85 | this.router(_router3.default); 86 | this._context = { 87 | store: this._store, 88 | hub: _router3.default.hub, 89 | tags: {} 90 | }; 91 | _riot2.default.util.tmpl.errorHandler = function (e) {}; 92 | this.emitter = _riot2.default.observable({}); 93 | container.widgets = this._widgets = {}; 94 | } 95 | 96 | _createClass(Ninjia, [{ 97 | key: 'set', 98 | value: function set(prop, val) { 99 | switch (this.accseptSet(prop)) { 100 | 101 | case 'mode': 102 | this['_' + prop] = val; 103 | var initialState = {}; 104 | if (this.store) { 105 | initialState = this.store.getState(); 106 | } 107 | this._store = (0, _store.configureStore)(initialState, this.reducer, this.middlewares, this._mode); 108 | if (this._router) { 109 | _riotRouterRedux2.default.syncHistoryWithStore(this._router.hub, this._store); 110 | this.mixin('router', this._router); 111 | } 112 | break; 113 | 114 | case 'context': 115 | _util2.default.mixin(this._context, val); 116 | break; 117 | 118 | case 'routes': 119 | if (!this._router || !this._router.hub) { 120 | throw new Error('ninjia compose routes expected a router hub.'); 121 | } 122 | this._router.hub.routes = val; 123 | this.router(this._router); 124 | break; 125 | 126 | case 'entry': 127 | this['_' + prop] = val; 128 | this.hub.root = val; 129 | // set provider for redux. 130 | (0, _riotRedux.provider)(this.store)(val); 131 | break; 132 | 133 | default: 134 | this['_' + prop] = val; 135 | } 136 | } 137 | }, { 138 | key: 'accseptSet', 139 | value: function accseptSet(val) { 140 | if (this.buildInProps.indexOf(val) >= 0) { 141 | return val; 142 | } 143 | return null; 144 | } 145 | }, { 146 | key: 'router', 147 | value: function router(_router) { 148 | this._router = _router; 149 | _router.app = this; 150 | _riotRouterRedux2.default.syncHistoryWithStore(this._router.hub, this._store); 151 | return this; 152 | } 153 | }, { 154 | key: 'registerWidget', 155 | value: function registerWidget(_ref2) { 156 | var _this2 = this; 157 | 158 | var name = _ref2.name, 159 | methods = _ref2.methods; 160 | 161 | var components = _riot2.default.mount(name); 162 | var component = components[0]; 163 | this._context.tags[name] = component; 164 | var upperName = name.replace(/(\w)/, function (v) { 165 | return v.toUpperCase(); 166 | }); 167 | this._widgets[upperName] = {}; 168 | methods.forEach(function (method) { 169 | _this2._widgets[upperName][method] = function () { 170 | for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { 171 | args[_key] = arguments[_key]; 172 | } 173 | 174 | component[method].apply(component, args); 175 | }; 176 | }); 177 | } 178 | }, { 179 | key: 'start', 180 | value: function () { 181 | var _ref3 = _asyncToGenerator(regeneratorRuntime.mark(function _callee(bootstrap) { 182 | return regeneratorRuntime.wrap(function _callee$(_context) { 183 | while (1) { 184 | switch (_context.prev = _context.next) { 185 | case 0: 186 | _context.next = 2; 187 | return bootstrap(); 188 | 189 | case 2: 190 | if (this.entry) { 191 | _context.next = 4; 192 | break; 193 | } 194 | 195 | throw new Error('application expected a entry component'); 196 | 197 | case 4: 198 | this._router.hub.startup(); 199 | 200 | case 5: 201 | case 'end': 202 | return _context.stop(); 203 | } 204 | } 205 | }, _callee, this); 206 | })); 207 | 208 | function start(_x) { 209 | return _ref3.apply(this, arguments); 210 | } 211 | 212 | return start; 213 | }() 214 | }, { 215 | key: 'mixin', 216 | value: function mixin() { 217 | for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 218 | args[_key2] = arguments[_key2]; 219 | } 220 | 221 | return this.framework.mixin.apply(this.framework, args); 222 | } 223 | }, { 224 | key: 'on', 225 | value: function on() { 226 | for (var _len3 = arguments.length, args = Array(_len3), _key3 = 0; _key3 < _len3; _key3++) { 227 | args[_key3] = arguments[_key3]; 228 | } 229 | 230 | return this.emitter.on.apply(this.emitter, args); 231 | } 232 | }, { 233 | key: 'one', 234 | value: function one() { 235 | for (var _len4 = arguments.length, args = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 236 | args[_key4] = arguments[_key4]; 237 | } 238 | 239 | return this.emitter.one.apply(this.emitter, args); 240 | } 241 | }, { 242 | key: 'off', 243 | value: function off() { 244 | for (var _len5 = arguments.length, args = Array(_len5), _key5 = 0; _key5 < _len5; _key5++) { 245 | args[_key5] = arguments[_key5]; 246 | } 247 | 248 | return this.emitter.off.apply(this.emitter, args); 249 | } 250 | }, { 251 | key: 'trigger', 252 | value: function trigger() { 253 | for (var _len6 = arguments.length, args = Array(_len6), _key6 = 0; _key6 < _len6; _key6++) { 254 | args[_key6] = arguments[_key6]; 255 | } 256 | 257 | return this.emitter.trigger.apply(this.emitter, args); 258 | } 259 | }, { 260 | key: 'container', 261 | get: function get() { 262 | return this._container; 263 | }, 264 | set: function set(val) { 265 | this._container = val; 266 | } 267 | }, { 268 | key: 'hub', 269 | get: function get() { 270 | return this._router.hub; 271 | } 272 | }, { 273 | key: 'context', 274 | get: function get() { 275 | return this._context; 276 | } 277 | }, { 278 | key: 'store', 279 | get: function get() { 280 | return this._store; 281 | }, 282 | set: function set(val) { 283 | this._store = val; 284 | } 285 | }, { 286 | key: 'mode', 287 | get: function get() { 288 | return this._mode; 289 | } 290 | }, { 291 | key: 'entry', 292 | get: function get() { 293 | return this._entry; 294 | } 295 | }, { 296 | key: 'env', 297 | get: function get() { 298 | return this._env; 299 | } 300 | }]); 301 | 302 | return Ninjia; 303 | }(); 304 | 305 | Ninjia.Component = Component; 306 | 307 | 308 | var appCreator = function appCreator(params) { 309 | return new Ninjia(params); 310 | }; 311 | 312 | exports.default = appCreator; 313 | exports.connect = _riotRedux.connect; 314 | exports.provider = _riotRedux.provider; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 |

Micro and elegant frontend framework

7 | 8 | Like a ninja, Ninjajs dexterous and elegant, and its body full of magic weapons that simple but powerful. 9 | Ninjajs will help you to use the simple way to build complex systems. 10 | "Practical but not fancy" is its talisman. 11 | 12 | ## Geting Started 13 | 14 | ## Install 15 | 16 | ```shell 17 | $ npm install ninjajs 18 | ``` 19 | 20 | ## Pay A Glance 21 | 22 | ``` 23 | $ npm run dev 24 | ``` 25 | 26 | http://localhost:8080 27 | 28 | ## Usage 29 | 30 | ### Startup 31 | 32 | main.js 33 | 34 | ```javascript 35 | import App from 'path to xxx component' 36 | 37 | let app = Ninja({container: window, reducer, middlewares, state: {}}) // container, reducer, middlewares, initialState 38 | 39 | app.set('routes', routes) 40 | 41 | app.start(async () => { 42 | // set entry for the application. 43 | app.set('entry', new App(document.getElementById('app'))) 44 | }) 45 | ``` 46 | 47 | ### API 48 | 49 | **set(key, val)** 50 | 51 | * key \ 52 | * val \ 53 | 54 | buildin config - key -> value 55 | * env \ application environment - production, development, test 56 | * mode \ browser or history 57 | * context \ application context obj (store, tags, ...others) 58 | * routes \ expect a plain obj to describe the routes (more in below) 59 | * entry \ application entry component 60 | 61 | 62 | **registerWidget(options)** 63 | 64 | * options.name \ 65 | * options.methods \ 66 | 67 | allow user to control component with method invocation. 68 | 69 | eg: 70 | 71 | app.registerWidget({ 72 | name: 'modal', 73 | methods: ['open'] 74 | }) 75 | 76 | **start()** 77 | 78 | all ready callback 79 | 80 | ### Component 81 | 82 | #### Define component 83 | 84 | ```javascript 85 | @Componnet // register this component to hub, in order to use it in html directly. 86 | export default class Todo extends Ninja.Component { 87 | 88 | get tmpl() { 89 | return require('path to template of component'); 90 | } 91 | 92 | onCreate(opts) {} 93 | } 94 | ``` 95 | 96 | #### State Management 97 | redux like: 98 | 99 | ```javascript 100 | @Connect( 101 | state => ({ 102 | ...states 103 | }), 104 | dispatch => ({ 105 | ...actions 106 | }) 107 | ) 108 | export default class Todo extends Ninja.Component {} 109 | ``` 110 | 111 | 112 | ### Router 113 | 114 | #### Define routes 115 | 116 | ```javascript 117 | import TodoList from '...path to component'; 118 | 119 | export default { 120 | component: App, 121 | path: '', 122 | children: [ 123 | { 124 | path: '/', 125 | component: TodoList, 126 | defaultRoute: true 127 | } 128 | ] 129 | } 130 | ``` 131 | #### Fields 132 | | field | type | desc | 133 | | ------------- |:-------------:| :-----| 134 | | path         | string | Corresponding URI, Relative path | 135 | | component | Object | Component constructors | 136 | | children | Array | sub routes outlets | 137 | | defaultRoute | Boolean       | specify that this is a default route | 138 | | components   | Object       | dynamic route defined, get identifier from query string | 139 | | abstract   | Boolean     | specify that this is a abstract route, no Corresponding component | 140 | | ...others | Any | will be get from context.req.body | 141 | 142 | #### Context 143 | 1. req \ 144 | 145 | | field | type | 146 | | -------- |:----------- | 147 | | params | Object     | 148 | | body | Object | 149 | | query | Object | 150 | 151 | #### Router outlet in html 152 | ```html 153 |
154 |
greeting!
155 | 156 |
157 | ``` 158 | 159 | #### Component life cycle about router 160 | 161 | | evts | 162 | | ------------ | 163 | | enter         | 164 | | before-leave | 165 | | leave     | 166 | | leaved | 167 | 168 | eg: 169 | 170 | ```javascript 171 | @View // this decorator specify that component will be a view, give the component relevant features 172 | export default class Todo extends Ninja.Component { 173 | // ...others 174 | onCreate(opts) { 175 | this.on('enter', ctx => { 176 | // todo 177 | }) 178 | this.on('before-leave', ctx => { 179 | // todo 180 | }) 181 | this.on('leave', ctx => { 182 | // todo 183 | }) 184 | this.on('leaved', ctx => { 185 | // todo 186 | }) 187 | } 188 | } 189 | ``` 190 | 191 | ```javascript 192 | // Advanced Usage 193 | class Foo extends Ninja.Component { 194 | // ...others 195 | 196 | // decorator onUse 197 | // @param , when nav to this component, the middlewares (defined in 'opts') will be invoke. 198 |  //   each middleware method will be injected a callback ( component will be present when the callback invoked ) and a   199 | //   router context object. 200 | // eg: const enterFoo = (next, ctx) 201 | @onUse('enterFoo') 202 | onCreate(opts) {} 203 | } 204 | ``` 205 | 206 | #### Route hooks 207 | 208 | **1. history-pending - callback(from, to, location, context[, next])** 209 | 210 | * from \ from which Component 211 | * to \ to which Component 212 | * location \ uri 213 | * context \ context object 214 | * req \ request object 215 | * params \ 216 | * body \ 217 | * query \ 218 | * next \ execution callback, if exists, the execution won`t be continue until next being executed 219 | 220 | **2. history-resolve - callback(from, to, context, routes, index[, next])** 221 | 222 | * from \ from which Component 223 | * to \ to which Component 224 | * context \ context object 225 | * req \ request object 226 | * params \ 227 | * body \ 228 | * query \ 229 | * routes \ uris 230 | * index \ current index in uris 231 | * next \ execution callback, if exists, the execution won`t be continue until next being executed 232 | 233 | eg: 234 | 235 | ``` 236 | app.hub.subscribe('history-pending', (from, to, location, context, next) => {}) 237 | 238 | app.hub.subscribe('history-resolve', (from, to, context, routes, index) => {}) 239 | ``` 240 | 241 | ### Form 242 | 243 | #### Buildin rules 244 | 245 | * required 246 | * max 247 | * min 248 | * maxlength 249 | * minlength 250 | * pattern 251 | 252 | #### API 253 | 254 | * registerValidators(name, fn) 255 | * name \ 256 | * fn \ 257 | 258 | Register customer validators 259 | 260 | #### Detail 261 | 262 | * Integrate with Redux 263 | A action will be dispatched when interact with inputs. 264 | You will get corrent value in state and opts. 265 | Attached to opts: 266 | * submit \ submit specific form manually. 267 | * forms \ forms map. 268 | 269 | * input fields & add class 270 | 271 | * field 272 | * $valid 273 | * $invalid 274 | * $dirty 275 | * $pristine 276 | * $error 277 | * $originVal 278 | * $val 279 | * class 280 | * f-valid 281 | * f-invalid 282 | * f-dirty 283 | * f-pristine 284 | 285 | * multi form validation supported 286 | 287 | #### Example 288 | 289 | ```javascript 290 | @Form({ 291 | username: { 292 | required: true, 293 | minlength: 2, 294 | maxlength: 20 295 | }, 296 | password: { 297 | required: true, 298 | min: 1, 299 | max: 10 300 | }, 301 | address: { 302 | pattern: /.*/ 303 | } 304 | }) 305 | class Foo extends Ninja.Component { 306 | // ...others 307 |  async onSubmit() { 308 | e.preventDefault(); 309 |    this.opts.submit('FormA')   // submit specific form manually 310 | if (this.opts.forms.FormA.$invalid) { 311 | return; 312 | } 313 | } 314 | } 315 | ``` 316 | 317 | ```html 318 | 330 |
331 |

username required!

332 | 333 |
334 | ``` 335 | 336 | ### Usage v2.* (Deprecated) 337 | 338 | #### routes.js 339 | ```javascript 340 | export default { 341 | component: 'app', 342 | children: [ 343 | { 344 | path: '/', 345 | defaultRoute: true, 346 | component: 'count', 347 | }, 348 | { 349 | path: '/test', 350 | component: 'test', 351 | children: [ 352 | { 353 | path: '/test2', 354 | component: 'test2' 355 | } 356 | ] 357 | } 358 | ] 359 | } 360 | ``` 361 | 362 | ```javascript 363 | //main.js 364 | import { router Ninja } from 'ninjajs'; 365 | 366 | let app = new Ninja(container, reducer, middlewares, initialState); // create ninja application 367 | 368 | app.set('env', process.env.NODE_ENV === 'production' ? 'production' : 'development'); 369 | 370 | app.set('mode', 'browser'); 371 | 372 | app.set('context', { store: app.store, hub: router.hub, tags: {} }); 373 | 374 | router.hub.routes = routes; // set routes 375 | 376 | app.router(router); 377 | 378 | app.start(async () => { 379 | //todo 380 | }) 381 | 382 | ``` 383 | 384 | ```javascript 385 | //component 386 | 387 | require('path-to-nest'); 388 | 389 | 390 |
Hello World
391 | 392 | 393 | import { connect } from 'ninjajs'; 394 | 395 | connect( //redux like 396 | state => ({}), 397 | dispatch => ({}) 398 | )(this) 399 | 400 | this.mixin('router'); // mixin router, if you wanna use middleware (the $use method) 401 | 402 | this.$use(function(next, ctx){ 403 | //trigger when nav to this component 404 | }) 405 |
406 | 407 | ``` 408 | 409 | ## Who's using 410 | 411 | 412 | 413 | 414 | 415 | ## Buy us a coffee 416 | 417 | 418 | 419 | ## More 420 | source for more detail 421 | 422 | ## Contact 423 | 424 | * WX: leekangtaqi 425 | * QQ: 2811786667 426 | -------------------------------------------------------------------------------- /src/ninja-redux/components/connect.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import warning from '../util/warning'; 3 | import invariant from '../util/invariant'; 4 | import isPlainObject from '../util/isPlainObject'; 5 | import shallowEqual from '../util/shallowEqual'; 6 | import { getProvider } from './provider'; 7 | import _ from '../../util'; 8 | 9 | const defaultMapStateToOpts = state => ({}); 10 | const defaultMapDispatchToOpts = dispatch => ({ dispatch }); 11 | const defaultMergeOpts = (stateOpts, dispatchOpts, parentOpts) => ({ 12 | ...parentOpts, 13 | ...stateOpts, 14 | ...dispatchOpts 15 | }); 16 | 17 | const getDisplayName = WrappedComponent => { 18 | return WrappedComponent.displayName || _.lineToCamel(WrappedComponent.name) || 'Component'; 19 | } 20 | 21 | let errorObject = { value: null }; 22 | function tryCatch(fn , ctx) { 23 | try { 24 | return fn.apply(ctx); 25 | } catch (e) { 26 | errorObject.value = e; 27 | return errorObject; 28 | } 29 | }; 30 | 31 | // Helps track hot reloading. 32 | let nextVersion = 0; 33 | 34 | function wrapActionCreators(actionCreators) { 35 | return dispatch => bindActionCreators(actionCreators, dispatch); 36 | } 37 | 38 | function hoistStatics(targetComponent, sourceComponent) { 39 | const RIOT_STATICS = { 40 | displayName: true, 41 | mixins: true, 42 | type: true 43 | }; 44 | 45 | const KNOWN_STATICS = { 46 | name: true, 47 | length: true, 48 | prototype: true, 49 | caller: true, 50 | arguments: true, 51 | arity: true 52 | }; 53 | 54 | var isGetOwnPropertySymbolsAvailable = typeof Object.getOwnPropertySymbols === 'function'; 55 | 56 | if (typeof sourceComponent !== 'string') { // don't hoist over string (html) components 57 | var keys = Object.getOwnPropertyNames(sourceComponent); 58 | 59 | /* istanbul ignore else */ 60 | if (isGetOwnPropertySymbolsAvailable) { 61 | keys = keys.concat(Object.getOwnPropertySymbols(sourceComponent)); 62 | } 63 | 64 | for (var i = 0; i < keys.length; ++i) { 65 | if (!RIOT_STATICS[keys[i]] && !KNOWN_STATICS[keys[i]] && (!customStatics || !customStatics[keys[i]])) { 66 | try { 67 | targetComponent[keys[i]] = sourceComponent[keys[i]]; 68 | } catch (error) { 69 | console.warn("hoistStatics failed."); 70 | } 71 | } 72 | } 73 | } 74 | 75 | return targetComponent; 76 | } 77 | 78 | /** 79 | * A HOC for connect the tag to redux store. (react-redux like) 80 | */ 81 | export default function Connect(mapStateToOpts, mapDispatchToOpts, mergeOpts, options={pure: true, withRef: false}) { 82 | const shouldSubscribe = Boolean(mapStateToOpts) 83 | const mapState = mapStateToOpts || defaultMapStateToOpts; 84 | 85 | const { pure, withRef } = options 86 | 87 | let mapDispatch = null; 88 | if (typeof mapDispatchToOpts === 'function') { 89 | mapDispatch = mapDispatchToOpts; 90 | } else if (!mapDispatchToOpts) { 91 | mapDispatch = defaultMapDispatchToOpts; 92 | } else { 93 | mapDispatch = wrapActionCreators(mapDispatchToOpts); 94 | } 95 | 96 | const finalMergeOpts = mergeOpts || defaultMergeOpts; 97 | const checkMergedEquals = pure && finalMergeOpts !== defaultMergeOpts; 98 | 99 | const version = nextVersion++; 100 | 101 | return function wrapWithConnect(WrappedComponent) { 102 | const connectDisplayName = `Connect(${getDisplayName(WrappedComponent)})`; 103 | 104 | function checkStateShape(opts, methodName) { 105 | if (!isPlainObject(opts)) { 106 | warning( 107 | `${methodName}() in ${connectDisplayName} must return a plain object. ` + 108 | `Instead received ${opts}.` 109 | ) 110 | } 111 | } 112 | 113 | function computeMergedOpts(stateOpts, dispatchOpts, parentOpts) { 114 | const mergedOpts = finalMergeOpts(stateOpts, dispatchOpts, parentOpts) 115 | if (process.env.NODE_ENV !== 'production') { 116 | checkStateShape(mergedOpts, 'mergeOpts'); 117 | } 118 | return mergedOpts; 119 | } 120 | 121 | class Connect extends WrappedComponent { 122 | get name() { 123 | return 'connect-' + (super.name || WrappedComponent.name).toLowerCase() 124 | } 125 | 126 | onCreate(opts) { 127 | this.version = version; 128 | this.store = opts.store || getProvider(this).opts.store; 129 | this.displayName = connectDisplayName; 130 | const storeState = this.store.getState(); 131 | 132 | invariant(this.store, 133 | `Could not find "store" in either the context or ` + 134 | `opts of "${connectDisplayName}". ` + 135 | `Either wrap the root component in a Provider, ` + 136 | `or explicitly pass "store" as a opt to "${connectDisplayName}".` 137 | ) 138 | 139 | this.state = { storeState } 140 | 141 | this.clearCache(); 142 | 143 | 144 | this.on('mount', this.componentDidMount); 145 | this.on('before-unmount', this.componentWillUnmount); 146 | this.on('update', this.render); 147 | 148 | super.onCreate(opts); 149 | } 150 | 151 | updateStateOptsIfNeeded() { 152 | const nextStateOpts = this.computeStateOpts(this.store, this.opts) 153 | if (this.stateOpts && shallowEqual(nextStateOpts, this.stateOpts)) { 154 | return false 155 | } 156 | 157 | this.stateOpts = nextStateOpts 158 | return true 159 | } 160 | 161 | updateDispatchOptsIfNeeded() { 162 | const nextDispatchOpts = this.computeDispatchOpts(this.store, this.opts) 163 | if (this.dispatchOpts && shallowEqual(nextDispatchOpts, this.dispatchOpts)) { 164 | return false 165 | } 166 | 167 | this.dispatchOpts = nextDispatchOpts 168 | return true 169 | } 170 | 171 | updateMergedOptsIfNeeded() { 172 | const nextMergedOpts = computeMergedOpts(this.stateOpts, this.dispatchOpts, this.opts) 173 | if (this.mergedOpts && checkMergedEquals && shallowEqual(nextMergedOpts, this.mergedOpts)) { 174 | return false 175 | } 176 | 177 | this.mergedOpts = nextMergedOpts 178 | return true 179 | } 180 | 181 | configureFinalMapState(store, opts) { 182 | const mappedState = mapState(store.getState(), opts) 183 | const isFactory = typeof mappedState === 'function' 184 | 185 | this.finalMapStateToOpts = isFactory ? mappedState : mapState 186 | this.doStateOptsDependOnOwnOpts = this.finalMapStateToOpts.length !== 1 187 | 188 | if (isFactory) { 189 | return this.computeStateOpts(store, opts) 190 | } 191 | 192 | if (process.env.NODE_ENV !== 'production') { 193 | checkStateShape(mappedState, 'mapStateToOpts') 194 | } 195 | return mappedState 196 | } 197 | 198 | handleChange() { 199 | if (!this.unsubscribe) { 200 | return 201 | } 202 | 203 | const storeState = this.store.getState() 204 | const prevStoreState = this.state.storeState 205 | 206 | if (pure && prevStoreState === storeState) { 207 | return 208 | } 209 | 210 | if (pure && !this.doStateOptsDependOnOwnOpts) { 211 | const haveStateOptsChanged = tryCatch(this.updateStateOptsIfNeeded, this) 212 | if (!haveStateOptsChanged) { 213 | return 214 | } 215 | if (haveStateOptsChanged === errorObject) { 216 | this.stateOptsPrecalculationError = errorObject.value 217 | } 218 | this.haveStateOptsBeenPrecalculated = true 219 | } 220 | 221 | 222 | this.hasStoreStateChanged = true 223 | this.update({state: storeState}) 224 | } 225 | 226 | componentDidMount() { 227 | this.render() 228 | this.trySubscribe() 229 | } 230 | 231 | componentWillUnmount() { 232 | this.tryUnsubscribe() 233 | this.clearCache() 234 | } 235 | 236 | trySubscribe() { 237 | if (shouldSubscribe && !this.unsubscribe) { 238 | this.unsubscribe = this.store.subscribe(this.handleChange.bind(this)) 239 | this.handleChange() 240 | } 241 | } 242 | 243 | tryUnsubscribe() { 244 | if (this.unsubscribe) { 245 | this.unsubscribe() 246 | this.unsubscribe = null 247 | } 248 | } 249 | 250 | isSubscribed() { 251 | return typeof this.unsubscribe === 'function' 252 | } 253 | 254 | clearCache() { 255 | this.dispatchOpts = null 256 | this.stateOpts = null 257 | this.mergedOpts = null 258 | this.haveOwnOptsChanged = true 259 | this.hasStoreStateChanged = true 260 | this.haveStateOptsBeenPrecalculated = false 261 | this.stateOptsPrecalculationError = null 262 | this.renderedElement = null 263 | this.finalMapDispatchToOpts = null 264 | this.finalMapStateToOpts = null 265 | } 266 | 267 | componentWillReceiveOpts(nextProps) { 268 | if (!pure || !shallowEqual(nextProps, this.props)) { 269 | this.haveOwnPropsChanged = true 270 | } 271 | } 272 | 273 | computeStateOpts(store, opts) { 274 | if (!this.finalMapStateToOpts) { 275 | return this.configureFinalMapState(store, opts) 276 | } 277 | 278 | const state = store.getState() 279 | const stateOpts = this.doStateOptsDependOnOwnOpts ? 280 | this.finalMapStateToOpts(state, opts) : 281 | this.finalMapStateToOpts(state) 282 | 283 | if (process.env.NODE_ENV !== 'production') { 284 | checkStateShape(stateOpts, 'mapStateToOpts') 285 | } 286 | return stateOpts 287 | } 288 | 289 | computeDispatchOpts(store, opts) { 290 | if (!this.finalMapDispatchToOpts) { 291 | return this.configureFinalMapDispatch(store, opts) 292 | } 293 | 294 | const { dispatch } = store 295 | const dispatchOpts = this.doDispatchOptsDependOnOwnProps ? 296 | this.finalMapDispatchToOpts(dispatch, opts) : 297 | this.finalMapDispatchToOpts(dispatch) 298 | 299 | return dispatchOpts 300 | } 301 | 302 | configureFinalMapDispatch(store, opts) { 303 | const mappedDispatch = mapDispatch(store.dispatch, opts) 304 | const isFactory = typeof mappedDispatch === 'function' 305 | 306 | this.finalMapDispatchToOpts = isFactory ? mappedDispatch : mapDispatch 307 | this.doDispatchOptsDependOnOwnOpts = this.finalMapDispatchToOpts.length !== 1 308 | 309 | if (isFactory) { 310 | return this.computeDispatchOpts(store, opts) 311 | } 312 | 313 | return mappedDispatch 314 | } 315 | 316 | render () { 317 | const { 318 | haveOwnOptsChanged, 319 | hasStoreStateChanged, 320 | haveStateOptsBeenPrecalculated, 321 | stateOptsPrecalculationError, 322 | renderedElement 323 | } = this 324 | 325 | this.haveOwnOptsChanged = false 326 | this.hasStoreStateChanged = false 327 | this.haveStateOptsBeenPrecalculated = false 328 | this.stateOptsPrecalculationError = null 329 | 330 | if (stateOptsPrecalculationError) { 331 | throw stateOptsPrecalculationError 332 | } 333 | 334 | let shouldUpdateStateOpts = true 335 | let shouldUpdateDispatchOpts = true 336 | 337 | if (pure && renderedElement) { 338 | shouldUpdateStateOpts = hasStoreStateChanged || ( 339 | haveOwnOptsChanged && this.doStateOptsDependOnOwnOpts 340 | ) 341 | shouldUpdateDispatchOpts = 342 | haveOwnOptsChanged && this.doDispatchOptsDependOnOwnOpts 343 | } 344 | 345 | let haveStateOptsChanged = false 346 | let haveDispatchOptsChanged = false 347 | 348 | if (haveStateOptsBeenPrecalculated) { 349 | haveStateOptsChanged = true 350 | } else if (shouldUpdateStateOpts) { 351 | haveStateOptsChanged = this.updateStateOptsIfNeeded() 352 | } 353 | if (shouldUpdateDispatchOpts) { 354 | haveDispatchOptsChanged = this.updateDispatchOptsIfNeeded() 355 | } 356 | 357 | let haveMergedOptsChanged = true 358 | if ( 359 | haveStateOptsChanged || 360 | haveDispatchOptsChanged || 361 | haveOwnOptsChanged 362 | ) { 363 | haveMergedOptsChanged = this.updateMergedOptsIfNeeded() 364 | } else { 365 | haveMergedOptsChanged = false 366 | } 367 | 368 | if (!haveMergedOptsChanged && renderedElement) { 369 | return; 370 | } 371 | 372 | this.renderedElement = true; 373 | 374 | Object.assign(this.opts, {...this.mergedOpts}); 375 | 376 | setTimeout(() => { 377 | this.update(); 378 | }, 0) 379 | 380 | } 381 | } 382 | 383 | Connect.displayName = connectDisplayName; 384 | Connect.WrappedComponent = WrappedComponent; 385 | 386 | return Connect; 387 | } 388 | } -------------------------------------------------------------------------------- /src/ninja-redux-form/hoc.js: -------------------------------------------------------------------------------- 1 | import { getProvider } from '../ninja-redux/components/provider'; 2 | import { validators } from './validator'; 3 | import _ from '../util'; 4 | 5 | /** 6 | * HOC: 7 | * opts: forms, submit 8 | */ 9 | export default function Form(inputRulePairs) { 10 | return function wrapComponent (WrappedComponent) { 11 | return class Form extends WrappedComponent { 12 | get name() { 13 | return 'form-' + (super.name || WrappedComponent.name).toLowerCase(); 14 | } 15 | onCreate(opts) { 16 | super.onCreate(opts); 17 | this.options = inputRulePairs; 18 | this.on('updated', this.onUpdated); 19 | this.mapDispatchToOpts(); 20 | } 21 | 22 | /** 23 | * del all forms and inputs. 24 | * unbind any event listener and reflush redux store. 25 | * trigger onUpdated event, init the whole forms. 26 | */ 27 | resetForm() { 28 | let formsToRemove = [] 29 | this.extractFormNamesFromRef().forEach(formName => { 30 | this.delForm(formName) 31 | formsToRemove.push({form: formName}) 32 | }) 33 | let store = this.getStore(); 34 | store.dispatch({type: 'forms/remove', payload: formsToRemove}); 35 | } 36 | 37 | /** 38 | * @param inputNames 39 | */ 40 | resetInputs(inputNames) { 41 | inputNames.forEach(inputName => this.resetInput(inputName)); 42 | } 43 | 44 | /** 45 | * @param inputName 46 | * @param val 47 | */ 48 | setRef(inputName, val='') { 49 | if (!inputName) { 50 | let err = new Error(`set ref method expecta input name`); 51 | throw err; 52 | } 53 | this.refs[inputName].value = val; 54 | triggerEvent(this.refs[inputName], 'change') 55 | function triggerEvent(el, type){ 56 | if ('createEvent' in document) { 57 | // modern browsers, IE9+ 58 | var e = document.createEvent('HTMLEvents'); 59 | e.initEvent(type, false, true); 60 | el.dispatchEvent(e); 61 | } else { 62 | // IE 8 63 | var e = document.createEventObject(); 64 | e.eventType = type; 65 | el.fireEvent('on'+e.eventType, e); 66 | } 67 | } 68 | } 69 | 70 | /** 71 | * unbind change listener, remove handler from this.$form 72 | * remove input of store 73 | * @param inputName 74 | */ 75 | resetInput(inputName) { 76 | if (!inputName) { 77 | let err = new Error(`[redux-form]: reset input expect a inputName`); 78 | throw err; 79 | } 80 | if (!this.refs[inputName] || 81 | !this.refs[inputName].form || 82 | !this.refs[inputName].form.getAttribute('ref') 83 | ) { 84 | return; 85 | } 86 | let formName = this.refs[inputName].form.getAttribute('ref'); 87 | this.delInput(inputName, formName); 88 | } 89 | 90 | mapDispatchToOpts() { 91 | let store = this.getStore(); 92 | this.opts.submit = formName => { 93 | if (!this.refs[formName]) { 94 | console.warn(`failed to submit the form, can not find the form [name]${formName}`) 95 | } 96 | let forms = this.extractFormNamesFromRef().map(fn => this.refs[fn]) 97 | forms.forEach(f => { 98 | let inputs = this.extractInputsFromForm(f).map(inp => this.refs[inp]); 99 | inputs.forEach(input => { 100 | this.validate(input, input.value) 101 | }) 102 | if (!inputs || !inputs.length) { 103 | store.dispatch({type: 'form/valid', payload: formName}) 104 | } 105 | }) 106 | 107 | store.dispatch({type: 'form/submit', payload: formName}) 108 | } 109 | this.opts.unsubmit = formName => { 110 | if (!this.refs[formName]) { 111 | console.warn(`failed to unsubmit the form, can not find the form [name]${formName}`) 112 | } 113 | store.dispatch({type: 'form/unsubmit', payload: formName}) 114 | } 115 | } 116 | 117 | unMapDispatchToOpts() { 118 | 119 | delete this.opts['forms'] 120 | delete this.opts['submit'] 121 | } 122 | 123 | /** 124 | * listen input change dispatch to store, and do validating. 125 | * when updated, check input modify or not, if it is, set rule again. 126 | */ 127 | onUpdated() { 128 | 129 | let store = this.getStore(); 130 | 131 | if (!this.refs) { 132 | return; 133 | } 134 | let formNames = this.extractFormNamesFromRef(); 135 | if (!formNames || !formNames.length) { 136 | return; 137 | } 138 | // the forms struct changed ? 139 | this.refDiff(this.extractFormsFromRef()); 140 | 141 | // rebind or not 142 | this.rebindInputs(); 143 | } 144 | 145 | rebindInputs() { 146 | for (let inputName in this.refs) { 147 | let handler = this.findHandlerInFormHandlersByInputName(inputName); 148 | let input = this.refs[inputName] 149 | if (handler && handler.input != input) { 150 | handler.input = input 151 | input.addEventListener('change', handler.bind(this)) 152 | } 153 | } 154 | } 155 | 156 | findHandlerInFormHandlersByInputName(inputName) { 157 | if (!this.$form || !this.$form.handlers) { 158 | return; 159 | } 160 | for (let handler of this.$form.handlers) { 161 | if (handler.input.getAttribute('ref') === inputName) { 162 | return handler; 163 | } 164 | } 165 | } 166 | 167 | extractFormNamesFromRef() { 168 | return Object.keys(this.refs).filter(r => this.refs[r].nodeName === 'FORM') 169 | } 170 | 171 | extractFormsFromRef() { 172 | return this.extractFormNamesFromRef().map(n => this.refs[n]) 173 | } 174 | 175 | extractInputsNames() { 176 | return Object.keys(this.refs).filter(r => this.refs[r].nodeName === ('INPUT' || 'SELECT')) 177 | } 178 | 179 | extractInputsFromRefs() { 180 | return Object.keys(this.refs).filter(r => this.refs[r].nodeName === ('INPUT' || 'SELECT')).map(f => this.refs[f]) 181 | } 182 | 183 | extractInputsFromForm(form) { 184 | return Object.keys(this.refs).filter(r => this.refs[r].form === form) 185 | } 186 | 187 | getStore() { 188 | return getProvider(this).opts.store; 189 | } 190 | 191 | getInputs(form) { 192 | return Object.keys(this.refs).filter(r => this.refs[r].form === form).map(k => this.refs[k]) 193 | } 194 | 195 | refDiff(forms) { 196 | let store = this.getStore(); 197 | let formsInStore = store.getState().forms; 198 | let { adds, dels } = this.distinctForm(forms, formsInStore, f => f.attributes["ref"].value); 199 | let remainForms = this.extractFormNamesFromRef().filter(formName => adds.indexOf(formName) < 0 && dels.indexOf() < 0); 200 | 201 | // resolve adds 202 | if (adds && adds.length) { 203 | let formsToUpdate = []; 204 | 205 | adds.forEach(f => { 206 | this.addForm(store, f) 207 | let inputs = this.getInputs(this.refs[f]) 208 | let inputsToUpdate = inputs.map(input => this.addInput(store, input, f)); 209 | formsToUpdate.push({ form: f, inputs: inputsToUpdate }) 210 | }) 211 | 212 | formsToUpdate.length && store.dispatch({type: 'forms/inputs/add', payload: formsToUpdate}); 213 | } 214 | 215 | // resolve dels remove all listen handlers 216 | if (dels && dels.length) { 217 | let formsToRemove = []; 218 | 219 | dels.forEach(f => { 220 | this.delForm(store, f) 221 | formsToRemove.push({ form: f }) 222 | }) 223 | 224 | store.dispatch({type: 'forms/remove', payload: formsToRemove}); 225 | } 226 | 227 | // extract not add and del form, check input struct 228 | if (remainForms && remainForms.length) { 229 | let { adds, dels } = this.resolveInputsInFormLoop(remainForms); 230 | 231 | if (adds && adds.length) { 232 | let formsToUpdate = []; 233 | 234 | adds.forEach(({formName, inputs}) => { 235 | if (inputs && inputs.length) { 236 | let inputsToUpdate = inputs.map(input => this.addInput(store, this.refs[input], formName)); 237 | formsToUpdate.push({ form: formName, inputs: inputsToUpdate }) 238 | } 239 | }) 240 | formsToUpdate.length && store.dispatch({type: 'forms/inputs/add', payload: formsToUpdate}); 241 | } 242 | 243 | if (dels && dels.length) { 244 | let formsToRemove = []; 245 | 246 | dels.forEach((formName, inputs) => { 247 | if (inputs && inputs.length) { 248 | inputs.forEach(inputName => { 249 | this.refs[inputName].forEach(input => this.delInput(input)); 250 | }) 251 | 252 | formsToRemove.push({ form: formName, inputs}) 253 | } 254 | }) 255 | formsToRemove.length && store.dispatch({type: 'forms/inputs/remove', payload: formsToRemove}); 256 | } 257 | 258 | } 259 | } 260 | 261 | distinctForm(curr, allPrev, fn) { 262 | let prev = Object.keys(allPrev).filter(p => allPrev[p].$meta._riot_id === this._riot_id).map(k => allPrev[k]) 263 | let prevFormNames = prev.map(p => p.$name); 264 | let adds = []; 265 | let dels = []; 266 | 267 | for (let i=0, len=curr.length; i= 0) { 271 | prevFormNames.splice(index, 1) 272 | continue; 273 | } else { 274 | adds.push(fn(form)) 275 | } 276 | } 277 | prevFormNames.length && ( dels = prevFormNames ) 278 | 279 | return { adds, dels } 280 | } 281 | 282 | distinctInput(curr, allPrev, fn) { 283 | let adds = []; 284 | let dels = []; 285 | let prevs = _.clone(allPrev); 286 | 287 | for (let i=0, len=curr.length; i= 0) { 292 | prevs.splice(index, 1) 293 | continue; 294 | } else { 295 | adds.push(fn(inputEl)) 296 | } 297 | } 298 | prevs.length && ( dels = prevs ) 299 | 300 | return { adds, dels } 301 | } 302 | 303 | bindInputChange(input) { 304 | if (!this.$form) { 305 | this.$form = {handlers: []}; 306 | } 307 | let handler = e => { 308 | let val = e.target.value; 309 | this.validate(input, val) 310 | } 311 | handler.input = input; 312 | this.$form.handlers.push(handler); 313 | input.addEventListener('change', handler.bind(this)) 314 | } 315 | 316 | /** 317 | * @param input 318 | * @return 319 | */ 320 | unbindInputChange(input) { 321 | if (!this.$form.handlers.length) { 322 | return; 323 | } 324 | let handlers = this.$form.handlers.filter(h => h.input === input); 325 | this.$form.handlers = this.$form.handlers.filter(h => h.input != input); 326 | if (handlers.length) { 327 | handlers.forEach((h, i) => { 328 | input.removeEventListener('change', h); 329 | }) 330 | } 331 | } 332 | 333 | /** 334 | * @param input 335 | * @param val value to validate 336 | */ 337 | validate(input, val) { 338 | let store = this.getStore() 339 | let formName = input.form.getAttribute('ref'); 340 | let form = store.getState().forms[formName]; 341 | let inputJson = form[input.getAttribute('ref')]; 342 | let rules = inputJson.$rule; 343 | let inputsToUpdate = []; 344 | 345 | Object.keys(rules).map(ruleName => { 346 | let validVal = rules[ruleName]; 347 | let validator = validators[ruleName] 348 | let invalid = validator(val, rules[ruleName]) 349 | let inputToUpdate = null; 350 | 351 | if (!invalid) { 352 | inputToUpdate = this.inputValid(input, inputJson, ruleName, val) 353 | } else { 354 | inputToUpdate = this.inputInvalid(input, inputJson, ruleName, val) 355 | } 356 | 357 | inputToUpdate.$val = val; 358 | 359 | this.resolveClass(inputJson, input) 360 | 361 | inputsToUpdate.push(inputToUpdate); 362 | }) 363 | 364 | store.dispatch({type: 'forms/inputs/update', payload: {form: formName, inputs: inputsToUpdate}}) 365 | } 366 | 367 | resolveInputsInFormLoop(formsNames) { 368 | let store = this.getStore(); 369 | let formsInStore = store.getState().forms; 370 | let finalAdds = []; 371 | let finalDels = []; 372 | 373 | for (let formName of formsNames) { 374 | let inputObjMap = _.extractField(formsInStore[formName]); 375 | let inputObjArr = Object.keys(inputObjMap).map(k => inputObjMap[k].$name); 376 | let { adds, dels } = this.distinctInput(this.extractInputsFromForm(this.refs[formName]), inputObjArr, i => i.attributes["ref"].value) 377 | finalAdds = finalAdds.concat({ formName, inputs: adds }); 378 | finalDels = finalDels.concat({ formName, inputs: dels }); 379 | } 380 | 381 | return { adds: finalAdds, dels: finalDels } 382 | } 383 | 384 | inputValid(inputEl, inputJson, ruleName, val) { 385 | inputJson.$valid = true; 386 | inputJson.$invalid = false; 387 | if (inputJson.$error[ruleName]) { 388 | delete inputJson.$error[ruleName] 389 | } 390 | 391 | if (val != inputJson.$originVal) { 392 | inputJson.$dirty = true; 393 | } else { 394 | inputJson.$dirty = false; 395 | } 396 | inputJson.$pristine = !inputJson.$dirty; 397 | return inputJson; 398 | } 399 | 400 | inputInvalid(inputEl, inputJson, ruleName, val) { 401 | inputJson.$valid = false; 402 | inputJson.$invalid = true; 403 | inputJson.$error[ruleName] = true 404 | 405 | if (val != inputJson.$originVal) { 406 | inputJson.$dirty = true; 407 | } else { 408 | inputJson.$dirty = false; 409 | } 410 | inputJson.$pristine = !inputJson.$dirty; 411 | return inputJson; 412 | } 413 | 414 | resolveClass(field, input) { 415 | if(Object.keys(field.$error).length > 0){ 416 | _.removeClass(input, 'f-valid'); 417 | _.addClass(input, 'f-invalid'); 418 | }else{ 419 | _.removeClass(input, 'f-invalid'); 420 | _.addClass(input, 'f-valid'); 421 | } 422 | if(field.$dirty) { 423 | _.addClass(input, 'f-dirty'); 424 | _.removeClass(input, 'f-pristine'); 425 | } 426 | if(field.$pristine){ 427 | _.addClass(input, 'f-pristine'); 428 | _.removeClass(input, 'f-dirty'); 429 | } 430 | } 431 | 432 | /** 433 | * @param store 434 | * @param input 435 | * @param formName 436 | */ 437 | addInput(store, input, formName) { 438 | let inputName = this.getInputName(input); 439 | let rulesMap = this.options[inputName]; 440 | let rules = Object.keys(rulesMap).map(k => ({name: k, value: rulesMap[k]})); 441 | let inputInstance = this.getInputInstance(input, formName); 442 | rules.map(r => this.addInputRule(inputInstance, r)) 443 | this.bindInputChange(input); 444 | return inputInstance 445 | } 446 | 447 | addInputRule(input, rule) { 448 | input.$rule[rule.name] = rule.value; 449 | return input; 450 | } 451 | 452 | getInputName(input) { 453 | return input.attributes['ref'].value; 454 | } 455 | 456 | getInputInstance(input, formName) { 457 | let inputPersisted = null; 458 | let inputName = this.getInputName(input); 459 | let state = this.getStore().getState(); 460 | let formInStore = state.forms[formName]; 461 | inputPersisted = formInStore[inputName]; 462 | if (!inputPersisted) { 463 | inputPersisted = { 464 | $name: inputName, 465 | $dirty: false, 466 | $pristine: true, 467 | $valid: true, 468 | $invalid: false, 469 | $error: {}, 470 | $rule: {}, 471 | $originVal: input.value 472 | }; 473 | } 474 | return inputPersisted; 475 | } 476 | 477 | delInput(inputName, formName) { 478 | let formsToRemove = []; 479 | let store = this.getStore(); 480 | let inputEl = this.refs[inputName]; 481 | inputEl.value = "" 482 | this.unbindInputChange(inputEl) 483 | formsToRemove.push({ form: formName, inputs: [inputName]}) 484 | store.dispatch({type: 'forms/inputs/remove', payload: formsToRemove}); 485 | } 486 | 487 | addForm(store, formName) { 488 | let validated = false; 489 | let form = { 490 | $meta: { 491 | _riot_id: this._riot_id 492 | }, 493 | $name: formName, 494 | $dirty: false, 495 | $pristine: true, 496 | $valid: false, 497 | $invalid: true, 498 | $submitted: false, 499 | $error: {} 500 | }; 501 | 502 | store.dispatch({type: 'form/add', payload: form}); 503 | } 504 | 505 | delForm(formName) { 506 | this.extractInputsFromForm(this.refs[formName]) 507 | .map(n => this.refs[n]) 508 | .forEach(input => { 509 | input.value = '' 510 | this.unbindInputChange(input) 511 | }) 512 | } 513 | 514 | trySubscribe() { 515 | super.trySubscribe() 516 | let store = this.getStore(); 517 | this.subscribe = store.subscribe(() => { 518 | let state = store.getState(); 519 | let lastAction = state.lastAction 520 | if ( 521 | (lastAction.type === 'forms/add') || 522 | (lastAction.type === 'forms/inputs/add') || 523 | (lastAction.type === 'forms/inputs/update' && 524 | this.extractFormNamesFromRef().indexOf(lastAction.payload.form) >= 0) || 525 | (lastAction.type === 'form/submit' && 526 | this.extractFormNamesFromRef().indexOf(lastAction.payload) >= 0) || 527 | this.concernActions(lastAction.type, lastAction.payload) 528 | ) { 529 | if (this.isMounted) { 530 | this.opts.forms = state.forms 531 | this.update(); 532 | } 533 | } 534 | }) 535 | } 536 | 537 | concernActions(type, payload) { 538 | let formNames = this.extractFormNamesFromRef(); 539 | if (type === 'forms/remove') { 540 | if (formNames && formNames.length) { 541 | let intersection = _.intersect(formNames, payload.map(p => p.form)) 542 | return intersection.length > 0; 543 | } 544 | } 545 | if (type === 'forms/inputs/remove') { 546 | let inputNames = payload.map(s => s.inputs).reduce((acc, curr) => acc.concat(curr), []); 547 | if (inputNames) { 548 | return _.intersect(inputNames, Object.keys(this.refs)).length > 0 549 | } 550 | } 551 | return false; 552 | } 553 | 554 | unSubscribe() { 555 | super.unSubscribe() 556 | if(this.subscribe) { 557 | this.subscribe() 558 | return true; 559 | } 560 | else { 561 | return false; 562 | } 563 | } 564 | 565 | } 566 | } 567 | } -------------------------------------------------------------------------------- /dist/riot-redux/components/connect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 8 | 9 | var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; 10 | 11 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 12 | 13 | exports.default = Connect; 14 | 15 | var _redux = require('redux'); 16 | 17 | var _warning = require('../util/warning'); 18 | 19 | var _warning2 = _interopRequireDefault(_warning); 20 | 21 | var _invariant = require('../util/invariant'); 22 | 23 | var _invariant2 = _interopRequireDefault(_invariant); 24 | 25 | var _isPlainObject = require('../util/isPlainObject'); 26 | 27 | var _isPlainObject2 = _interopRequireDefault(_isPlainObject); 28 | 29 | var _shallowEqual = require('../util/shallowEqual'); 30 | 31 | var _shallowEqual2 = _interopRequireDefault(_shallowEqual); 32 | 33 | var _provider = require('./provider'); 34 | 35 | var _util = require('../../util'); 36 | 37 | var _util2 = _interopRequireDefault(_util); 38 | 39 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 40 | 41 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 42 | 43 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 44 | 45 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 46 | 47 | var defaultMapStateToOpts = function defaultMapStateToOpts(state) { 48 | return {}; 49 | }; 50 | var defaultMapDispatchToOpts = function defaultMapDispatchToOpts(dispatch) { 51 | return { dispatch: dispatch }; 52 | }; 53 | var defaultMergeOpts = function defaultMergeOpts(stateOpts, dispatchOpts, parentOpts) { 54 | return _extends({}, parentOpts, stateOpts, dispatchOpts); 55 | }; 56 | 57 | var getDisplayName = function getDisplayName(WrappedComponent) { 58 | return WrappedComponent.displayName || _util2.default.lineToCamel(WrappedComponent.originName) || 'Component'; 59 | }; 60 | 61 | var errorObject = { value: null }; 62 | function tryCatch(fn, ctx) { 63 | try { 64 | return fn.apply(ctx); 65 | } catch (e) { 66 | errorObject.value = e; 67 | return errorObject; 68 | } 69 | }; 70 | 71 | // Helps track hot reloading. 72 | var nextVersion = 0; 73 | 74 | function wrapActionCreators(actionCreators) { 75 | return function (dispatch) { 76 | return (0, _redux.bindActionCreators)(actionCreators, dispatch); 77 | }; 78 | } 79 | 80 | function hoistStatics(targetComponent, sourceComponent) { 81 | var RIOT_STATICS = { 82 | displayName: true, 83 | mixins: true, 84 | type: true 85 | }; 86 | 87 | var KNOWN_STATICS = { 88 | name: true, 89 | length: true, 90 | prototype: true, 91 | caller: true, 92 | arguments: true, 93 | arity: true 94 | }; 95 | 96 | var isGetOwnPropertySymbolsAvailable = typeof Object.getOwnPropertySymbols === 'function'; 97 | 98 | if (typeof sourceComponent !== 'string') { 99 | // don't hoist over string (html) components 100 | var keys = Object.getOwnPropertyNames(sourceComponent); 101 | 102 | /* istanbul ignore else */ 103 | if (isGetOwnPropertySymbolsAvailable) { 104 | keys = keys.concat(Object.getOwnPropertySymbols(sourceComponent)); 105 | } 106 | 107 | for (var i = 0; i < keys.length; ++i) { 108 | if (!RIOT_STATICS[keys[i]] && !KNOWN_STATICS[keys[i]] && (!customStatics || !customStatics[keys[i]])) { 109 | try { 110 | targetComponent[keys[i]] = sourceComponent[keys[i]]; 111 | } catch (error) { 112 | console.warn("hoistStatics failed."); 113 | } 114 | } 115 | } 116 | } 117 | 118 | return targetComponent; 119 | } 120 | 121 | /** 122 | * A HOC for connect the tag to redux store. (react-redux like) 123 | */ 124 | function Connect(mapStateToOpts, mapDispatchToOpts, mergeOpts) { 125 | var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : { pure: true, withRef: false }; 126 | 127 | var shouldSubscribe = Boolean(mapStateToOpts); 128 | var mapState = mapStateToOpts || defaultMapStateToOpts; 129 | 130 | var pure = options.pure, 131 | withRef = options.withRef; 132 | 133 | 134 | var mapDispatch = null; 135 | if (typeof mapDispatchToOpts === 'function') { 136 | mapDispatch = mapDispatchToOpts; 137 | } else if (!mapDispatchToOpts) { 138 | mapDispatch = defaultMapDispatchToOpts; 139 | } else { 140 | mapDispatch = wrapActionCreators(mapDispatchToOpts); 141 | } 142 | 143 | var finalMergeOpts = mergeOpts || defaultMergeOpts; 144 | var checkMergedEquals = pure && finalMergeOpts !== defaultMergeOpts; 145 | 146 | var version = nextVersion++; 147 | 148 | return function wrapWithConnect(WrappedComponent) { 149 | var connectDisplayName = 'Connect(' + getDisplayName(WrappedComponent) + ')'; 150 | 151 | function checkStateShape(opts, methodName) { 152 | if (!(0, _isPlainObject2.default)(opts)) { 153 | (0, _warning2.default)(methodName + '() in ' + connectDisplayName + ' must return a plain object. ' + ('Instead received ' + opts + '.')); 154 | } 155 | } 156 | 157 | function computeMergedOpts(stateOpts, dispatchOpts, parentOpts) { 158 | var mergedOpts = finalMergeOpts(stateOpts, dispatchOpts, parentOpts); 159 | if (process.env.NODE_ENV !== 'production') { 160 | checkStateShape(mergedOpts, 'mergeOpts'); 161 | } 162 | return mergedOpts; 163 | } 164 | 165 | var Connect = function (_WrappedComponent) { 166 | _inherits(Connect, _WrappedComponent); 167 | 168 | function Connect() { 169 | _classCallCheck(this, Connect); 170 | 171 | return _possibleConstructorReturn(this, (Connect.__proto__ || Object.getPrototypeOf(Connect)).apply(this, arguments)); 172 | } 173 | 174 | _createClass(Connect, [{ 175 | key: 'onCreate', 176 | value: function onCreate(opts) { 177 | this.version = version; 178 | this.store = opts.store || (0, _provider.getProvider)(this).opts.store; 179 | this.displayName = connectDisplayName; 180 | var storeState = this.store.getState(); 181 | 182 | (0, _invariant2.default)(this.store, 'Could not find "store" in either the context or ' + ('opts of "' + connectDisplayName + '". ') + 'Either wrap the root component in a Provider, ' + ('or explicitly pass "store" as a opt to "' + connectDisplayName + '".')); 183 | 184 | this.state = { storeState: storeState }; 185 | 186 | this.clearCache(); 187 | 188 | this.on('mount', this.componentDidMount); 189 | this.on('before-unmount', this.componentWillUnmount); 190 | this.on('update', this.render); 191 | 192 | _get(Connect.prototype.__proto__ || Object.getPrototypeOf(Connect.prototype), 'onCreate', this).call(this, opts); 193 | } 194 | }, { 195 | key: 'updateStateOptsIfNeeded', 196 | value: function updateStateOptsIfNeeded() { 197 | var nextStateOpts = this.computeStateOpts(this.store, this.opts); 198 | if (this.stateOpts && (0, _shallowEqual2.default)(nextStateOpts, this.stateOpts)) { 199 | return false; 200 | } 201 | 202 | this.stateOpts = nextStateOpts; 203 | return true; 204 | } 205 | }, { 206 | key: 'updateDispatchOptsIfNeeded', 207 | value: function updateDispatchOptsIfNeeded() { 208 | var nextDispatchOpts = this.computeDispatchOpts(this.store, this.opts); 209 | if (this.dispatchOpts && (0, _shallowEqual2.default)(nextDispatchOpts, this.dispatchOpts)) { 210 | return false; 211 | } 212 | 213 | this.dispatchOpts = nextDispatchOpts; 214 | return true; 215 | } 216 | }, { 217 | key: 'updateMergedOptsIfNeeded', 218 | value: function updateMergedOptsIfNeeded() { 219 | var nextMergedOpts = computeMergedOpts(this.stateOpts, this.dispatchOpts, this.opts); 220 | if (this.mergedOpts && checkMergedEquals && (0, _shallowEqual2.default)(nextMergedOpts, this.mergedOpts)) { 221 | return false; 222 | } 223 | 224 | this.mergedOpts = nextMergedOpts; 225 | return true; 226 | } 227 | }, { 228 | key: 'configureFinalMapState', 229 | value: function configureFinalMapState(store, opts) { 230 | var mappedState = mapState(store.getState(), opts); 231 | var isFactory = typeof mappedState === 'function'; 232 | 233 | this.finalMapStateToOpts = isFactory ? mappedState : mapState; 234 | this.doStateOptsDependOnOwnOpts = this.finalMapStateToOpts.length !== 1; 235 | 236 | if (isFactory) { 237 | return this.computeStateOpts(store, opts); 238 | } 239 | 240 | if (process.env.NODE_ENV !== 'production') { 241 | checkStateShape(mappedState, 'mapStateToOpts'); 242 | } 243 | return mappedState; 244 | } 245 | }, { 246 | key: 'handleChange', 247 | value: function handleChange() { 248 | if (!this.unsubscribe) { 249 | return; 250 | } 251 | 252 | var storeState = this.store.getState(); 253 | var prevStoreState = this.state.storeState; 254 | 255 | if (pure && prevStoreState === storeState) { 256 | return; 257 | } 258 | 259 | if (pure && !this.doStateOptsDependOnOwnOpts) { 260 | var haveStateOptsChanged = tryCatch(this.updateStateOptsIfNeeded, this); 261 | if (!haveStateOptsChanged) { 262 | return; 263 | } 264 | if (haveStateOptsChanged === errorObject) { 265 | this.stateOptsPrecalculationError = errorObject.value; 266 | } 267 | this.haveStateOptsBeenPrecalculated = true; 268 | } 269 | 270 | this.hasStoreStateChanged = true; 271 | this.update({ state: storeState }); 272 | } 273 | }, { 274 | key: 'componentDidMount', 275 | value: function componentDidMount() { 276 | this.render(); 277 | this.trySubscribe(); 278 | } 279 | }, { 280 | key: 'componentWillUnmount', 281 | value: function componentWillUnmount() { 282 | this.tryUnsubscribe(); 283 | this.clearCache(); 284 | } 285 | }, { 286 | key: 'trySubscribe', 287 | value: function trySubscribe() { 288 | if (shouldSubscribe && !this.unsubscribe) { 289 | this.unsubscribe = this.store.subscribe(this.handleChange.bind(this)); 290 | this.handleChange(); 291 | } 292 | } 293 | }, { 294 | key: 'tryUnsubscribe', 295 | value: function tryUnsubscribe() { 296 | if (this.unsubscribe) { 297 | this.unsubscribe(); 298 | this.unsubscribe = null; 299 | } 300 | } 301 | }, { 302 | key: 'isSubscribed', 303 | value: function isSubscribed() { 304 | return typeof this.unsubscribe === 'function'; 305 | } 306 | }, { 307 | key: 'clearCache', 308 | value: function clearCache() { 309 | this.dispatchOpts = null; 310 | this.stateOpts = null; 311 | this.mergedOpts = null; 312 | this.haveOwnOptsChanged = true; 313 | this.hasStoreStateChanged = true; 314 | this.haveStateOptsBeenPrecalculated = false; 315 | this.stateOptsPrecalculationError = null; 316 | this.renderedElement = null; 317 | this.finalMapDispatchToOpts = null; 318 | this.finalMapStateToOpts = null; 319 | } 320 | }, { 321 | key: 'componentWillReceiveOpts', 322 | value: function componentWillReceiveOpts(nextProps) { 323 | if (!pure || !(0, _shallowEqual2.default)(nextProps, this.props)) { 324 | this.haveOwnPropsChanged = true; 325 | } 326 | } 327 | }, { 328 | key: 'computeStateOpts', 329 | value: function computeStateOpts(store, opts) { 330 | if (!this.finalMapStateToOpts) { 331 | return this.configureFinalMapState(store, opts); 332 | } 333 | 334 | var state = store.getState(); 335 | var stateOpts = this.doStateOptsDependOnOwnOpts ? this.finalMapStateToOpts(state, opts) : this.finalMapStateToOpts(state); 336 | 337 | if (process.env.NODE_ENV !== 'production') { 338 | checkStateShape(stateOpts, 'mapStateToOpts'); 339 | } 340 | return stateOpts; 341 | } 342 | }, { 343 | key: 'computeDispatchOpts', 344 | value: function computeDispatchOpts(store, opts) { 345 | if (!this.finalMapDispatchToOpts) { 346 | return this.configureFinalMapDispatch(store, opts); 347 | } 348 | 349 | var dispatch = store.dispatch; 350 | 351 | var dispatchOpts = this.doDispatchOptsDependOnOwnProps ? this.finalMapDispatchToOpts(dispatch, opts) : this.finalMapDispatchToOpts(dispatch); 352 | 353 | return dispatchOpts; 354 | } 355 | }, { 356 | key: 'configureFinalMapDispatch', 357 | value: function configureFinalMapDispatch(store, opts) { 358 | var mappedDispatch = mapDispatch(store.dispatch, opts); 359 | var isFactory = typeof mappedDispatch === 'function'; 360 | 361 | this.finalMapDispatchToOpts = isFactory ? mappedDispatch : mapDispatch; 362 | this.doDispatchOptsDependOnOwnOpts = this.finalMapDispatchToOpts.length !== 1; 363 | 364 | if (isFactory) { 365 | return this.computeDispatchOpts(store, opts); 366 | } 367 | 368 | return mappedDispatch; 369 | } 370 | }, { 371 | key: 'render', 372 | value: function render() { 373 | var _this2 = this; 374 | 375 | var haveOwnOptsChanged = this.haveOwnOptsChanged, 376 | hasStoreStateChanged = this.hasStoreStateChanged, 377 | haveStateOptsBeenPrecalculated = this.haveStateOptsBeenPrecalculated, 378 | stateOptsPrecalculationError = this.stateOptsPrecalculationError, 379 | renderedElement = this.renderedElement; 380 | 381 | 382 | this.haveOwnOptsChanged = false; 383 | this.hasStoreStateChanged = false; 384 | this.haveStateOptsBeenPrecalculated = false; 385 | this.stateOptsPrecalculationError = null; 386 | 387 | if (stateOptsPrecalculationError) { 388 | throw stateOptsPrecalculationError; 389 | } 390 | 391 | var shouldUpdateStateOpts = true; 392 | var shouldUpdateDispatchOpts = true; 393 | 394 | if (pure && renderedElement) { 395 | shouldUpdateStateOpts = hasStoreStateChanged || haveOwnOptsChanged && this.doStateOptsDependOnOwnOpts; 396 | shouldUpdateDispatchOpts = haveOwnOptsChanged && this.doDispatchOptsDependOnOwnOpts; 397 | } 398 | 399 | var haveStateOptsChanged = false; 400 | var haveDispatchOptsChanged = false; 401 | 402 | if (haveStateOptsBeenPrecalculated) { 403 | haveStateOptsChanged = true; 404 | } else if (shouldUpdateStateOpts) { 405 | haveStateOptsChanged = this.updateStateOptsIfNeeded(); 406 | } 407 | if (shouldUpdateDispatchOpts) { 408 | haveDispatchOptsChanged = this.updateDispatchOptsIfNeeded(); 409 | } 410 | 411 | var haveMergedOptsChanged = true; 412 | if (haveStateOptsChanged || haveDispatchOptsChanged || haveOwnOptsChanged) { 413 | haveMergedOptsChanged = this.updateMergedOptsIfNeeded(); 414 | } else { 415 | haveMergedOptsChanged = false; 416 | } 417 | 418 | if (!haveMergedOptsChanged && renderedElement) { 419 | return; 420 | } 421 | 422 | this.renderedElement = true; 423 | 424 | Object.assign(this.opts, _extends({}, this.mergedOpts)); 425 | 426 | setTimeout(function () { 427 | _this2.update(); 428 | }, 0); 429 | } 430 | }, { 431 | key: 'name', 432 | get: function get() { 433 | return 'connect-' + _get(Connect.prototype.__proto__ || Object.getPrototypeOf(Connect.prototype), 'name', this) || WrappedComponent.name; 434 | } 435 | }]); 436 | 437 | return Connect; 438 | }(WrappedComponent); 439 | 440 | Connect.displayName = connectDisplayName; 441 | Connect.WrappedComponent = WrappedComponent; 442 | 443 | return Connect; 444 | }; 445 | } --------------------------------------------------------------------------------