Repository: Raynos/main-loop Branch: master Commit: 0f539cd8a8ef Files: 9 Total size: 12.1 KB Directory structure: gitextract_vgylkcq1/ ├── .gitignore ├── .jshintrc ├── .travis.yml ├── LICENCE ├── README.md ├── docs.mli ├── index.js ├── package.json └── test/ └── index.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .DS_Store .monitor .*.swp .nodemonignore releases *.log *.err fleet.json public/browserify bin/*.json .bin build compile .lock-wscript coverage node_modules ================================================ FILE: .jshintrc ================================================ { "maxdepth": 4, "maxstatements": 200, "maxcomplexity": 12, "maxlen": 80, "maxparams": 5, "curly": true, "eqeqeq": true, "immed": true, "latedef": false, "noarg": true, "noempty": true, "nonew": true, "undef": true, "unused": "vars", "trailing": true, "quotmark": true, "expr": true, "asi": true, "browser": false, "esnext": true, "devel": false, "node": false, "nonstandard": false, "predef": ["require", "module", "__dirname", "__filename"] } ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - 0.8 - "0.10" before_script: - npm install - npm install istanbul coveralls script: npm run travis-test ================================================ FILE: LICENCE ================================================ Copyright (c) 2014 Raynos. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # main-loop A rendering loop for diffable UIs `main-loop` is an optimization module for a virtual DOM system. Normally you would re-create the virtual tree every time your state changes. This is not optimum, with main-loop you will only update your virtual tree at most once per request animation frame. `main-loop` basically gives you batching of your virtual DOM changes, which means if you change your model multiple times it will be rendered once asynchronously on the next request animation frame. ## Example ```js var mainLoop = require("main-loop") var h = require("virtual-dom/h") var initState = { fruits: ["apple", "banana"], name: "Steve" } function render(state) { return h("div", [ h("div", [ h("span", "hello "), h("span.name", state.name) ]), h("ul", state.fruits.map(renderFruit)) ]) function renderFruit(fruitName) { return h("li", [ h("span", fruitName) ]) } } // set up a loop var loop = mainLoop(initState, render, { create: require("virtual-dom/create-element"), diff: require("virtual-dom/diff"), patch: require("virtual-dom/patch") }) document.body.appendChild(loop.target) // update the loop with the new application state loop.update({ fruits: ["apple", "banana", "cherry"], name: "Steve" }) loop.update({ fruits: ["apple", "banana", "cherry"], name: "Stevie" }) ``` ## var loop = mainLoop(initState, render, opts) Create a loop object with some initial state, a render function, and some options. Your `function render (state) {}` receives the current state as its argument and must return a virtual-dom object. You must supply: `opts.diff`, `opts.patch`, and `opts.create`. These can be obtained directly from `require("virtual-dom")`. Optionally supply an `opts.target` and `opts.initialTree`. ## loop.target The main-loop root DOM element. Insert this element to the page. ## loop.update(newState) Update the page state, automatically re-rendering the page as necessary. ## loop.state Read the current main-loop state. To modify the loop state, use `loop.update()`. ## Installation `npm install main-loop` ## Contributors - Raynos ## MIT Licenced [1]: https://secure.travis-ci.org/Raynos/main-loop.png [2]: https://travis-ci.org/Raynos/main-loop [3]: https://badge.fury.io/js/main-loop.png [4]: https://badge.fury.io/js/main-loop [5]: https://coveralls.io/repos/Raynos/main-loop/badge.png [6]: https://coveralls.io/r/Raynos/main-loop [7]: https://gemnasium.com/Raynos/main-loop.png [8]: https://gemnasium.com/Raynos/main-loop [9]: https://david-dm.org/Raynos/main-loop.png [10]: https://david-dm.org/Raynos/main-loop [11]: https://ci.testling.com/Raynos/main-loop.png [12]: https://ci.testling.com/Raynos/main-loop ================================================ FILE: docs.mli ================================================ import { VElem, VPatch } from "vtree" import { DOMElement } from "jsig.dom" type MainLoop : ( initialState: T, view: (T) => VElem, opts?: { create?: (VElem, opts: Object) => DOMElement, diff?: (prev: VElem, curr: VElem, opts: Object) => Array, patch?: ( target: DOMElement, patches: Array, opts: Object ) => void, initialTree?: VElem, target?: DOMElement, createOnly?: Boolean } ) => { target: DOMElement, update: (T) => void } main-loop : MainLoop ================================================ FILE: index.js ================================================ var raf = require("raf") var TypedError = require("error/typed") var InvalidUpdateInRender = TypedError({ type: "main-loop.invalid.update.in-render", message: "main-loop: Unexpected update occurred in loop.\n" + "We are currently rendering a view, " + "you can't change state right now.\n" + "The diff is: {stringDiff}.\n" + "SUGGESTED FIX: find the state mutation in your view " + "or rendering function and remove it.\n" + "The view should not have any side effects.\n" + "This may also have happened if rendering did not complete due to an error.\n", diff: null, stringDiff: null }) module.exports = main function main(initialState, view, opts) { opts = opts || {} var currentState = initialState var create = opts.create var diff = opts.diff var patch = opts.patch var redrawScheduled = false var tree = opts.initialTree || view(currentState, 0); var target = opts.target || create(tree, opts) var inRenderingTransaction = false currentState = null var loop = { state: initialState, target: target, update: update } return loop function update(state) { if (inRenderingTransaction) { throw InvalidUpdateInRender({ diff: state._diff, stringDiff: JSON.stringify(state._diff) }) } if (currentState === null && !redrawScheduled) { redrawScheduled = true raf(redraw) } currentState = state loop.state = state } function redraw(time) { redrawScheduled = false if (currentState === null) { return } inRenderingTransaction = true var newTree = view(currentState, time) if (opts.createOnly) { inRenderingTransaction = false create(newTree, opts) } else { var patches = diff(tree, newTree, opts) inRenderingTransaction = false target = patch(target, patches, opts) } tree = newTree currentState = null } } ================================================ FILE: package.json ================================================ { "name": "main-loop", "version": "3.4.0", "description": "A rendering loop for diffable UIs", "keywords": [], "author": "Raynos ", "repository": "git://github.com/Raynos/main-loop.git", "main": "index", "homepage": "https://github.com/Raynos/main-loop", "contributors": [ { "name": "Raynos" } ], "bugs": { "url": "https://github.com/Raynos/main-loop/issues", "email": "raynos2@gmail.com" }, "dependencies": { "error": "^4.1.1", "raf": "^2.0.1" }, "devDependencies": { "global": "^3.0.0", "istanbul": "^0.3.0", "tape": "^2.13.3", "virtual-dom": "0.0.23" }, "licenses": [ { "type": "MIT", "url": "http://github.com/Raynos/main-loop/raw/master/LICENSE" } ], "scripts": { "test": "node ./test/index.js", "start": "node ./index.js", "watch": "nodemon -w ./index.js index.js", "travis-test": "istanbul cover ./test/index.js && ((cat coverage/lcov.info | coveralls) || exit 0)", "cover": "istanbul cover --report none --print detail ./test/index.js", "view-cover": "istanbul report html && google-chrome ./coverage/index.html", "test-browser": "testem-browser ./test/browser/index.js", "testem": "testem-both -b=./test/browser/index.js" }, "testling": { "files": "test/index.js", "browsers": [ "ie/8..latest", "firefox/16..latest", "firefox/nightly", "chrome/22..latest", "chrome/canary", "opera/12..latest", "opera/next", "safari/5.1..latest", "ipad/6.0..latest", "iphone/6.0..latest", "android-browser/4.2..latest" ] } } ================================================ FILE: test/index.js ================================================ var test = require("tape") var h = require("virtual-dom/virtual-hyperscript") var document = require("global/document") var raf = require("raf") var mainLoop = require("../index") test("mainLoop is a function", function (assert) { assert.equal(typeof mainLoop, "function") assert.end() }) test("can set up main loop", function (assert) { var initState = { fruits: ["apple", "banana"], name: "Steve" } function render(state) { return h("div", [ h("div", [ h("span", "hello "), h("span.name", state.name) ]), h("ul", state.fruits.map(renderFruit)) ]) function renderFruit(fruitName) { return h("li", [ h("span", fruitName) ]) } } // set up a loop var loop = mainLoop(initState, render, { document: document, create: require("virtual-dom/create-element"), diff: require("virtual-dom/diff"), patch: require("virtual-dom/patch") }) document.body.appendChild(loop.target) var div = loop.target var span = div.childNodes[0].childNodes[1] var ul = div.childNodes[1] assert.equal(div.tagName, "DIV") assert.equal(span.childNodes[0].data, "Steve") assert.equal(ul.childNodes.length, 2) // update the loop with the new application state loop.update({ fruits: ["apple", "banana", "cherry"], name: "Steve" }) raf(function () { assert.equal(ul.childNodes.length, 3) loop.update({ fruits: ["apple", "banana", "cherry"], name: "Stevie" }) raf(function () { assert.equal(span.childNodes[0].data, "Stevie") document.body.removeChild(loop.target) assert.end() }) }) }) test("loop.state exposed", function (assert) { var loop = mainLoop({ n: 0 }, render, { document: document, create: require("virtual-dom/create-element"), diff: require("virtual-dom/diff"), patch: require("virtual-dom/patch") }) assert.equal(loop.state.n, 0) loop.update({ n: 4 }) assert.equal(loop.state.n, 4) assert.end() function render(state) { return h('div', String(state.n)) } }) test("render called with monotonically increasing times", function(assert){ assert.plan(2) var times = [] var loop = mainLoop({ n: 0 }, render, { document: document, create: require("virtual-dom/create-element"), diff: require("virtual-dom/diff"), patch: require("virtual-dom/patch") }) function render(state, time) { times.push(time); return h('div', String(state.n)) } raf(function () { loop.update({ n: 1}) raf(function () { loop.update({ n: 2}) assert.equal(times.length, 2) assert.ok(times[0] < times[1], "should be increasing") assert.end() }) }) });