├── .gitignore ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE-MIT ├── README.md ├── before_script.sh ├── bin ├── lazo └── postinstall ├── conf.json ├── docs ├── Context.md ├── LazoApp.md ├── LazoApplicationStrucutre.md ├── LazoCollection.md ├── LazoCollectionView.md ├── LazoConfigurationProvider.md ├── LazoController.md ├── LazoModel.md ├── LazoState.md ├── LazoSyncher.md ├── LazoView.md ├── LazoWidget.md └── ServiceProxy.md ├── index.js ├── lazo.js ├── lazojs.png ├── lib ├── client │ ├── app.js │ ├── assetsProvider.js │ ├── bootstrap.js │ ├── destroyCmp.js │ ├── execute.js │ ├── loader.js │ ├── prime.js │ ├── proxy.js │ ├── rehydrate │ │ ├── main.js │ │ ├── model.js │ │ └── view.js │ ├── state.js │ └── viewManager.js ├── common │ ├── app.js │ ├── base.js │ ├── config.js │ ├── context.js │ ├── error.js │ ├── logger.js │ ├── renderer.js │ ├── requestFilters.js │ ├── resolver │ │ ├── assets.js │ │ ├── component.js │ │ ├── file.js │ │ ├── main.js │ │ ├── model.js │ │ ├── paths.json │ │ ├── requireConfigure.js │ │ └── route.js │ ├── templates │ │ ├── 403.hbs │ │ ├── 404.hbs │ │ ├── 500.hbs │ │ └── page.hbs │ └── utils │ │ ├── assetsMixin.js │ │ ├── cmpProcessor.js │ │ ├── ctlSerializor.js │ │ ├── dataschema-json.js │ │ ├── document.js │ │ ├── handlebarsEngine.js │ │ ├── loader.js │ │ ├── model.js │ │ ├── modelLoader.js │ │ ├── module.js │ │ ├── prune.js │ │ ├── sanitizer.js │ │ ├── template.js │ │ ├── treeMixin.js │ │ └── uniqueId.js ├── public │ ├── assets.js │ ├── bundle.js │ ├── collection.js │ ├── controller.js │ ├── model.js │ ├── server │ │ ├── server.js │ │ └── syncher.js │ ├── views │ │ ├── base.js │ │ ├── collection.js │ │ ├── state.js │ │ ├── view.js │ │ └── widget.js │ └── widget.js └── server │ ├── app.js │ ├── assetsProvider.js │ ├── backbone.js │ ├── cls-facade.js │ ├── forbidden.js │ ├── handlers │ ├── app │ │ ├── html.js │ │ ├── main.js │ │ └── processor.js │ ├── assets.js │ ├── safe.js │ ├── stream.js │ ├── tunnel.js │ └── utils.js │ ├── httpResponse.js │ ├── jquery.js │ ├── loader.js │ ├── logger │ ├── fileSink.js │ └── utils.js │ ├── proxy.js │ ├── resolver │ └── file.js │ ├── scanner.js │ ├── server.js │ └── serviceProxy.js ├── package.json ├── run.js ├── start.js ├── stop.js ├── templates └── README.md ├── test ├── application │ ├── app │ │ ├── assets │ │ │ ├── en-US │ │ │ │ ├── img │ │ │ │ │ └── logo.png │ │ │ │ └── strings.json │ │ │ ├── info.pdf │ │ │ └── strings.json │ │ └── imports │ │ │ └── index.html │ └── components │ │ ├── bar │ │ ├── assets │ │ │ ├── en-US │ │ │ │ ├── img │ │ │ │ │ └── logo.png │ │ │ │ └── strings.json │ │ │ ├── info.pdf │ │ │ └── strings.json │ │ └── imports │ │ │ └── index.html │ │ └── foo │ │ └── views │ │ ├── child.js │ │ └── index.hbs ├── mocks │ ├── css │ │ ├── a.css │ │ ├── b.css │ │ ├── c.css │ │ └── d.css │ ├── lazo.js │ └── server │ │ ├── continuation-local-storage.js │ │ ├── hapi.js │ │ └── request.js └── unit │ ├── client-server │ ├── common │ │ ├── config.js │ │ ├── logger.js │ │ ├── renderer.js │ │ ├── resolver │ │ │ ├── assets.js │ │ │ ├── component.js │ │ │ ├── file.js │ │ │ └── route.js │ │ └── utils │ │ │ ├── handlebarsEngine.js │ │ │ ├── model.js │ │ │ └── template.js │ ├── public │ │ ├── bundle.js │ │ ├── collection.js │ │ ├── controller.js │ │ ├── model.js │ │ ├── views │ │ │ ├── base.js │ │ │ ├── collection.js │ │ │ ├── state.js │ │ │ └── widget.js │ │ └── widget.js │ └── utils │ │ ├── ctlSerializor.js │ │ └── treeMixin.js │ ├── client │ ├── common │ │ ├── app.js │ │ ├── resolver │ │ │ └── requireConfigure.js │ │ └── utils │ │ │ └── document.js │ ├── context.js │ ├── destroyCmp.js │ ├── loader.js │ ├── public │ │ └── views │ │ │ └── state.js │ ├── state.js │ └── viewManager.js │ ├── conf.client.js │ ├── conf.client.local.js │ ├── conf.client.phantomjs.js │ ├── conf.server.js │ ├── server │ ├── assetsProvider.js │ ├── common │ │ ├── app.js │ │ ├── resolver │ │ │ └── requireConfigure.js │ │ └── utils │ │ │ └── document.js │ ├── context.js │ ├── forbidden.js │ ├── handlers │ │ ├── safe.skip.js │ │ └── tunnel.js │ ├── httpResponse.js │ ├── loader.js │ ├── public │ │ └── controller.js │ ├── serviceProxy.js │ └── streamHandler.skip.js │ └── utils.js └── version.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | lib-cov 3 | node_modules 4 | reports 5 | test/specs/html/** 6 | node_modules 7 | npm-debug.log 8 | lib/vendor 9 | .DS_Store 10 | phantomjsdriver.log 11 | logs/ 12 | lazo.pid 13 | lib/optimized -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | !lib/optimized 2 | !lib/vendor 3 | .idea 4 | lib-cov 5 | node_modules 6 | reports 7 | test/specs/html/** 8 | node_modules 9 | npm-debug.log 10 | .DS_Store 11 | phantomjsdriver.log 12 | logs/ 13 | lazo.pid -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | before_install: npm install -g grunt-cli 5 | branches: 6 | only: 7 | - master 8 | - development 9 | - v2 10 | notifications: 11 | email: 12 | - jstrimpel@gmail.com 13 | - pknepper@walmartlabs.com 14 | before_script: ./before_script.sh -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2014 WalmartLabs 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | lazojs 3 | 4 | 5 | 6 |

7 |

8 |
9 |

10 | 11 | Lazojs is a client-server web framework built on Node.js that allows front-end developers to easily create a 12 | 100% SEO compliant, component MVC structured web application with an optimized first page 13 | load using a familiar tool stack comprised of [Backbone.js](http://backbonejs.org/), 14 | [RequireJS](http://requirejs.org/), and [jQuery](http://jquery.com/). 15 | 16 | > Have a question? Want to keep up-to-date on changes and releases? Post questions to the [LazoJS Google Group](https://groups.google.com/forum/#!forum/lazojs) and follow [@lazojs](https://twitter.com/lazojs) on Twitter. 17 | 18 | > Looking for documentation? Check out our [wiki](https://github.com/walmartlabs/lazojs/wiki). 19 | 20 | ### Problem 21 | The single page application ([SPA](http://en.wikipedia.org/wiki/Single-page_application)) model is an excellent 22 | approach for separating application logic from data retrieval; consolidating UI code to a single language and run 23 | time; and delegating rendering to browsers. However, the SPA model fails to adequately address SEO concerns and 24 | time to first page render making it a major concern for any public facing website. As such, developers rely on 25 | work-arounds such as the hashbang hack or running the DOM on the server so they can realize the benefits of the 26 | SPA model and address SEO concerns. These work-arounds, however, have significant performance and maintenance drawbacks. 27 | 28 | ### Solution 29 | Lazo was created by WalmartLabs to address these issues and provide front-end engineers with a familiar environment for 30 | creating web applications. Pages are constructed via reusable, nestable components that have their own life cycles 31 | allowing developers to easily create complex views while providing excellent encapsulation and separation of concerns. 32 | These pages are mapped to fully qualified, SEO compliant URIs. Lazo renders the first page load on the server via a 33 | rendering engine that uses string concatenation. Subsequent page requests are rendered by browsers that support HTML5's 34 | [pushstate](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history) capability. For those 35 | browsers that do not support pushstate, Lazo falls back to rendering views on the server. This approach allows developers to 36 | reap the SEO benefits of the traditional web application model, while still working in a context with which they are 37 | familiar, and realizing all SPA model benefits without coding for them. 38 | 39 | [**Learn More**](https://github.com/walmartlabs/lazojs/wiki/Overview) 40 | 41 | ## Getting Started 42 | Lazo is a [node module](https://npmjs.org/). Installing and creating a new Lazo application is as easy as uno, dos, tres. 43 | 44 | ### Installation and Application Creation 45 | 46 | To install Lazo execute the following command: 47 | 48 | ```shell 49 | npm install -g --production lazo 50 | ``` 51 | 52 | Next clone [Lazo TodoMVC example](https://github.com/lazojs/lazo-todomvc): 53 | 54 | ```shell 55 | git clone git@github.com:lazojs/lazo-todomvc.git 56 | ``` 57 | 58 | Install dependencies (from local lazo-todomvc repo): 59 | 60 | ```shell 61 | npm install 62 | ``` 63 | 64 | Finally start the new application (from local lazo-todomvc repo): 65 | 66 | ```shell 67 | npm start 68 | ``` 69 | 70 | You are done. 71 | 72 | Open `http://localhost:8080` in a browser to verify that the application is running. 73 | -------------------------------------------------------------------------------- /before_script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$TRAVIS_SECURE_ENV_VARS" = "false" ]; then 4 | node node_modules/selenium-server/bin/selenium & 5 | fi -------------------------------------------------------------------------------- /bin/lazo: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | var lazo = require('../run.js'); 6 | lazo(); -------------------------------------------------------------------------------- /bin/postinstall: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | 6 | ['lib/vendor', 'lib/vendor/client'].forEach(function (dir) { 7 | if (!fs.existsSync(dir)) { 8 | fs.mkdirSync(dir); 9 | } 10 | }); 11 | 12 | [ 13 | 'backbone', 14 | 'jquery', 15 | 'underscore', 16 | 'requirejs', 17 | 'requirejs-plugins', 18 | 'handlebars', 19 | 'jquery.cookie', 20 | 'hermes-conrad', 21 | 'async', 22 | 'lodash', 23 | 'flexo.js', 24 | 'htmlparser-tostring', 25 | 'htmlparser', 26 | 'requirejs-text' 27 | ].forEach(function (moduleId) { 28 | var modulePath = require.resolve(moduleId); 29 | var client = ''; 30 | if (moduleId === 'requirejs') { 31 | // resolve to node_modules/requirejs/bin/r.js; pop off file name and last dir 32 | // and append the browser lib 33 | modulePath = path.dirname(path.dirname(modulePath)) + '/require.js'; 34 | moduleId = 'require'; 35 | } else if (moduleId === 'hermes-conrad') { 36 | modulePath = path.dirname(modulePath) + '/hermes.amd.js'; 37 | moduleId = 'hermes.amd'; 38 | client = 'client/'; 39 | } else if (moduleId === 'flexo.js') { 40 | modulePath = path.dirname(modulePath) + '/flexo.amd.js'; 41 | moduleId = 'flexo.amd'; 42 | } else if (moduleId === 'jquery.cookie') { 43 | client = 'client/'; 44 | moduleId = 'jquery-cookie'; 45 | } else if (moduleId === 'jquery') { 46 | client = 'client/'; 47 | } else if (moduleId === 'requirejs-plugins') { 48 | moduleId = 'json'; 49 | } else if (moduleId === 'handlebars') { 50 | moduleId = 'handlebars.amd'; 51 | modulePath = path.dirname(path.dirname(modulePath)) + '/dist/' + moduleId + '.js'; 52 | } else if (moduleId === 'htmlparser-tostring') { 53 | modulePath = path.dirname(path.dirname(modulePath)) + '/dist/index.amd.js'; 54 | } else if (moduleId === 'htmlparser') { 55 | modulePath = path.dirname(path.dirname(modulePath)) + '/lib/htmlparser.js'; 56 | } else if (moduleId === 'requirejs-text') { 57 | moduleId = 'text'; 58 | } 59 | 60 | fs.writeFileSync('lib/vendor/' + client + moduleId + '.js', fs.readFileSync(modulePath)); 61 | }); -------------------------------------------------------------------------------- /conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "directories": { 3 | "application": "application", 4 | "components": "components", 5 | "models": "models" 6 | }, 7 | "server": { 8 | "instances": { 9 | "primary": { 10 | "port": 8080 11 | } 12 | }, 13 | "defaults": { 14 | "timeout": { 15 | "server": 30000, 16 | "client": 30000 17 | }, 18 | "maxBytes": 10485760, 19 | "debug": false, 20 | "state": { 21 | "cookies": { 22 | "failAction": "log", 23 | "strictHeader": false 24 | } 25 | } 26 | }, 27 | "maxSockets": 500 28 | }, 29 | "client": {}, 30 | "requirejs": { 31 | "common": {}, 32 | "server": {}, 33 | "client": { 34 | "shim": { 35 | "handlebars": { 36 | "exports": "Handlebars" 37 | }, 38 | "htmlparser": { 39 | "exports": "Tautologistics.NodeHtmlParser" 40 | } 41 | }, 42 | "paths": {} 43 | } 44 | }, 45 | "libPath": "/lib/optimized/lib.js" 46 | } -------------------------------------------------------------------------------- /docs/Context.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazojs/lazo/0b91b8b3b9f5246414b23458e8202fc4940ebd45/docs/Context.md -------------------------------------------------------------------------------- /docs/LazoApplicationStrucutre.md: -------------------------------------------------------------------------------- 1 | Lazo keeps your code organized by breaking your code up into three main directories. In addition to these directories there are a few subdirectories and some naming conventions: 2 | 3 | * `server` directories contain code that is only loaded and executed on the server, e.g., a model syncher. Code that resides in “server” and “node_modules” directories are never served to the client. 4 | * `client` directories contain code that is only loaded and executed on the client, .e.g., CSS files or JavaScript widgets that rely on the DOM. 5 | * Code that resides outside of these directories is considered common code and will be loaded both on the server and the client. 6 | 7 | These naming conventions are used in conjunction with the Lazo module loader. A further breakdown of these main directories can be seen in the [components](LazoComponent.md), and [models](LazoModel.md) documentation. 8 | 9 | ```shell 10 | /app 11 | Application level code such as application.js, CSS, and utility modules 12 | 13 | /components 14 | Components - business logic encapsulation that ultimately produces HTML to be rendered. 15 | 16 | /models 17 | Models and collections 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/LazoCollection.md: -------------------------------------------------------------------------------- 1 | Lazo collections extend [Backbone.Collection](http://backbonejs.org/#Collection). 2 | Lazo also has the concept of a proxy layer – one for the client and one for the server. On the client it leverages a custom 3 | [Backbone.sync](http://backbonejs.org/#Sync) that sends all requests through a tunnel end 4 | point on the Lazo application server. On the server it either forwards the request directly 5 | to an service end point or if a [LazoSyncher](LazoSyncher.md) exists for the collection Lazo forwards 6 | the request to the LazoSyncher. 7 | 8 | ```javascript 9 | define(['lazoCollection'], function (LazoCollection) { 10 | 11 | 'use strict'; 12 | 13 | return LazoCollection.extend({ 14 | 15 | doSomething: function () { 16 | return 'something'; 17 | } 18 | 19 | }); 20 | 21 | }); 22 | ``` 23 | 24 | 25 | ### `constructor(models, options)` 26 | 27 | Creates a new `LazoCollection` instance. 28 | You may override it if you need to perform some initialization while the instance is created. 29 | The `LazoCollection` constructor must be called though. 30 | 31 | Calls the [Backbone.Collection.constructor](http://backbonejs.org/#Collection-constructor). 32 | 33 | #### Arguments 34 | 1. `models` *(Object)*: See [Backbone.Collection.constructor](http://backbonejs.org/#Collection-constructor). 35 | 1. `options` *(Object)*: See [Backbone.Collection.constructor](http://backbonejs.org/#Collection-constructor). 36 | - `name` *(String)*: Collection name. 37 | - `params` *(Object)*: A hash of name-value pairs used in url substitution. 38 | - `ctx` *(Object)*: The current context for the request. See *TODO: ADD LINK*. 39 | - `[modelName]` *(String)*: Specify the LazoModel class that the collection contains. This should be the name of the model in the repo. This *MUST* be used with the Backbone.Collection model property.. 40 | 41 | #### Example 42 | ```js 43 | var models = [{ name: 'Foo' }, { name: 'Bar' }, { name: 'Baz' }]; 44 | var options = { 45 | name: 'LazoCollection', 46 | params: {}, 47 | ctx: {} 48 | }; 49 | 50 | new LazoCollection(models, options); 51 | ``` 52 | 53 | 54 | ### `call(name, arguments, options)` 55 | 56 | Calls the `name` method on the syncher, passing in `arguments` and `options`. 57 | 58 | *Note - The syncher is a server only concept, so this will initiate a tunnel call.* 59 | 60 | #### Arguments 61 | 1. `name` *(String)*: Name of function to call on the syncher. 62 | 1. `arguments` *(*)*: Passed to the function as the first param. 63 | 1. `options` *(Object)*: Passed to the function as the second param. 64 | - `success` *(Function)*: Function to call when successful. 65 | - `error` *(Function)*: Function to call if there is a failure. 66 | 67 | #### Example 68 | ```js 69 | var method = 'validate'; 70 | var arguments = lazoColleciton.toJSON(); 71 | var options = { 72 | success: function(){}, 73 | error: function: function(){} 74 | }; 75 | lazoCollection.call(method, arguments, options); 76 | ``` 77 | -------------------------------------------------------------------------------- /docs/LazoCollectionView.md: -------------------------------------------------------------------------------- 1 | LazoCollectionViews extend [flexo.CollectionView](https://github.com/lazojs/flexo/blob/master/docs/index.md#lexocollectionview). 2 | They are designed to run within the Lazo rendering life cycle. The following properties and methods should not be overridden: 3 | 4 | * `hasTemplate` 5 | * `eventNameSpace` 6 | * `attributeNameSpace` 7 | * `getAttributes` 8 | * `augment` 9 | * `getInnerHtml` 10 | * `attachItemEmptyViews` 11 | 12 | 13 | All of the properties and methods can be overridden and will function within the Lazo rendering life cycle allowing 14 | you to plugin any rendering solution that is environment agnostic and returns a string. For more information on the 15 | different properties and methods please consult the flexo 16 | [documentation](https://github.com/lazojs/flexo/blob/master/docs/index.md#lexocollectionview) 17 | 18 | ```js 19 | define(['LazoCollectionView'], function (LazoView) { 20 | 21 | return LazoView.extend({ 22 | 23 | itemView: 'itemViewFileName', 24 | 25 | collection: 'collectionNameInCtx' 26 | 27 | }); 28 | 29 | }); 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/LazoConfigurationProvider.md: -------------------------------------------------------------------------------- 1 | Lazo comes packaged with a Configuration Provider, `LAZO.config`. Configuration data can be provided statically (`LAZO.config.HashPlugin`) or via a JSON configuration file (`LAZO.config.JSONPlugin`). Here is an example of how to use the JSONPlugin configuration provider: 2 | 3 | ```javascript 4 | define(['lazoApp'], function (LazoApp) { 5 | 6 | 'use strict'; 7 | 8 | return LazoApp.extend({ 9 | 10 | initialize: function (callback) { 11 | var jsonPlugin = new LAZO.config.JSONPlugin({ 12 | 13 | file: 'app/env.json', 14 | 15 | // called up on construction 16 | success: function () { 17 | callback(); 18 | }, 19 | 20 | error: function (err) { 21 | LAZO.logger.error('[app.initialize] Could not load env config', err); 22 | callback(); 23 | } 24 | 25 | }); 26 | 27 | LAZO.config.addPlugin(jsonPlugin); 28 | } 29 | 30 | }); 31 | 32 | }); 33 | ``` 34 | 35 | In order to support integration with more advanced configuration management systems, Lazo also provides a plug-in model that allows for custom configuration providers to be plugged into Lazo. Here is an example of a custom plugin that initializes the configuration data from your own content management system: 36 | 37 | ```javascript 38 | define(['lazoApp'], function (LazoApp) { 39 | 40 | 'use strict'; 41 | 42 | return LazoApp.extend({ 43 | 44 | initialize: function (callback) { 45 | 46 | var cmPlugin = Config.Plugin.extend({ 47 | constructor: function (options) { 48 | if (LAZO.isServer) { 49 | //initialize configuration data from Content Management System. 50 | this._initServer(options); 51 | } else { 52 | //Use the Content Management System's client to initialize configuration data. 53 | this._initClient(options); 54 | } 55 | }, ... 56 | }); 57 | 58 | LAZO.config.addPlugin((new cmPlugin()); 59 | } 60 | 61 | }); 62 | 63 | }); 64 | ``` 65 | 66 | In certain cases you may want contextual information to be passed into your configuration provider, so that you can make your application change configuration, i.e. behavior, at execution time. This is most useful when implementing an authorization model in which the component configurations might be different for each user. Here is an example of a custom plugin that retrieves the configuration data differently based on the context options parameter: 67 | 68 | ```javascript 69 | define(['lazoApp'], function (LazoApp) { 70 | 71 | 'use strict'; 72 | 73 | return LazoApp.extend({ 74 | 75 | initialize: function (callback) { 76 | 77 | var contextPlugin = Config.Plugin.extend({ 78 | get: function (key, options) { 79 | if (options && options.context) { 80 | // retrieve configuration data based on the user's context. 81 | } else { 82 | // otherwise, retrieve configuration data normally. 83 | } 84 | } 85 | }); 86 | 87 | LAZO.config.addPlugin((new contextPlugin()); 88 | } 89 | 90 | }); 91 | 92 | }); 93 | ``` 94 | -------------------------------------------------------------------------------- /docs/LazoState.md: -------------------------------------------------------------------------------- 1 | LazoState is a mixin that adds the ability to set CSS state classes on [LazoView](#LazoView) 2 | and [LazoWidget](#LazoWidget) instances: 3 | 4 | * "lazo-focus" 5 | * "lazo-disabled" 6 | * "lazo-visible" 7 | * "lazo-hidden" 8 | 9 | ### `setState(state, on)` 10 | 11 | Set a CSS state class on a view or widget. 12 | 13 | #### Arguments 14 | 1. `state` *(String)*: Name of the state to set. 15 | 2. `on` *(Boolean)*: Toggle the state on or off. 16 | 17 | #### Example 18 | ```js 19 | // sets "lazo-focus" class on widget element 20 | .setState('focus', true); 21 | 22 | // removes "lazo-disabled" class from a view element 23 | .setState('disabled', false); 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/LazoView.md: -------------------------------------------------------------------------------- 1 | LazoViews extend [flexo.View](https://github.com/lazojs/flexo/blob/master/docs/index.md#flexoview). They are designed 2 | to run within the Lazo rendering life cycle. The following properties and methods should **not** be overridden: 3 | 4 | * `hasTemplate` 5 | * `eventNameSpace` 6 | * `attributeNameSpace` 7 | * `getAttributes` 8 | * `augment` 9 | 10 | All of the properties and methods can be overridden and will function within the Lazo rendering life cycle allowing 11 | you to plugin any rendering solution that is environment agnostic. For more information on the different properties 12 | and methods please consult the flexo [documentation](https://github.com/lazojs/flexo/blob/master/docs/index.md#flexoview). 13 | 14 | ```js 15 | define(['lazoView'], function (LazoView) { 16 | 17 | return LazoView.extend({ 18 | 19 | doSomething: function () { 20 | return 'something'; 21 | } 22 | 23 | }); 24 | 25 | }); 26 | ``` 27 | 28 | ### CSS Classes 29 | Lazo adds CSS classes to view elements at different times in the life cycle: 30 | 31 | * "lazo-detached": Markup is in the DOM, but `attach` has not been called 32 | * "lazo-attached": Markup is in the DOM and `attach` has been called 33 | 34 | ### Setting View States 35 | [LazoState](#LazoState) is mixed into LazoView and can be used to set different states on a view. 36 | 37 | ### `[viewDefinitions]` 38 | 39 | An object that contains property names that contain paths to the child view definitions. When a definition 40 | is resolved the instance is stored in the parent view's `children` object using the definition property 41 | name. 42 | 43 | #### Example 44 | ```js 45 | define(['lazoView'], function (LazoView) { 46 | 47 | return LazoView.extend({ 48 | 49 | viewDefinitions: { 50 | foo: 'app/views/foo' 51 | } 52 | 53 | }); 54 | 55 | }); 56 | ``` 57 | 58 | ### `createWidget()` 59 | 60 | Used to programatically create a widget instance. Creates an instance of a widget. Renders widget in `el` 61 | if `el` does not contain children. Attaches widget to the `el`. Pushes widget instance to the correspondingly 62 | named array in `widgets` object of the parent widget. 63 | 64 | *Note - Should only called on the client.* 65 | 66 | #### Arguments 67 | 1. `el` *(Object)*: Element to which to attach widget. 68 | 2. `name` *(String)*: Name of widget definition to which to resolve. 69 | 3. `options` *(Object)*: 70 | - `success` *(Function)*: Function to call when successful. Returns widget instance. 71 | - `error` *(Function)*: Function to call if there is a failure. Returns error object. 72 | - `attributes` *(Object)*: Attributes to be passed to widget upon creation. 73 | 74 | #### Example 75 | ```js 76 | .createWidget(this.el.querySelector('.some-class'), 'widget-def-key', { 77 | success: function (widget) { 78 | // do something with newly create widget instances 79 | }, 80 | error: function (err) { 81 | // handle error 82 | } 83 | }); 84 | ``` 85 | 86 | ### `getWidgetByEl(el)` 87 | 88 | Get a widget instance by an element. 89 | 90 | #### Arguments 91 | 1. `el` *(Object)*: Widget element. 92 | 93 | #### Returns 94 | *(Object)*: Widget instance 95 | 96 | #### Example 97 | ```js 98 | var el = .el.querySelector('#some-id'); 99 | var widget = .getWidgetByEl(el); 100 | ``` 101 | 102 | ### `getWidgetsByName(name)` 103 | 104 | Get a widget instance by an element. 105 | 106 | #### Arguments 107 | 1. `name` *(String)*: Widget name. 108 | 109 | #### Returns 110 | *(Array)*: Widget instances 111 | 112 | #### Example 113 | ```js 114 | var widgets = .getWidgetByEl('foo'); 115 | ``` -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var commands = { 3 | start: require('./start'), 4 | stop: require('./stop'), 5 | version: require('./version') 6 | } 7 | 8 | module.exports = function (cmd, options) { 9 | if (cmd === 'version') { 10 | return commands.version(); 11 | } 12 | 13 | commands[cmd](_.extend(options || {}, { version: commands.version() })); 14 | }; -------------------------------------------------------------------------------- /lazo.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | var lazo = require('./run.js'); 6 | lazo(); -------------------------------------------------------------------------------- /lazojs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazojs/lazo/0b91b8b3b9f5246414b23458e8202fc4940ebd45/lazojs.png -------------------------------------------------------------------------------- /lib/client/app.js: -------------------------------------------------------------------------------- 1 | define(['jquery', 'underscore', 'backbone', 'lazoCtl', 'viewManager', 'assetsProvider', 'async', 'hermes', 'rehydrate/main', 'execute', 'lib/client/state', 'lib/client/prime'], 2 | function ($, _, Backbone, LazoCtl, viewManager, AssetsProvider, async, hermes, rehydrate, execute, state, prime) { 3 | 4 | 'use strict'; 5 | 6 | LAZO.app._getModules = function (rootCtx) { 7 | var modules = rootCtx.dependencies.modules ? rootCtx.dependencies.modules : rootCtx.modules, 8 | wait = rootCtx.dependencies.modules ? true : false; 9 | 10 | return { 11 | modules: modules, 12 | wait : wait 13 | }; 14 | }; 15 | 16 | return { 17 | 18 | initialize: function (options) { 19 | LAZO.logger.debug('[client.app.initialize] Initializing client...', options); 20 | 21 | var self = this, 22 | modules = LAZO.app._getModules(LAZO.initConf.rootCtx); 23 | 24 | this.currentLayout = LAZO.initConf.layout; 25 | 26 | // if window.rootCtx.dependencies.modules then combo handled and we need to wait on response 27 | // else prefetch modules so that they are loading in parallel while rehydrate runs 28 | if (modules.wait) { // bundles loaded by bootstrap.js 29 | return self._initialize(options); 30 | } 31 | prime(modules.modules, 'js', modules.wait, function (err) { 32 | if (err) { 33 | return; // TODO: throw error 34 | } 35 | 36 | self._initialize(options); 37 | }); 38 | }, 39 | 40 | _initialize: function () { 41 | this._clickHandler(); 42 | this._defineRoutes(); 43 | hermes.start({ 44 | state: state.createStateObj(LAZO.initConf.rootCtx), 45 | cache: true, 46 | routeNotMatched: function (routePathName) { 47 | LAZO.error.render({ statusCode: 404, error: 'Not found', message: 'This is not the page you are looking for.' }); 48 | } 49 | }); 50 | 51 | LAZO.app.trigger('application:initialize'); 52 | LAZO.files = LAZO.initConf.files; 53 | rehydrate(LAZO.initConf.rootCtl, LAZO.initConf.rootCtx, function (ctl) { 54 | LAZO.ctl = ctl; 55 | LAZO.layout = LAZO.initConf.layout; 56 | viewManager.attachViews(ctl, function (err, success) { 57 | if (err) { 58 | LAZO.logger.error('[client.app.initialize] Error attaching views', err); 59 | } 60 | 61 | LAZO.app.trigger('application:ready'); 62 | delete LAZO.initConf; 63 | }); 64 | }); 65 | }, 66 | 67 | _clickHandler: function () { 68 | $('body').on('click', '[lazo-navigate]', function (e) { 69 | var $currentTarget = $(e.currentTarget); 70 | var path = $currentTarget.attr('lazo-navigate').length && $currentTarget.attr('lazo-navigate') !== 'lazo-navigate' ? 71 | $currentTarget.attr('lazo-navigate') : $currentTarget.attr('href'); 72 | 73 | if (path) { 74 | e.preventDefault(); 75 | LAZO.app.navigate(null, path); 76 | } 77 | }); 78 | }, 79 | 80 | _defineRoutes: function () { 81 | var self = this; 82 | 83 | LAZO.router = hermes; 84 | _.each(LAZO.routes, function (value, path) { 85 | hermes.route(path, path, function (pathname, params, state) { 86 | var eventData = { 87 | route: path, 88 | pathname: pathname, 89 | parameters: _.extend(params, state.parameters) 90 | }; 91 | 92 | LAZO.app.trigger('navigate:application:begin', eventData); 93 | execute(eventData); // pass event data, so that other nav events can pick it up 94 | }); 95 | }); 96 | } 97 | 98 | }; 99 | 100 | }); -------------------------------------------------------------------------------- /lib/client/assetsProvider.js: -------------------------------------------------------------------------------- 1 | define(['jquery', 'underscore', 'resolver/assets'], function ($, _, utils) { 2 | 3 | 'use strict'; 4 | 5 | return _.extend({ 6 | 7 | get: function (components, ctx, options) { 8 | var fetchAssets = []; 9 | var assets = {}; 10 | var asset; 11 | var i = components.length; 12 | var self = this; 13 | 14 | while (i--) { 15 | if ((asset = LAZO.ctl.ctx._rootCtx.assets[components[i]])) { 16 | assets[components[i]] = asset; 17 | } else { 18 | fetchAssets.push(components[i]); 19 | } 20 | } 21 | 22 | if (!fetchAssets.length) { 23 | return options.success(assets); 24 | } 25 | 26 | $.ajax({ 27 | data: { 28 | components: fetchAssets.join(',') 29 | }, 30 | error: function (error) { 31 | options.error(error); 32 | }, 33 | success: function (result) { 34 | _.extend(LAZO.ctl.ctx._rootCtx.assets, result); 35 | options.success(_.extend(assets, result)); 36 | }, 37 | url: '/assets' 38 | }); 39 | } 40 | 41 | }, utils); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /lib/client/bootstrap.js: -------------------------------------------------------------------------------- 1 | define(['resolver/requireConfigure'], function (resolver) { 2 | 3 | resolver.get('client', function (err, config) { 4 | if (err) { // TODO: error handling 5 | throw err; 6 | } 7 | 8 | config.paths.assets = LAZO.initConf.assets || config.paths.assets; 9 | config.paths.bundler = LAZO.initConf.bundler || config.paths.lazoBundle; 10 | var lazoRequire = requirejs.config(config); 11 | 12 | function bootstrap() { 13 | lazoRequire(['config'], function (config) { 14 | LAZO.config = config; 15 | lazoRequire(['app/application', 'handlebars', 'error', 'logger', 'json!app/app.json'], 16 | function (App, handlebars, error, logger, appConf) { 17 | 18 | LAZO.app = new App(); 19 | LAZO.error = error; 20 | LAZO.app.isServer = false; 21 | LAZO.app.isClient = true; 22 | LAZO.app.js = appConf.js || []; 23 | LAZO.app.css = appConf.css || []; 24 | LAZO.app.getAssets = appConf.assets || false; 25 | LAZO.app.defaultLayout = appConf.defaultLayout; 26 | LAZO.app.assets = LAZO.initConf.rootCtx.assets.app; 27 | LAZO.logger = logger; 28 | LAZO.routes = appConf.routes; 29 | 30 | lazoRequire(['clientApp'], function (clientApp) { 31 | LAZO.app.args = LAZO.initConf.args; 32 | lazoRequire(LAZO.app.js, function () { 33 | LAZO.app.initialize(function () { 34 | clientApp.initialize(); 35 | }); 36 | }); 37 | }); 38 | }); 39 | }); 40 | } 41 | 42 | LAZO.require = lazoRequire; 43 | LAZO.isServer = false; 44 | LAZO.isClient = true; 45 | 46 | // combo handled modules; these should be loaded first 47 | if (LAZO.initConf.rootCtx.dependencies.modules) { 48 | lazoRequire(LAZO.initConf.rootCtx.dependencies.modules, function () { 49 | bootstrap(); 50 | }); 51 | } else { 52 | bootstrap(); 53 | } 54 | }); 55 | 56 | }); -------------------------------------------------------------------------------- /lib/client/destroyCmp.js: -------------------------------------------------------------------------------- 1 | define(['renderer'], function (renderer) { 2 | 3 | 'use strict'; 4 | 5 | return function destroy(ctl) { 6 | var components = renderer.getList('component', ctl); 7 | var i = components.length; 8 | 9 | while (i) { 10 | i--; 11 | try { 12 | components[i].currentView.remove(); 13 | components[i]._getEl().remove(); 14 | } catch (e) { 15 | LAZO.logger.warn('[client.destroy] Error while destroying component %s: %s', ctl.name, e.message); 16 | } 17 | } 18 | }; 19 | 20 | }); -------------------------------------------------------------------------------- /lib/client/loader.js: -------------------------------------------------------------------------------- 1 | define(['module'], function (mod) { 2 | 3 | function isServerOnly(name, paths) { 4 | var names = name.indexOf('/') ? name.split('/') : [name]; 5 | for (var i = 0; i < names.length; i++) { 6 | if (paths[names[i]] && paths[names[i]].indexOf('/server/') !== -1) { 7 | return true; 8 | } 9 | } 10 | 11 | // path is not defined for the server; assume server only defined path 12 | return names.length === 1 && !paths[names[0]] ? true : false; 13 | } 14 | 15 | return { 16 | load: function (name, req, onload, config) { 17 | //req has the same API as require(). 18 | if (name !== null && name.indexOf('/server/') === -1 && !isServerOnly(name, config.paths)) { 19 | 20 | req([name], function (value) { 21 | onload(value); 22 | }); 23 | 24 | } else { 25 | //Returning null for client side dependencies on server 26 | onload(null); 27 | } 28 | } 29 | }; 30 | 31 | }); 32 | -------------------------------------------------------------------------------- /lib/client/prime.js: -------------------------------------------------------------------------------- 1 | define(['jquery'], function ($) { 2 | 3 | function primeCss(links, wait, callback) { 4 | var processed = 0, 5 | linkCount = links.length; 6 | 7 | if (!LAZO.app.prefetchCss) { 8 | return callback ? callback(null) : void(0); 9 | } 10 | 11 | for (var i = 0; i < linkCount; i++) { 12 | $.ajax({ 13 | url: links[i].href, 14 | dataType: 'text', 15 | success: function () { 16 | processed++; 17 | if (wait && processed === linkCount && callback) { 18 | callback(null); 19 | } 20 | }, 21 | error: function (jqXHR, textStatus, errorThrown) { 22 | if (callback) { 23 | callback(errorThrown); 24 | } 25 | } 26 | }); 27 | } 28 | 29 | if (!wait && callback) { 30 | callback(null); 31 | } 32 | } 33 | 34 | function primeJs(modules, wait, callback) { 35 | LAZO.require(modules, function () { 36 | if (wait && callback) { // combo handled 37 | callback(); 38 | } 39 | }); 40 | 41 | if (!wait && callback) { // not combo handled; prefetch and prime the cache 42 | callback(null); 43 | } 44 | } 45 | 46 | return function (resources, resourcesType, wait, callback) { 47 | if (resourcesType === 'css') { 48 | primeCss(resources, wait, callback); 49 | } 50 | if (resourcesType === 'js') { 51 | primeJs(resources, wait, callback); 52 | } 53 | }; 54 | 55 | }); -------------------------------------------------------------------------------- /lib/client/rehydrate/main.js: -------------------------------------------------------------------------------- 1 | define(['lazoCtl', 'context', 'rehydrate/view', 'rehydrate/model'], 2 | function (LazoCtl, Context, rehydrateView, rehydrateModels) { 3 | 4 | 'use strict'; 5 | 6 | var done; 7 | var rootCtl; 8 | 9 | function rehydrateComponent(cmpDef, counters, callback) { 10 | counters.components++; 11 | LazoCtl.create(cmpDef.name, _.pick(cmpDef, 'name', 'isBase'), { 12 | success: function (ctl) { 13 | var children = cmpDef.children, 14 | kids; 15 | cmpDef.ctx._rootCtx = cmpDef._rootCtx; 16 | ctl.ctx = new Context(cmpDef.ctx); 17 | _.each(ctl.ctx.params, function (param, k) { 18 | delete ctl.ctx.params[k]; 19 | ctl.ctx.params[decodeURIComponent(k)] = decodeURIComponent(param); 20 | }); 21 | ctl.cid = cmpDef.cid; 22 | 23 | kids = ctl.children = {}; 24 | for (var container in children) { 25 | (function (container) { 26 | kids[container] = []; 27 | for (var i = 0; i < children[container].length; i++) { 28 | (function (i) { 29 | var child = children[container][i]; 30 | child._rootCtx = cmpDef._rootCtx; 31 | rehydrateComponent(child, counters, function (ctl) { 32 | kids[container][i] = ctl; 33 | rehydrate(ctl, child, counters); 34 | }); 35 | })(i); 36 | } 37 | })(container); 38 | } 39 | callback(ctl); 40 | }, 41 | error: function (err) { 42 | ; // TODO: error loading component 43 | } 44 | }); 45 | } 46 | 47 | function rehydrate(ctl, cmpDef, counters) { 48 | counters.components--; 49 | counters.models++; 50 | 51 | rehydrateModels(ctl, cmpDef.ctx._rootCtx, function () { 52 | counters.models--; 53 | counters.views++; 54 | // views are dependent on models & collections so views cannot load in parallel; 55 | // we could get clever, setInterval and catch errors, but there is no way to determine 56 | // what is causing the type error, so it would not be a reliable approach; additionally, 57 | // in production these will be combo handled so it will not have a big impact on performance 58 | rehydrateView(ctl, cmpDef, function () { 59 | counters.views--; 60 | isDone(counters); 61 | }); 62 | isDone(counters); 63 | }); 64 | 65 | isDone(counters); 66 | } 67 | 68 | function isDone(counters) { 69 | if (!counters.components && !counters.models && 70 | !counters.views && done) { 71 | done(rootCtl); 72 | done = void 0; 73 | rootCtl = void 0; 74 | return true; 75 | } 76 | 77 | return false; 78 | } 79 | 80 | return function (cmpDef, ctx, callback) { 81 | var counters = { 82 | components: 0, 83 | models: 0, 84 | views: 0 85 | }; 86 | 87 | done = callback; 88 | cmpDef._rootCtx = ctx; 89 | rehydrateComponent(cmpDef, counters, function (ctl) { 90 | rootCtl = ctl; 91 | rehydrate(ctl, cmpDef, counters); 92 | }); 93 | }; 94 | 95 | }); -------------------------------------------------------------------------------- /lib/client/rehydrate/model.js: -------------------------------------------------------------------------------- 1 | define(['lazoModel'], function (LazoModel) { 2 | 3 | 'use strict'; 4 | 5 | return function (ctl, rootCtx, callback) { 6 | 7 | LazoModel._deserialize(ctl, rootCtx, { 8 | success: function () { 9 | callback(null); 10 | }, 11 | 12 | error: function (err) { 13 | callback(err); 14 | } 15 | }); 16 | 17 | }; 18 | 19 | }); -------------------------------------------------------------------------------- /lib/client/rehydrate/view.js: -------------------------------------------------------------------------------- 1 | define(['lazoView'], function (LazoView) { 2 | 3 | 'use strict'; 4 | 5 | return function (ctl, cmpDef, callback) { 6 | if (cmpDef.currentView.isBase) { 7 | ctl.currentView = ctl._createView(LazoView, cmpDef.currentView); 8 | return callback(null); 9 | } 10 | 11 | LAZO.require([cmpDef.currentView.ref], function (View) { 12 | ctl.currentView = ctl._createView(View, cmpDef.currentView); 13 | callback(null); 14 | }, function (err) { 15 | return callback(new Error('rehydrate load view failed for ' + cmpDef.currentView.ref + ': ' + err.message)); 16 | }); 17 | }; 18 | 19 | }); -------------------------------------------------------------------------------- /lib/client/state.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 3 | 'use strict'; 4 | 5 | return { 6 | 7 | get: function (url) { 8 | url = url || window.location.pathname + window.location.search; 9 | return LAZO.router.getItem(url); 10 | }, 11 | 12 | set: function (rootCtx) { 13 | var url = window.location.pathname + window.location.search; 14 | LAZO.router.updateState(this.createStateObj(rootCtx), url); 15 | }, 16 | 17 | getAddRemoveLinks: function (type) { 18 | return { 19 | add: this._getRequestLinks(type), 20 | remove: this._getRequestLinks(type, LAZO.router.getPreviousUrl()) 21 | }; 22 | }, 23 | 24 | _getRequestLinks: function (type, url) { 25 | var request = this.get(url); 26 | request = request ? request.state : {}; 27 | return request.dependencies && request.dependencies[type] ? 28 | request.dependencies[type].slice(0) : []; 29 | }, 30 | 31 | createStateObj: function (rootCtx) { 32 | var deps = { // we only care about CSS 33 | css: rootCtx.dependencies.css.slice(0), 34 | imports: rootCtx.dependencies.imports.slice(0) 35 | }; 36 | 37 | return { dependencies: deps }; 38 | }, 39 | 40 | cleanUpLinkDependencies: function (links, cmpLinks) { 41 | var cleanList = []; 42 | 43 | for (var i = 0; i < links.length; i++) { 44 | if (links[i].href.indexOf('/components/') !== 0) { 45 | cleanList.push(links[i]); 46 | } 47 | } 48 | 49 | return cleanList.concat(cmpLinks); 50 | } 51 | 52 | }; 53 | 54 | }); -------------------------------------------------------------------------------- /lib/client/viewManager.js: -------------------------------------------------------------------------------- 1 | define(['underscore', 'jquery', 'utils/treeMixin'], function (_, $, tree) { 2 | 3 | 'use strict'; 4 | 5 | return _.extend({ 6 | 7 | // Clean up view DOM bindings and event listeners. 8 | cleanup: function (rootNode, viewKey) { 9 | var views = this.getList('view', rootNode); 10 | var self = this; 11 | 12 | _.each(views, function (view) { 13 | view.remove(); 14 | }); 15 | 16 | return this; 17 | }, 18 | 19 | attachViews: function (rootNode, callback) { 20 | var views = this.getList('view', rootNode); 21 | var self = this; 22 | var expected = _.size(views); 23 | var loaded = 0; 24 | 25 | _.each(views, function (view) { 26 | view.attach($('[' + self._attrs.viewId + '="' + view.cid + '"]')[0], { 27 | success: function (response) { 28 | view.afterRender(); 29 | loaded++; 30 | if (loaded === expected) { 31 | callback(null, true); 32 | } 33 | }, 34 | error: function (err) { 35 | callback(err, null); 36 | } 37 | }); 38 | }); 39 | 40 | return this; 41 | } 42 | 43 | }, tree); 44 | 45 | }); -------------------------------------------------------------------------------- /lib/common/base.js: -------------------------------------------------------------------------------- 1 | /*global define:false*/ 2 | 3 | /** 4 | * Base object. Should be used to create all other objects. 5 | */ 6 | define(['backbone'], function (Backbone) { 7 | 8 | 'use strict'; 9 | 10 | var Base = function () {}; 11 | 12 | Base.extend = Backbone.Model.extend; 13 | 14 | return Base; 15 | }); 16 | -------------------------------------------------------------------------------- /lib/common/context.js: -------------------------------------------------------------------------------- 1 | define(['jquery', 'underscore', 'l!jquerycookie'], function ($, _) { 2 | 3 | 'use strict'; 4 | 5 | if (LAZO.isClient) { 6 | // do not encode or decode cookie values 7 | $.cookie.raw = true; 8 | } 9 | 10 | function normalizeLocation(location, headers, info) { 11 | var keys = ['host', 'hostname', 'search', 'href', 'pathname', 'port', 'protocol']; 12 | var retVal = _.pick(location, keys); 13 | var host; 14 | 15 | if (headers && headers.host) { 16 | host = headers.host.split(':'); 17 | retVal.host = headers.host; 18 | retVal.hostname = host[0]; 19 | retVal.port = host[1]; 20 | } 21 | 22 | if(info){ 23 | retVal.protocol = info.protocol; 24 | } 25 | 26 | 27 | return retVal; 28 | } 29 | 30 | function Context(options) { 31 | var defaults = { 32 | _rootCtx: { 33 | modelList: {}, 34 | modelInstances: {}, 35 | data: options && options.data ? options.data : {}, 36 | pageTitle: options && options.pageTitle ? options.pageTitle : LAZO.app.defaultTitle 37 | }, 38 | assets: {}, 39 | collections: {}, 40 | models: {}, 41 | params: {}, 42 | meta: {}, 43 | headers: {} 44 | }; 45 | 46 | // Create a copy and fill in default values 47 | options = _.defaults(_.extend({}, options), defaults); 48 | 49 | this.assets = options.assets; 50 | this.collections = options.collections; 51 | this.models = options.models; 52 | this.params = _.extend({}, options.params); 53 | this.meta = options.meta; 54 | this.headers = options.headers; 55 | 56 | // Root context 57 | this._rootCtx = options._rootCtx; 58 | 59 | if (LAZO.app.isServer && options._request) { 60 | // Do not serialize this! 61 | this._request = options._request; 62 | this._reply = options._reply; 63 | this.location = normalizeLocation(options._request.url, this.headers, options._request.server.info); 64 | this.userAgent = this.headers['user-agent']; 65 | this.isTunnelRequest = options.isTunnelRequest || false; 66 | this.response = options.response; 67 | } else if (LAZO.app.isClient) { 68 | this.location = normalizeLocation(window.location); 69 | this.userAgent = window.navigator.userAgent; 70 | } 71 | } 72 | 73 | Context.prototype = { 74 | setSharedData: function (key, val) { 75 | this._rootCtx.data[key] = val; 76 | return this; 77 | }, 78 | 79 | getSharedData: function (key) { 80 | return this._rootCtx.data[key]; 81 | }, 82 | 83 | getCookie: function (name) { 84 | if (LAZO.app.isServer) { 85 | return this._request.state[name] ? this._request.state[name] : undefined; 86 | } else { 87 | return $.cookie(name); 88 | } 89 | }, 90 | 91 | setCookie: function (name, value, options) { 92 | if (LAZO.app.isServer) { 93 | options.ttl = options.expires || null; 94 | this._reply.state(name, value, options); 95 | } else { 96 | $.cookie(name, value, options); 97 | } 98 | }, 99 | 100 | clearCookie: function (name, options) { 101 | if (LAZO.app.isServer) { 102 | this._reply.state(name, null, _.extend({ ttl: 0, path: '/' }, options)); 103 | } else { 104 | $.removeCookie(name, _.extend({ path: '/' }, options)); 105 | } 106 | } 107 | 108 | }; 109 | 110 | return Context; 111 | }); -------------------------------------------------------------------------------- /lib/common/error.js: -------------------------------------------------------------------------------- 1 | define(['jquery', 'underscore'], function ($, _) { 2 | 3 | 'use strict'; 4 | 5 | return { 6 | 7 | clear: function () { 8 | $('[lazo-error]').remove(); 9 | $('body > [lazo-cmp-name]').show(); 10 | }, 11 | 12 | render: function (errContext) { 13 | LAZO.require(['text!' + errContext.statusCode, 'utils/template'], function (errTemplate, template) { 14 | var templateEngine = template.getDefaultTemplateEngine(); 15 | var compiledTemplate = templateEngine.compile(errTemplate); 16 | var $lazoErr = $('
'); 17 | 18 | $('body > [lazo-cmp-name]').hide(); 19 | $('body').append($lazoErr); 20 | $lazoErr.html(compiledTemplate(errContext)); 21 | }); 22 | } 23 | 24 | }; 25 | 26 | }); -------------------------------------------------------------------------------- /lib/common/requestFilters.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 3 | 'use strict'; 4 | 5 | var filters = {}; 6 | 7 | function iterate(path, params, ctx, fns, i, callback) { 8 | if (fns[i]) { 9 | fns[i](path, params, ctx, { 10 | success: function (redirect) { 11 | if (redirect) { 12 | return callback(redirect); 13 | } else { 14 | i += 1; 15 | iterate(path, params, ctx, fns, i, callback); 16 | } 17 | }, 18 | error: function (err) { 19 | throw err instanceof Error ? err : new Error(err); 20 | } 21 | }); 22 | } else { 23 | return callback(); 24 | } 25 | } 26 | 27 | return { 28 | 29 | apply: function (path, params, ctx, callback) { 30 | var fns = []; 31 | var match; 32 | for (var key in filters) { 33 | if (path.match(new RegExp(key))) { 34 | fns = fns.concat(filters[key]); 35 | } 36 | } 37 | 38 | if (!fns.length) { 39 | return callback(); 40 | } 41 | 42 | iterate(path, params, ctx, fns, 0, callback); 43 | }, 44 | 45 | add: function (regex, func) { 46 | filters[regex] = filters[regex] || []; 47 | filters[regex].push(func); 48 | return this; 49 | } 50 | 51 | }; 52 | 53 | }); -------------------------------------------------------------------------------- /lib/common/resolver/assets.js: -------------------------------------------------------------------------------- 1 | define(['underscore'], function (_) { 2 | 3 | 'use strict'; 4 | 5 | return { 6 | 7 | keyRegex: /^(?:[a-z]{2}(?:-[A-Z]{2})?[\/\\])(.+)/, 8 | 9 | getLocales: function (ctx) { 10 | var languages = []; 11 | 12 | if (LAZO.isClient) { 13 | return languages.concat(navigator.languages || [navigator.language]).concat(['defaults']); 14 | } else { 15 | return languages.concat(this.resolveAcceptLanguge(ctx._request.raw.req.headers['accept-language'])).concat(['defaults']); 16 | } 17 | }, 18 | 19 | resolveAssets: function (map, ctx) { 20 | var locales = this.getLocales(ctx); 21 | var i = locales.length; 22 | var assets = {}; 23 | var localeAssets; 24 | 25 | while (i--) { 26 | localeAssets = map[locales[i]]; 27 | if (!_.isUndefined(localeAssets)) { 28 | _.extend(assets, localeAssets); 29 | } 30 | } 31 | 32 | return assets; 33 | }, 34 | 35 | resolveAssetKey: function (key, ctx) { 36 | return key.replace(this.keyRegex, '$1'); 37 | }, 38 | 39 | resolveAssetPath: function (path, component, ctx) { 40 | var prefix = component === 'app' ? '/' : '/components/'; 41 | return prefix + component + '/assets/' + path; 42 | }, 43 | 44 | resolveAcceptLanguge: function (languages, ctx) { 45 | languages = _.map(languages.split(','), function (language) { 46 | var langQuality = language.split(';'); 47 | var languageParts = langQuality[0].split('-'); 48 | return { 49 | language: languageParts[0], 50 | langLocale: langQuality[0], 51 | quality: langQuality[1] ? parseFloat(langQuality[1].split('=')[1]) : 1 52 | }; 53 | }); 54 | 55 | languages.sort(function (a, b) { 56 | if (a.language === b.language) { 57 | if (a.quality < b.quality) { 58 | return 1; 59 | } 60 | if (a.quality > b.quality) { 61 | return -1; 62 | } 63 | } 64 | 65 | return 0; 66 | }); 67 | 68 | return _.map(languages, function (lang) { 69 | return lang.langLocale; 70 | }); 71 | } 72 | 73 | }; 74 | 75 | }); -------------------------------------------------------------------------------- /lib/common/resolver/component.js: -------------------------------------------------------------------------------- 1 | define(['underscore', 'resolver/file'], function (_, file) { 2 | 3 | 'use strict'; 4 | 5 | return { 6 | 7 | getDef: function (route) { 8 | var defaultLayout = LAZO.app.defaultLayout; 9 | var def = LAZO.routes[route]; 10 | var isObj; 11 | var name = (isObj = _.isObject(def)) ? def.component : def; 12 | var compParts = name.split('#'); 13 | var action; 14 | var layout; 15 | 16 | name = compParts.length ? compParts[0] : name; 17 | action = compParts.length && compParts[1] ? compParts[1] : 'index'; 18 | // possible scenarios 19 | // 1. no layout: def is a string, e.g. foo#baz, foo 20 | // 2. layout: def is an object with a layout 21 | // 3. default layout and layout: def is an object, app has a default layout 22 | // 4. default layout and layout === false: def is an object, app has a default layout, but route def def.layout = false 23 | layout = (isObj && def.layout) ? def.layout : (defaultLayout && !isObj || isObj && !_.isBoolean(def.layout)) ? 24 | defaultLayout : null; 25 | 26 | return _.extend({}, isObj ? def : {}, { 27 | name: name, 28 | action: action, 29 | layout: layout 30 | }); 31 | }, 32 | 33 | getLinks: function (cmpName, type) { 34 | return file.getComponentFiles([cmpName], function (link) { 35 | if (type === 'css') { 36 | return link.substr(-4, 4) === '.css' && link.indexOf('/imports/') === -1; 37 | } else { 38 | return link.indexOf('/imports/') !== -1 && link.substr(-5, 5) === '.html'; 39 | } 40 | }); 41 | } 42 | 43 | }; 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /lib/common/resolver/file.js: -------------------------------------------------------------------------------- 1 | define(['l!serverFileResolver', 'underscore', 'utils/template'], function (serverFileResolver, _, template) { 2 | 3 | 'use strict'; 4 | 5 | return _.extend({ 6 | 7 | getPath: function (moduleName, cmpName, moduleType) { 8 | var pathPrefix = moduleName.indexOf('a:') === 0 ? 'app' : ''; 9 | 10 | moduleName = pathPrefix ? moduleName.split(':')[1] : moduleName; 11 | pathPrefix = pathPrefix && (moduleType === 'view' || moduleType === 'template') ? 12 | pathPrefix + '/views' : pathPrefix; 13 | 14 | return (pathPrefix ? pathPrefix : ('components/' + cmpName)) + 15 | (!pathPrefix && (moduleType === 'view' || moduleType === 'template') ? '/views' : '') + 16 | '/' + moduleName; 17 | }, 18 | 19 | getBasePath: function (moduleName, cmpName, moduleType) { 20 | var modulePath = this.getPath(moduleName, cmpName, moduleType); 21 | return modulePath.substr(0, modulePath.lastIndexOf('/')); 22 | }, 23 | 24 | getTemplateName: function (view) { 25 | return _.result(view, 'templateName') || view.name; 26 | }, 27 | 28 | getTemplatePath: function (view) { 29 | return this.getPath(this.getTemplateName(view) + '.' + template.getTemplateExt(view.templateEngine), view.ctl.name, 'template'); 30 | }, 31 | 32 | // TODO: deprecated, can be removed once load_view is merged to dev 33 | convertTemplatePath: function (templatePath) { 34 | return 'tmp/' + templatePath.substr(0, templatePath.lastIndexOf('.')) + '.js'; 35 | }, 36 | 37 | getComponentFiles: function (components, filter) { 38 | var list = LAZO.files.components; 39 | var files = []; 40 | 41 | filter = filter || function () { return true; }; 42 | 43 | for (var i = 0; i < components.length; i++) { 44 | for (var k in list) { 45 | if (k.indexOf('components/' + components[i] + '/') === 0 && filter(k)) { 46 | files.push(k); 47 | } 48 | } 49 | } 50 | 51 | return files; 52 | } 53 | 54 | }, serverFileResolver); 55 | 56 | }); -------------------------------------------------------------------------------- /lib/common/resolver/main.js: -------------------------------------------------------------------------------- 1 | define(['resolver/requireConfigure', 'resolver/route', 'resolver/file', 'resolver/component'], 2 | function (requireConfigure, routeRes, file, component) { 3 | 4 | 'use strict'; 5 | 6 | return { 7 | 8 | // requirejs config generator 9 | getReqConfig: function (env, options, callback) { 10 | requireConfigure.get(env, options, callback); 11 | }, 12 | 13 | // route transformer 14 | transformRoute: function (route) { 15 | return routeRes.transform(route); 16 | }, 17 | 18 | // file helpers 19 | isBase: function (modulePath, moduleType, callback, options) { 20 | var isExtendedClass = (LAZO.files.components[modulePath + '.js'] || 21 | LAZO.files.appViews[modulePath + '.js']) || 22 | LAZO.files.models[modulePath + '.js']; 23 | 24 | return callback(isExtendedClass ? false : true); 25 | }, 26 | 27 | getPath: function (moduleName, cmpName, moduleType) { 28 | return file.getPath(moduleName, cmpName, moduleType); 29 | }, 30 | 31 | getBasePath: function (moduleName, cmpName, moduleType) { 32 | return file.getBasePath(moduleName, cmpName, moduleType); 33 | }, 34 | 35 | getTemplateName: function (view) { 36 | return file.getTemplateName(view); 37 | }, 38 | 39 | getTemplatePath: function (view) { 40 | return file.getTemplatePath(view); 41 | }, 42 | 43 | convertTemplatePath: function (templatePath) { 44 | return file.convertTemplatePath(templatePath); 45 | }, 46 | 47 | getComponentCss: function (cmpName) { 48 | return component.getLinks(cmpName, 'css'); 49 | }, 50 | 51 | getComponentImports: function (cmpName) { 52 | return component.getLinks(cmpName, 'import'); 53 | }, 54 | 55 | resolvePath: function (from, to) { 56 | return file.resolvePath(from, to); 57 | } 58 | 59 | }; 60 | 61 | }); -------------------------------------------------------------------------------- /lib/common/resolver/model.js: -------------------------------------------------------------------------------- 1 | define(['underscore'], function (_) { 2 | 3 | 'use strict'; 4 | 5 | var subRegEx = /\{\{\s*([^|}]+?)\s*(?:\|([^}]*))?\s*\}\}/g; 6 | 7 | return { 8 | 9 | methodMap: { 10 | 'create': 'POST', 11 | 'update': 'PUT', 12 | 'patch': 'PATCH', 13 | 'delete': 'DELETE', 14 | 'read': 'GET' 15 | }, 16 | 17 | substitute: function (s, o) { 18 | return s.replace ? s.replace(subRegEx, function (match, key) { 19 | return _.isUndefined(o[key]) ? match : o[key]; 20 | }) : s; 21 | } 22 | 23 | 24 | }; 25 | 26 | }); -------------------------------------------------------------------------------- /lib/common/resolver/requireConfigure.js: -------------------------------------------------------------------------------- 1 | define(['json!resolver/paths.json'], function (paths) { 2 | 3 | var configs, 4 | loader; 5 | 6 | function getLoader(basePath) { 7 | if (loader) { 8 | return loader; 9 | } 10 | 11 | loader = requirejs.config({ 12 | baseUrl: basePath, 13 | context: 'configloader', 14 | paths: { // set paths for bootstrapping 15 | 'json': 'lib/vendor/json', 16 | 'text': 'lib/vendor/text', 17 | 'common': 'lib/common' 18 | }}); 19 | 20 | return loader; 21 | } 22 | 23 | function resolvePath(path, env, options) { 24 | var basePath; 25 | 26 | if (env === 'server') { 27 | path = (options.basePath ? options.basePath + '/' : '') + path; 28 | } 29 | 30 | return path; 31 | } 32 | 33 | function getPaths(env, options) { 34 | var _paths = JSON.parse(JSON.stringify(paths)), 35 | needle = '/{env}/', 36 | replace = '/' + env + '/'; 37 | 38 | for (var k in _paths.common) { // update env specific implementation paths 39 | _paths.common[k] = resolvePath(_paths.common[k].replace(needle, replace), env, options); 40 | } 41 | for (k in _paths[env]) { // merge env specific paths 42 | _paths.common[k] = resolvePath(_paths[env][k], env, options); 43 | } 44 | 45 | return _paths.common; 46 | } 47 | 48 | function augment(receiver, giver) { 49 | for (var key in giver) { 50 | receiver[key] = giver[key]; 51 | } 52 | 53 | return receiver; 54 | } 55 | 56 | configs = { 57 | 58 | client: function (options, callback) { 59 | 60 | function augmentConf(options) { 61 | return augment(options, { 62 | baseUrl: '/', 63 | map: { 64 | '*': { 65 | 'l': '/lib/client/loader.js' 66 | } 67 | }, 68 | paths: augment(getPaths('client', options), options.paths), 69 | shim: options.shim 70 | }); 71 | } 72 | 73 | try { // client 74 | window; // attempt to access window object; if server catch err & use requirejs loader 75 | // use globals set by server render to simplePage.hbs, lazoShim, lazoPaths 76 | callback(null, augmentConf(LAZO.initConf.config)); 77 | } catch (e) { // server 78 | callback(null, augmentConf(options)); 79 | } 80 | }, 81 | 82 | server: function (options, callback) { 83 | 84 | function augmentConf(options) { 85 | return augment(options, { 86 | shim: options.shim || {}, 87 | baseUrl: options.baseUrl, 88 | context: 'application', 89 | map: { 90 | '*': { 91 | 's': options.basePath + '/lib/server/loader.js' 92 | } 93 | }, 94 | paths: augment(getPaths('server', options), options.paths) 95 | }); 96 | } 97 | 98 | callback(null, augmentConf(options)); 99 | } 100 | 101 | }; 102 | 103 | return { 104 | get: function (env, options, callback) { 105 | if (arguments.length === 2) { 106 | callback = arguments[1]; 107 | } 108 | return configs[env](options, callback); 109 | } 110 | }; 111 | 112 | }); -------------------------------------------------------------------------------- /lib/common/resolver/route.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 3 | 'use strict'; 4 | 5 | var REGEX_QUERY_STRING = /(\(?\?.*)$/; 6 | var REGEX_PATH_PARAM = /:(\w+)/g; 7 | var REGEX_OPT_PATH_PARAM = /\(\/\{(\w+)\}(\/?)\)$/; 8 | var REGEX_OPT_TRAILING_SLASH = /(.*)\(\/\)$/; 9 | var REGEX_DYNAMIC_PATH = /(\*)(\w+)/g; 10 | 11 | return { 12 | 13 | transform: function (route) { 14 | var routeTrailingSlash = null; 15 | 16 | route = '/' + route; 17 | route = route.replace(REGEX_QUERY_STRING, ''); 18 | route = route.replace(REGEX_PATH_PARAM, '{$1}'); 19 | route = route.replace(REGEX_OPT_PATH_PARAM, '/{$1?}$2'); 20 | 21 | if (REGEX_OPT_TRAILING_SLASH.test(route)) { 22 | route = route.replace(REGEX_OPT_TRAILING_SLASH, '$1'); 23 | // handle case of developer adding a trailing slash to a dynamic path: 24 | // test-dynamic-routes/*nodes(/) 25 | if (!REGEX_DYNAMIC_PATH.test(route)) { 26 | routeTrailingSlash = route + '/'; 27 | } 28 | } 29 | 30 | // transform backbone style splats to hapi splats: 31 | // test-dynamic-routes/*nodes -> test-dynamic-routes/{nodes*} 32 | if (LAZO.app.isServer) { 33 | route = route.replace(REGEX_DYNAMIC_PATH, '{$2$1}'); 34 | } 35 | 36 | return { 37 | routeTrailingSlash: routeTrailingSlash, 38 | route: route 39 | }; 40 | } 41 | 42 | }; 43 | 44 | }); -------------------------------------------------------------------------------- /lib/common/templates/403.hbs: -------------------------------------------------------------------------------- 1 |

You shall not pass!

2 |
    3 |
  • Code: {{statusCode}}
  • 4 |
  • Error: {{error}}
  • 5 |
  • Message: {{message}}
  • 6 |
-------------------------------------------------------------------------------- /lib/common/templates/404.hbs: -------------------------------------------------------------------------------- 1 |

These are not the droids you are looking for.

2 |
    3 |
  • Code: {{statusCode}}
  • 4 |
  • Error: {{error}}
  • 5 |
  • Message: {{message}}
  • 6 |
-------------------------------------------------------------------------------- /lib/common/templates/500.hbs: -------------------------------------------------------------------------------- 1 |

Damn it Jim!

2 |
    3 |
  • Code: {{statusCode}}
  • 4 |
  • Error: {{error}}
  • 5 |
  • Message: {{message}}
  • 6 |
-------------------------------------------------------------------------------- /lib/common/templates/page.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{!-- 3 | htmlTag is generated by LAZO.app.setHtmlTag; 4 | removing this if, else block will cause LAZO.app.setHtmlTag 5 | to not have any effect on the application 6 | --}} 7 | {{#if htmlTag}} 8 | {{{htmlTag}}} 9 | {{else}} 10 | 11 | {{/if}} 12 | 13 | 14 | {{!-- 15 | START: DO NOT REMOVE 16 | If any of the code between "START" and "END" is removed it will break lazo. 17 | --}} 18 | {{#each tags}} 19 | <{{name}} {{#each attributes}}{{@key}}="{{this}}" {{/each}}>{{content}} 20 | {{/each}} 21 | {{#each dependencies.imports}} 22 | 23 | {{/each}} 24 | {{#each dependencies.css}} 25 | 26 | {{/each}} 27 | 28 | 42 | 43 | {{#if lib}} 44 | 59 | {{else}} 60 | 75 | {{/if}} 76 | {{!-- END: DO NOT REMOVE --}} 77 | 78 | {{!-- 79 | bodyClass is generated by LAZO.app.setBodyClass; 80 | removing this if, else block will cause LAZO.app.setBodyClass 81 | to not have any effect on the application 82 | --}} 83 | 84 | {{!-- DO NOT REMOVE --}} 85 | {{{body}}} 86 | 87 | -------------------------------------------------------------------------------- /lib/common/utils/assetsMixin.js: -------------------------------------------------------------------------------- 1 | define(['assets'], function (LazoAssets) { 2 | 3 | 'use strict'; 4 | 5 | var assets = new LazoAssets(); 6 | 7 | return { 8 | 9 | getAssets: function (cmpName, ctx, options) { 10 | if (!LAZO.app.getAssets) { 11 | return options.success({}); 12 | } 13 | 14 | assets.get([cmpName], ctx, { 15 | success: function (assets) { 16 | options.success(assets[cmpName]); 17 | }, 18 | error: function (err) { 19 | options.error(err); 20 | } 21 | }); 22 | } 23 | 24 | }; 25 | 26 | }); -------------------------------------------------------------------------------- /lib/common/utils/cmpProcessor.js: -------------------------------------------------------------------------------- 1 | define(['underscore', 'utils/loader', 'context', 'resolver/component', 'utils/assetsMixin'], 2 | function (_, loader, Context, cmpResolver, assetsMixin) { 3 | 4 | 'use strict'; 5 | 6 | return _.extend({ 7 | 8 | getRootCtxForReply: function (ctx, ctl) { 9 | var components = this.getComponents(ctl, []); 10 | var assets = {}; 11 | var i = components.length; 12 | 13 | while (i--) { 14 | assets[components[i].name] = components[i].ctx.assets; 15 | } 16 | 17 | return _.extend(_.omit(ctx._rootCtx, 'modelInstances'), { 18 | dependencies: ctx._rootCtx.dependencies, 19 | assets: assets, 20 | modelInstances: {} 21 | }); 22 | }, 23 | 24 | getDef: function (route) { 25 | return cmpResolver.getDef(route); 26 | }, 27 | 28 | createCtx: function (options) { 29 | return new Context(options); 30 | }, 31 | 32 | getComponents: function (ctl, list) { 33 | list.push(ctl); 34 | 35 | if (_.size(ctl.children)) { 36 | for (var k in ctl.children) { 37 | for (var i = 0; i < ctl.children[k].length; i++) { 38 | this.getComponents(ctl.children[k][i], list); 39 | } 40 | } 41 | } 42 | 43 | return list; 44 | }, 45 | 46 | getComponentNames: function (ctl) { 47 | return _.map(this.getComponents(ctl, []), function (ctl) { 48 | return ctl.name; 49 | }); 50 | }, 51 | 52 | process: function (options) { 53 | var self = this; 54 | var cmpDef = options.def; 55 | var ctx = options.ctx; 56 | var hasLayoutChanged = !cmpDef.layout || (cmpDef.layout !== LAZO.layout) ? true : false; 57 | var cmpToLoad = (hasLayoutChanged && cmpDef.layout) ? cmpDef.layout : cmpDef.name; 58 | 59 | function onError(err) { 60 | return options.error(err); 61 | } 62 | 63 | if (LAZO.isClient && !hasLayoutChanged && cmpDef.layout) { 64 | return LAZO.ctl.addChild('lazo-layout-body', cmpDef.name, { 65 | params: ctx.params, 66 | action: cmpDef.action, 67 | success: function (ctl) { 68 | options.success(ctl, ctx, cmpDef, options); 69 | }, 70 | error: onError 71 | }); 72 | } 73 | 74 | this.getAssets(cmpDef.name, ctx, { 75 | success: function (assets) { 76 | ctx.assets = assets; 77 | loader(cmpToLoad, { 78 | ctx: ctx, 79 | action: hasLayoutChanged && cmpDef.layout ? 'index' : cmpDef.action, 80 | error: onError, 81 | success: function (ctl) { 82 | if (cmpDef.layout) { 83 | ctl.addChild('lazo-layout-body', cmpDef.name, { 84 | params: ctx.params, 85 | action: cmpDef.action, 86 | success: function () { 87 | options.success(ctl, ctx, cmpDef, options); 88 | }, 89 | error: onError 90 | }); 91 | } else { 92 | options.success(ctl, ctx, cmpDef, options); 93 | } 94 | } 95 | }); 96 | }, 97 | error: onError 98 | }); 99 | } 100 | 101 | }, assetsMixin); 102 | 103 | }); -------------------------------------------------------------------------------- /lib/common/utils/ctlSerializor.js: -------------------------------------------------------------------------------- 1 | define(['underscore', 'lazoModel'], function (_, Model) { 2 | 3 | 'use strict'; 4 | 5 | return { 6 | 7 | serialize: function (ctl, rootCtx) { 8 | var serObj = {}, 9 | viewRef, 10 | omit = rootCtx ? ['cookies', '_request', '_reply', 'models', 'collections', 'location', 'userAgent', 'headers', 'response'] : 11 | ['cookies', '_request', '_reply', 'models', 'collections', '_rootCtx', 'location', 'userAgent', 'headers', 'response']; 12 | 13 | serObj.cid = ctl.cid; 14 | serObj.ctx = _.omit(ctl.ctx, omit); 15 | _.each(serObj.ctx.params, function (param, k) { 16 | delete serObj.ctx.params[k]; 17 | // encode to prevent xss of serialized context 18 | serObj.ctx.params[encodeURIComponent(k)] = encodeURIComponent(param); 19 | }); 20 | 21 | serObj.ctx.models = {}; 22 | serObj.ctx.collections = {}; 23 | serObj.isBase = ctl.isBase; 24 | serObj.name = ctl.name; 25 | if (ctl.currentView && ctl.currentView) { 26 | viewRef = ctl.currentView.ref; 27 | 28 | serObj.currentView = { 29 | cid: ctl.currentView.cid, 30 | name: ctl.currentView.name, 31 | ref: ctl.currentView.ref, 32 | templatePath: ctl.currentView.templatePath, 33 | compiledTemplatePath: ctl.currentView.compiledTemplatePath, 34 | basePath: ctl.currentView.basePath, 35 | isBase: ctl.currentView.isBase, 36 | hasTemplate: ctl.currentView.hasTemplate 37 | }; 38 | } 39 | 40 | //children 41 | var serializedKids = {}; 42 | _.each(ctl.children, function (regionChildren, region) { 43 | var comps = []; 44 | _.each(regionChildren, function (child) { 45 | comps.push(child.toJSON()); 46 | }); 47 | serializedKids[region] = comps; 48 | }); 49 | serObj.children = serializedKids; 50 | 51 | //models 52 | Model._serialize(ctl.ctx.models, serObj.ctx.models); 53 | Model._serialize(ctl.ctx.collections, serObj.ctx.collections); 54 | 55 | _.each(ctl.ctx.collections, function (value, key, list) { 56 | serObj.ctx.collections[key] = value._getGlobalId(); 57 | }); 58 | 59 | return serObj; 60 | }, 61 | 62 | deserialize: function (ctl, options) { 63 | 64 | } 65 | 66 | }; 67 | 68 | }); -------------------------------------------------------------------------------- /lib/common/utils/handlebarsEngine.js: -------------------------------------------------------------------------------- 1 | define(['handlebars'], function (Handlebars) { 2 | 3 | return { 4 | compile: function(template) { 5 | return Handlebars.default.compile(template); 6 | }, 7 | precompile: function (template) { 8 | return Handlebars.default.precompile(template); 9 | }, 10 | execute: function(template, context, templateName) { 11 | return template(context); 12 | }, 13 | engine: Handlebars.default 14 | }; 15 | }); 16 | -------------------------------------------------------------------------------- /lib/common/utils/loader.js: -------------------------------------------------------------------------------- 1 | define(['underscore'], function (_) { 2 | 3 | 'use strict'; 4 | 5 | return function (cmpName, options) { 6 | if (typeof cmpName !== 'string') { // TODO: better error handling??? just prints the stack to the screen 7 | return onError(new Error('The parameter "cmpName" must be a string')); 8 | } 9 | 10 | function onError(error) { 11 | _.delay(options.error, 0, error); 12 | } 13 | 14 | function onCtlLoad(controller) { 15 | try { 16 | controller._execute(options.action, { 17 | success: function (controller) { 18 | _.delay(options.success, 0, controller); 19 | }, 20 | error: function (err) { 21 | return onError(err); 22 | } 23 | }); 24 | } catch (error) { 25 | return onError(error); 26 | } 27 | } 28 | 29 | options = _.defaults(options || {}, { 30 | action: 'index', 31 | name: cmpName, 32 | error: function () { 33 | return; 34 | }, 35 | success: function () { 36 | return; 37 | }}); 38 | 39 | // loader is used by ctl.addChild; requiring at run time prevents the circular dependency 40 | LAZO.require(['lazoCtl'], function (Ctl) { 41 | Ctl.create(cmpName, _.pick(options, 'ctx', 'name'), { 42 | success: onCtlLoad, 43 | error: onError 44 | }); 45 | }); 46 | }; 47 | 48 | }); -------------------------------------------------------------------------------- /lib/common/utils/modelLoader.js: -------------------------------------------------------------------------------- 1 | define(['lazoModel', 'lazoCollection', 'resolver/main'], function (LazoModel, LazoCollection, resolver) { 2 | 'use strict'; 3 | 4 | var modelLoader = function (name, type, cb) { 5 | resolver.isBase(('models/' + name + '/' + type), 'model', function (isBase) { 6 | if (isBase) { 7 | cb(type === 'model' ? LazoModel : LazoCollection, true); 8 | } else { 9 | LAZO.require(['models/' + name + '/' + type], 10 | function (model) { 11 | // https://github.com/jrburke/requirejs/issues/922 12 | if (typeof model === 'function') { 13 | cb(model); 14 | } 15 | } 16 | ); 17 | } 18 | }); 19 | }; 20 | 21 | return modelLoader; 22 | }); -------------------------------------------------------------------------------- /lib/common/utils/module.js: -------------------------------------------------------------------------------- 1 | define(['resolver/file', 'underscore', 'text', 'context', 'utils/loader', 'async', 'utils/assetsMixin'], 2 | function (file, _, text, Context, loader, async, assetsMixin) { 3 | 4 | 'use strict'; 5 | 6 | return _.extend({ 7 | 8 | addPath: function (path, ctx) { 9 | var modules; 10 | if (LAZO.app.isServer) { 11 | modules = ctx._rootCtx.modules = ctx._rootCtx.modules || []; 12 | modules.push(path); 13 | ctx.js = ctx.js || []; 14 | ctx.js.push(path); 15 | } 16 | 17 | return this; 18 | }, 19 | 20 | getView: function (path, callback) { 21 | LAZO.require([path], function (View) { 22 | return callback(null, View); 23 | }, function (err) { // TODO: error handling 24 | return callback(new Error('module.getView failed for ' + path + ' : ' + err.message), null); 25 | }); 26 | }, 27 | 28 | getTemplate: function (templatePath, callback) { 29 | LAZO.require(['text!' + templatePath], function (template) { 30 | return callback(null, template); 31 | }, function (err) { // TODO: error handling 32 | return callback(new Error('Controller _getTemplate failed for ' + templatePath + ' : ' + err.message), null); 33 | }); 34 | }, 35 | 36 | addChild: function (container, cmpName, parent, options) { 37 | var childOptions, 38 | childContext, 39 | defaults = { 40 | action: 'index', 41 | render: false 42 | }; 43 | 44 | function onError(error) { 45 | _.delay(options.error, 0, error); 46 | } 47 | 48 | function onComponentLoad(child) { 49 | if (!parent.children) { 50 | parent.children = {}; 51 | } 52 | 53 | if (!parent.children[container]) { 54 | parent.children[container] = []; 55 | } 56 | 57 | if (_.isNumber(options.index)) { 58 | parent.children[container].splice(options.index, 0, child); 59 | } else { 60 | parent.children[container].push(child); 61 | } 62 | 63 | _.delay(options.success, 0, child); 64 | } 65 | 66 | options = _.defaults(options || {}, { 67 | error: function () { 68 | return; 69 | }, 70 | success: function () { 71 | return; 72 | } 73 | }); 74 | 75 | if (typeof container !== 'string' || !container) { 76 | return onError(new TypeError()); // TODO: error handling 77 | } 78 | 79 | if (typeof cmpName !== 'string' || !cmpName) { 80 | return onError(new TypeError()); // TODO: error handling 81 | } 82 | 83 | childOptions = _.defaults(_.pick(options, 'action', 'rootComponent'), defaults); 84 | childContext = new Context(_.extend({}, _.pick(parent.ctx, ['_rootCtx', '_request', '_reply', 'headers', 'meta', 'response']), { 85 | params : options.params })); 86 | 87 | if (parent.ctx && parent.ctx._rootCtx) { 88 | childContext._rootCtx = parent.ctx._rootCtx; 89 | } 90 | 91 | this.getAssets(cmpName, childContext, { 92 | success: function (assets) { 93 | childContext.assets = assets; 94 | childOptions.ctx = childContext; 95 | loader(cmpName, _.extend({}, childOptions, { 96 | success: onComponentLoad, error: onError 97 | })); 98 | }, 99 | error: onError 100 | }); 101 | } 102 | 103 | }, assetsMixin); 104 | 105 | }); -------------------------------------------------------------------------------- /lib/common/utils/prune.js: -------------------------------------------------------------------------------- 1 | define(['lazoModel'], function (LazoModel) { 2 | 3 | 'use strict'; 4 | 5 | function getRefs(ctl, refs) { 6 | var ctx = ctl.ctx; 7 | var models = ctx.models; 8 | var collections = ctx.collections; 9 | var k; 10 | var gid; 11 | var i = 0; 12 | var child; 13 | 14 | for (k in models) { 15 | if (models[k] && models[k].name) { 16 | gid = LazoModel._getGlobalId(models[k].name, models[k].params); 17 | refs[gid] = true; 18 | } 19 | } 20 | 21 | for (k in collections) { 22 | if (collections[k] && collections[k].name) { 23 | gid = LazoModel._getGlobalId(collections[k].name, collections[k].params); 24 | refs[gid] = true; 25 | } 26 | } 27 | 28 | if (ctl.children) { 29 | for (k in ctl.children) { 30 | child = ctl.children[k]; 31 | for (i = 0; i < child.length; i++) { 32 | getRefs(child[i], refs); 33 | } 34 | } 35 | } 36 | 37 | return refs; 38 | } 39 | 40 | return function (ctl) { 41 | var refs = getRefs(ctl, {}); 42 | var modelInstances = ctl.ctx._rootCtx.modelInstances; 43 | var modelList = ctl.ctx._rootCtx.modelList; 44 | var k; 45 | 46 | for (k in modelInstances) { 47 | if (!refs[k]) { 48 | delete modelInstances[k]; 49 | } 50 | } 51 | for (k in modelList) { 52 | if (!refs[k]) { 53 | delete modelList[k]; 54 | } 55 | } 56 | 57 | }; 58 | 59 | }); -------------------------------------------------------------------------------- /lib/common/utils/sanitizer.js: -------------------------------------------------------------------------------- 1 | define([], function () { 2 | 3 | 'use strict'; 4 | 5 | return { 6 | 7 | /** 8 | * Encodes an entity for safe display in html element text or attributes 9 | */ 10 | encode: function (value) { 11 | 12 | // TODO: This should be expanded to include other scenarios. 13 | // See: https://github.com/angular/angular.js/blob/v1.3.14/src/ngSanitize/sanitize.js#L435 14 | 15 | return String(value) 16 | .replace(/&/g, '&') 17 | .replace(/"/g, '"') 18 | .replace(/'/g, ''') 19 | .replace(//g, '>') 21 | .replace(/`/g, '`'); 22 | } 23 | 24 | }; 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /lib/common/utils/template.js: -------------------------------------------------------------------------------- 1 | define(['utils/handlebarsEngine'], function (handlebarsEngine) { 2 | 3 | 'use strict'; 4 | 5 | var defaultExt = { 6 | 7 | handlebars: 'hbs', 8 | micro: 'mt' 9 | 10 | }; 11 | 12 | var defaultTemplateEngineName = 'handlebars', 13 | _engines = { 14 | handlebars: { 15 | extension: 'hbs', 16 | handler: handlebarsEngine, 17 | exp: 'Handlebars' 18 | } 19 | }; 20 | 21 | function resolveEnginePath(engineDef) { 22 | if (engineDef.path) { 23 | return engineDef.path; 24 | } 25 | 26 | switch (engineDef.name) { 27 | case 'micro': 28 | return 'underscore'; 29 | } 30 | } 31 | 32 | return { 33 | 34 | getDefaultExt: function (engineName) { 35 | return this.getTemplateExt() || defaultExt[engineName]; 36 | }, 37 | 38 | setTemplateExt: function (engineName, ext) { 39 | var engineDef = this.getTemplateEngineDef(engineName); 40 | if (engineDef) { 41 | engineDef.extension = ext; 42 | } 43 | }, 44 | 45 | registerTemplateEngine: function (name, extension, handler, path, exp) { 46 | return (_engines[name] = { extension: extension, handler: handler, path: path, exp: exp }); 47 | }, 48 | 49 | getTemplateEngine: function (engineName) { 50 | var engine = _engines[engineName]; 51 | return engine ? engine.handler : undefined; 52 | }, 53 | 54 | getTemplateExt: function (engineName) { 55 | var engineDef = this.getTemplateEngineDef(engineName); 56 | return engineDef ? engineDef.extension : undefined; 57 | }, 58 | 59 | getTemplateEngineDef: function (engineName) { 60 | return _engines[engineName]; 61 | }, 62 | 63 | getDefaultTemplateEngine: function () {; 64 | return this.getTemplateEngine(defaultTemplateEngineName); 65 | }, 66 | 67 | getDefaultTemplateEngineName: function () { 68 | return defaultTemplateEngineName; 69 | }, 70 | 71 | setDefaultTemplateEngine: function (engineName) { 72 | var engine = _engines[engineName]; 73 | if (!engine) { 74 | throw new Error('Invalid template engine name, ' + engineName); 75 | } 76 | 77 | defaultTemplateEngineName = engineName; 78 | }, 79 | 80 | loadTemplateEngine: function (engineDef, options) { 81 | var engine, 82 | self = this; 83 | 84 | options.error = options.error || function (err) { throw err; }; 85 | 86 | if ((engine = this.getTemplateEngine(engineDef.name))) { 87 | options.success(engine); 88 | } else { 89 | LAZO.require([resolveEnginePath(engineDef)], function (engine) { 90 | self.registerTemplateEngine(engineDef.name, engineDef.extension, engineDef.handler(engine), engineDef.exp); 91 | options.success(self.getTemplateEngine(engineDef.name)); 92 | }, function (err) { 93 | options.error(err); 94 | }); 95 | } 96 | }, 97 | 98 | engHandlerMaker: function (engineName) { 99 | switch (engineName) { 100 | case 'micro': 101 | return function (engine) { 102 | return { 103 | compile: function (template) { 104 | return _.template(template); 105 | }, 106 | execute: function (compiledTemplate, data) { 107 | return compiledTemplate(data); 108 | }, 109 | engine: _.template 110 | }; 111 | }; 112 | } 113 | } 114 | 115 | }; 116 | 117 | }); -------------------------------------------------------------------------------- /lib/common/utils/treeMixin.js: -------------------------------------------------------------------------------- 1 | define(['underscore'], function (_, $) { 2 | 3 | 'use strict'; 4 | 5 | return { 6 | 7 | getList: function (nodeType, rootNode) { 8 | var self = this; 9 | 10 | return (function flatten(nodeType, node, nodes) { 11 | var children; 12 | 13 | if (nodeType === 'view' && node.currentView) { 14 | nodes.push(node.currentView); 15 | } else if (nodeType === 'component') { 16 | nodes.push(node); 17 | } 18 | 19 | if ((children = self.getNodeChildren(node)).length) { 20 | _.each(children, function (child) { 21 | nodes = flatten(nodeType, child, nodes); 22 | }); 23 | } 24 | 25 | return nodes; 26 | })(nodeType, rootNode, []); 27 | }, 28 | 29 | getNodeType: function (node) { 30 | var ret; 31 | 32 | if (!node) { 33 | ret = null; 34 | } else if (node.currentView) { 35 | ret = 'component'; 36 | } else if (node.setElement) { 37 | ret = 'view'; 38 | } 39 | 40 | return ret; 41 | }, 42 | 43 | getNodeChildren: function (node) { 44 | var nodeType = this.getNodeType(node); 45 | if (nodeType !== 'component') { 46 | return []; 47 | } 48 | 49 | var children = node.children; 50 | var child; 51 | var nodes = []; 52 | node.currentView.parent = node; 53 | // nodes.push(node.currentView); 54 | for (var k in children) { 55 | for (var i = 0; i < children[k].length; i++) { 56 | child = children[k][i]; 57 | child.parent = node; 58 | nodes.push(child); 59 | } 60 | } 61 | 62 | return nodes; 63 | }, 64 | 65 | _attrs: { 66 | cmpName: 'lazo-cmp-name', 67 | cmpId: 'lazo-cmp-id', 68 | compContainerName: 'lazo-cmp-container', 69 | viewId: 'lazo-view-id' 70 | } 71 | 72 | }; 73 | 74 | }); -------------------------------------------------------------------------------- /lib/common/utils/uniqueId.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 3 | 'use strict'; 4 | 5 | var idCounter = LAZO && LAZO.initConf ? LAZO.initConf.rootCtx.idCounter : 0; 6 | 7 | return function (prefix) { 8 | var id = ++idCounter + ''; 9 | return prefix ? prefix + id : id; 10 | }; 11 | 12 | }); -------------------------------------------------------------------------------- /lib/public/assets.js: -------------------------------------------------------------------------------- 1 | define(['assetsProvider', 'base'], function (assetsProvider, Base) { 2 | 3 | 'use strict'; 4 | 5 | return Base.extend(assetsProvider); 6 | 7 | }); -------------------------------------------------------------------------------- /lib/public/bundle.js: -------------------------------------------------------------------------------- 1 | define(['underscore', 'base', 'resolver/component', 'jquery'], function (_, Base, cmpResolver, $) { 2 | 3 | 'use strict'; 4 | 5 | var supportsImports = (function () { 6 | return LAZO.isClient && 'import' in document.createElement('link'); 7 | })(); 8 | 9 | return Base.extend({ 10 | 11 | response: function (route, uri, options) { 12 | options.success(null); 13 | }, 14 | 15 | getLibPath: function () { 16 | return LAZO.conf.libPath; 17 | }, 18 | 19 | resolveBundleUrl: function (bundle) { 20 | return bundle; 21 | }, 22 | 23 | getComponentDef: function (route) { 24 | return cmpResolver.getDef(route); 25 | }, 26 | 27 | getComponentCss: function (name) { 28 | return cmpResolver.getLinks(name, 'css'); 29 | }, 30 | 31 | // TODO: deprecate next major 32 | getComponentCSS: function (name) { 33 | return cmpResolver.getLinks(name, 'css'); 34 | }, 35 | 36 | getComponentImports: function (name) { 37 | return cmpResolver.getLinks(name, 'import'); 38 | }, 39 | 40 | sortCss: function (links) { 41 | return links; 42 | }, 43 | 44 | sortImports: function (links) { 45 | return links; 46 | }, 47 | 48 | resolveImport: function (relativePath, namespace) { 49 | if (LAZO.app.isServer) { 50 | LAZO.logger.warn('bundle.resolveImport', 'Cannot call on server.'); 51 | return null; 52 | } 53 | 54 | var path = namespace === 'application' ? relativePath : 55 | 'components/' + namespace + '/imports/' + relativePath; 56 | var $import = $('link[href*="' + path + '"][lazo-link-ctx="' + namespace + '"]'); 57 | var linkNode = $import[0]; 58 | 59 | return linkNode ? (supportsImports ? linkNode.import : linkNode) : null; 60 | }, 61 | 62 | _createLink: function (link, defaults) { 63 | if (_.isString(link)) { 64 | return _.extend({ href: link }, defaults); 65 | } else { 66 | return _.extend(defaults, link); 67 | } 68 | }, 69 | 70 | _createCSSLink: function (link) { 71 | return this._createLink(link, { 72 | rel: 'stylesheet', 73 | type: 'text/css', 74 | 'lazo-link': 'css', 75 | 'lazo-link-ctx': 'application' 76 | }); 77 | }, 78 | 79 | _createCSSLinks: function (links) { 80 | return _.map(links, _.bind(this._createCSSLink, this)); 81 | }, 82 | 83 | _createImportLink: function (link) { 84 | return this._createLink(link, { 85 | rel: 'import', 86 | 'lazo-link': 'import', 87 | 'lazo-link-ctx': 'application' 88 | }); 89 | }, 90 | 91 | _createImportLinks: function (links) { 92 | return _.map(links, _.bind(this._createImportLink, this)); 93 | } 94 | 95 | }); 96 | 97 | }); -------------------------------------------------------------------------------- /lib/public/collection.js: -------------------------------------------------------------------------------- 1 | define(['underscore', 'backbone', 'proxy', 'lazoModel'], function (_, Backbone, Proxy, LazoModel) { 2 | 'use strict'; 3 | 4 | /** 5 | * A base class for collections 6 | * 7 | * @class LazoCollection 8 | * @constructor 9 | * @param {Array Models} models An array of models 10 | * @param {Object} options 11 | * @param {String} options.name The name of the collection in the repo 12 | * @param {Object} options.params A hash of name-value pairs used in url substitution 13 | * @param {Context} options.ctx The current context for the request 14 | * @param {String} [options.modelName] Specify the LazoModel class that the collection contains. This 15 | * should be the name of the model in the repo. This *MUST* be used with the Backbone.Collection model property. 16 | * @extends Backbone.Collection 17 | */ 18 | var Collection = Backbone.Collection.extend({ 19 | 20 | constructor: function(attributes, options) { 21 | this._initialize(attributes, options); 22 | this.cid = _.uniqueId('c'); 23 | if (options.modelName) { 24 | this.modelName = options.modelName; 25 | } 26 | Backbone.Collection.apply(this, arguments); 27 | }, 28 | 29 | sync: function(method, model, options) { 30 | var self = this, 31 | success = options.success; 32 | 33 | options.success = function (resp) { 34 | self._resp = resp; 35 | 36 | if (success) { 37 | success(resp); 38 | } 39 | }; 40 | 41 | Proxy.prototype.sync.call(this, method, options); 42 | }, 43 | 44 | call: function(fname, args, options) { 45 | Proxy.prototype.callSyncher.call(this, fname, args, options); 46 | }, 47 | 48 | _initialize: function(attributes, options) { 49 | LazoModel.prototype._initialize.apply(this, arguments); 50 | }, 51 | 52 | _getGlobalId: function() { 53 | return LazoModel.prototype._getGlobalId.apply(this); 54 | }, 55 | 56 | // taken directly from Backbone.Collection._prepareModel, except where noted by comments 57 | _prepareModel: function(attrs, options) { 58 | 59 | // begin lazo specific overrides 60 | var modelName = null; 61 | if (this.modelName) { 62 | if (_.isFunction(this.modelName)) { 63 | modelName = this.modelName(attrs, options); 64 | } 65 | else { 66 | modelName = this.modelName; 67 | } 68 | } 69 | // end lazo specific overrides 70 | 71 | if (attrs instanceof Backbone.Model) { 72 | if (!attrs.collection) attrs.collection = this; 73 | if (!attrs.name && modelName) attrs.name = modelName; // lazo specific 74 | return attrs; 75 | } 76 | options || (options = {}); 77 | options.collection = this; 78 | 79 | // begin lazo specific overrides 80 | if (modelName) { 81 | options.name = modelName; 82 | } 83 | 84 | options.ctx = this.ctx; 85 | // end lazo specific overrides 86 | 87 | var model = new this.model(attrs, options); 88 | if (!model._validate(attrs, options)) { 89 | this.trigger('invalid', this, attrs, options); 90 | return false; 91 | } 92 | return model; 93 | } 94 | 95 | }); 96 | 97 | return Collection; 98 | }); 99 | -------------------------------------------------------------------------------- /lib/public/server/server.js: -------------------------------------------------------------------------------- 1 | define(['base'], function (Base) { 2 | 3 | 'use strict'; 4 | 5 | return Base.extend({ 6 | 7 | setup: function (hapi, pack, servers, options) { 8 | options.success(); 9 | }, 10 | 11 | afterInitialize: function (hapi, pack, servers, options) { 12 | options.success(); 13 | } 14 | 15 | }); 16 | 17 | }); -------------------------------------------------------------------------------- /lib/public/views/base.js: -------------------------------------------------------------------------------- 1 | define(['flexo', 'lazoViewMixin', 'underscore', 'lazoWidgetMixin'], function (flexo, lazoViewMixin, _, lazoWidgetMixin) { 2 | 3 | 'use strict'; 4 | 5 | return flexo.View.extend(_.extend({ 6 | 7 | getInnerHtml: function (options) { 8 | var self = this; 9 | flexo.View.prototype.getInnerHtml.call(this, { 10 | success: function (html) { 11 | self.getWidgetsHtml(html, { 12 | success: options.success, 13 | error: options.error 14 | }); 15 | }, 16 | error: options.error 17 | }); 18 | }, 19 | 20 | attach: function (el, options) { 21 | flexo.View.prototype.attach.call(this, el, options); 22 | this.$el.removeClass(this._getStateClass('detached')).addClass(this._getStateClass('attached')); 23 | this._uiStates = this._getStates(); 24 | }, 25 | 26 | _onRemove: function () { 27 | var self = this; 28 | 29 | this._removeWidgets(); 30 | this._removeChildViews(); 31 | }, 32 | 33 | _removeChildViews: function () { 34 | if (!this.children) { 35 | return; 36 | } 37 | 38 | for (var k in this.children) { 39 | this.children[k].remove(); 40 | } 41 | } 42 | 43 | }, lazoViewMixin, lazoWidgetMixin)); 44 | 45 | }); -------------------------------------------------------------------------------- /lib/public/views/state.js: -------------------------------------------------------------------------------- 1 | define(['jquery', 'underscore'], function ($, _) { 2 | 3 | var prefix = 'lazo'; 4 | var states = { 5 | focus: true, 6 | disabled: true, 7 | visible: true, 8 | hidden: true 9 | }; 10 | 11 | return { 12 | 13 | stateClassPrefix: 'lazo', 14 | 15 | _getStateClass: function (state) { 16 | return this.stateClassPrefix + '-' + state; 17 | }, 18 | 19 | _getValidStates: function () { 20 | return states; 21 | }, 22 | 23 | _getStates: function () { 24 | if (LAZO.app.isServer) { 25 | LAZO.logger.warn('[getStates] Client only method'); 26 | } 27 | 28 | var self = this; 29 | var retVal = {}; 30 | var state; 31 | var classNames = this.el ? this.el.className.split(' ') : []; 32 | var validStates = this._getValidStates(); 33 | var states = _.filter(classNames, function (className) { 34 | var classNameParts = className.split('-'); 35 | return classNameParts[0] === self.stateClassPrefix && validStates[classNameParts[1]] && 36 | classNameParts.length === 2; 37 | }); 38 | 39 | for (var i = 0; i < states.length; i++) { 40 | state = states[i].split('-')[1]; 41 | retVal[state] = state; 42 | } 43 | 44 | return retVal; 45 | }, 46 | 47 | setState: function (state, on) { 48 | if (!states[state]) { 49 | LAZO.logger.warn('[setState] Invalid state, ' + state); 50 | return; 51 | } 52 | 53 | if (LAZO.app.isClient) { 54 | $(this.el)[on ? 'addClass' : 'removeClass'](this._getStateClass(state)); 55 | } 56 | 57 | this._uiStates = this._uiStates || {}; 58 | if (on) { 59 | this._uiStates[state] = state; 60 | } else { 61 | delete this._uiStates[state]; 62 | } 63 | } 64 | 65 | }; 66 | 67 | }); -------------------------------------------------------------------------------- /lib/server/backbone.js: -------------------------------------------------------------------------------- 1 | define(['backboneServer'], function (Backbone) { 2 | 3 | function noop() { 4 | return this; 5 | } 6 | 7 | // ignore client events on server 8 | Backbone.Events.on = noop; 9 | Backbone.Events.off = noop; 10 | Backbone.Events.trigger = noop; 11 | Backbone.Events.once = noop; 12 | Backbone.Events.listenTo = noop; 13 | Backbone.Events.stopListening = noop; 14 | Backbone.Events.listenToOnce = noop; 15 | 16 | Backbone.Model.prototype.on = noop; 17 | Backbone.Model.prototype.off = noop; 18 | Backbone.Model.prototype.trigger = noop; 19 | Backbone.Model.prototype.once = noop; 20 | Backbone.Model.prototype.listenTo = noop; 21 | Backbone.Model.prototype.stopListening = noop; 22 | Backbone.Model.prototype.listenToOnce = noop; 23 | 24 | Backbone.Collection.prototype.on = noop; 25 | Backbone.Collection.prototype.off = noop; 26 | Backbone.Collection.prototype.trigger = noop; 27 | Backbone.Collection.prototype.once = noop; 28 | Backbone.Collection.prototype.listenTo = noop; 29 | Backbone.Collection.prototype.stopListening = noop; 30 | Backbone.Collection.prototype.listenToOnce = noop; 31 | 32 | Backbone.View.prototype.on = noop; 33 | Backbone.View.prototype.off = noop; 34 | Backbone.View.prototype.trigger = noop; 35 | Backbone.View.prototype.once = noop; 36 | Backbone.View.prototype.listenTo = noop; 37 | Backbone.View.prototype.stopListening = noop; 38 | Backbone.View.prototype.listenToOnce = noop; 39 | 40 | // Do not use Backbone history on server 41 | Backbone.history.start = noop; 42 | 43 | // Disable view events on server 44 | Backbone.View.prototype._ensureElement = noop; 45 | Backbone.View.prototype.delegateEvents = noop; 46 | Backbone.View.prototype.undelegateEvents = noop; 47 | 48 | return Backbone; 49 | 50 | }); 51 | -------------------------------------------------------------------------------- /lib/server/cls-facade.js: -------------------------------------------------------------------------------- 1 | define(['underscore'], function (_) { 2 | 3 | 'use strict'; 4 | 5 | var namespaceObj = { 6 | store: {}, 7 | get: function (key) { 8 | return this.store[key]; 9 | }, 10 | set: function (key, val) { 11 | return this.store[key] = val; 12 | }, 13 | run: function (fn) { 14 | return fn.call(this); 15 | } 16 | }; 17 | 18 | return { 19 | createNamespace: function (name) { 20 | var domain = require('domain'); 21 | var active = domain.active; 22 | var namespace = _.extend({}, namespaceObj); 23 | 24 | if (active) { 25 | if (active[name]) { 26 | return active[name]; 27 | } 28 | 29 | active[name] = namespace; 30 | } 31 | 32 | return namespace; 33 | }, 34 | 35 | getNamespace: function (name) { 36 | var domain = require('domain'); 37 | var active = domain.active; 38 | 39 | return (active && active[name]) || _.extend({}, namespaceObj); 40 | } 41 | }; 42 | }); -------------------------------------------------------------------------------- /lib/server/forbidden.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 3 | 'use strict'; 4 | 5 | var regexes = [/\/server(\/)?/, /\/node_modules(\/)?/]; 6 | 7 | return function (path) { 8 | var i = 0; 9 | var len = regexes.length; 10 | 11 | for (i; i < len; i++) { 12 | if (path.match(regexes[i])) { 13 | return true; 14 | } 15 | } 16 | 17 | return false; 18 | }; 19 | 20 | }); -------------------------------------------------------------------------------- /lib/server/handlers/app/html.js: -------------------------------------------------------------------------------- 1 | define(['text!pageTemplate', 'handlebars'], function (pageTemplate, Handlebars) { 2 | 3 | var compiledPage = Handlebars.default.compile(pageTemplate); 4 | 5 | return function (options) { 6 | return compiledPage(options); 7 | }; 8 | 9 | }); -------------------------------------------------------------------------------- /lib/server/handlers/app/main.js: -------------------------------------------------------------------------------- 1 | define(['handlers/utils', 'handlers/app/processor', 'underscore', 'httpResponse'], function (utils, processor, _, httpResponse) { 2 | 3 | return function (request, reply, route, svr) { 4 | var payload = request.payload; 5 | var options = utils.createCtxOptions(request, reply); 6 | 7 | LAZO.logger.debug('[server.handlers.app.main.processor.reply] Processing route request...', route, options.url.href); 8 | 9 | processor.reply(route, options, { 10 | error: function (err) { 11 | err = err instanceof Error ? err : new Error(err); 12 | LAZO.logger.warn('[server.handlers.app.main.processor.reply] Error processing request...', route, options.url.href, err); 13 | throw err; // hapi domain catches error 14 | }, 15 | success: function (html, httpResponseData) { 16 | var response = reply(html); 17 | httpResponse.applyHttpResponseData(response, httpResponseData); 18 | } 19 | }); 20 | }; 21 | 22 | }); -------------------------------------------------------------------------------- /lib/server/handlers/assets.js: -------------------------------------------------------------------------------- 1 | define(['assets', 'context', 'handlers/utils'], function (LazoAssets, Context, utils) { 2 | 3 | var assets = new LazoAssets(); 4 | 5 | return function (request, reply) { 6 | var ctx = new Context(utils.createCtxOptions(request, reply)); 7 | 8 | function onError(err) { 9 | LAZO.logger.warn('[assets.get] an error occurred while querying the assets end point.', err); 10 | reply({}); 11 | } 12 | 13 | function onSuccess(assets) { 14 | reply(assets); 15 | } 16 | 17 | if (!request.query) { 18 | return onError(new Error('Query parameters not defined.')); 19 | } 20 | 21 | assets.get(request.query.components.split(','), ctx, { 22 | success: onSuccess, 23 | error: onError 24 | }); 25 | }; 26 | 27 | }); -------------------------------------------------------------------------------- /lib/server/handlers/safe.js: -------------------------------------------------------------------------------- 1 | define(['continuation-local-storage', 'cluster', 'domain', 'hapi'], function (cls, cluster, domain, hapi) { 2 | 3 | var onServerStop = function () { 4 | LAZO.logger.error('[server.handlers.safe] Server stopped. Killing process...'); 5 | process.exit(1); 6 | }; 7 | 8 | return function (server, handler, context) { 9 | var lazoNs = cls.getNamespace('lazojs') || cls.createNamespace('lazojs'); 10 | 11 | if (!server || server.constructor !== hapi.Server) { 12 | throw new TypeError(); 13 | } 14 | 15 | if (typeof handler !== 'function') { 16 | throw new TypeError(); 17 | } 18 | 19 | return function (request, reply) { 20 | var handlerArgs = arguments; 21 | var handlerDomain = domain.create(); 22 | var uncaughtCount = 0; 23 | 24 | var onError = function (error) { 25 | lazoNs.run(function () { 26 | lazoNs.set('request', request); 27 | 28 | LAZO.logger.warn('[server.handlers.safe] Unhandled error: %s %j', error.message, error.stack); 29 | 30 | switch (uncaughtCount++) { 31 | case 0: 32 | reply(error); 33 | break; 34 | case 1: 35 | if (cluster.isWorker) { 36 | cluster.worker.disconnect(); 37 | } 38 | LAZO.logger.error('[server.handlers.safe] Stopping server...'); 39 | server.stop(onServerStop); 40 | break; 41 | default: 42 | // Ignore 43 | break; 44 | } 45 | }); 46 | }; 47 | 48 | handlerDomain.on('error', onError); 49 | 50 | handlerDomain.run(function () { 51 | handler.apply(context || this, handlerArgs); 52 | }); 53 | }; 54 | }; 55 | 56 | }); 57 | -------------------------------------------------------------------------------- /lib/server/handlers/stream.js: -------------------------------------------------------------------------------- 1 | define(['hapi'], function (Hapi) { 2 | 3 | 'use strict'; 4 | 5 | return function (req, reply) { 6 | var payload = req.payload; 7 | var componentName = req.params.compName; 8 | var callback = payload ? payload.callback : null; 9 | var fn_name = req.params.action || 'upload'; 10 | var _path = (componentName ? 'components/' + componentName : 'app') + '/server/utilActions'; 11 | LAZO.require([_path], function (util) { 12 | var fn = util[fn_name]; 13 | if (fn) { 14 | fn(req, { 15 | success: function (ret) { 16 | var resp = []; 17 | if (callback) { 18 | resp.push(""); 26 | } 27 | reply(resp.join('')); 28 | }, 29 | error: function (ret, options) { 30 | var error = Hapi.error.internal(ret); 31 | 32 | if (options && options.statusCode) { 33 | error.response.code = options.statusCode; 34 | } 35 | 36 | reply(error); 37 | }, 38 | done: function () { 39 | LAZO.logger.debug('[server.handlers.stream] Handled by application...', _path); 40 | } 41 | }); 42 | } 43 | 44 | }, function (err) { 45 | LAZO.logger.error('[server.handlers.stream] Error loading "%s" module.', _path, err); 46 | reply(err); 47 | }); 48 | }; 49 | 50 | }); -------------------------------------------------------------------------------- /lib/server/handlers/utils.js: -------------------------------------------------------------------------------- 1 | define(['underscore'], function (_) { 2 | 3 | 'use strict'; 4 | 5 | return { 6 | 7 | getParams: function (request) { 8 | var params = {}, 9 | reqParams, 10 | qryParams, 11 | key; 12 | 13 | if ((reqParams = _.extend(request.params, request.payload ? request.payload : {}))) { 14 | for (key in reqParams) { 15 | params[key] = reqParams[key]; 16 | } 17 | } 18 | if ((qryParams = request.query)) { 19 | for (key in qryParams) { 20 | params[key] = qryParams[key]; 21 | } 22 | } 23 | 24 | return params; 25 | }, 26 | 27 | getCookies: function (request) { 28 | var cookies = {}, 29 | state; 30 | if (!(state = request.state)) { 31 | return cookies; 32 | } 33 | 34 | for (var key in state) { 35 | cookies[key] = state[key]; 36 | } 37 | 38 | return cookies; 39 | }, 40 | 41 | getHeaders: function (request) { 42 | return request.raw.req.headers; 43 | }, 44 | 45 | getParsedUrl: function (request) { 46 | return request.url; 47 | }, 48 | 49 | createCtxOptions: function (request, reply, options) { 50 | return _.extend({ 51 | params: this.getParams(request), 52 | cookies: this.getCookies(request), 53 | _request: request, 54 | _reply: reply, 55 | headers: this.getHeaders(request), 56 | url: this.getParsedUrl(request), 57 | response: { 58 | statusCode: null, 59 | httpHeaders: [], 60 | varyParams: [] 61 | } 62 | }, options); 63 | } 64 | 65 | }; 66 | 67 | }); -------------------------------------------------------------------------------- /lib/server/httpResponse.js: -------------------------------------------------------------------------------- 1 | define(['underscore'], function (_) { 2 | 3 | 'use strict'; 4 | 5 | var httpHeaders = [], 6 | varyHeader = []; 7 | 8 | return { 9 | 10 | addHttpHeader: function (name, value, options) { 11 | httpHeaders.push({ name: name, value: value, options: options || null }); 12 | return this; 13 | }, 14 | 15 | getHttpHeaders: function () { 16 | return httpHeaders; 17 | }, 18 | 19 | addVaryParam: function (varyParam) { 20 | varyHeader.push(varyParam); 21 | return this; 22 | }, 23 | 24 | getVaryParams: function () { 25 | return varyHeader; 26 | }, 27 | 28 | mergeHttpResponseData: function (ctl) { 29 | return { 30 | statusCode: ctl.getHttpStatusCode() || null, 31 | httpHeaders: httpHeaders.concat(ctl.getHttpHeaders()), 32 | varyParams: _.union(varyHeader, ctl.getHttpVaryParams()) 33 | }; 34 | }, 35 | 36 | applyHttpResponseData: function (response, httpResponseData) { 37 | if (httpResponseData.statusCode) { 38 | response.code(httpResponseData.statusCode); 39 | } 40 | 41 | _.each(httpResponseData.httpHeaders, function (header) { 42 | response.header(header.name, header.value, header.options || { override: true }); 43 | }); 44 | 45 | _.each(httpResponseData.varyParams, function (headerName) { 46 | response.vary(headerName); 47 | }); 48 | } 49 | 50 | } 51 | }); -------------------------------------------------------------------------------- /lib/server/jquery.js: -------------------------------------------------------------------------------- 1 | define('jquery', [], function () { return function () {}; }); // noop on server -------------------------------------------------------------------------------- /lib/server/logger/fileSink.js: -------------------------------------------------------------------------------- 1 | /*global __dirname:false*/ 2 | 3 | define(['underscore', 'fs', 'moment', 'os', 'path', 'util'], function (_, fs, moment, os, path, util) { 4 | 5 | var DEFAULT_FILE_NAME = 'lazojs.log'; 6 | 7 | var DEFAULT_FILE_PATH = path.join(process.cwd(), DEFAULT_FILE_NAME); 8 | 9 | var MB = 1024 * 1024; 10 | 11 | var DEFAULT_MAX_SIZE = 10 * MB; 12 | 13 | var DEFAULT = { 14 | filePath: DEFAULT_FILE_PATH, 15 | maxFiles: 10, 16 | maxSize: DEFAULT_MAX_SIZE, 17 | roll: true 18 | }; 19 | 20 | var dateChanged = function (a, b) { 21 | return a === null || b === null || 22 | a.getUTCFullYear() !== b.getUTCFullYear() || 23 | a.getUTCMonth() !== b.getUTCMonth() || 24 | a.getUTCDate() !== b.getUTCDate(); 25 | }; 26 | 27 | var getFilename = function (filePath) { 28 | return _.last(filePath.split(path.sep)); 29 | }; 30 | 31 | var getNextFilePath = function (filePath, maxFiles) { 32 | var dirPath = path.dirname(filePath); 33 | var files = fs.readdirSync(dirPath); 34 | var fileName = getFilename(filePath); 35 | var regExp = new RegExp('^' + fileName + '\\.(\\d+)$'); 36 | var filtered = _.filter(files, function (file) { 37 | return regExp.test(file); 38 | }); 39 | var last = _.max(filtered, function (fileName) { 40 | var filePath = path.join(dirPath, fileName); 41 | return fs.existsSync(filePath) ? fs.statSync(filePath).mtime : 0; 42 | }); 43 | var match = last && regExp.exec(last); 44 | var pointer = match && match[1] ? parseInt(match[1]) : 0; 45 | var sequence = pointer >= 1 && pointer < maxFiles ? pointer + 1 : 1; 46 | 47 | return util.format('%s.%d', filePath, sequence); 48 | }; 49 | 50 | var rollFileSync = function (filePath, maxFiles) { 51 | try { 52 | if (!fs.existsSync(filePath)) { 53 | return; 54 | } 55 | 56 | var nextFilePath = getNextFilePath(filePath, maxFiles); 57 | fs.renameSync(filePath, nextFilePath); 58 | } catch (error) { 59 | console.error(util.format('FileSink, error while rolling file "%s": %s', filePath, error.message)); 60 | } 61 | }; 62 | 63 | var FileSink = function (options) { 64 | 65 | var _options = _.defaults({}, options, DEFAULT); 66 | 67 | // Check if directory exists 68 | 69 | var dirPath = path.dirname(_options.filePath); 70 | 71 | if (!fs.existsSync(dirPath)) { 72 | throw new Error(util.format('FileSink, given path does not exist: %s', dirPath)); 73 | } 74 | 75 | // Check if can write file 76 | 77 | fs.closeSync(fs.openSync(_options.filePath, 'a')); 78 | 79 | // Initial status 80 | 81 | var stats = fs.statSync(_options.filePath); 82 | var fileSize = stats.size; 83 | var lastModified = stats.mtime; 84 | 85 | // Sink function 86 | 87 | return function (message) { 88 | var now = new Date(); 89 | var messageBuffer = new Buffer(message + os.EOL); 90 | var messageLength = messageBuffer.length; 91 | 92 | if ((fileSize + messageLength) > _options.maxSize || (_options.roll && dateChanged(lastModified, now))) { 93 | rollFileSync(_options.filePath, _options.maxFiles); 94 | fileSize = 0; 95 | } 96 | 97 | fs.appendFile(_options.filePath, messageBuffer, function (error) { 98 | if (error) { 99 | return console.error(util.format('FileSink, error while appending to "%s": %s', _options.filePath, error.message)); 100 | } 101 | 102 | fileSize += messageLength; 103 | lastModified = now; 104 | }); 105 | }; 106 | }; 107 | 108 | return FileSink; 109 | 110 | }); 111 | -------------------------------------------------------------------------------- /lib/server/logger/utils.js: -------------------------------------------------------------------------------- 1 | define(['continuation-local-storage', 'util'], function (cls, util) { 2 | 3 | var getRequest = function () { 4 | var lazoNs = cls.getNamespace('lazojs'); 5 | var request = lazoNs && lazoNs.get('request'); 6 | return request; 7 | }; 8 | 9 | var serverStringify = function (object) { 10 | var options = { 11 | depth: 1 12 | }; 13 | 14 | return util.inspect(object, options).replace(/\s+/g, ' '); 15 | }; 16 | 17 | return { 18 | getRequest: getRequest, 19 | serverStringify: serverStringify 20 | }; 21 | 22 | }); 23 | -------------------------------------------------------------------------------- /lib/server/proxy.js: -------------------------------------------------------------------------------- 1 | define(['base', 'underscore', 'serviceProxy', 'fs', 'path'], function (Base, _, ServiceProxy, fs, path) { 2 | 3 | var Proxy = Base.extend({ 4 | sync: function (method, options) { 5 | var model = this; 6 | var validationResp = doValidate(model); 7 | if (validationResp) { 8 | if (options.error) { 9 | return options.error(validationResp); 10 | } else { 11 | throw new Error('Error back not defined'); 12 | } 13 | } 14 | 15 | findSyncer(model, { 16 | success: function (Syncher) { 17 | var sync = new Syncher(); 18 | sync.proxy.ctx = model.ctx; 19 | 20 | var opts = _.extend({ params: model.params || {} }, options, { model: model }); 21 | 22 | LAZO.logger.debug('[server.proxy.sync] Calling CRUD syncher...', model.name); 23 | 24 | switch (method) { 25 | case 'read': 26 | sync.fetch(opts); 27 | break; 28 | case 'create': 29 | sync.add(model.attributes, opts); 30 | break; 31 | case 'update': 32 | sync.update(model.attributes, opts); 33 | break; 34 | case 'delete': 35 | sync.destroy(model.attributes, opts); 36 | break; 37 | } 38 | }, 39 | error: function (err) { 40 | LAZO.logger.debug('[server.proxy.sync] Error calling CRUD syncher...', model.name, err); 41 | 42 | ServiceProxy.prototype.sync.call(this, method, model, options); 43 | } 44 | }); 45 | }, 46 | 47 | callSyncher: function (fname, args, options) { 48 | var model = this; 49 | var validationResp = doValidate(model); 50 | if (validationResp) { 51 | if (options.error) { 52 | return options.error(validationResp); 53 | } else { 54 | throw new Error('Error back not defined'); 55 | } 56 | } 57 | 58 | findSyncer(model, { 59 | success: function (Syncher) { 60 | var sync = new Syncher(); 61 | sync.proxy.ctx = model.ctx; 62 | 63 | var opts = _.extend(options, { ctx: model.ctx }); 64 | 65 | LAZO.logger.debug('[server.proxy.callSyncher] Calling NON-CRUD syncher...', fname); 66 | 67 | if (typeof(sync[fname]) === 'function') { 68 | return sync[fname](args, opts); 69 | } 70 | 71 | return options.error({error: 'Method not found in syncher: ' + fname}); 72 | }, 73 | error: function (err) { 74 | return options.error({error: 'No syncher defined for model: ' + model.name}); 75 | } 76 | }); 77 | } 78 | }); 79 | 80 | function doValidate(model) { 81 | if (!model.name) { 82 | return {error: 'model does not have a name'}; 83 | } 84 | if (!model.ctx) { 85 | return {error: 'model does not have a context'}; 86 | } 87 | return null; 88 | } 89 | 90 | function findSyncer(model, options) { 91 | var exists = fs.existsSync(path.join(LAZO.FILE_REPO_PATH, 'models', model.name, 'server', 'syncher.js')); 92 | if (!exists) { 93 | return options.error(Error("syncher.js not found for model " + model.name)); 94 | } 95 | LAZO.require(['models/' + model.name + '/server/syncher'], options.success, options.error); 96 | } 97 | 98 | return Proxy; 99 | }); 100 | -------------------------------------------------------------------------------- /lib/server/resolver/file.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 3 | 'use strict'; 4 | 5 | var fs = require('fs'), 6 | path = require('path'), 7 | dir = require('node-dir'); 8 | 9 | return { 10 | 11 | getErrorTemplatePaths: function (callback) { 12 | var templatePaths = { 13 | '404': { 14 | server: LAZO.BASE_PATH + '/lib/common/templates/404.hbs', 15 | client: 'lib/common/templates/404.hbs' 16 | }, 17 | '500': { 18 | server: LAZO.BASE_PATH + '/lib/common/templates/500.hbs', 19 | client: 'lib/common/templates/500.hbs' 20 | } 21 | }, 22 | basename, 23 | clientPath, 24 | serverPath, 25 | directory = path.normalize(LAZO.FILE_REPO_PATH + '/app/' + 'views'); 26 | 27 | this.list(directory, { ext: '.hbs' }, function (err, files) { 28 | if (err) { 29 | throw err; 30 | } 31 | 32 | for (var i = 0; i < files.length; i++) { 33 | 34 | basename = path.basename(files[i], '.hbs'); 35 | serverPath = path.normalize(files[i]); 36 | clientPath = path.normalize(serverPath.replace(LAZO.FILE_REPO_PATH, '/')); 37 | if (basename === '404' || basename === '500') { 38 | templatePaths[basename] = { 39 | server: serverPath, 40 | client: clientPath 41 | }; 42 | } 43 | } 44 | 45 | callback(null, templatePaths); 46 | }); 47 | }, 48 | 49 | resolvePath: function (from, to) { 50 | if (LAZO.app.isClient) { 51 | LAZO.logger.warn('[server.resolver.resolvePath]', 'Should not be called on client.'); 52 | return from; 53 | } 54 | 55 | return path.resolve(from, to); 56 | }, 57 | 58 | list: function (directory, options, callback) { 59 | var filtered = []; 60 | 61 | if (LAZO.app.isClient) { 62 | LAZO.logger.warn('[server.resolver.resolvePath]', 'Should only be called on the server.'); 63 | return callback(null, []); 64 | } 65 | 66 | directory = options.basePath ? path.resolve(options.basePath, directory) : directory; 67 | fs.exists(directory, function (exists) { 68 | if (!exists) { 69 | return callback(null, []); 70 | } 71 | 72 | dir.files(directory, function (err, files) { 73 | if (err) { // TODO: error handling 74 | throw err; 75 | } 76 | 77 | if (!options.basePath && !options.ext) { 78 | return files; 79 | } 80 | 81 | for (var i = 0; i < files.length; i++) { 82 | if (options.ext && path.extname(files[i]) !== options.ext) { 83 | continue; 84 | } 85 | files[i] = options.basePath ? files[i].replace(options.basePath + '/', '') : files[i]; 86 | filtered.push(files[i]); 87 | } 88 | 89 | callback(err, filtered || []); 90 | }); 91 | }); 92 | } 93 | 94 | }; 95 | 96 | }); -------------------------------------------------------------------------------- /lib/server/scanner.js: -------------------------------------------------------------------------------- 1 | // returns a hash used to determine if the base class should be loaded 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var dir = require('node-dir'); 5 | 6 | function scan(appPath, callback) { 7 | var processed = 0; 8 | var retVal = { 9 | components: { subdir: '/components', filters: ['controller.js', path.sep + 'views' + path.sep, '.css', path.sep + 'imports' + path.sep] }, 10 | models: { subdir: '/models', filters: ['model.js', 'collection.js'] }, 11 | appViews: { subdir: '/app/views', filters: [] } 12 | }; 13 | 14 | function getRelativePath(appPath, absolutePath) { 15 | return absolutePath.replace(appPath + path.sep, '').replace(/\\/g, '/'); 16 | } 17 | 18 | console.log('Scanning application directory. Please wait...'); 19 | 20 | for (var k in retVal) { 21 | (function (k, retVal) { 22 | var subdir = path.normalize(appPath + retVal[k].subdir); 23 | var filesHash = {}; 24 | dir.files(subdir, function (err, files) { 25 | if (err) { 26 | console.log('INFO: ' + err.path + ' does not exist'); 27 | } else { 28 | files.forEach(function (file) { 29 | if (retVal[k].filters.length) { 30 | retVal[k].filters.forEach(function (filter) { 31 | if (file.indexOf(filter) !== -1) { 32 | filesHash[getRelativePath(appPath, file)] = true; 33 | } 34 | }); 35 | } else { 36 | filesHash[getRelativePath(appPath, file)] = true; 37 | } 38 | }); 39 | } 40 | 41 | retVal[k] = filesHash; 42 | processed++; 43 | if (processed === 3) { 44 | callback(retVal); 45 | } 46 | }); 47 | })(k, retVal); 48 | } 49 | } 50 | 51 | module.exports = scan; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lazo", 3 | "version": "4.0.0", 4 | "description": "A client-server web framework built on Node.js that allows front-end developers to easily create a 100% SEO compliant, component MVC structured web application with an optimized first page load.", 5 | "homepage": "https://github.com/lazojs/lazo", 6 | "keywords": [ 7 | "backbone", 8 | "requirejs", 9 | "hapi", 10 | "mvc", 11 | "client-server", 12 | "lazo", 13 | "lazojs", 14 | "isomorphic javascript", 15 | "SEO" 16 | ], 17 | "licenses": [ 18 | { 19 | "type": "MIT", 20 | "url": "/blob/master/LICENSE-MIT" 21 | } 22 | ], 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/lazojs/lazo" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/lazojs/lazo/issues" 29 | }, 30 | "private": false, 31 | "scripts": { 32 | "start": "lazo", 33 | "prepublish": "node bin/postinstall; grunt requirejs", 34 | "test": "grunt test" 35 | }, 36 | "main": "index.js", 37 | "bin": { 38 | "lazo": "lazo.js" 39 | }, 40 | "dependencies": { 41 | "crumb": "3.1.0", 42 | "request": "2.40.0", 43 | "forever": "0.10.8", 44 | "fs-extra": "0.6.3", 45 | "handlebars": "2.0.0", 46 | "yargs": "3.6.0", 47 | "node-dir": "0.1.5", 48 | "lodash": "2.4.1", 49 | "hapi": "6.7.1", 50 | "requirejs": "2.1.11", 51 | "backbone": "1.1.1", 52 | "underscore": "1.6.0", 53 | "jquery": "1.11.1", 54 | "requirejs-text": "2.0.12", 55 | "hermes-conrad": "1.x.x", 56 | "jquery.cookie": "1.4.1", 57 | "requirejs-plugins": "1.0.2", 58 | "async": "0.6.2", 59 | "flexo.js": "0.2.x", 60 | "htmlparser": "1.7.7", 61 | "htmlparser-tostring": "0.0.2" 62 | }, 63 | "devDependencies": { 64 | "grunt-contrib-requirejs": "0.4.x", 65 | "grunt": "0.4.5", 66 | "grunt-contrib-watch": "0.5.x", 67 | "intern": "2.1.1", 68 | "sinon-chai": "2.6.0", 69 | "sinon": "1.10.3", 70 | "phantomjs": "1.9.13", 71 | "selenium-server": "2.43.1", 72 | "grunt-exec": "0.4.6" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /run.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var lazoPath = path.dirname(module.filename); 4 | var os = require('os'); 5 | var lazo = require('./index.js') 6 | var args = parseArgs(); 7 | 8 | // helper functions 9 | function getAppDir() { 10 | var fileRepo = args._[1] ? path.resolve(args._[1]) : undefined; 11 | return fileRepo && fs.existsSync(fileRepo) ? fileRepo : 0; 12 | } 13 | 14 | function parseArgs() { 15 | return require('yargs') 16 | .alias('c', 'cluster') 17 | .alias('v', 'version') 18 | .alias('d', 'daemon') 19 | .alias('p', 'port') 20 | .default('port', '8080') 21 | .alias('r', 'robust') 22 | .boolean(['d', 'r']) 23 | .string(['p', 'c']) 24 | .argv; 25 | } 26 | 27 | // get the options for a lazo command 28 | function getStartEnvOptions() { 29 | var options = {}; 30 | 31 | for (var k in args) { 32 | switch (k) { 33 | case 'daemon': 34 | case 'robust': 35 | options[k] = args[k] === true ? '1' : '0'; 36 | break; 37 | case 'cluster': 38 | options[k] = args[k] ? args[k] : os.cpus().length; 39 | break; 40 | case 'port': 41 | options[k] = args[k]; 42 | break; 43 | } 44 | } 45 | 46 | return options; 47 | } 48 | 49 | // lazo --help [command] 50 | function help(command) { 51 | switch (command) { 52 | case 'start': 53 | console.log('\nUsage: lazo start app_dir -c [num] -d -p [num]\n'); 54 | console.log('Options:\n'); 55 | console.log('app_dir [required] application directory'); 56 | console.log('-c [optional] cluster, value [optional]\n'); 57 | console.log('-d [optional] daemonize process using forever\n'); 58 | console.log('-p [optional] port, value [required]\n'); 59 | break; 60 | case 'stop': 61 | console.log('\nUsage: lazo stop\n'); 62 | break; 63 | default: 64 | console.log('\nUsage: lazo --help command\n'); 65 | console.log('Available commands: start stop\n'); 66 | console.log('Options:\n'); 67 | console.log('command [optional]\n'); 68 | break; 69 | } 70 | } 71 | 72 | // entry point 73 | function main() { 74 | if (args._.length) { 75 | switch (args._[0]) { 76 | case 'start': 77 | args.app_dir = getAppDir(); 78 | lazo('start', args); 79 | break; 80 | case 'stop': 81 | lazo('stop', args); 82 | break; 83 | default: 84 | help(); 85 | break; 86 | } 87 | } else { 88 | if (args.version) { 89 | console.log('v' + lazo('version')); 90 | } else if (args.help) { 91 | help(args.help); 92 | } else { 93 | help(); 94 | } 95 | } 96 | } 97 | 98 | module.exports = main; -------------------------------------------------------------------------------- /start.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var lazoPath = path.dirname(module.filename); 4 | var os = require('os'); 5 | 6 | function setEnv(options) { 7 | process.env['BASE_PATH'] = lazoPath; 8 | process.env['BASE_REPO_DIR'] = path.join(lazoPath, 'base'); 9 | process.env['LAZO_VERSION'] = options.version; 10 | process.env['FILE_REPO_DIR'] = options.app_dir; 11 | process.env['PORT'] = options.port; 12 | 13 | for (var k in options) { 14 | process.env[k.toUpperCase()] = options[k]; 15 | } 16 | } 17 | 18 | function daemon() { 19 | var fsx = require('fs-extra'); 20 | var forever = require('forever'); 21 | 22 | fsx.mkdirs(lazoPath + '/logs', function (err) { 23 | if (!err) { 24 | forever.load({ root: lazoPath + '/logs', pidPath: lazoPath }); 25 | forever.startDaemon('./lib/server/app.js', { 26 | logFile: 'lazo.log', 27 | pidFile: 'lazo.pid', 28 | errFile: 'lazo.err', 29 | sourceDir: lazoPath, 30 | a: true 31 | }); 32 | } else { 33 | console.error(err); 34 | } 35 | }); 36 | } 37 | 38 | module.exports = function (options) { 39 | setEnv(options); 40 | if (options.daemon) { 41 | return daemon(); 42 | } 43 | 44 | console.log('Starting Lazo! Please wait...'); 45 | // starts application server 46 | var lazo = require('./lib/server/app.js'); 47 | } -------------------------------------------------------------------------------- /stop.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | var forever = require('forever'); 3 | var fsx = require('fs-extra'); 4 | 5 | if (fsx.existsSync(lazoPath + '/lazo.pid')) { 6 | try { 7 | forever.stop('lib/server/app.js', true); 8 | forever.cleanUp(); 9 | fsx.remove(lazoPath + '/lazo.pid'); 10 | console.log('Lazo! stopped'); 11 | } catch (err) { 12 | console.log('Error stopping Lazo!'); 13 | process.exit(1); 14 | } 15 | } 16 | }; -------------------------------------------------------------------------------- /templates/README.md: -------------------------------------------------------------------------------- 1 | This will contain templates for the create and add commands. -------------------------------------------------------------------------------- /test/application/app/assets/en-US/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazojs/lazo/0b91b8b3b9f5246414b23458e8202fc4940ebd45/test/application/app/assets/en-US/img/logo.png -------------------------------------------------------------------------------- /test/application/app/assets/en-US/strings.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Käthe Kollwitz" 3 | } -------------------------------------------------------------------------------- /test/application/app/assets/info.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazojs/lazo/0b91b8b3b9f5246414b23458e8202fc4940ebd45/test/application/app/assets/info.pdf -------------------------------------------------------------------------------- /test/application/app/assets/strings.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Robert Crumb" 3 | } -------------------------------------------------------------------------------- /test/application/app/imports/index.html: -------------------------------------------------------------------------------- 1 |

I am an application import.

-------------------------------------------------------------------------------- /test/application/components/bar/assets/en-US/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazojs/lazo/0b91b8b3b9f5246414b23458e8202fc4940ebd45/test/application/components/bar/assets/en-US/img/logo.png -------------------------------------------------------------------------------- /test/application/components/bar/assets/en-US/strings.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Käthe Kollwitz" 3 | } -------------------------------------------------------------------------------- /test/application/components/bar/assets/info.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazojs/lazo/0b91b8b3b9f5246414b23458e8202fc4940ebd45/test/application/components/bar/assets/info.pdf -------------------------------------------------------------------------------- /test/application/components/bar/assets/strings.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Robert Crumb" 3 | } -------------------------------------------------------------------------------- /test/application/components/bar/imports/index.html: -------------------------------------------------------------------------------- 1 |

I am an import for component bar.

-------------------------------------------------------------------------------- /test/application/components/foo/views/child.js: -------------------------------------------------------------------------------- 1 | define(['lazoView'], function (LazoView) { 2 | 3 | return LazoView.extend({ 4 | child: true, 5 | getTemplate: function (options) { 6 | options.success('I am the child template.'); 7 | } 8 | }); 9 | 10 | }); -------------------------------------------------------------------------------- /test/application/components/foo/views/index.hbs: -------------------------------------------------------------------------------- 1 | I am the template. -------------------------------------------------------------------------------- /test/mocks/css/a.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazojs/lazo/0b91b8b3b9f5246414b23458e8202fc4940ebd45/test/mocks/css/a.css -------------------------------------------------------------------------------- /test/mocks/css/b.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazojs/lazo/0b91b8b3b9f5246414b23458e8202fc4940ebd45/test/mocks/css/b.css -------------------------------------------------------------------------------- /test/mocks/css/c.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazojs/lazo/0b91b8b3b9f5246414b23458e8202fc4940ebd45/test/mocks/css/c.css -------------------------------------------------------------------------------- /test/mocks/css/d.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazojs/lazo/0b91b8b3b9f5246414b23458e8202fc4940ebd45/test/mocks/css/d.css -------------------------------------------------------------------------------- /test/mocks/lazo.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 3 | var LAZO = {}, 4 | isServer = true, 5 | isClient = true; 6 | 7 | try { 8 | window; 9 | isServer = false; 10 | } catch (err) { 11 | isClient = false; 12 | } 13 | 14 | LAZO.app = { 15 | isServer: isServer, 16 | isClient: isClient 17 | }; 18 | 19 | LAZO.logger = { 20 | debug: function () { 21 | }, 22 | error: function () { 23 | }, 24 | log: function () { 25 | }, 26 | warn: function () { 27 | }, 28 | info: function () { 29 | } 30 | }; 31 | 32 | LAZO.config = { 33 | get: function (key) { 34 | return this[key]; 35 | }, 36 | set: function (key, value) { 37 | this[key] = value; 38 | } 39 | }; 40 | 41 | LAZO.files = { 42 | components: [], 43 | models: [], 44 | appViews: [] 45 | }; 46 | 47 | LAZO.FILE_REPO_PATH = './test'; 48 | 49 | return LAZO; 50 | 51 | }); -------------------------------------------------------------------------------- /test/mocks/server/continuation-local-storage.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 3 | return { 4 | getNamespace: function () { 5 | 6 | }, 7 | 8 | createNamespace: function () { 9 | 10 | } 11 | }; 12 | 13 | }); -------------------------------------------------------------------------------- /test/mocks/server/hapi.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 3 | return function Hapi() { 4 | 5 | }; 6 | 7 | }); -------------------------------------------------------------------------------- /test/mocks/server/request.js: -------------------------------------------------------------------------------- 1 | define(function () { 2 | 3 | function request (uri, options, callback) { 4 | requestStub(uri, options, callback); 5 | } 6 | 7 | return request; 8 | 9 | }); -------------------------------------------------------------------------------- /test/unit/client-server/common/config.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'underscore', 3 | 'intern!bdd', 4 | 'intern/chai!expect', 5 | 'test/unit/utils', 6 | 'sinon', 7 | 'intern/chai!', 8 | 'sinon-chai', 9 | 'lib/common/config' 10 | ], function (_, bdd, expect, utils, sinon, chai, sinonChai, config) { 11 | chai.use(sinonChai); 12 | 13 | with (bdd) { 14 | 15 | describe('config', function () { 16 | 17 | config.addPlugin(new function(){ 18 | return { 19 | data: {"key": "value"}, 20 | 21 | get: function (key, options) { 22 | var ret; 23 | if (options && options.context) { 24 | ret = options.context[key]; 25 | } else if (this.data && key in this.data) { 26 | ret = this.data[key]; 27 | } 28 | 29 | return (options && _.isFunction(options.success)) ? options.success(ret) : ret; 30 | } 31 | } 32 | }); 33 | 34 | 35 | it('should return the value for a given key', function () { 36 | expect(config.get("key")).to.be.equal('value'); 37 | }); 38 | 39 | it('should return the value for a given key in the provided context', function () { 40 | expect(config.get("key", {context:{"key":"contextValue"}})).to.be.equal('contextValue'); 41 | }); 42 | 43 | it('should return the value for a given key via a success callback', function () { 44 | var dfd = this.async(); 45 | 46 | config.get("key",{ 47 | success: function(value){ 48 | expect(value).to.be.equal('value'); 49 | dfd.resolve(); 50 | } 51 | }); 52 | }); 53 | 54 | it('should return the value for a given key in the provided context via a success callback', function () { 55 | var dfd = this.async(); 56 | 57 | config.get("key",{ 58 | context: {"key":"contextValue"}, 59 | success: function(value){ 60 | expect(value).to.be.equal('contextValue'); 61 | dfd.resolve(); 62 | } 63 | }); 64 | }); 65 | 66 | }); 67 | } 68 | }); -------------------------------------------------------------------------------- /test/unit/client-server/common/logger.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!expect', 4 | 'test/unit/utils', 5 | 'sinon', 6 | 'intern/chai!', 7 | 'sinon-chai', 8 | 'lib/common/logger' 9 | ], function (bdd, expect, utils, sinon, chai, sinonChai, logger) { 10 | chai.use(sinonChai); 11 | 12 | with (bdd) { 13 | 14 | describe('logger', function () { 15 | 16 | var noop = function () { 17 | }; 18 | 19 | it('should have error as default level', function () { 20 | expect(logger.getLevel()).to.be.equal('error'); 21 | }); 22 | 23 | it('should have console as default sink', function () { 24 | var sinks = logger.getSinks(); 25 | expect(sinks.console).to.exist; 26 | expect(sinks.console).to.be.a.function; 27 | }); 28 | 29 | it('should format messages correctly', function () { 30 | this.skip(); 31 | var dfd = this.async(); 32 | sinon.stub(console, 'log'); 33 | 34 | var error = new Error('consectetur adipiscing elit'); 35 | error.stack = 'nullam vel tempus massa'; 36 | 37 | expect(logger.error('Lorem ipsum dolor sit amet')).to.be.equal('2012-01-27T12:30:00.000Z\tERROR\t-\tLorem ipsum dolor sit amet'); 38 | expect(logger.error('Lorem ipsum dolor sit amet', {foo: 123, bar: 456})).to.be.equal('2012-01-27T12:30:00.000Z\tERROR\t-\tLorem ipsum dolor sit amet {"foo":123,"bar":456}'); 39 | expect(logger.error('Lorem ipsum dolor sit amet', error)).to.be.equal('2012-01-27T12:30:00.000Z\tERROR\t-\tLorem ipsum dolor sit amet {"message":"consectetur adipiscing elit","stack":"nullam vel tempus massa"}'); 40 | expect(logger.error('Lorem %s dolor %s amet', 'ipsum', 'sit')).to.be.equal('2012-01-27T12:30:00.000Z\tERROR\t-\tLorem ipsum dolor sit amet'); 41 | expect(logger.error('Lorem %s dolor %s amet')).to.be.equal('2012-01-27T12:30:00.000Z\tERROR\t-\tLorem undefined dolor undefined amet'); 42 | expect(logger.error('Lorem ipsum dolor sit amet %d %f', 3.14159, 3.14159)).to.be.equal('2012-01-27T12:30:00.000Z\tERROR\t-\tLorem ipsum dolor sit amet 3 3.14159'); 43 | 44 | setTimeout(function () { 45 | expect(console.log.callCount).to.be.equal(6); 46 | console.log.restore(); 47 | dfd.resolve(); 48 | }, 0); 49 | }); 50 | 51 | it('should change the log level', function () { 52 | logger.setLevel('warn'); 53 | expect(logger.getLevel()).to.be.equal('warn'); 54 | logger.setLevel('info'); 55 | expect(logger.getLevel()).to.be.equal('info'); 56 | logger.setLevel('debug'); 57 | expect(logger.getLevel()).to.be.equal('debug'); 58 | }); 59 | 60 | it('should call new registered sink', function () { 61 | var dfd = this.async(); 62 | var stubSink = sinon.stub(); 63 | 64 | logger.addSink('stub', stubSink); 65 | 66 | expect(logger.getSinks()['stub']).to.be.a.function; 67 | 68 | logger.error('Lorem ipsum dolor sit amet'); 69 | 70 | setTimeout(function () { 71 | expect(stubSink).to.be.called.once; 72 | dfd.resolve(); 73 | }, 0); 74 | }); 75 | 76 | it('should remove registered sink', function () { 77 | expect(logger.getSinks()['stub']).to.be.a.function; 78 | logger.removeSink('stub'); 79 | expect(logger.getSinks()['stub']).to.not.exist; 80 | }); 81 | 82 | }); 83 | } 84 | }); -------------------------------------------------------------------------------- /test/unit/client-server/common/renderer.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!expect', 4 | 'test/unit/utils', 5 | 'sinon', 6 | 'intern/chai!', 7 | 'sinon-chai', 8 | 'renderer' 9 | ], function (bdd, expect, utils, sinon, chai, sinonChai, renderer) { 10 | chai.use(sinonChai); 11 | 12 | with (bdd) { 13 | 14 | describe('Renderer', function () { 15 | 16 | it('should get the index for an markup insertion in a string', function () { 17 | var dfd = this.async(); 18 | 19 | utils.setUpApp(function () { 20 | var html = '
'; 21 | var insertStr = '

hello

'; 22 | var expectStr = '
' + insertStr + '
'; 23 | var match = renderer.getInsertIndex('lazo-cmp-container', 'foo', html); 24 | var open = html.substr(0, match.index + match[0].length); 25 | var close = html.substr(match.index + match[0].length); 26 | var testStr = open + insertStr + close; 27 | 28 | expect(testStr).to.be.equal(expectStr); 29 | dfd.resolve(); 30 | }); 31 | }); 32 | 33 | it('should render a tree', function () { 34 | var dfd = this.async(); 35 | 36 | utils.setUpApp(function () { 37 | utils.createCtlTree(function (ctl) { 38 | renderer.getTreeHtml(ctl, null, null, function (html) { 39 | var regex = /
I am a template!<\/div><\/div>
I am a template!<\/div><\/div><\/div><\/div><\/div>/; 40 | var match = html.match(regex); 41 | 42 | expect(match.length).to.be.equal(1); 43 | expect(match.index).to.be.equal(0); 44 | dfd.resolve(); 45 | }); 46 | }); 47 | }); 48 | }); 49 | }); 50 | 51 | } 52 | }); -------------------------------------------------------------------------------- /test/unit/client-server/common/resolver/file.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'resolver/file' 9 | ], function (bdd, chai, expect, sinon, sinonChai, utils, file) { 10 | chai.use(sinonChai); 11 | 12 | with (bdd) { 13 | describe('file resolver', function () { 14 | 15 | it('should get a view\'s path', function () { 16 | var cmpViewPath = file.getPath('module_foo', 'cmp_bar', 'view'), 17 | appViewPath = file.getPath('a:module_bar', 'cmp_bar', 'view'); 18 | 19 | expect(cmpViewPath).to.be.equal('components/cmp_bar/views/module_foo'); 20 | expect(appViewPath).to.be.equal('app/views/module_bar'); 21 | 22 | }); 23 | 24 | it('should get a template\'s path', function () { 25 | var view1 = { 26 | templateName: 'view_tmp', 27 | ctl: { 28 | name: 'cmp_name' 29 | }, 30 | templateEngine: 'handlebars' 31 | }, 32 | view2 = { 33 | templateName: 'a:view_tmp', 34 | ctl: { 35 | name: 'cmp_name' 36 | }, 37 | templateEngine: 'handlebars' 38 | }, 39 | cmpTemplatePath = file.getTemplatePath(view1), 40 | appTemplatePath = file.getTemplatePath(view2); 41 | 42 | expect(cmpTemplatePath).to.be.equal('components/cmp_name/views/view_tmp.hbs'); 43 | expect(appTemplatePath).to.be.equal('app/views/view_tmp.hbs'); 44 | 45 | }); 46 | 47 | it('should get a template\'s name', function () { 48 | var view1 = { 49 | templateName: 'view_tmp', 50 | ctl: { 51 | name: 'cmp_name' 52 | }, 53 | templateEngine: 'handlebars' 54 | }, 55 | view2 = { 56 | templateName: function () { return 'a:view_tmp' }, 57 | ctl: { 58 | name: 'cmp_name' 59 | }, 60 | templateEngine: 'handlebars' 61 | }, 62 | strTemplateName = file.getTemplateName(view1), 63 | fnTemplateName = file.getTemplateName(view2); 64 | 65 | expect(strTemplateName).to.be.equal('view_tmp'); 66 | expect(fnTemplateName).to.be.equal('a:view_tmp'); 67 | 68 | }); 69 | 70 | it('should get a view\'s base path', function () { 71 | var cmpViewPath = file.getBasePath('module_foo', 'cmp_bar', 'view'), 72 | appViewPath = file.getBasePath('a:module_bar', 'cmp_bar', 'view'); 73 | 74 | expect(cmpViewPath).to.be.equal('components/cmp_bar/views'); 75 | expect(appViewPath).to.be.equal('app/views'); 76 | 77 | }); 78 | 79 | }); 80 | } 81 | }); -------------------------------------------------------------------------------- /test/unit/client-server/common/resolver/route.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'resolver/route' 9 | ], function (bdd, chai, expect, sinon, sinonChai, utils, route) { 10 | chai.use(sinonChai); 11 | 12 | with (bdd) { 13 | describe('route resolver', function () { 14 | it('should transform routes', function () { 15 | var foo = route.transform('foo(/)'); 16 | var bar = route.transform('bar'); 17 | var baz = route.transform(''); 18 | var dynamic = route.transform('foo/*bar'); 19 | 20 | expect(foo.routeTrailingSlash).to.be.equal('/foo/'); 21 | expect(foo.route).to.be.equal('/foo'); 22 | expect(bar.routeTrailingSlash).to.be.null; 23 | expect(bar.route).to.be.equal('/bar'); 24 | expect(baz.routeTrailingSlash).to.be.null; 25 | expect(baz.route).to.be.equal('/'); 26 | 27 | if (LAZO.app.isServer) { 28 | // hapi style splat 29 | expect(dynamic.route).to.be.equal('/foo/{bar*}'); 30 | } else { 31 | // backbone style splat 32 | expect(dynamic.route).to.be.equal('/foo/*bar'); 33 | } 34 | }); 35 | }); 36 | } 37 | }); -------------------------------------------------------------------------------- /test/unit/client-server/common/utils/handlebarsEngine.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'handlebars', 9 | 'utils/handlebarsEngine' 10 | ], function (bdd, chai, expect, sinon, sinonChai, utils, handlebars, hbsEng) { 11 | chai.use(sinonChai); 12 | 13 | with (bdd) { 14 | describe('handlebarsEngine', function () { 15 | it('should compile a template', function () { 16 | var template = hbsEng.compile('I am {{fname}} {{lname}}.'); 17 | 18 | expect(template).to.be.function; 19 | }); 20 | 21 | it('should precompile a template', function () { 22 | var template = hbsEng.precompile('I am {{fname}} {{lname}}.'); 23 | 24 | expect(template).to.be.function; 25 | }); 26 | 27 | it('should execute a template', function () { 28 | var context = { 29 | fname: 'John', 30 | lname: 'Doe' 31 | }; 32 | var template = hbsEng.compile('I am {{fname}} {{lname}}.'); 33 | 34 | expect(hbsEng.execute(template, context)).to.be.equal('I am John Doe.'); 35 | }); 36 | 37 | it('should get handlebars', function () { 38 | expect(hbsEng.engine.VERSION).to.be.equal(handlebars.default.VERSION); 39 | }); 40 | 41 | }); 42 | } 43 | }); -------------------------------------------------------------------------------- /test/unit/client-server/public/collection.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'lazoModel', 9 | 'lazoCollection' 10 | ], function (bdd, chai, expect, sinon, sinonChai, utils, LazoModel, LazoCollection) { 11 | chai.use(sinonChai); 12 | 13 | with (bdd) { 14 | describe('publicCollection', function () { 15 | 16 | it('should assign the correct model name to collection items', function () { 17 | 18 | var ChildModel = LazoModel.extend({}); 19 | var ParentCollection = LazoCollection.extend({ 20 | model: ChildModel 21 | }); 22 | 23 | var parent = new ParentCollection(null, { modelName: 'childModel' }); 24 | parent.add(new ChildModel({ id: 1 }, {})); 25 | 26 | expect(parent.models[0].name).to.exist; 27 | }); 28 | 29 | }); 30 | 31 | } 32 | }); -------------------------------------------------------------------------------- /test/unit/client-server/public/views/state.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'uiStateMixin', 9 | 'jquery' 10 | ], function (bdd, chai, expect, sinon, sinonChai, utils, uiStateMixin, $) { 11 | chai.use(sinonChai); 12 | 13 | with (bdd) { 14 | describe('Lazo View, Widget State Mixin', function () { 15 | 16 | it('should set the state class for a widget or view', function () { 17 | if (LAZO.app.isClient) { 18 | uiStateMixin.el = $('
')[0]; 19 | } 20 | uiStateMixin.setState('disabled', true); 21 | expect(uiStateMixin._uiStates.disabled).to.be.equal('disabled'); 22 | if (LAZO.app.isClient) { 23 | expect($(uiStateMixin.el).hasClass('lazo-disbaled')); 24 | } 25 | }); 26 | 27 | }); 28 | } 29 | }); -------------------------------------------------------------------------------- /test/unit/client-server/public/widget.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'lazoWidget' 9 | ], function (bdd, chai, expect, sinon, sinonChai, utils, LazoWidget) { 10 | chai.use(sinonChai); 11 | 12 | with (bdd) { 13 | describe('Lazo Widget Interface', function () { 14 | var MyWidget = LazoWidget.extend({}); 15 | var spy = sinon.spy(MyWidget.prototype, 'initialize'); 16 | var widget = new MyWidget({ 17 | view: { 18 | ctl: { 19 | ctx: { 20 | foo: 1, 21 | bar: true, 22 | baz: 'baz' 23 | } 24 | } 25 | }, 26 | obj: '{ foo: true }', 27 | arr: '[1, 2, 3]', 28 | 'data-foo': '$.foo', 29 | 'data-bar': '$.bar', 30 | 'data-baz': '$.baz', 31 | num1: '1.03', 32 | num2: '8', 33 | bool1: 'true', 34 | bool2: 'false' 35 | }); 36 | 37 | it('should have empty implementation and default values', function () { 38 | expect(widget.initialize).to.be.Function; 39 | expect(widget.render).to.be.Function; 40 | expect(widget.bind).to.be.Function; 41 | expect(widget.unbind).to.be.Function; 42 | expect(widget.afterRender).to.be.Function; 43 | expect(widget.attrValCoercion).to.be.true; 44 | }); 45 | 46 | it('should construct a LazoWidget instance', function () { 47 | expect(widget).to.be.instanceof(LazoWidget); 48 | expect(spy).to.have.been.calledOnce; 49 | }); 50 | 51 | it('should coerce values', function () { 52 | expect(widget.attributes.obj).to.be.Object; 53 | expect(widget.attributes.arr).to.be.Array; 54 | expect(widget.attributes.num1).to.be.equal(1.03); 55 | expect(widget.attributes.num2).to.be.equal(8); 56 | expect(widget.attributes.bool1).to.be.true; 57 | expect(widget.attributes.bool2).to.be.false; 58 | }); 59 | 60 | it('should resolve context values', function () { 61 | expect(widget.attributes['data-foo']).to.be.equal(1); 62 | expect(widget.attributes['data-bar']).to.be.true; 63 | expect(widget.attributes['data-baz']).to.be.equal('baz'); 64 | }); 65 | 66 | }); 67 | } 68 | }); -------------------------------------------------------------------------------- /test/unit/client-server/utils/ctlSerializor.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'utils/ctlSerializor' 9 | ], function (bdd, chai, expect, sinon, sinonChai, utils, ctlSerializor) { 10 | chai.use(sinonChai); 11 | 12 | var id = 0; 13 | 14 | function generateCtl(name) { 15 | return { 16 | name: name, 17 | cid: name + (++id), 18 | ctx: { 19 | models: {}, 20 | collections: {}, 21 | _rootCtx: { 22 | blah: 1 23 | }, 24 | params: { 25 | '': '', 26 | good: 1, 27 | bad: '' 28 | } 29 | }, 30 | children: {}, 31 | currentView: { 32 | cid:'view' + (++id), 33 | name:'index', 34 | ref:'components/' + name + '/views/index', 35 | templatePath:'components/' + name + '/views/index.hbs', 36 | basePath:'components/' + name + '/views', 37 | isBase: false, 38 | hasTemplate: true 39 | }, 40 | toJSON: function (rootCtx) { 41 | return ctlSerializor.serialize(this); 42 | } 43 | }; 44 | } 45 | 46 | with (bdd) { 47 | describe('Controller Serializor', function () { 48 | 49 | it('should serialize a component controller for transport', function () { 50 | var serializedCtl; 51 | var ctl = generateCtl('foo'); 52 | 53 | ctl.children.bar = [generateCtl('bar')]; 54 | ctl.children.bar[0].children.baz = [generateCtl('baz')]; 55 | serializedCtl = ctlSerializor.serialize(ctl); 56 | 57 | expect(serializedCtl).to.include.keys('cid', 'name', 'ctx', 'isBase', 'currentView', 'children'); 58 | // check if children were serialized 59 | expect(serializedCtl.children.bar[0]).to.be.Object; 60 | expect(serializedCtl.children.bar[0].children.baz).to.be.Object; 61 | // check if views were serialized 62 | expect(serializedCtl.currentView).to.be.Object; 63 | expect(serializedCtl.children.bar[0].currentView).to.be.Object; 64 | expect(serializedCtl.currentView).to.include 65 | .keys('cid', 'name', 'ref', 'templatePath', 'compiledTemplatePath', 'basePath', 'isBase', 'hasTemplate'); 66 | // check if params were encoded 67 | expect(serializedCtl.ctx.params['%3Cscript%3Ealert(%22hello%22)%3C%2Fscript%3E']).to.exist; 68 | expect(serializedCtl.ctx.params.bad).to.be.equal('%3Cscript%3Ealert(%22hello%22)%3C%2Fscript%3E'); 69 | }); 70 | 71 | }); 72 | } 73 | }); -------------------------------------------------------------------------------- /test/unit/client-server/utils/treeMixin.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'utils/treeMixin' 9 | ], function (bdd, chai, expect, sinon, sinonChai, utils, treeMixin) { 10 | chai.use(sinonChai); 11 | 12 | with (bdd) { 13 | describe('Tree Mixin', function () { 14 | 15 | it('should get a list of nodes', function () { 16 | var dfd = this.async(); 17 | 18 | utils.setUpApp(function () { 19 | utils.createCtlTree(function (ctl) { 20 | var views = treeMixin.getList('view', ctl); 21 | var components = treeMixin.getList('component', ctl); 22 | var i; 23 | 24 | expect(views.length).to.be.equal(3); 25 | for (i = 0; i < 3; i++) { 26 | expect(views[i].setElement).to.be.function; 27 | } 28 | 29 | expect(components.length).to.be.equal(3); 30 | for (i = 0; i < 3; i++) { 31 | expect(components[i].currentView).to.be.defined; 32 | } 33 | 34 | dfd.resolve(); 35 | }); 36 | }); 37 | }); 38 | 39 | it('should get a node\'s type', function () { 40 | utils.createCtlTree(function (ctl) { 41 | expect(treeMixin.getNodeType(ctl.currentView)).to.be.equal('view'); 42 | expect(treeMixin.getNodeType(ctl)).to.be.equal('component'); 43 | }); 44 | }); 45 | 46 | it('should get a node\'s children', function () { 47 | utils.createCtlTree(function (ctl) { 48 | var children = treeMixin.getNodeChildren(ctl); 49 | expect(children.length).to.be.equal(2); 50 | }); 51 | }); 52 | 53 | }); 54 | } 55 | }); -------------------------------------------------------------------------------- /test/unit/client/common/app.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'lazoApp' 9 | ], function (bdd, chai, expect, sinon, sinonChai, utils, LazoApp) { 10 | chai.use(sinonChai); 11 | 12 | with (bdd) { 13 | describe('Lazo Application - client', function () { 14 | 15 | it('should no-op on add http vary params', function () { 16 | 17 | var lazoApp = new LazoApp({}); 18 | lazoApp.isClient = true; 19 | expect(lazoApp.getHttpVaryParams().length).to.equal(0); 20 | var app = lazoApp.addHttpVaryParam('user-agent'); 21 | 22 | var params = lazoApp.getHttpVaryParams(); 23 | expect(params.length).to.equal(0); 24 | expect(app == lazoApp).to.be.true; 25 | 26 | }); 27 | 28 | it('should no-op on add http headers', function () { 29 | var lazoApp = new LazoApp({}); 30 | lazoApp.isClient = true; 31 | expect(lazoApp.getHttpHeaders().length).to.equal(0); 32 | var app = lazoApp.addHttpHeader('X-Frame-Options', 'deny'); 33 | 34 | var headers = lazoApp.getHttpHeaders(); 35 | expect(headers.length).to.equal(0); 36 | expect(app == lazoApp).to.be.true; 37 | }); 38 | }); 39 | } 40 | }); -------------------------------------------------------------------------------- /test/unit/client/common/resolver/requireConfigure.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'resolver/requireConfigure' 9 | ], function (bdd, chai, expect, sinon, sinonChai, utils, conf) { 10 | chai.use(sinonChai); 11 | 12 | with (bdd) { 13 | describe('requirejs configure', function () { 14 | 15 | beforeEach(function () { 16 | LAZO.initConf = {}; // mock for test 17 | }); 18 | 19 | afterEach(function () { 20 | delete LAZO.initConf; // clean up test mock 21 | }); 22 | 23 | it('get client configuration', function () { 24 | this.skip(); 25 | var dfd = this.async(); 26 | var config; 27 | var options = { 28 | basePath: 'base/path', 29 | baseUrl: 'base/url' 30 | }; 31 | 32 | conf.get('client', options, function (err, conf) { 33 | config = conf; 34 | 35 | expect(config.baseUrl).to.be.equal('base/url'); 36 | expect(config.context).to.be.equal('application'); 37 | expect(config.map['*'].s.indexOf('\/client\/')).to.not.be.equal(-1); 38 | 39 | dfd.resolve(); 40 | }); 41 | }); 42 | 43 | }); 44 | } 45 | }); -------------------------------------------------------------------------------- /test/unit/client/common/utils/document.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'utils/document', 9 | 'jquery' 10 | ], function (bdd, chai, expect, sinon, sinonChai, utils, doc, $) { 11 | chai.use(sinonChai); 12 | 13 | with (bdd) { 14 | describe('document utils', function () { 15 | 16 | it('set page title', function () { 17 | var $title = $('title'); 18 | doc.setTitle('foobar'); 19 | expect($title.text()).to.be.equal('foobar'); 20 | }); 21 | 22 | // link.onload is not being called when test is executed in phantomjs because it 23 | // is not supported by the version of webkit phantomjs is running 24 | it('should update css', function () { 25 | this.skip(); // test failing regardless of env because link.onload never executes 26 | // if (window.navigator.userAgent.indexOf('PhantomJS') !== -1 || window.lazoLocalTesting) { 27 | // this.skip(); 28 | // } 29 | 30 | var add = [{ href: '../../test/mocks/css/b.css' }, { href: '../../test/mocks/css/c.css' }, { href: '../../test/mocks/css/d.css' }]; 31 | var remove = [{ href: '../../test/mocks/css/a.css' }]; 32 | var $head = $('head'); 33 | var dfd = this.async(); 34 | 35 | $head.append(''); 36 | 37 | doc.updateLinks(add, remove, 'css', function () { 38 | var $links = $('link[lazo-link="css"]'); 39 | expect($links.length).to.be.equal(3); 40 | $links.each(function (i) { 41 | expect($(this).attr('href')).to.be.equal(add[i]); 42 | }); 43 | dfd.resolve(); 44 | }); 45 | }); 46 | 47 | it('add, get page tags', function () { 48 | 49 | var ctx = { 50 | _rootCtx: {}, 51 | meta: {} 52 | }; 53 | 54 | expect(doc.getPageTags(ctx, false).length).to.equal(0); 55 | doc.addPageTag(ctx, false, 'meta', { description: 'text' }); 56 | expect(doc.getPageTags(ctx, false).length).to.equal(1); 57 | }); 58 | 59 | it('updates page tags', function () { 60 | var dfd = this.async(); 61 | var ctx = { 62 | _rootCtx: { 63 | pageTags: [ 64 | { name: 'meta', attributes: { description: 'text' }, content: null }, 65 | { name: 'meta', attributes: { keywords: 'keyword' }, content: null } 66 | ] 67 | }, 68 | meta: { 69 | pageTags: [] 70 | } 71 | }; 72 | 73 | // simulate initial update on client 74 | doc.updatePageTags(ctx, function (err) { 75 | expect(err).to.not.exist; 76 | expect(ctx._rootCtx.pageTags.length).to.equal(0); 77 | expect(ctx.meta.pageTags.length).to.equal(0); 78 | 79 | doc.addPageTag(ctx, false, 'meta', { description: 'text' }); 80 | doc.addPageTag(ctx, false, 'meta', { keywords: 'keyword' }); 81 | expect(ctx.meta.pageTags.length).to.equal(2); 82 | 83 | // simulate secondary update on client 84 | doc.updatePageTags(ctx, function (err) { 85 | expect(err).to.not.exist; 86 | expect(ctx._rootCtx.pageTags.length).to.equal(2); 87 | expect(ctx.meta.pageTags.length).to.equal(0); 88 | dfd.resolve(); 89 | }); 90 | }); 91 | }); 92 | }); 93 | } 94 | }); -------------------------------------------------------------------------------- /test/unit/client/context.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'context' 9 | ], function (bdd, chai, expect, sinon, sinonChai, utils, Context) { 10 | chai.use(sinonChai); 11 | 12 | with (bdd) { 13 | describe('Context', function () { 14 | 15 | it('common client', function () { 16 | var ctx = new Context({ 17 | _request: { 18 | url: { 19 | pathname: 'foo/bar/baz' 20 | }, 21 | raw: { // this is expected on the server 22 | req: { 23 | headers: {} 24 | } 25 | } 26 | } 27 | }); 28 | 29 | // this will be equal to whatever the clients path is when phantom runs or the file is opened in the browser 30 | chai.expect(ctx.location.pathname).to.not.be.equal('foo/bar/baz'); 31 | }); 32 | 33 | }); 34 | } 35 | }); -------------------------------------------------------------------------------- /test/unit/client/destroyCmp.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'lazoView', 9 | 'destroyCmp' 10 | ], function (bdd, chai, expect, sinon, sinonChai, utils, LazoView, destroyCmp) { 11 | chai.use(sinonChai); 12 | 13 | with (bdd) { 14 | describe('Destroy Component', function () { 15 | 16 | it('should clean up a context tree', function () { 17 | var dfd = this.async(); 18 | utils.setUpApp(function () { 19 | utils.createCtlTree(function (ctl) { 20 | var parentViewSpy = sinon.spy(ctl.currentView, 'remove'); 21 | var parentCtlSpy = sinon.spy(ctl._getEl(), 'remove'); 22 | var firstChildViewSpy = sinon.spy(ctl.children.foo[0].currentView, 'remove'); 23 | var firstChildCtlSpy = sinon.spy(ctl.children.foo[0]._getEl(), 'remove'); 24 | var secondChildViewSpy = sinon.spy(ctl.children.foo[1].currentView, 'remove'); 25 | var secondChildCtlSpy = sinon.spy(ctl.children.foo[1]._getEl(), 'remove'); 26 | 27 | destroyCmp(ctl); 28 | expect(parentViewSpy.calledOnce).to.be.true; 29 | expect(parentCtlSpy.calledOnce).to.be.true; 30 | expect(firstChildViewSpy.calledOnce).to.be.true; 31 | expect(firstChildCtlSpy.calledOnce).to.be.true; 32 | expect(secondChildViewSpy.calledOnce).to.be.true; 33 | expect(secondChildCtlSpy.calledOnce).to.be.true; 34 | 35 | dfd.resolve(); 36 | }); 37 | }); 38 | }); 39 | 40 | }); 41 | } 42 | }); -------------------------------------------------------------------------------- /test/unit/client/loader.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'l' 9 | ], function (bdd, chai, expect, sinon, sinonChai, utils, loader) { 10 | chai.use(sinonChai); 11 | 12 | with (bdd) { 13 | describe('Client Loader', function () { 14 | 15 | it('should not load server files', function () { 16 | var dfd = this.async(); 17 | loader.load('foo/server/bar', null, function (module) { 18 | expect(module).to.be.null; 19 | dfd.resolve(); 20 | }, {}); 21 | 22 | }); 23 | 24 | }); 25 | } 26 | }); -------------------------------------------------------------------------------- /test/unit/client/public/views/state.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'uiStateMixin', 9 | 'jquery' 10 | ], function (bdd, chai, expect, sinon, sinonChai, utils, uiStateMixin, $) { 11 | chai.use(sinonChai); 12 | 13 | uiStateMixin.el = $('
')[0]; 14 | 15 | with (bdd) { 16 | describe('Lazo View, Widget State Mixin', function () { 17 | 18 | it('should get the valid attribute states', function () { 19 | var states = uiStateMixin._getValidStates(); 20 | expect(states.focus).to.be.true; 21 | expect(states.disabled).to.be.true; 22 | expect(states.visible).to.be.true; 23 | expect(states.hidden).to.be.true; 24 | }); 25 | 26 | it('should get the states for a widget or view', function () { 27 | var states = uiStateMixin._getStates(); 28 | expect(states.focus).to.be.equal('focus'); 29 | expect(states.disabled).to.be.equal('disabled'); 30 | expect(states.visible).to.be.equal('visible'); 31 | expect(states.hidden).to.be.equal('hidden'); 32 | }); 33 | 34 | }); 35 | } 36 | }); -------------------------------------------------------------------------------- /test/unit/client/viewManager.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'lazoView', 9 | 'viewManager' 10 | ], function (bdd, chai, expect, sinon, sinonChai, utils, LazoView, viewManager) { 11 | chai.use(sinonChai); 12 | 13 | with (bdd) { 14 | describe('View Manager', function () { 15 | 16 | it('should attach views in a tree', function () { 17 | var dfd = this.async(); 18 | utils.setUpApp(function () { 19 | utils.createCtlTree(function (ctl) { 20 | var spy = sinon.spy(ctl.currentView, 'attach'); 21 | viewManager.attachViews(ctl, function (err) { 22 | if (err) { 23 | throw err; 24 | } 25 | expect(spy.calledOnce).to.be.true; 26 | dfd.resolve(); 27 | }); 28 | }); 29 | }); 30 | }); 31 | 32 | it('should clean up a tree branch', function () { 33 | var dfd = this.async(); 34 | utils.setUpApp(function () { 35 | utils.createCtlTree(function (ctl) { 36 | var spy = sinon.spy(LazoView.prototype, 'remove'); 37 | 38 | viewManager.cleanup(ctl, ctl.currentView.cid); 39 | expect(spy.calledThrice).to.be.true; 40 | dfd.resolve(); 41 | }); 42 | }); 43 | }); 44 | 45 | }); 46 | } 47 | }); -------------------------------------------------------------------------------- /test/unit/conf.client.js: -------------------------------------------------------------------------------- 1 | define(['intern/dojo/text!lib/common/resolver/paths.json', 'test/mocks/lazo', 'intern/dojo/text!conf.json'], 2 | function (paths, lazo, conf) { 3 | 4 | 'use strict'; 5 | 6 | paths = JSON.parse(paths); 7 | conf = JSON.parse(conf); 8 | 9 | try { 10 | window.LAZO = lazo; 11 | } catch (err) { 12 | global.LAZO = lazo; 13 | } 14 | LAZO.app.isServer = false; 15 | LAZO.app.isClient = true; 16 | LAZO.isServer = false; 17 | LAZO.isClient = true; 18 | 19 | var needle = '/{env}/'; 20 | var serverPaths = {}; 21 | var env = LAZO.app.isServer ? 'server' : 'client'; 22 | var replace = '/' + env + '/'; 23 | 24 | for (var k in paths.common) { // update env specific implementation paths 25 | paths.common[k] = paths.common[k].replace(needle, replace); 26 | } 27 | for (k in paths[env]) { // merge env specific paths 28 | paths.common[k] = paths[env][k]; 29 | } 30 | 31 | return { 32 | 33 | proxyPort: 9000, 34 | 35 | proxyUrl: 'http://localhost:9000/', 36 | 37 | capabilities: { 38 | 'selenium-version': '2.40.0' 39 | }, 40 | 41 | // latest 2 browser version available, https://saucelabs.com/platforms 42 | environments: [ 43 | // IE 44 | { browserName: 'internet explorer', version: '11', platform: 'Windows 8.1' }, 45 | // FF 46 | { browserName: 'firefox', version: '31', platform: [ 'OS X 10.9', 'Windows 7' ] }, 47 | // Chrome 48 | { browserName: 'chrome', version: '36', platform: [ 'OS X 10.9', 'Windows 7' ] }, 49 | // Safari 50 | { browserName: 'safari', version: '7', platform: 'OS X 10.9' } 51 | ], 52 | 53 | tunnel: 'SauceLabsTunnel', 54 | 55 | excludeInstrumentation: /^(?:test|node_modules|lib\/vendor)\//, 56 | 57 | useLoader: { 58 | 'host-browser': '../../node_modules/requirejs/require.js' 59 | }, 60 | 61 | loader: { 62 | shim: conf.requirejs.client.shim, 63 | paths: paths.common, 64 | map: { 65 | intern: { 66 | dojo: 'intern/node_modules/dojo', 67 | chai: 'intern/node_modules/chai/chai' 68 | }, 69 | '*': { 70 | // testing libs 71 | sinon: '../../node_modules/sinon/lib/sinon.js', 72 | 'sinon-chai': '../../node_modules/sinon-chai/lib/sinon-chai.js' 73 | } 74 | } 75 | } 76 | 77 | }; 78 | 79 | }); -------------------------------------------------------------------------------- /test/unit/conf.client.local.js: -------------------------------------------------------------------------------- 1 | define(['intern/dojo/text!lib/common/resolver/paths.json', 'test/mocks/lazo', 'intern/dojo/text!conf.json'], 2 | function (paths, lazo, conf) { 3 | 4 | 'use strict'; 5 | 6 | paths = JSON.parse(paths); 7 | conf = JSON.parse(conf); 8 | 9 | try { 10 | window.LAZO = lazo; 11 | // used to skip tests in testing environments that do not support specific 12 | // browser event, e.g., link.onload 13 | window.lazoLocalTesting = true; 14 | } catch (err) { 15 | global.LAZO = lazo; 16 | } 17 | LAZO.app.isServer = false; 18 | LAZO.app.isClient = true; 19 | LAZO.isServer = false; 20 | LAZO.isClient = true; 21 | 22 | var needle = '/{env}/'; 23 | var serverPaths = {}; 24 | var env = LAZO.app.isServer ? 'server' : 'client'; 25 | var replace = '/' + env + '/'; 26 | 27 | for (var k in paths.common) { // update env specific implementation paths 28 | paths.common[k] = paths.common[k].replace(needle, replace); 29 | } 30 | for (k in paths[env]) { // merge env specific paths 31 | paths.common[k] = paths[env][k]; 32 | } 33 | 34 | return { 35 | 36 | environments: [ 37 | // { browserName: 'firefox' }, 38 | // { browserName: 'safari' }, 39 | { browserName: 'chrome' } 40 | ], 41 | 42 | excludeInstrumentation: /^(?:test|node_modules|lib\/vendor)\//, 43 | 44 | useLoader: { 45 | 'host-browser': '../../node_modules/requirejs/require.js' 46 | }, 47 | 48 | loader: { 49 | shim: conf.requirejs.client.shim, 50 | paths: paths.common, 51 | map: { 52 | intern: { 53 | dojo: 'intern/node_modules/dojo', 54 | chai: 'intern/node_modules/chai/chai' 55 | }, 56 | '*': { 57 | // testing libs 58 | sinon: '../../node_modules/sinon/lib/sinon.js', 59 | 'sinon-chai': '../../node_modules/sinon-chai/lib/sinon-chai.js', 60 | 'bundler': 'lazoBundle' 61 | } 62 | } 63 | }, 64 | 65 | webdriver: { 66 | host: 'localhost', 67 | port: 4444 68 | }, 69 | 70 | useSauceConnect: false 71 | 72 | }; 73 | 74 | }); -------------------------------------------------------------------------------- /test/unit/conf.client.phantomjs.js: -------------------------------------------------------------------------------- 1 | define(['intern/dojo/text!lib/common/resolver/paths.json', 'test/mocks/lazo', 'intern/dojo/text!conf.json'], 2 | function (paths, lazo, conf) { 3 | 4 | 'use strict'; 5 | 6 | paths = JSON.parse(paths); 7 | conf = JSON.parse(conf); 8 | 9 | try { 10 | window.LAZO = lazo; 11 | } catch (err) { 12 | global.LAZO = lazo; 13 | } 14 | LAZO.app.isServer = false; 15 | LAZO.app.isClient = true; 16 | LAZO.isServer = false; 17 | LAZO.isClient = true; 18 | 19 | var needle = '/{env}/'; 20 | var serverPaths = {}; 21 | var env = LAZO.app.isServer ? 'server' : 'client'; 22 | var replace = '/' + env + '/'; 23 | 24 | for (var k in paths.common) { // update env specific implementation paths 25 | paths.common[k] = paths.common[k].replace(needle, replace); 26 | } 27 | for (k in paths[env]) { // merge env specific paths 28 | paths.common[k] = paths[env][k]; 29 | } 30 | 31 | return { 32 | 33 | tunnel: 'NullTunnel', 34 | 35 | environments: [{ browserName: 'phantomjs' }], 36 | 37 | excludeInstrumentation: /^(?:test|node_modules|lib\/vendor)\//, 38 | 39 | useLoader: { 40 | 'host-browser': '../../node_modules/requirejs/require.js' 41 | }, 42 | 43 | loader: { 44 | shim: conf.requirejs.client.shim, 45 | paths: paths.common, 46 | map: { 47 | intern: { 48 | dojo: 'intern/node_modules/dojo', 49 | chai: 'intern/node_modules/chai/chai' 50 | }, 51 | '*': { 52 | // testing libs 53 | sinon: '../../node_modules/sinon/lib/sinon.js', 54 | 'sinon-chai': '../../node_modules/sinon-chai/lib/sinon-chai.js', 55 | 'bundler': 'lazoBundle' 56 | } 57 | } 58 | } 59 | 60 | }; 61 | 62 | }); -------------------------------------------------------------------------------- /test/unit/conf.server.js: -------------------------------------------------------------------------------- 1 | define(['intern/dojo/text!lib/common/resolver/paths.json', 'test/mocks/lazo'], function (paths, lazo) { 2 | 3 | 'use strict'; 4 | 5 | paths = JSON.parse(paths); 6 | 7 | try { 8 | window.LAZO = lazo; 9 | } catch (err) { 10 | global.LAZO = lazo; 11 | } 12 | LAZO.app.isServer = true; 13 | LAZO.app.isClient = false; 14 | LAZO.isServer = true; 15 | LAZO.isClient = false; 16 | 17 | var needle = '/{env}/'; 18 | var serverPaths = {}; 19 | var env = LAZO.app.isServer ? 'server' : 'client'; 20 | var replace = '/' + env + '/'; 21 | 22 | for (var k in paths.common) { // update env specific implementation paths 23 | paths.common[k] = paths.common[k].replace(needle, replace); 24 | } 25 | for (k in paths[env]) { // merge env specific paths 26 | paths.common[k] = paths[env][k]; 27 | } 28 | 29 | return { 30 | 31 | excludeInstrumentation: /^(?:test|node_modules|lib\/vendor)\//, 32 | 33 | useLoader: { 34 | 'host-node': 'requirejs' 35 | }, 36 | 37 | loader: { 38 | paths: paths.common, 39 | map: { 40 | intern: { 41 | dojo: 'intern/node_modules/dojo', 42 | chai: 'intern/node_modules/chai/chai' 43 | }, 44 | '*': { 45 | // mocks 46 | request: 'test/mocks/server/request', 47 | 'continuation-local-storage': 'test/mocks/server/continuation-local-storage', 48 | hapi: 'test/mocks/server/hapi', 49 | 'bundler': 'lazoBundle' 50 | } 51 | } 52 | } 53 | 54 | }; 55 | 56 | }); -------------------------------------------------------------------------------- /test/unit/server/assetsProvider.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'intern/dojo/node!path', 9 | 'assetsProvider' 10 | ], function (bdd, chai, expect, sinon, sinonChai, utils, path, assetsProvider) { 11 | chai.use(sinonChai); 12 | 13 | with (bdd) { 14 | describe('Assets Provider', function () { 15 | 16 | before(function () { 17 | LAZO.FILE_REPO_PATH = path.resolve('test/application'); 18 | }); 19 | 20 | function getCtx() { 21 | return { 22 | _request: { 23 | raw: { 24 | req: { 25 | headers: { 26 | 'accept-language': 'en-US,en;q=0.8' 27 | } 28 | } 29 | } 30 | } 31 | }; 32 | } 33 | 34 | it('should get the assets for a list of components', function () { 35 | var ctx = getCtx(); 36 | var dfd = this.async(); 37 | assetsProvider.get(['foo', 'bar', 'baz'], ctx, { 38 | success: function (assets) { 39 | expect(assets.foo).to.be.empty; 40 | expect(assets.baz).to.be.empty; 41 | expect(assets.bar['info.pdf']).to.be.equal('/components/bar/assets/info.pdf'); 42 | 43 | expect(assets.bar.name).to.be.equal('Käthe Kollwitz'); 44 | expect(assets.bar['img/logo.png']).to.be.equal('/components/bar/assets/en-US/img/logo.png'); 45 | dfd.resolve(); 46 | } 47 | }); 48 | }); 49 | 50 | it('should get the assets for an application', function () { 51 | var ctx = getCtx(); 52 | var dfd = this.async(); 53 | assetsProvider.get(['app'], ctx, { 54 | success: function (assets) { 55 | expect(assets.app['info.pdf']).to.be.equal('/app/assets/info.pdf'); 56 | expect(assets.app.name).to.be.equal('Käthe Kollwitz'); 57 | expect(assets.app['img/logo.png']).to.be.equal('/app/assets/en-US/img/logo.png'); 58 | dfd.resolve(); 59 | } 60 | }); 61 | }); 62 | 63 | }); 64 | } 65 | }); -------------------------------------------------------------------------------- /test/unit/server/common/app.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'lazoApp' 9 | ], function (bdd, chai, expect, sinon, sinonChai, utils, LazoApp) { 10 | chai.use(sinonChai); 11 | 12 | with (bdd) { 13 | describe('Lazo Application - server', function () { 14 | 15 | it('should add http vary params', function () { 16 | 17 | var lazoApp = new LazoApp({}); 18 | lazoApp.isServer = true; 19 | var count = lazoApp.getHttpVaryParams().length; 20 | var app = lazoApp.addHttpVaryParam('user-agent'); 21 | 22 | var params = lazoApp.getHttpVaryParams(); 23 | expect(params.length).to.equal(count + 1); 24 | expect(params[0]).to.equal('user-agent'); 25 | expect(app == lazoApp).to.be.true; 26 | 27 | }); 28 | 29 | it('should add http headers', function () { 30 | var lazoApp = new LazoApp({}); 31 | lazoApp.isServer = true; 32 | var count = lazoApp.getHttpHeaders().length; 33 | var app = lazoApp.addHttpHeader('X-Frame-Options', 'deny'); 34 | 35 | var headers = lazoApp.getHttpHeaders(); 36 | expect(headers.length).to.equal(count + 1); 37 | expect(headers[0].name).to.equal('X-Frame-Options'); 38 | expect(headers[0].value).to.equal('deny'); 39 | expect(headers[0].options).to.equal(null); 40 | expect(app == lazoApp).to.be.true; 41 | }); 42 | }); 43 | } 44 | }); -------------------------------------------------------------------------------- /test/unit/server/common/resolver/requireConfigure.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'resolver/requireConfigure' 9 | ], function (bdd, chai, expect, sinon, sinonChai, utils, conf) { 10 | chai.use(sinonChai); 11 | 12 | with (bdd) { 13 | describe('requirejs configure', function () { 14 | 15 | it('get server configuration', function (done) { 16 | var dfd = this.async(); 17 | var config; 18 | var options = { 19 | basePath: process.cwd(), 20 | baseUrl: 'some/path' 21 | }; 22 | 23 | conf.get('server', options, function (err, conf) { 24 | config = conf; 25 | 26 | expect(config.baseUrl).to.be.equal('some/path'); 27 | expect(config.context).to.be.equal('application'); 28 | expect(config.map['*'].s.indexOf('\/server\/')).to.not.be.equal(-1); 29 | 30 | dfd.resolve(); 31 | }); 32 | }); 33 | 34 | }); 35 | } 36 | }); -------------------------------------------------------------------------------- /test/unit/server/common/utils/document.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'utils/document' 9 | ], function (bdd, chai, expect, sinon, sinonChai, utils, doc) { 10 | chai.use(sinonChai); 11 | 12 | with (bdd) { 13 | describe('document utils', function () { 14 | 15 | it('set, get html tag', function () { 16 | var htmlTag = ''; 17 | 18 | expect(doc.getHtmlTag()).to.be.equal(''); 19 | doc.setHtmlTag(htmlTag); 20 | expect(doc.getHtmlTag()).to.be.equal(htmlTag); 21 | }); 22 | 23 | 24 | it('set, get body class', function () { 25 | var bodyClass = 'foobar'; 26 | 27 | expect(doc.getBodyClass()).to.be.equal(''); 28 | doc.setBodyClass(bodyClass); 29 | expect(doc.getBodyClass()).to.be.equal(bodyClass); 30 | }); 31 | 32 | it('add tag', function () { 33 | var metaTag; 34 | var tags; 35 | 36 | expect(doc.getTags().length).to.be.equal(0); 37 | doc.addTag('meta', { name: 'description', content: 'the most awesome web page ever' }, 'tag content'); 38 | tags = doc.getTags(); 39 | expect(tags.length).to.be.equal(1); 40 | metaTag = tags[0]; 41 | expect(metaTag.name).to.be.equal('meta'); 42 | expect(metaTag.content).to.be.equal('tag content'); 43 | expect(metaTag.attributes.name).to.be.equal('description'); 44 | expect(metaTag.attributes.content).to.be.equal('the most awesome web page ever'); 45 | }); 46 | 47 | it('add, get page tags', function () { 48 | 49 | var ctx = { 50 | _rootCtx: {}, 51 | meta: {} 52 | }; 53 | 54 | expect(doc.getPageTags(ctx, true).length).to.equal(0); 55 | doc.addPageTag(ctx, true, 'meta', { description: 'text' }); 56 | expect(doc.getPageTags(ctx, true).length).to.equal(1); 57 | }); 58 | }); 59 | } 60 | }); -------------------------------------------------------------------------------- /test/unit/server/context.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'context' 9 | ], function (bdd, chai, expect, sinon, sinonChai, utils, Context) { 10 | chai.use(sinonChai); 11 | 12 | with (bdd) { 13 | describe('Context', function () { 14 | 15 | it('common server', function () { 16 | var ctx = new Context({ 17 | _request: { 18 | url: { 19 | pathname: 'foo/bar/baz' 20 | }, 21 | raw: { // this is expected on the server 22 | req: { 23 | headers: { 24 | host: 'localhost:8080' 25 | } 26 | } 27 | }, 28 | server: { 29 | info: { 30 | protocol: 'http' 31 | } 32 | } 33 | }, 34 | headers: { 35 | host: 'localhost:8080' 36 | } 37 | }); 38 | 39 | expect(ctx.location.pathname).to.be.equal('foo/bar/baz'); 40 | }); 41 | 42 | it('handles undefined host', function () { 43 | var ctx = new Context({ 44 | _request: { 45 | url: { 46 | pathname: 'foo/bar/baz' 47 | }, 48 | raw: { // this is expected on the server 49 | req: { 50 | headers: { 51 | host: 'localhost:8080' 52 | } 53 | } 54 | }, 55 | server: { 56 | info: { 57 | protocol: 'http' 58 | } 59 | } 60 | }, 61 | headers: {} 62 | }); 63 | 64 | expect(ctx.location.host).to.be.undefined; 65 | }); 66 | 67 | }); 68 | } 69 | }); -------------------------------------------------------------------------------- /test/unit/server/forbidden.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'forbidden' 9 | ], function (bdd, chai, expect, sinon, sinonChai, utils, forbidden) { 10 | chai.use(sinonChai); 11 | 12 | with (bdd) { 13 | describe('forbidden', function () { 14 | 15 | it('should not allow access to "server" directories', function () { 16 | expect(forbidden('/server/foo/bar')).to.be.true; 17 | expect(forbidden('/foo/server/bar')).to.be.true; 18 | expect(forbidden('/server')).to.be.true; 19 | }); 20 | 21 | it('should not allow access to "node_modules" directories', function () { 22 | expect(forbidden('/node_modules/foo/bar')).to.be.true; 23 | expect(forbidden('/foo/node_modules/bar')).to.be.true; 24 | expect(forbidden('/node_modules')).to.be.true; 25 | }); 26 | 27 | it('should allow access to common directories', function () { 28 | expect(forbidden('/lib/foo/bar')).to.be.false; 29 | expect(forbidden('/foo/client/bar')).to.be.false; 30 | expect(forbidden('/common')).to.be.false; 31 | }); 32 | 33 | }); 34 | } 35 | }); -------------------------------------------------------------------------------- /test/unit/server/httpResponse.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'httpResponse', 9 | 'lazoCtl' 10 | ], function (bdd, chai, expect, sinon, sinonChai, utils, httpResponse, LazoController) { 11 | chai.use(sinonChai); 12 | 13 | with (bdd) { 14 | describe('httpResponse', function () { 15 | 16 | var getController = function (options) { 17 | var ctlOptions = { 18 | name: 'home', 19 | ctx: { 20 | response: { 21 | statusCode: null, 22 | httpHeaders: [], 23 | varyParams: [] 24 | } 25 | } 26 | }; 27 | 28 | var MyController = LazoController.extend({}); 29 | MyController.create('home', ctlOptions, options); 30 | }; 31 | 32 | it('can add httpHeader', function () { 33 | 34 | var count = httpResponse.getHttpHeaders().length; 35 | httpResponse.addHttpHeader('X-Frame-Options', 'deny'); 36 | expect(httpResponse.getHttpHeaders().length).to.equal(count + 1); 37 | 38 | }); 39 | 40 | it('can add vary param', function () { 41 | 42 | var count = httpResponse.getVaryParams().length; 43 | httpResponse.addVaryParam('user-agent'); 44 | expect(httpResponse.getVaryParams().length).to.equal(count + 1); 45 | 46 | }); 47 | 48 | it('can merge http response data', function () { 49 | 50 | var dfd = this.async(); 51 | getController({ 52 | success: function (myController) { 53 | var controller = myController; 54 | var headerCount = httpResponse.getHttpHeaders().length; 55 | var varyCount = httpResponse.getVaryParams().length; 56 | 57 | controller.setHttpStatusCode(410); 58 | controller.addHttpHeader('X-XSS-Protection', '1; mode=block'); 59 | controller.addHttpVaryParam('accept'); 60 | 61 | var responseData = httpResponse.mergeHttpResponseData(controller); 62 | expect(responseData).to.exist; 63 | expect(responseData.statusCode).to.equal(410); 64 | expect(responseData.httpHeaders.length).to.equal(headerCount + 1); 65 | expect(responseData.varyParams.length).to.equal(varyCount + 1); 66 | 67 | dfd.resolve(); 68 | }, 69 | error: function () { 70 | dfd.reject(); 71 | } 72 | }); 73 | 74 | }); 75 | 76 | }); 77 | } 78 | }); -------------------------------------------------------------------------------- /test/unit/server/loader.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'l' 9 | ], function (bdd, chai, expect, sinon, sinonChai, utils, loader) { 10 | chai.use(sinonChai); 11 | 12 | with (bdd) { 13 | describe('Server Loader', function () { 14 | 15 | it('should not load client files', function () { 16 | var dfd = this.async(); 17 | 18 | loader.load('foo/client/bar', null, function (module) { 19 | expect(module).to.be.null; 20 | dfd.resolve(); 21 | }, {}); 22 | 23 | }); 24 | 25 | it('should not load client configured file', function () { 26 | var dfd = this.async(); 27 | 28 | LAZO.conf.requirejs = { 29 | client: { 30 | paths: { 31 | aClientModule: 'foo' 32 | } 33 | } 34 | }; 35 | 36 | loader.load('aClientModule', null, function (module) { 37 | expect(module).to.be.null; 38 | delete LAZO.conf.requirejs; 39 | dfd.resolve(); 40 | }, {paths:['.']}); 41 | 42 | }); 43 | 44 | 45 | 46 | }); 47 | } 48 | }); -------------------------------------------------------------------------------- /test/unit/server/public/controller.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'lazoCtl' 9 | ], function (bdd, chai, expect, sinon, sinonChai, utils, LazoController) { 10 | chai.use(sinonChai); 11 | 12 | with (bdd) { 13 | describe('Lazo Controller', function () { 14 | 15 | var getController = function (options) { 16 | var ctlOptions = { 17 | name: 'home', 18 | ctx: { 19 | response: { 20 | statusCode: null, 21 | httpHeaders: [], 22 | varyParams: [] 23 | } 24 | } 25 | }; 26 | 27 | var MyController = LazoController.extend({}); 28 | MyController.create('home', ctlOptions, options); 29 | }; 30 | 31 | it('should get/set the http status code', function () { 32 | var dfd = this.async(); 33 | getController({ 34 | success: function (controller) { 35 | 36 | expect(controller.getHttpStatusCode()).to.equal(200); 37 | var ctl = controller.setHttpStatusCode(410); 38 | expect(controller.getHttpStatusCode()).to.equal(410); 39 | expect(ctl == controller).to.be.true; 40 | 41 | dfd.resolve(); 42 | }, 43 | error: function () { 44 | dfd.reject(); 45 | } 46 | }); 47 | }); 48 | 49 | it('should add http vary params', function () { 50 | var dfd = this.async(); 51 | getController({ 52 | success: function (controller) { 53 | 54 | expect(controller.getHttpVaryParams().length).to.equal(0); 55 | var ctl = controller.addHttpVaryParam('user-agent'); 56 | 57 | var params = controller.getHttpVaryParams(); 58 | expect(params.length).to.equal(1); 59 | expect(params[0]).to.equal('user-agent'); 60 | expect(ctl == controller).to.be.true; 61 | 62 | dfd.resolve(); 63 | }, 64 | error: function () { 65 | dfd.reject(); 66 | } 67 | }); 68 | }); 69 | 70 | it('should add http headers', function () { 71 | var dfd = this.async(); 72 | getController({ 73 | success: function (controller) { 74 | 75 | expect(controller.getHttpHeaders().length).to.equal(0); 76 | var ctl = controller.addHttpHeader('X-Frame-Options', 'deny'); 77 | 78 | var headers = controller.getHttpHeaders(); 79 | expect(headers.length).to.equal(1); 80 | expect(headers[0].name).to.equal('X-Frame-Options'); 81 | expect(headers[0].value).to.equal('deny'); 82 | expect(headers[0].options).to.equal(null); 83 | expect(ctl == controller).to.be.true; 84 | 85 | dfd.resolve(); 86 | }, 87 | error: function () { 88 | dfd.reject(); 89 | } 90 | }); 91 | }); 92 | 93 | }); 94 | } 95 | }); -------------------------------------------------------------------------------- /test/unit/server/streamHandler.skip.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'intern!bdd', 3 | 'intern/chai!', 4 | 'intern/chai!expect', 5 | 'sinon', 6 | 'sinon-chai', 7 | 'test/unit/utils', 8 | 'handlers/stream' 9 | ], function (bdd, chai, expect, sinon, sinonChai, utils, StreamHandler) { 10 | chai.use(sinonChai); 11 | 12 | with (bdd) { 13 | describe('Stream Handler Test', function () { 14 | 15 | var request = {}; 16 | var lazoSpy; 17 | var handler = { 18 | 'func' : function (req, options) { 19 | options.success("done"); 20 | } 21 | }; 22 | 23 | LAZO.require = function(path, cb){ 24 | cb(handler); 25 | }; 26 | lazoSpy = sinon.spy(LAZO, 'require'); 27 | 28 | afterEach(function (done) { 29 | lazoSpy.reset(); 30 | done(); 31 | }); 32 | 33 | it('Custom Action Without Callback and without component', function () { 34 | this.skip(); 35 | var req = { 36 | params : { 37 | componentName: null, 38 | action: "func" 39 | }, 40 | paylood: null, 41 | reply : function(){} 42 | }; 43 | 44 | var spy = sinon.spy(); 45 | StreamHandler(req, spy); 46 | expect(JSON.stringify("done")).to.be.equal(spy.args[0][0]); 47 | expect("app/server/utilActions").to.be.equal(lazoSpy.args[0][0][0]); 48 | }); 49 | 50 | it('Custom Action Without Callback and with component', function () { 51 | this.skip(); 52 | var req = { 53 | params : { 54 | compName: "test", 55 | action: "func" 56 | }, 57 | paylood: null, 58 | reply : function(){} 59 | }; 60 | 61 | var spy = sinon.spy(); 62 | StreamHandler(req, spy); 63 | expect(JSON.stringify("done")).to.be.equal(spy.args[0][0]); 64 | expect("components/test/server/utilActions").to.be.equal(lazoSpy.args[0][0][0]); 65 | }); 66 | 67 | 68 | }); 69 | } 70 | }); -------------------------------------------------------------------------------- /test/unit/utils.js: -------------------------------------------------------------------------------- 1 | define(['test/mocks/lazo'], function (lazo) { 2 | 3 | function createView(ctl, id, LazoView, _) { 4 | var el; 5 | 6 | el = LAZO.app.isClient ? $('
') : null; 7 | return new LazoView({ 8 | el: el, 9 | templateEngine: 'micro', 10 | getTemplate: function (options) { 11 | options.success('I am a template!'); 12 | }, 13 | ctl: ctl 14 | }); 15 | } 16 | 17 | // these paths do not exist until after the intern configuration has 18 | // been set. requirejs will be defined at this point. 19 | return { 20 | 21 | setUpApp: function (callback) { 22 | requirejs(['underscore'], function (_) { 23 | // var template = _.template('I am a template!'); 24 | LAZO.require = requirejs; 25 | LAZO.app.getDefaultTemplateEngineName = function () {}; 26 | LAZO.app.getTemplateEngine = function () { 27 | return { 28 | compile: function (template) { 29 | return _.template(template); 30 | }, 31 | execute: function (compiledTemplate, data) { 32 | return compiledTemplate(data); 33 | }, 34 | engine: _.template 35 | }; 36 | }; 37 | 38 | callback(); 39 | }); 40 | }, 41 | 42 | createCtlTree: function (callback) { 43 | 44 | function _getEl() { 45 | if (LAZO.app.isClient) { 46 | return this.$el || (this.$el = $('
')); 47 | } 48 | } 49 | 50 | requirejs(['lazoView', 'underscore'], function (LazoView, _) { 51 | var childCtl; 52 | var ctl = { 53 | currentView: null, 54 | children: { 55 | foo: [] 56 | }, 57 | _getEl: _getEl 58 | }; 59 | 60 | for (var i = 0; i < 3; i++) { 61 | if (!i) { 62 | ctl.currentView = createView(ctl, i, LazoView, _); 63 | ctl.currentView.getTemplate = function (options) { 64 | options.success('
'); 65 | }; 66 | ctl.cid = i; 67 | ctl.name = 'name' + i; 68 | ctl.ctx = {}; 69 | } else { 70 | childCtl = { 71 | currentView: null, 72 | cid: i, 73 | name: 'name' + i, 74 | ctx: {}, 75 | _getEl: _getEl 76 | }; 77 | childCtl.currentView = createView(childCtl, i, LazoView, _); 78 | ctl.children.foo.push(childCtl); 79 | } 80 | } 81 | 82 | callback(ctl); 83 | }); 84 | } 85 | 86 | }; 87 | 88 | }); -------------------------------------------------------------------------------- /version.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | var lazoPath = path.dirname(module.filename); 5 | var packageJson = JSON.parse(fs.readFileSync(path.normalize(lazoPath + '/package.json'), 'utf8')); 6 | return 'v' + packageJson.version; 7 | } --------------------------------------------------------------------------------