├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── package.json └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Mikko Haapoja 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bigwheel 2 | ![bigwheel logo](https://raw.githubusercontent.com/bigwheel-framework/documentation/master/images/bigwheellogo.png) 3 | 4 | [![stable](http://badges.github.io/stability-badges/dist/stable.svg)](http://github.com/badges/stability-badges) 5 | 6 | bigwheel is an unopinionated, minimalist framework which handles frontend application state. It can be used to organize your application into "sections"/pages which are brought in by routes. Animation is a first class citizen and is accounted for when managing application states. bigwheel does not conform to a specific render engine framework so a project which is based on the DOM, WebGL, Canvas2D, SVG, or even Console applications can be built using bigwheel. 7 | 8 | ## Full Documentation 9 | 10 | [https://github.com/bigwheel-framework/documentation](https://github.com/bigwheel-framework/documentation) 11 | 12 | ## Usage 13 | 14 | [![NPM](https://nodei.co/npm/bigwheel.png)](https://www.npmjs.com/package/bigwheel) 15 | 16 | ## Example 17 | 18 | Note this is not a "best practice" example but simply a concise example that shows many of the features of `bigwheel`. Refer to the documentation link above for best practices and other information. 19 | 20 | ```javascript 21 | var bigwheel = require('bigwheel'); 22 | var Tween = require('gsap'); 23 | 24 | // create our framework instance 25 | var framework = bigwheel( function(done) { 26 | 27 | // the function passed to bigwheel should return 28 | // a setting object or alternately you can pass 29 | // the setting object to the callback defined as 30 | // done. This is nice if you need to do assynchronous 31 | // loading before content should be shown 32 | return { 33 | 34 | // define our routes 35 | // routes are associated to "sections" 36 | // sections are functions or objects 37 | // Any route can contain a routes object to specify subroutes. This example adds the '/Gallery' route and '/Gallery/:id' 38 | routes: { 39 | '/': Section, 40 | '/about': Section, 41 | '/contact': Section, 42 | '/Gallery': {section: Section, routes: { 43 | '/:id': {section: Section} 44 | } 45 | } 46 | } 47 | }; 48 | }); 49 | 50 | // this will start bigwheel and it will start resolving routes 51 | framework.init(); 52 | 53 | // This is the definition for the sections which bigwheel will run 54 | // sections can define init, resize, animateIn, animateOut, destroy functions 55 | // these will methods will be called by bigwheel 56 | function Section() { 57 | 58 | var el; 59 | 60 | return { 61 | 62 | // the init function creates the view and initializes it 63 | // after init finishes the view should not be visible 64 | init: function(req, done) { 65 | el = createEl(req); 66 | el.onclick = function() { 67 | framework.go(getToSection(req)); 68 | }; 69 | done(); 70 | }, 71 | 72 | // the resize function will be called imediately after init 73 | // here you can apply "responsive" calculations on your view 74 | resize: function(width, height) { 75 | var fontSize = width / 500 * 30; 76 | el.style.fontSize = fontSize + 'px'; 77 | el.style.top = Math.round(( height - fontSize ) * 0.5) + 'px'; 78 | }, 79 | 80 | // in animateIn you'll animate in your hidden content that 81 | // was created in init 82 | animateIn: function(req, done) { 83 | Tween.from(el, 1, { 84 | y: -100, 85 | opacity: 0, 86 | ease: Back.easeOut, 87 | onComplete: done 88 | }); 89 | }, 90 | 91 | // in animateOut you'll animate out your content that 92 | // was created in init 93 | animateOut: function(req, done) { 94 | Tween.to(el, 0.25, { 95 | y: 100, 96 | opacity: 0, 97 | ease: Back.easeIn, 98 | onComplete: done 99 | }); 100 | }, 101 | 102 | // in destroy you'll clean up the content which was 103 | // created in init 104 | destroy: function(req, done) { 105 | el.parentNode.removeChild(el); 106 | } 107 | }; 108 | } 109 | 110 | // this is just a utility function created for this example to create 111 | // an element which will be added to the dom and initialized 112 | function createEl(req) { 113 | var el = document.createElement('a'); 114 | el.innerHTML = 'Click to go from "' + req.route + '" to "' + getToSection(req) + '"'; 115 | el.style.position = 'absolute'; 116 | el.style.cursor = 'pointer'; 117 | return document.body.appendChild(el); 118 | } 119 | 120 | // this function acts as almost like a model for this example 121 | // generally you'd either load your model from a server or 122 | // have a static model object 123 | function getToSection(req) { 124 | return { 125 | '/': '/about', 126 | '/about': '/contact', 127 | '/contact': '/' 128 | }[ req.route ]; 129 | } 130 | ``` 131 | 132 | ## License 133 | 134 | MIT, see [LICENSE.md](http://github.com/bigwheel-framework/bigwheel/blob/master/LICENSE) for details. 135 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** @module bigwheel */ 2 | 3 | var vm = require('bw-vm'); 4 | var viewmediator = require('bw-viewmediator'); 5 | var router = require('bw-router'); 6 | var on = require('dom-event'); 7 | var EventEmitter = require('events').EventEmitter; 8 | 9 | /** 10 | * When instantiating bigwheel you must pass in a setup function. 11 | * 12 | * In this function you may do any preparation that must be done for your 13 | * application such as creating a global Canvas element or something else. 14 | * 15 | * The setup function must either return a settings object for bigwheel or 16 | * this function must receive a callback which you will call with the settings 17 | * object. Furthermore you can pass back a promise from this settings function 18 | * which will receive the settings object. 19 | * 20 | * The following documents what can be passed in the settings object: 21 | * ```javascript 22 | * { 23 | * ///// REQUIRED ///// 24 | * 25 | * // routes defines all the routes for your website it can also define a 26 | * // 404 section which will be opened if the route is incorrect 27 | * routes: { 28 | * postHash: '#!', // this string is appended before the route. 29 | * // by default it's value is '#!' 30 | * '/': someSection, 31 | * '/someOther': someOtherSection, 32 | * '404': sectionFourOhFour 33 | * }, 34 | * 35 | * ///// OPTIONAL ///// 36 | * initSection: preSection, // this could be a section that is run always 37 | * // before routes are even evaluated. This is 38 | * // usefulf for site preloaders or landing pages 39 | * // such as age verification (something the user 40 | * // must see) 41 | * 42 | * autoResize: true, // by default this value is true. When this value is 43 | * // true a resize listener is added to the window 44 | * // whenever the window changes size it's width and 45 | * // height is passed to all instantiated sections 46 | * } 47 | * ``` 48 | * 49 | * @class bigwheel 50 | * @param {Function} settingsFunc This settings function will be used to 51 | * initialize bigwheel. 52 | */ 53 | function bigwheel(settingsFunc) { 54 | 55 | if(!(this instanceof bigwheel)) 56 | return new bigwheel(settingsFunc); 57 | 58 | this.settingsFunc = settingsFunc; 59 | EventEmitter.call(this); 60 | 61 | } 62 | 63 | bigwheel.prototype = Object.create(EventEmitter.prototype); 64 | 65 | /** 66 | * init must be called to start the framework. This was done to allow for 67 | * a developer to have full control of when bigwheel starts doing it's thing. 68 | */ 69 | bigwheel.prototype.init = function() { 70 | 71 | var onSettingComplete = function(settings) { 72 | 73 | var s = this.s = settings; 74 | 75 | if(s === undefined) 76 | throw new Error('Your settings function must return a settings Object'); 77 | 78 | if(s.routes === undefined) 79 | throw new Error('Your settings object must define routes'); 80 | 81 | s.autoResize = s.autoResize === undefined ? true : s.autoResize; 82 | 83 | this.previousRoute = undefined; 84 | 85 | this.depth = []; 86 | this.vms = []; 87 | this.routes = {}; 88 | this.parseRoutes(settings.routes); 89 | 90 | // setup the router 91 | this.router = router(this.routes); 92 | this.router.on('route', this.show.bind(this)); 93 | 94 | // Re-dispatch routes 95 | this.router.on('route',this.emit.bind(this,'route')); 96 | 97 | // check if 98 | if(s.autoResize && global.innerWidth !== undefined && global.innerHeight !== undefined) { 99 | 100 | on(global, 'resize', this.onResize.bind(this)); 101 | 102 | this.onResize(); 103 | } 104 | 105 | // handle if there is an init section this should be shown even before 106 | // the router resolves 107 | if(s.initSection) 108 | this.show({section: s.initSection.bind(undefined, this.router.init.bind(this.router))}); 109 | else 110 | this.router.init(); 111 | }.bind(this); 112 | 113 | 114 | var rVal = this.settingsFunc(onSettingComplete); 115 | 116 | // check if promises are used instead 117 | // it might be good to remove this since theres no 118 | // need for promises in this case 119 | if(rVal && rVal.then) 120 | rVal.then(onSettingComplete); 121 | // check if just an object was returned which has .routes 122 | else if(rVal && rVal.routes) 123 | onSettingComplete(rVal); 124 | 125 | return this; 126 | }; 127 | 128 | bigwheel.prototype.parseRoutes = function(routes,prefix) { 129 | var depth = (prefix || '').split('/').length; 130 | if (this.vms.lengthdepth.length-1) { 211 | this.vms[i].clear(req); 212 | } else if (prevDepth[i]!=depth[i]) { 213 | this.vms[i].show(this.parseSection(views[i]),req); 214 | } 215 | } 216 | } else { 217 | this.vms[0].show(this.parseSection(section.section || section),req); 218 | } 219 | 220 | this.previousRoute = info.route; 221 | }; 222 | 223 | bigwheel.prototype.rebuildRoute = function(route,path) { 224 | var path = path.split('/') 225 | path.length = route.split('/').length; 226 | return path.join('/'); 227 | }; 228 | 229 | bigwheel.prototype.parseSection = function(section) { 230 | if (Array.isArray(section)) { 231 | for (var i=0; i", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/bigwheel-framework/bigwheel/issues" 27 | }, 28 | "homepage": "https://github.com/bigwheel-framework/bigwheel", 29 | "dependencies": { 30 | "bw-router": "^2.0.1", 31 | "bw-viewmediator": "^2.2.0", 32 | "bw-vm": "^2.1.0", 33 | "dom-event": "github:mikkoh/dom-event" 34 | }, 35 | "devDependencies": { 36 | "promise": "^6.0.1", 37 | "tape": "^3.0.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | var bigwheel = require('./..'); 3 | 4 | setTimeout(function() {}, 1000); 5 | 6 | test('testing single routes', function( t) { 7 | 8 | t.plan(6); 9 | 10 | reset(); 11 | 12 | var framework = bigwheel(function(done) { 13 | 14 | done( { 15 | 16 | routes: { 17 | 18 | '/': { 19 | 20 | init: function(req, done) { 21 | 22 | t.equal(req.route, '/', '"/" init received "/"'); 23 | 24 | done(); 25 | }, 26 | 27 | animateIn: function(req, done) { 28 | 29 | t.equal(req.route, '/', '"/" animateIn received "/"'); 30 | 31 | done(); 32 | 33 | framework.go('/about'); 34 | }, 35 | 36 | animateOut: function(req, done) { 37 | 38 | t.equal(req.route, '/about', '"/" animateOut received "/about"'); 39 | 40 | done(); 41 | }, 42 | 43 | destroy: function(req, done) { 44 | 45 | t.pass('"/" destroy'); 46 | 47 | done(); 48 | } 49 | }, 50 | 51 | '/about': function() { 52 | 53 | return { 54 | 55 | init: function(req, done) { 56 | 57 | t.equal(req.route, '/about', '"/about" init received "/about"'); 58 | 59 | done(); 60 | }, 61 | 62 | animateIn: function(req, done) { 63 | 64 | t.equal(req.route, '/about', '"/about" animateIn received "/about"'); 65 | done(); 66 | 67 | framework.destroy(); 68 | t.end(); 69 | } 70 | }; 71 | } 72 | } 73 | }); 74 | }); 75 | 76 | framework.init(); 77 | }); 78 | 79 | test('testing sub frameworks', function(t) { 80 | reset(); 81 | 82 | t.plan(9); 83 | 84 | var gallery = function() { 85 | return { 86 | init: function(req,done) { 87 | t.equal(req.route,'/gallery','/gallery init received /gallery'); 88 | req.framework.go('/gallery/type'); 89 | done(); 90 | }, 91 | destroy: function(req,done) { 92 | t.equal(req.route,'/end','/gallery destroy received /end'); 93 | done(); 94 | } 95 | } 96 | }; 97 | 98 | var galleryType = function() { 99 | return { 100 | init: function(req,done) { 101 | t.equal(req.route,'/gallery/:type','/gallery/:type init received /gallery/:type'); 102 | req.framework.go('/gallery/type/1'); 103 | done(); 104 | }, 105 | destroy: function(req,done) { 106 | t.equal(req.route,'/end','/gallery/:type destroy received /end'); 107 | done(); 108 | } 109 | } 110 | }; 111 | 112 | var count = 0; 113 | var galleryTypeImage = function() { 114 | return { 115 | init: function(req,done) { 116 | count++; 117 | t.equal(req.route,'/gallery/:type/:image','/gallery/:type/:image init received /gallery/:type/:image'); 118 | if (count<2) { 119 | req.framework.go('/gallery/type/2'); 120 | } else { 121 | req.framework.go('/end'); 122 | } 123 | done(); 124 | }, 125 | destroy: function(req,done) { 126 | if (count<2) { 127 | t.equal(req.route,'/gallery/:type/:image','/gallery/:type/:image destroy received /gallery/:type/:image'); 128 | } else { 129 | t.equal(req.route,'/end','/gallery/:type/:image destroy received /end'); 130 | } 131 | done(); 132 | } 133 | } 134 | }; 135 | 136 | var end = function() { 137 | return { 138 | init: function(req,done) { 139 | t.equal(req.route,'/end','/end init received /end'); 140 | done(); 141 | }, 142 | animateIn: function(req,done) { 143 | done(); 144 | setTimeout(function() { 145 | t.end(); 146 | req.framework.destroy(); 147 | },100); 148 | }, 149 | destroy: function(req,done) { 150 | done(); 151 | } 152 | } 153 | }; 154 | 155 | bigwheel(function(done) { 156 | done({ 157 | routes: { 158 | '/': '/gallery', 159 | '/end': end, 160 | '/gallery': {section: gallery, routes: { 161 | '/:type': {section: galleryType, duplicate: true, routes: { 162 | '/:image': {section: galleryTypeImage, duplicate: true} 163 | }} 164 | }} 165 | } 166 | }) 167 | }).init(); 168 | }); 169 | 170 | function reset() { 171 | if(global.location) { 172 | global.location.hash = ''; 173 | } 174 | if (global.history && global.history.pushState) { 175 | global.history.pushState({},'','/'); 176 | } 177 | } 178 | --------------------------------------------------------------------------------