├── samples ├── sample-ui │ ├── app │ │ ├── data.js │ │ ├── helpers.js │ │ ├── views.js │ │ ├── controllers.js │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── models │ │ │ ├── .gitkeep │ │ │ ├── versionTier.js │ │ │ ├── service.js │ │ │ └── version.js │ │ ├── routes │ │ │ └── index.js │ │ ├── views │ │ │ └── .gitkeep │ │ ├── controllers │ │ │ └── .gitkeep │ │ ├── routes.js │ │ ├── templates │ │ │ ├── application.hbs │ │ │ └── index.hbs │ │ ├── app.js │ │ ├── assets │ │ │ ├── robots.txt │ │ │ ├── favicon.ico │ │ │ ├── apple-touch-icon.png │ │ │ ├── img │ │ │ │ ├── glyphicons-halflings.png │ │ │ │ └── glyphicons-halflings-white.png │ │ │ ├── apple-touch-icon-precomposed.png │ │ │ ├── apple-touch-icon-57x57-precomposed.png │ │ │ ├── apple-touch-icon-72x72-precomposed.png │ │ │ ├── apple-touch-icon-114x114-precomposed.png │ │ │ ├── apple-touch-icon-144x144-precomposed.png │ │ │ ├── humans.txt │ │ │ ├── crossdomain.xml │ │ │ ├── index.html │ │ │ ├── 404.html │ │ │ └── javascripts │ │ │ │ └── modernizr-2.6.2.min.js │ │ ├── styles │ │ │ └── application.styl │ │ ├── templates.js │ │ ├── models.js │ │ ├── initialize.js │ │ └── index.html │ ├── generators │ │ ├── template │ │ │ ├── template.hbs.hbs │ │ │ ├── template-require.js.hbs │ │ │ ├── helpers.js │ │ │ └── generator.json │ │ ├── view │ │ │ ├── view-require.js.hbs │ │ │ ├── helpers.js │ │ │ ├── view.js.hbs │ │ │ └── generator.json │ │ ├── helper │ │ │ ├── helper-require.js.hbs │ │ │ ├── helpers.js │ │ │ ├── helper.js.hbs │ │ │ └── generator.json │ │ ├── model │ │ │ ├── model-require.js.hbs │ │ │ ├── helpers.js │ │ │ ├── model.js.hbs │ │ │ └── generator.json │ │ ├── route │ │ │ ├── route-require.js.hbs │ │ │ ├── helpers.js │ │ │ ├── route.js.hbs │ │ │ └── generator.json │ │ ├── controller │ │ │ ├── controller-require.js.hbs │ │ │ ├── helpers.js │ │ │ ├── controller.js.hbs │ │ │ └── generator.json │ │ ├── helpers.js │ │ └── index.js │ ├── test │ │ ├── helpers.coffee │ │ ├── sample-tests │ │ │ └── array_test.coffee │ │ ├── karma.conf.js │ │ └── vendor │ │ │ └── scripts │ │ │ └── sinon-chai-2.2.0.js │ ├── .gitignore │ ├── vendor │ │ ├── scripts │ │ │ └── console-polyfill.js │ │ └── styles │ │ │ └── normalize.css │ ├── Cakefile │ ├── package.json │ ├── LICENSE │ ├── config.coffee │ └── README.md └── sample │ ├── package.json │ ├── public │ ├── index.html │ └── js │ │ └── milo.js │ └── server.js ├── website ├── theme │ ├── theme.json │ ├── partials │ │ ├── index.handlebars │ │ ├── files.handlebars │ │ ├── props.handlebars │ │ ├── module.handlebars │ │ ├── events.handlebars │ │ ├── attrs.handlebars │ │ ├── classes.handlebars │ │ ├── method.handlebars │ │ └── sidebar.handlebars │ └── layouts │ │ └── main.handlebars ├── less │ ├── colors.less │ ├── syntax.less │ ├── batch.less │ ├── ubuntu.less │ ├── yuidoc.less │ ├── milo.less │ ├── normalize.less │ └── preboot.less ├── assets │ ├── mascot.png │ ├── batch-icons-webfont.eot │ ├── batch-icons-webfont.ttf │ └── batch-icons-webfont.woff ├── index.html └── javascript │ └── rainbow-custom.min.js ├── .travis.yml ├── test ├── serialization.js ├── test.html ├── queryable.js ├── core.js └── find.js ├── .gitignore ├── src ├── namespace.js ├── core │ ├── deferred.js │ ├── arrayProxy.js │ ├── proxy.js │ ├── api.js │ └── queryable.js ├── model.js ├── dsl │ └── property.js └── adapters │ ├── defaultSerializer.js │ └── defaultAdapter.js ├── package.json ├── README.md └── Gruntfile.js /samples/sample-ui/app/data.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/sample-ui/app/helpers.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/sample-ui/app/views.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/theme/theme.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /samples/sample-ui/app/controllers.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/sample-ui/app/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/sample-ui/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/sample-ui/app/routes/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/sample-ui/app/views/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/sample-ui/app/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/sample-ui/generators/template/template.hbs.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /samples/sample-ui/app/routes.js: -------------------------------------------------------------------------------- 1 | require('routes/index'); 2 | -------------------------------------------------------------------------------- /samples/sample-ui/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | {{outlet}} 2 | -------------------------------------------------------------------------------- /website/less/colors.less: -------------------------------------------------------------------------------- 1 | @violet: #4a3b6a; 2 | @darkViolet: #362f45; -------------------------------------------------------------------------------- /samples/sample-ui/app/app.js: -------------------------------------------------------------------------------- 1 | module.exports = Em.Application.create({}); -------------------------------------------------------------------------------- /samples/sample-ui/app/assets/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org/ 2 | 3 | User-agent: * 4 | -------------------------------------------------------------------------------- /samples/sample-ui/generators/view/view-require.js.hbs: -------------------------------------------------------------------------------- 1 | require('{{path}}/{{name}}'); 2 | -------------------------------------------------------------------------------- /samples/sample-ui/generators/helper/helper-require.js.hbs: -------------------------------------------------------------------------------- 1 | require('{{path}}/{{name}}'); 2 | -------------------------------------------------------------------------------- /samples/sample-ui/generators/model/model-require.js.hbs: -------------------------------------------------------------------------------- 1 | require('{{path}}/{{name}}'); 2 | -------------------------------------------------------------------------------- /samples/sample-ui/generators/route/route-require.js.hbs: -------------------------------------------------------------------------------- 1 | require('{{path}}/{{name}}'); 2 | -------------------------------------------------------------------------------- /samples/sample-ui/generators/template/template-require.js.hbs: -------------------------------------------------------------------------------- 1 | require('{{path}}/{{name}}'); 2 | -------------------------------------------------------------------------------- /website/assets/mascot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/milo/master/website/assets/mascot.png -------------------------------------------------------------------------------- /samples/sample-ui/app/styles/application.styl: -------------------------------------------------------------------------------- 1 | @import 'nib' 2 | 3 | html, body 4 | margin 20px 5 | -------------------------------------------------------------------------------- /samples/sample-ui/generators/controller/controller-require.js.hbs: -------------------------------------------------------------------------------- 1 | require('{{path}}/{{name}}'); 2 | -------------------------------------------------------------------------------- /samples/sample-ui/app/templates.js: -------------------------------------------------------------------------------- 1 | require('templates/application'); 2 | require('templates/index'); 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | before_script: 5 | - npm install -g grunt-cli 6 | -------------------------------------------------------------------------------- /website/assets/batch-icons-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/milo/master/website/assets/batch-icons-webfont.eot -------------------------------------------------------------------------------- /website/assets/batch-icons-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/milo/master/website/assets/batch-icons-webfont.ttf -------------------------------------------------------------------------------- /samples/sample-ui/app/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/milo/master/samples/sample-ui/app/assets/favicon.ico -------------------------------------------------------------------------------- /samples/sample-ui/app/models.js: -------------------------------------------------------------------------------- 1 | require('models/versionTier'); 2 | require('models/version'); 3 | require('models/service'); 4 | -------------------------------------------------------------------------------- /website/assets/batch-icons-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/milo/master/website/assets/batch-icons-webfont.woff -------------------------------------------------------------------------------- /website/theme/partials/index.handlebars: -------------------------------------------------------------------------------- 1 |

API Docs - Main Index

2 |

Something smart and pretty should probably go here.

3 | -------------------------------------------------------------------------------- /samples/sample-ui/app/assets/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/milo/master/samples/sample-ui/app/assets/apple-touch-icon.png -------------------------------------------------------------------------------- /website/theme/partials/files.handlebars: -------------------------------------------------------------------------------- 1 |

{{fileName}}

2 | 3 |
4 | {{fileData}}
5 | 
6 | 7 | -------------------------------------------------------------------------------- /test/serialization.js: -------------------------------------------------------------------------------- 1 | describe('Serialization', function () { 2 | describe('Primitive serialization', function () { 3 | 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /samples/sample-ui/app/assets/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/milo/master/samples/sample-ui/app/assets/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /samples/sample-ui/generators/view/helpers.js: -------------------------------------------------------------------------------- 1 | module.exports = function(Handlebars) { 2 | var helpers = require ( '../helpers' ); 3 | helpers(Handlebars); 4 | }; -------------------------------------------------------------------------------- /samples/sample-ui/generators/helper/helpers.js: -------------------------------------------------------------------------------- 1 | module.exports = function(Handlebars) { 2 | var helpers = require ( '../helpers' ); 3 | helpers(Handlebars); 4 | }; -------------------------------------------------------------------------------- /samples/sample-ui/generators/model/helpers.js: -------------------------------------------------------------------------------- 1 | module.exports = function(Handlebars) { 2 | var helpers = require ( '../helpers' ); 3 | helpers(Handlebars); 4 | }; -------------------------------------------------------------------------------- /samples/sample-ui/generators/route/helpers.js: -------------------------------------------------------------------------------- 1 | module.exports = function(Handlebars) { 2 | var helpers = require ( '../helpers' ); 3 | helpers(Handlebars); 4 | }; -------------------------------------------------------------------------------- /samples/sample-ui/generators/template/helpers.js: -------------------------------------------------------------------------------- 1 | module.exports = function(Handlebars) { 2 | var helpers = require ( '../helpers' ); 3 | helpers(Handlebars); 4 | }; -------------------------------------------------------------------------------- /samples/sample-ui/app/assets/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/milo/master/samples/sample-ui/app/assets/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /samples/sample-ui/app/assets/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/milo/master/samples/sample-ui/app/assets/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /samples/sample-ui/generators/controller/helpers.js: -------------------------------------------------------------------------------- 1 | module.exports = function(Handlebars) { 2 | var helpers = require ( '../helpers' ); 3 | helpers(Handlebars); 4 | }; -------------------------------------------------------------------------------- /samples/sample-ui/app/assets/apple-touch-icon-57x57-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/milo/master/samples/sample-ui/app/assets/apple-touch-icon-57x57-precomposed.png -------------------------------------------------------------------------------- /samples/sample-ui/app/assets/apple-touch-icon-72x72-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/milo/master/samples/sample-ui/app/assets/apple-touch-icon-72x72-precomposed.png -------------------------------------------------------------------------------- /samples/sample-ui/app/assets/apple-touch-icon-114x114-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/milo/master/samples/sample-ui/app/assets/apple-touch-icon-114x114-precomposed.png -------------------------------------------------------------------------------- /samples/sample-ui/app/assets/apple-touch-icon-144x144-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pose/milo/master/samples/sample-ui/app/assets/apple-touch-icon-144x144-precomposed.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | app/dist 3 | app/doc 4 | sample-ui/vendor/scripts/milo.js 5 | test/npm-debug.log 6 | website/stylesheets 7 | website/docs 8 | website/doc 9 | website/api 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /samples/sample-ui/test/helpers.coffee: -------------------------------------------------------------------------------- 1 | # Create `window.describe` etc. for our BDD-like tests. 2 | mocha.setup ui: 'bdd' 3 | 4 | # Create another global variable for simpler syntax. 5 | window.expect = chai.expect 6 | -------------------------------------------------------------------------------- /samples/sample-ui/generators/view/view.js.hbs: -------------------------------------------------------------------------------- 1 | var App = require('app'); 2 | 3 | App.{{#camelize}}{{name}}{{/camelize}}View = Em.View.extend({ 4 | }); 5 | 6 | module.exports = App.{{#camelize}}{{name}}{{/camelize}}View; -------------------------------------------------------------------------------- /samples/sample-ui/generators/helper/helper.js.hbs: -------------------------------------------------------------------------------- 1 | var App = require('app'); 2 | 3 | App.helpers.{{#camelize}}{{name}}{{/camelize}}: function () { 4 | }; 5 | 6 | module.exports = App.helpers.{{#camelize}}{{name}}{{/camelize}}; -------------------------------------------------------------------------------- /samples/sample-ui/generators/model/model.js.hbs: -------------------------------------------------------------------------------- 1 | var App = require('app'); 2 | 3 | App.{{#camelize}}{{name}}{{/camelize}}Model = Em.Object.extend({ 4 | }); 5 | 6 | module.exports = App.{{#camelize}}{{name}}{{/camelize}}Model; -------------------------------------------------------------------------------- /samples/sample-ui/generators/route/route.js.hbs: -------------------------------------------------------------------------------- 1 | var App = require('app'); 2 | 3 | App.{{#camelize}}{{name}}{{/camelize}}Route = Em.Route.extend({ 4 | }); 5 | 6 | module.exports = App.{{#camelize}}{{name}}{{/camelize}}Route; -------------------------------------------------------------------------------- /samples/sample/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-server", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "express": "4.17.1" 6 | }, 7 | "devDependencies": { 8 | "nstore": "~0.5.2" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/sample-ui/generators/controller/controller.js.hbs: -------------------------------------------------------------------------------- 1 | var App = require('app'); 2 | 3 | App.{{#camelize}}{{name}}{{/camelize}}Controller = Em.ObjectController.extend({ 4 | }); 5 | 6 | module.exports = App.{{#camelize}}{{name}}{{/camelize}}Controller; -------------------------------------------------------------------------------- /samples/sample-ui/generators/helpers.js: -------------------------------------------------------------------------------- 1 | module.exports = function(Handlebars) { 2 | Handlebars.registerHelper('date', (function() { 3 | var date = new Date(); 4 | return function(options) { 5 | return date.toString(); 6 | }; 7 | })()); 8 | }; -------------------------------------------------------------------------------- /samples/sample-ui/test/sample-tests/array_test.coffee: -------------------------------------------------------------------------------- 1 | # Sample test case 2 | describe 'Array', -> 3 | describe '#indexOf()', -> 4 | it 'should return -1 when the value is not present', -> 5 | expect( [1,2,3].indexOf(5) ).to.equal -1 6 | expect( [1,2,3].indexOf(0) ).to.equal -1 7 | -------------------------------------------------------------------------------- /samples/sample-ui/app/assets/humans.txt: -------------------------------------------------------------------------------- 1 | # humanstxt.org/ 2 | # The humans responsible & technology colophon 3 | 4 | # TEAM 5 | 6 | -- -- 7 | 8 | # THANKS 9 | 10 | 11 | 12 | # TECHNOLOGY COLOPHON 13 | 14 | HTML5, CSS3 15 | Normalize.css, jQuery, Modernizr 16 | -------------------------------------------------------------------------------- /samples/sample-ui/app/models/versionTier.js: -------------------------------------------------------------------------------- 1 | var App = require('app'); 2 | 3 | App.VersionTier = Milo.Model.extend({ 4 | rootElement: 'tiers', 5 | uriTemplate: Milo.UriTemplate('/services/%@/versions/%@/tiers/%@', { 6 | namedParams: ['serviceId', 'versionId', 'tierId'] 7 | }) 8 | }); 9 | 10 | module.exports = App.VersionTier; -------------------------------------------------------------------------------- /samples/sample-ui/generators/view/generator.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | { 4 | "from": "view.js.hbs", 5 | "to": "app/{{path}}/{{name}}.js" 6 | }, 7 | { 8 | "from": "view-require.js.hbs", 9 | "to": "app/views.js", 10 | "method" : "append" 11 | } 12 | ], 13 | "dependencies": [] 14 | } 15 | -------------------------------------------------------------------------------- /samples/sample-ui/generators/model/generator.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | { 4 | "from": "model.js.hbs", 5 | "to": "app/{{path}}/{{name}}.js" 6 | }, 7 | { 8 | "from": "model-require.js.hbs", 9 | "to": "app/models.js", 10 | "method" : "append" 11 | } 12 | ], 13 | "dependencies": [] 14 | } 15 | -------------------------------------------------------------------------------- /samples/sample-ui/generators/route/generator.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | { 4 | "from": "route.js.hbs", 5 | "to": "app/{{path}}/{{name}}.js" 6 | }, 7 | { 8 | "from": "route-require.js.hbs", 9 | "to": "app/routes.js", 10 | "method" : "append" 11 | } 12 | ], 13 | "dependencies": [] 14 | } 15 | -------------------------------------------------------------------------------- /samples/sample-ui/generators/helper/generator.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | { 4 | "from": "helper.js.hbs", 5 | "to": "app/{{path}}/{{name}}.js" 6 | }, 7 | { 8 | "from": "helper-require.js.hbs", 9 | "to": "app/helpers.js", 10 | "method" : "append" 11 | } 12 | ], 13 | "dependencies": [] 14 | } 15 | -------------------------------------------------------------------------------- /samples/sample-ui/generators/template/generator.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | { 4 | "from": "template.hbs.hbs", 5 | "to": "app/{{path}}/{{name}}.hbs" 6 | }, 7 | { 8 | "from": "template-require.js.hbs", 9 | "to": "app/templates.js", 10 | "method" : "append" 11 | } 12 | ], 13 | "dependencies": [] 14 | } 15 | -------------------------------------------------------------------------------- /samples/sample-ui/generators/controller/generator.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | { 4 | "from": "controller.js.hbs", 5 | "to": "app/{{path}}/{{name}}.js" 6 | }, 7 | { 8 | "from": "controller-require.js.hbs", 9 | "to": "app/controllers.js", 10 | "method" : "append" 11 | } 12 | ], 13 | "dependencies": [] 14 | } 15 | -------------------------------------------------------------------------------- /src/namespace.js: -------------------------------------------------------------------------------- 1 | /** 2 | @namespace Milo 3 | @module milo 4 | @extends {Ember.Namespace} 5 | */ 6 | window.Milo = Em.Namespace.create({ 7 | revision: 1 8 | }); 9 | 10 | var _apiFromModelClass = function (modelClass) { 11 | var modelClassName = modelClass.toString(); 12 | 13 | return Em.get(modelClassName.substring(0, modelClassName.indexOf('.'))); 14 | }; 15 | 16 | -------------------------------------------------------------------------------- /samples/sample-ui/app/models/service.js: -------------------------------------------------------------------------------- 1 | var App = require('app'); 2 | 3 | App.Service = Milo.Model.extend({ 4 | rootElement: 'services', 5 | uriTemplate: Milo.UriTemplate('/services/%@'), 6 | 7 | name: Milo.property('string', { 8 | defaultValue: '', 9 | operations: ['put', 'post'], 10 | validationRules: { 11 | required: true 12 | } 13 | }), 14 | 15 | versions: Milo.collection(App.Version) 16 | }); 17 | 18 | module.exports = App.Service; -------------------------------------------------------------------------------- /samples/sample-ui/.gitignore: -------------------------------------------------------------------------------- 1 | # Numerous always-ignore extensions 2 | *.diff 3 | *.err 4 | *.orig 5 | *.log 6 | *.rej 7 | *.swo 8 | *.swp 9 | *.vi 10 | *~ 11 | *.sass-cache 12 | *.orig 13 | 14 | # OS or Editor folders 15 | .DS_Store 16 | .cache 17 | .project 18 | .settings 19 | .tmproj 20 | nbproject 21 | Thumbs.db 22 | 23 | # NPM packages folder. 24 | node_modules/ 25 | 26 | # Brunch folder for temporary files. 27 | tmp/ 28 | 29 | # Brunch output folder. 30 | public/ 31 | -------------------------------------------------------------------------------- /website/theme/partials/props.handlebars: -------------------------------------------------------------------------------- 1 | 2 |
3 | `{{name}}` <{{#crossLink type}}{{/crossLink}}>{{#if final}} (final){{/if}}{{#if static}} (static){{/if}}
4 | `{{file}}:{{line}}` 5 | {{{propertyDescription}}} 6 | {{#if example}} 7 |
Example
8 | {{{example}}} 9 | {{/if}} 10 |
11 | -------------------------------------------------------------------------------- /samples/sample-ui/app/templates/index.hbs: -------------------------------------------------------------------------------- 1 |

Services

2 |
    3 | {{#if content.isLoading}} 4 | loading... 5 | {{else}} 6 | {{#each item in content}} 7 |
  • 8 |

    {{item.name}}

    9 |
      10 | {{#each version in item.versions}} 11 |
    • {{version.major}}.{{version.minor}}.{{version.revision}}
    • 12 | {{/each}} 13 |
    14 |
  • 15 | {{/each}} 16 | {{/if}} 17 |
18 | -------------------------------------------------------------------------------- /samples/sample-ui/app/models/version.js: -------------------------------------------------------------------------------- 1 | var App = require('app'); 2 | 3 | App.Version = Milo.Model.extend({ 4 | rootElement: 'versions', 5 | uriTemplate: Milo.UriTemplate('/services/%@/versions/%@', { 6 | namedParams: ['serviceId', 'versionId'] 7 | }), 8 | 9 | tiers: function () { 10 | return App.VersionTier.find({ 11 | serviceId: this.get('serviceId'), 12 | versionId: this.get('versionId') 13 | }); 14 | }.property().volatile() //// Avoiding Ember Cache, 15 | }); 16 | 17 | module.exports = App.Version; -------------------------------------------------------------------------------- /samples/sample-ui/app/initialize.js: -------------------------------------------------------------------------------- 1 | // ===== Namespace ===== 2 | var App = require('app'); 3 | 4 | 5 | // ===== Router ===== 6 | App.Router.map(function () { 7 | 8 | }); 9 | 10 | // ===== Routes ===== 11 | require('routes'); 12 | 13 | // ===== Data ===== 14 | require('data'); 15 | 16 | // ===== Models ===== 17 | require('models'); 18 | 19 | // ===== Views ===== 20 | require('views') 21 | 22 | // ===== Controllers ===== 23 | require('controllers'); 24 | 25 | // ===== Helpers ===== 26 | require('helpers'); 27 | 28 | // ===== Templates ===== 29 | require('templates'); 30 | -------------------------------------------------------------------------------- /samples/sample-ui/vendor/scripts/console-polyfill.js: -------------------------------------------------------------------------------- 1 | // Console-polyfill. MIT license. 2 | // https://github.com/paulmillr/console-polyfill 3 | // Make it safe to do console.log() always. 4 | (function (con) { 5 | var method; 6 | var dummy = function() {}; 7 | var methods = ('assert,count,debug,dir,dirxml,error,exception,group,' + 8 | 'groupCollapsed,groupEnd,info,log,markTimeline,profile,profileEnd,' + 9 | 'time,timeEnd,trace,warn').split(','); 10 | while (method = methods.pop()) { 11 | con[method] = con[method] || dummy; 12 | } 13 | })(window.console = window.console || {}); 14 | -------------------------------------------------------------------------------- /samples/sample-ui/app/assets/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /src/core/deferred.js: -------------------------------------------------------------------------------- 1 | /** 2 | @namespace Milo 3 | @module milo-core 4 | @class Deferred 5 | @extends {Ember.Mixin} 6 | */ 7 | Milo.Deferred = Em.Mixin.create({ 8 | /** 9 | This method will be called when the ajax request has successfully finished 10 | 11 | @method done 12 | @return {Milo.Deferred} 13 | */ 14 | done: function (callback) { 15 | this.get('deferred').done(callback); 16 | return this; 17 | }, 18 | 19 | /** 20 | This method will be called when the ajax request fails 21 | 22 | @method fail 23 | @return {Milo.Deferred} 24 | */ 25 | fail: function (callback) { 26 | this.get('deferred').fail(callback); 27 | return this; 28 | } 29 | }); -------------------------------------------------------------------------------- /src/core/arrayProxy.js: -------------------------------------------------------------------------------- 1 | /** 2 | @namespace Milo 3 | @module milo-core 4 | @class ArrayProxy 5 | @extends {Ember.ArrayProxy} 6 | @uses {Milo.Deferred} 7 | */ 8 | Milo.ArrayProxy = Em.ArrayProxy.extend(Milo.Deferred, { 9 | /** 10 | Indicates if the entity is in the 'loading' state 11 | 12 | @attribute isLoading 13 | @default false 14 | @type boolean 15 | */ 16 | isLoading: false, 17 | 18 | /** 19 | Indicates if the entity has errors 20 | 21 | @attribute isError 22 | @default false 23 | @type boolean 24 | */ 25 | isError: false, 26 | 27 | /** 28 | @private 29 | @attribute errors 30 | @default null 31 | @type Array 32 | */ 33 | errors: null 34 | }); -------------------------------------------------------------------------------- /website/theme/layouts/main.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Milojs 7 | 8 | 9 | 10 | 11 |
12 |

MiloJS

13 | 18 |
19 |
20 | 23 |
24 | {{>layout_content}} 25 |
26 |
27 | 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "milo", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "test": "grunt ci --verbose" 6 | }, 7 | "dependencies": { 8 | "mocha": "1.9.0", 9 | "chai": "1.5.0", 10 | "sinon-chai": "2.4.0" 11 | }, 12 | "devDependencies": { 13 | "grunt": "~0.4.1", 14 | "grunt-contrib-jshint": "~0.1.1", 15 | "grunt-contrib-nodeunit": "~0.1.2", 16 | "grunt-contrib-concat": "~0.3.0", 17 | "grunt-contrib-uglify": "~0.2.0", 18 | "grunt-contrib-watch": "~0.3.1", 19 | "grunt-contrib-less": "~0.5.1", 20 | "grunt-mocha": "~0.3.1", 21 | "grunt-mocha-phantomjs": "~0.2.7", 22 | "grunt-contrib-yuidoc": "~0.4.0", 23 | "grunt-contrib-clean": "~0.4.1", 24 | "grunt-contrib-connect": "~0.3.0", 25 | "grunt-string-replace": "~0.2.4", 26 | "grunt-zip": "~0.9.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /samples/sample-ui/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Milo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /website/theme/partials/module.handlebars: -------------------------------------------------------------------------------- 1 | 2 |

  {{moduleName}} Module

3 |
{{{moduleDescription}}}
4 | 5 |
6 |
7 | {{#if moduleClasses}} 8 |

Classes and Namespaces

9 |
    10 | {{#moduleClasses}} 11 |
  • {{displayName}}
  • 12 | {{/moduleClasses}} 13 |
14 | {{/if}} 15 |
16 |
17 | {{#if subModules}} 18 |

This module has the following submodules:

19 |
    20 | {{#subModules}} 21 |
  • {{displayName}}

    {{{description}}}

  • 22 | {{/subModules}} 23 |
24 | {{/if}} 25 |
26 |
-------------------------------------------------------------------------------- /samples/sample-ui/Cakefile: -------------------------------------------------------------------------------- 1 | http = require 'http' 2 | fs = require 'fs' 3 | 4 | 5 | # Test runner 6 | task 'test', -> 7 | server = (require 'karma').server 8 | server.start configFile: './test/karma.conf.js', (exitCode) -> 9 | console.log "Karma has exited with #{exitCode}" 10 | process.exit exitCode 11 | 12 | 13 | # Gets latest Ember Data 14 | task 'getemberdata', 'download latest build of Ember Data', (options) -> 15 | file = fs.createWriteStream 'vendor/scripts/ember-data-latest.js' 16 | request = http.get 'http://builds.emberjs.com.s3.amazonaws.com/ember-data-latest.js', (response) -> 17 | response.pipe file 18 | 19 | # Get latest Ember 20 | task 'getember', 'download and build the latest ember-data.js', (options) -> 21 | file = fs.createWriteStream 'vendor/scripts/ember-latest.js' 22 | request = http.get 'http://builds.emberjs.com.s3.amazonaws.com/ember-latest.js', (response) -> 23 | response.pipe file 24 | -------------------------------------------------------------------------------- /samples/sample-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Giovanni Collazo", 3 | "name": "brunch-with-ember-reloaded", 4 | "description": "A new and up-to-date Brunch skeleton for developing Ember applications", 5 | "version": "0.0.3", 6 | "homepage": "", 7 | "repository": { 8 | "type": "git", 9 | "url": "" 10 | }, 11 | "engines": { 12 | "node": "~0.6.10 || 0.8 || 0.9" 13 | }, 14 | "scripts": { 15 | "start": "brunch watch --server" 16 | }, 17 | "dependencies": { 18 | "javascript-brunch": ">= 1.0 < 1.6", 19 | "coffee-script-brunch": ">= 1.0 < 1.6", 20 | 21 | "css-brunch": ">= 1.0 < 1.6", 22 | "stylus-brunch": ">= 1.0 < 1.6", 23 | 24 | "uglify-js-brunch": ">= 1.0 < 1.6", 25 | "clean-css-brunch": ">= 1.0 < 1.6", 26 | 27 | "auto-reload-brunch": ">= 1.0 < 1.6", 28 | "ember-handlebars-brunch": "git://github.com/icholy/ember-handlebars-brunch.git" 29 | }, 30 | "devDependencies": { 31 | "karma": "0.8.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /samples/sample-ui/LICENSE: -------------------------------------------------------------------------------- 1 | All of emberjs-template-with-scaffold is licensed under the MIT license. 2 | 3 | Copyright (c) 2013 Alejo Fernandez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /website/theme/partials/events.handlebars: -------------------------------------------------------------------------------- 1 | 2 |
3 | `{{name}}` {{#if type}}<{{#crossLink type}}{{/crossLink}}>{{/if}} 4 | {{#if extended_from}}`/* Extended from {{extended_from}} */`{{/if}} 5 | {{#if overwritten_from}}`/* Overwritten from {{name}} */`{{/if}} 6 |
7 | `{{file}}:{{line}}` 8 | {{{eventDescription}}} 9 | {{#if params}} 10 | Extra event object properties: 11 |
    12 | {{#params}} 13 |
  • 14 | {{#if optional}} 15 | `[{{name}}{{#if optdefault}}={{optdefault}}{{/if}}]` <{{#crossLink type}}{{/crossLink}}> 16 | {{else}} 17 | `{{name}}` <{{#crossLink type}}{{/crossLink}}> 18 | {{/if}} 19 | {{#if multiple}} 20 | (*..n) 21 | {{/if}} 22 | {{{description}}} 23 | {{#if props}} 24 |
      25 | {{#props}} 26 |
    • `{{name}}` <{{#crossLink type}}{{/crossLink}}> {{{description}}} 27 | {{/props}} 28 |
    29 | {{/if}} 30 |
  • 31 | {{/params}} 32 |
33 | {{/if}} 34 |
35 | 36 | -------------------------------------------------------------------------------- /samples/sample/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Actor - Movie Database 6 | 7 | 8 | 9 | 10 | 11 | 12 |

Movie Database

13 |

Actor retrieved:

14 | 15 |

16 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /samples/sample-ui/test/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | 3 | 4 | // base path, that will be used to resolve files and exclude 5 | basePath = '../'; 6 | 7 | // list of files / patterns to load in the browser 8 | files = [ 9 | MOCHA, 10 | MOCHA_ADAPTER, 11 | 12 | 'public/javascripts/vendor.js', 13 | 'public/javascripts/app.js', 14 | 'public/test/javascripts/test-vendor.js', 15 | 16 | 'test/helpers.coffee', 17 | 18 | 'test/**/*_test.coffee' 19 | ]; 20 | 21 | // test results reporter to use 22 | // possible values: dots || progress 23 | reporter = 'progress'; 24 | 25 | // web server port 26 | port = 8080; 27 | 28 | // cli runner port 29 | runnerPort = 9100; 30 | 31 | // enable / disable colors in the output (reporters and logs) 32 | colors = true; 33 | 34 | // level of logging 35 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 36 | logLevel = LOG_INFO; 37 | 38 | // enable / disable watching file and executing tests whenever any file changes 39 | autoWatch = false; 40 | 41 | // Start these browsers, currently available: 42 | // - Chrome 43 | // - ChromeCanary 44 | // - Firefox 45 | // - Opera 46 | // - Safari 47 | // - PhantomJS 48 | browsers = ['PhantomJS']; 49 | 50 | // Continuous Integration mode 51 | // if true, it capture browsers, run tests and exit 52 | singleRun = true; 53 | 54 | // compile coffee scripts 55 | preprocessors = { 56 | '**/*.coffee': 'coffee' 57 | }; 58 | -------------------------------------------------------------------------------- /src/model.js: -------------------------------------------------------------------------------- 1 | /** 2 | @namespace Milo 3 | @module milo 4 | @class Model 5 | @extends {Ember.Object} 6 | @uses {Milo.Queryable} 7 | */ 8 | Milo.Model = Em.Object.extend(Milo.Queryable, { 9 | meta: Milo.property('object'), 10 | 11 | _getAPI: function () { 12 | return _apiFromModelClass(this.constructor); 13 | }, 14 | 15 | /** 16 | @method data 17 | */ 18 | data: function () { 19 | if (!this.get('_data')) { 20 | this.set('_data', Em.A()); 21 | } 22 | 23 | return this.get('_data'); 24 | }.property(), 25 | 26 | /** 27 | @method rollback 28 | */ 29 | rollback: function () { 30 | this.get('data').forEach(function (element) { 31 | this.set(element.key, element.orig); 32 | }.bind(this)); 33 | 34 | this.set('isDirty', false); 35 | }, 36 | 37 | /** 38 | @method save 39 | */ 40 | save: function () { 41 | _apiFromModelClass(this).options('defaultAdapter').save(this.constructor, this); 42 | }, 43 | 44 | /** 45 | @method remove 46 | */ 47 | remove: function () { 48 | _apiFromModelClass(this).get('defaultAdapter').remove(this.constructor, this); 49 | } 50 | }); 51 | 52 | Milo.Model.reopenClass({ 53 | /** 54 | @method find 55 | @static 56 | */ 57 | find: function (clause) { 58 | return this.create().find(clause); 59 | } 60 | }); 61 | -------------------------------------------------------------------------------- /website/less/syntax.less: -------------------------------------------------------------------------------- 1 | pre#main { 2 | padding-left: 30px; 3 | padding-right: 30px; 4 | padding-top: 20px; 5 | margin-bottom: 20px; 6 | } 7 | 8 | pre { 9 | background: @darkViolet; 10 | word-wrap: break-word; 11 | margin: 0px; 12 | color: #F8F8F8; 13 | font-size: 13px; 14 | padding: 10px; 15 | .border-top-radius(6px); 16 | .border-bottom-radius(6px); 17 | margin-bottom: 20px; 18 | } 19 | 20 | pre, code { 21 | font-family: 'Monaco', courier, monospace; 22 | } 23 | 24 | pre .comment { 25 | color: #a5a0a0; 26 | } 27 | 28 | pre .constant.numeric { 29 | color: #eae7f8; 30 | } 31 | 32 | pre .constant { 33 | color: #889AB4; 34 | } 35 | 36 | pre .constant.symbol, pre .constant.language { 37 | color: #D87D50; 38 | } 39 | 40 | pre .storage { 41 | color: #F9EE98; 42 | } 43 | 44 | pre .string { 45 | //color: #6e54a3; 46 | color: #e51e25; 47 | } 48 | 49 | pre .string.regexp { 50 | color: #E9C062; 51 | } 52 | 53 | pre .keyword, pre .selector, pre .storage { 54 | color: #b9b8b8; 55 | } 56 | 57 | pre .inherited-class { 58 | color: #9B5C2E; 59 | } 60 | 61 | pre .entity { 62 | color: #FF6400; 63 | } 64 | 65 | pre .support { 66 | color: #9B859D; 67 | } 68 | 69 | pre .support.magic { 70 | color: #DAD69A; 71 | } 72 | 73 | pre .variable { 74 | color: #7587A6; 75 | } 76 | 77 | pre .function, pre .entity.class { 78 | color: #b6a7d4; 79 | } 80 | 81 | pre .support.class-name, pre .support.type { 82 | color: #AB99AC; 83 | } -------------------------------------------------------------------------------- /website/theme/partials/attrs.handlebars: -------------------------------------------------------------------------------- 1 | 2 |
3 | `{{name}}` {{#if type}}<{{#crossLink type}}{{/crossLink}}>{{/if}} 4 | {{#if extended_from}}`/* Extended from {{extended_from}} */`{{/if}} 5 | {{#if overwritten_from}}`/* Overwritten from {{name}} */`{{/if}} 6 |
7 | `{{file}}:{{line}}` 8 | {{{attrDescription}}} 9 | {{#if emit}} 10 |
11 | Fires: `{{name}}Change(e)` 12 |

Fires when the value for the configuration attribute `{{name}}` is changed. You can listen for the event using the `on` method if you wish to be notified before the attribute's value has changed, or using the `after` method if you wish to be notified after the attribute's value has changed.

13 | Parameters:
14 | `e` <EventFacade> An Event Facade object with the following attribute specific properties added: 15 |
    16 |
  • `prevVal` The value of the attribute, prior to it being set
  • 17 |
  • `newVal` The value the attribute is to be set to
  • 18 |
  • `attrName` The name of the attribute being set
  • 19 |
  • `subAttrName` If setting a property within the attribute's value, the name of the sub-attribute property being set
  • 20 |
21 |
22 | {{/if}} 23 |
24 | -------------------------------------------------------------------------------- /samples/sample-ui/app/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Ember Starter Kit 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /samples/sample-ui/config.coffee: -------------------------------------------------------------------------------- 1 | sysPath = require 'path' 2 | 3 | exports.config = 4 | # See http://brunch.io/#documentation for documentation. 5 | files: 6 | javascripts: 7 | joinTo: 8 | 'javascripts/app.js': /^app/ 9 | 'javascripts/vendor.js': /^vendor/ 10 | 'test/javascripts/test-vendor.js': /^test(\/|\\)(?=vendor)/ 11 | 12 | order: 13 | before: [ 14 | 'vendor/scripts/console-polyfill.js' 15 | 'vendor/scripts/jquery-1.9.1.js' 16 | 'vendor/scripts/handlebars-1.0.rc.3.js' 17 | 'vendor/scripts/ember-1.0.0-rc.3.js' 18 | 'vendor/scripts/milo.js' 19 | ] 20 | 21 | stylesheets: 22 | joinTo: 23 | 'stylesheets/app.css': /^(app|vendor)/ 24 | order: 25 | before: ['vendor/styles/normalize.css'] 26 | 27 | templates: 28 | precompile: true 29 | root: 'templates' 30 | joinTo: 'javascripts/app.js' : /^app/ 31 | 32 | modules: 33 | addSourceURLs: true 34 | 35 | # allow _ prefixed templates so partials work 36 | conventions: 37 | ignored: (path) -> 38 | startsWith = (string, substring) -> 39 | string.indexOf(substring, 0) is 0 40 | sep = sysPath.sep 41 | if path.indexOf("app#{sep}templates#{sep}") is 0 42 | false 43 | else 44 | startsWith sysPath.basename(path), '_' 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##Milo.js 2 | 3 | [![Build Status](https://travis-ci.org/mulesoft/milo.png?branch=master)](https://travis-ci.org/mulesoft/milo) 4 | 5 | ## Sample App 6 | 7 | In `sample-ui` directory you can find an example about how to use Milo.js with an [Ember.js](http://emberjs.com/) application. 8 | 9 | ### Setup 10 | 11 | 1. Run `npm install -g brunch` to install [Brunch](http://brunch.io/). 12 | 2. Run `npm install .` inside the `sample-ui` directory to install the required dependencies. 13 | 14 | ### Running the sample 15 | 16 | * Run `brunch watch --server`. 17 | * The application will be available at [http://localhost:3333](http://localhost:3333). 18 | 19 | ## Building Milo.js 20 | 21 | 1. Run `npm install .` to fetch the necessary npm packages. 22 | 2. Run `grunt dist` to build Milo.js. Two builds will b e placed in the `dist` directory. 23 | * `milo.js` and `milo.min.js` - unminified and minified build of Milo.js 24 | 25 | NOTE: Please be sure you already have installed [Node.js](http://nodejs.org/) 26 | 27 | ## How to Run Unit Tests 28 | 29 | ### Setup 30 | 31 | In order to run the tests you need `mocha-phantomjs`. You can do it by installing: 32 | 33 | 1. Install [Node.js](http://nodejs.org/) with NPM. 34 | 2. Run `npm install -g grunt-cli.` to install [Grunt](http://gruntjs.com/getting-started). 35 | 3. Run `npm install .` inside the project root to install the required npm packages. 36 | 37 | ### Running tests using Grunt 38 | 39 | 1. Go to the project root directory. 40 | 2. Run `grunt test`. 41 | 42 | ## Build documentation 43 | 44 | * From your local repository, run `grunt doc` 45 | * The documentation will be built into the `doc` directory 46 | -------------------------------------------------------------------------------- /website/less/batch.less: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: Batch; 3 | src: url('../assets/batch-icons-webfont.eot'); 4 | src: url('../assets/batch-icons-webfont.eot?#iefix') format('embedded-opentype'), 5 | url('../assets/batch-icons-webfont.woff') format('woff'), 6 | url('../assets/batch-icons-webfont.ttf') format('truetype'), 7 | url('../assets/batch-icons-webfont.svg#batchregular') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | [class^="batch-"], 13 | [class*=" batch-"] { 14 | font-family: 'Batch'; 15 | font-weight: normal; 16 | font-style: normal; 17 | text-decoration: inherit; 18 | display: inline; 19 | width: auto; 20 | height: auto; 21 | line-height: normal; 22 | vertical-align: baseline; 23 | background-image: none !important; 24 | background-position: 0% 0%; 25 | background-repeat: repeat; 26 | } 27 | 28 | [class^="batch-"]:before, 29 | [class*=" batch-"]:before { 30 | text-decoration: none; // Customized 31 | display: inline-block; 32 | speak: none; 33 | } 34 | 35 | 36 | .batch-download:before { 37 | content: "\F0EA"; 38 | } 39 | 40 | .batch-fork:before { 41 | content: "\F0B3"; 42 | } 43 | 44 | .batch-brackets:before { 45 | content: "\F061"; 46 | } 47 | 48 | .batch-fluent:before { 49 | content: "\F177"; 50 | } 51 | 52 | .batch-interop:before { 53 | content: "\F067"; 54 | } 55 | 56 | .batch-module:before { 57 | content: "\F0A9"; 58 | } 59 | 60 | .batch-class:before { 61 | content: "\F062"; 62 | } 63 | 64 | .batch-large { font-size:32px; } 65 | .batch-xlarge { font-size:36px; } 66 | .batch-huge { font-size:64px; } 67 | .batch-natural { font-size:inherit; } -------------------------------------------------------------------------------- /samples/sample/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | nStore = require('nstore'), 3 | app = express(); 4 | 5 | nStore = nStore.extend(require('nstore/query')()); 6 | 7 | var movies = nStore.new('data/movies.db', function () { 8 | movies.save(1, { 9 | name: "The Matrix", 10 | year: 1999, 11 | rating: { 12 | value: 8.5, 13 | votes: 274 14 | }, 15 | genres: ['Adventure', 'Action', 'Thriller', 'Science Fiction'], 16 | cast: [{ 17 | name: 'Keanu Reeves', 18 | character: 'Neo' 19 | }, { 20 | name: 'Carrie-Anne Moss', 21 | character: 'Trinity' 22 | }, { 23 | name: 'Laurence Fishburne', 24 | character: 'Morpheus' 25 | }, { 26 | name: 'Hugo Weaving', 27 | character: 'Agent Smith' 28 | }], 29 | crew: { 30 | directors: ['Andy Wachowski', 'Lana Wachowski'], 31 | writers: ['Andy Wachowski', 'Lana Wachowski'] 32 | } 33 | }) 34 | }); 35 | 36 | app.use('/static', express.static(__dirname + '/public')); 37 | app.use(express.logger()); 38 | 39 | app.get('/api/movies', function (req, res) { 40 | 41 | movies.all(function (err, results) { 42 | if (!err) { 43 | res.send(200, results); 44 | } 45 | }); 46 | }); 47 | 48 | app.get('/api/movies/:id', function (req, res) { 49 | movies.get(req.params.id, function (err, doc, key) { 50 | if (err) { 51 | res.send(404, 'Actor not found'); 52 | } else { 53 | res.send(200, doc); 54 | } 55 | }); 56 | }); 57 | 58 | 59 | app.listen(3000); -------------------------------------------------------------------------------- /src/core/proxy.js: -------------------------------------------------------------------------------- 1 | /** 2 | @namespace Milo 3 | @module milo-core 4 | @class Proxy 5 | @extends {Ember.ObjectProxy} 6 | @uses {Milo.Deferred} 7 | */ 8 | Milo.Proxy = Em.ObjectProxy.extend(Milo.Deferred, { 9 | /** 10 | Indicates if the entity is in the 'loading' state 11 | 12 | @attribute isLoading 13 | @default false 14 | @type boolean 15 | */ 16 | isLoading: false, 17 | 18 | /** 19 | Indicates if the entity is in the 'saving' state 20 | 21 | @attribute isSaving 22 | @default false 23 | @type boolean 24 | */ 25 | isSaving: false, 26 | 27 | /** 28 | Indicates if the entity is in the 'deleting' state 29 | 30 | @attribute isDeleting 31 | @default false 32 | @type boolean 33 | */ 34 | isDeleting: false, 35 | 36 | /** 37 | Indicates if the entity is new 38 | 39 | @attribute isNew 40 | @default false 41 | @type boolean 42 | */ 43 | isNew: false, 44 | 45 | /** 46 | Indicates if the entity is in the 'dirty' state 47 | 48 | @attribute isDirty 49 | @default false 50 | @type boolean 51 | */ 52 | isDirty: false, 53 | 54 | /** 55 | Indicates if the entity has errors 56 | 57 | @attribute isError 58 | @default false 59 | @type boolean 60 | */ 61 | isError: false, 62 | 63 | /** 64 | @private 65 | @attribute errors 66 | @default null 67 | @type Array 68 | */ 69 | errors: null, 70 | 71 | /** 72 | Rollbacks the current state of the entity 73 | 74 | @method rollback 75 | */ 76 | rollback: function () { 77 | this.get('content').rollback(); 78 | } 79 | }); -------------------------------------------------------------------------------- /website/theme/partials/classes.handlebars: -------------------------------------------------------------------------------- 1 |

  {{moduleName}} Class

2 | {{#if uses}} 3 | Uses: 4 | {{#each uses}} 5 | {{this}} 6 | {{/each}} 7 |
8 | {{/if}} 9 | {{#if extension_for}} 10 | Extension For: 11 | {{#each extension_for}} 12 | {{this}} 13 | {{/each}} 14 |
15 | {{/if}} 16 | {{#if extends}} 17 | Extends: {{#crossLink extends}}{{/crossLink}}
18 | {{/if}} 19 |
{{{classDescription}}}
20 | 21 | {{#if is_constructor}} 22 | {{#is_constructor}} 23 | {{>method}} 24 | {{/is_constructor}} 25 | {{/if}} 26 | 27 |
28 |
    29 | {{#if methods}} 30 |
  • Methods
  • 31 | {{/if}} 32 | {{#if properties}} 33 |
  • Properties
  • 34 | {{/if}} 35 | {{#if attrs}} 36 |
  • Attributes
  • 37 | {{/if}} 38 | {{#if events}} 39 |
  • Events
  • 40 | {{/if}} 41 |
42 |
43 | {{#if methods}} 44 |
45 | {{#methods}} 46 | {{>method}} 47 | {{/methods}} 48 |
49 | {{/if}} 50 | {{#if properties}} 51 |
52 | {{#properties}} 53 | {{>props}} 54 | {{/properties}} 55 |
56 | {{/if}} 57 | {{#if attrs}} 58 |
59 | {{#attrs}} 60 | {{>attrs}} 61 | {{/attrs}} 62 |
63 | {{/if}} 64 | {{#if events}} 65 |
66 | {{#events}} 67 | {{>events}} 68 | {{/events}} 69 |
70 | {{/if}} 71 |
72 |
73 | -------------------------------------------------------------------------------- /website/theme/partials/method.handlebars: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{#if final}}final {{/if}}{{name}} ({{paramsList}}) {{#if access}}`/* {{access}} method */`{{/if}} 4 |
5 | {{{methodDescription}}} 6 | {{#if params}} 7 | Parameters: 8 |
    9 | {{#params}} 10 |
  • 11 | {{#if optional}} 12 | `[{{name}}{{#if optdefault}}={{optdefault}}{{/if}}]` <{{#crossLink type}}{{/crossLink}}> 13 | {{else}} 14 | `{{name}}` <{{#crossLink type}}{{/crossLink}}> 15 | {{/if}} 16 | {{#if multiple}} 17 | (*..n) 18 | {{/if}} 19 | {{{description}}} 20 | {{#if props}} 21 |
      22 | {{#props}} 23 |
    • `{{name}}` <{{#crossLink type}}{{/crossLink}}> {{{description}}} 24 | {{#if props}} 25 |
        26 | {{#props}} 27 |
      • `{{name}}` <{{#crossLink type}}{{/crossLink}}> {{{description}}}
      • 28 | {{/props}} 29 |
      30 | {{/if}} 31 |
    • 32 | {{/props}} 33 |
    34 | {{/if}} 35 |
  • 36 | {{/params}} 37 |
38 | {{/if}} 39 | {{#if return}} 40 | {{#return}} 41 |
Returns: {{#if type}}<{{#crossLink type}}{{/crossLink}}> {{/if}}{{{description}}}
42 | {{/return}} 43 | {{/if}} 44 | {{#if example}} 45 |
Example:

46 | {{{example}}} 47 | {{/if}} 48 |
49 | -------------------------------------------------------------------------------- /website/less/ubuntu.less: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Ubuntu'; 3 | font-style: normal; 4 | font-weight: 300; 5 | src: local('Ubuntu Light'), local('Ubuntu-Light'), url(http://themes.googleusercontent.com/static/fonts/ubuntu/v4/_aijTyevf54tkVDLy-dlnLO3LdcAZYWl9Si6vvxL-qU.woff) format('woff'); 6 | } 7 | @font-face { 8 | font-family: 'Ubuntu'; 9 | font-style: normal; 10 | font-weight: 400; 11 | src: local('Ubuntu'), url(http://themes.googleusercontent.com/static/fonts/ubuntu/v4/vRvZYZlUaogOuHbBTT1SNevvDin1pK8aKteLpeZ5c0A.woff) format('woff'); 12 | } 13 | @font-face { 14 | font-family: 'Ubuntu'; 15 | font-style: normal; 16 | font-weight: 500; 17 | src: local('Ubuntu Medium'), local('Ubuntu-Medium'), url(http://themes.googleusercontent.com/static/fonts/ubuntu/v4/OsJ2DjdpjqFRVUSto6IffLO3LdcAZYWl9Si6vvxL-qU.woff) format('woff'); 18 | } 19 | @font-face { 20 | font-family: 'Ubuntu'; 21 | font-style: normal; 22 | font-weight: 700; 23 | src: local('Ubuntu Bold'), local('Ubuntu-Bold'), url(http://themes.googleusercontent.com/static/fonts/ubuntu/v4/0ihfXUL2emPh0ROJezvraLO3LdcAZYWl9Si6vvxL-qU.woff) format('woff'); 24 | } 25 | @font-face { 26 | font-family: 'Ubuntu'; 27 | font-style: italic; 28 | font-weight: 300; 29 | src: local('Ubuntu Light Italic'), local('Ubuntu-LightItalic'), url(http://themes.googleusercontent.com/static/fonts/ubuntu/v4/DZ_YjBPqZ88vcZCcIXm6VrrIa-7acMAeDBVuclsi6Gc.woff) format('woff'); 30 | } 31 | @font-face { 32 | font-family: 'Ubuntu'; 33 | font-style: italic; 34 | font-weight: 400; 35 | src: local('Ubuntu Italic'), local('Ubuntu-Italic'), url(http://themes.googleusercontent.com/static/fonts/ubuntu/v4/kbP_6ONYVgE-bLa9ZRbvvnYhjbSpvc47ee6xR_80Hnw.woff) format('woff'); 36 | } 37 | @font-face { 38 | font-family: 'Ubuntu'; 39 | font-style: italic; 40 | font-weight: 500; 41 | src: local('Ubuntu Medium Italic'), local('Ubuntu-MediumItalic'), url(http://themes.googleusercontent.com/static/fonts/ubuntu/v4/ohKfORL_YnhBMzkCPoIqwrrIa-7acMAeDBVuclsi6Gc.woff) format('woff'); 42 | } 43 | @font-face { 44 | font-family: 'Ubuntu'; 45 | font-style: italic; 46 | font-weight: 700; 47 | src: local('Ubuntu Bold Italic'), local('Ubuntu-BoldItalic'), url(http://themes.googleusercontent.com/static/fonts/ubuntu/v4/OMD20Sg9RTs7sUORCEN-7brIa-7acMAeDBVuclsi6Gc.woff) format('woff'); 48 | } -------------------------------------------------------------------------------- /src/dsl/property.js: -------------------------------------------------------------------------------- 1 | var _computedPropery = Ember.computed(function (key, value, oldValue) { 2 | var temp = this.get('data').findProperty('key', key); 3 | 4 | if (!temp) { 5 | temp = this.get('data').pushObject(Em.Object.create({ 6 | key: key, 7 | value: value, 8 | orig: value 9 | })); 10 | } else { 11 | temp.value = value; 12 | } 13 | 14 | if (oldValue) { 15 | this.set('isDirty', true); 16 | } 17 | 18 | return temp.value; 19 | }); 20 | 21 | /** 22 | @namespace Milo 23 | @module milo-dsl 24 | @class property 25 | */ 26 | Milo.property = function (type, options) { 27 | options = options || {}; 28 | options.occurrences = "one"; 29 | options.embedded = true; 30 | options.type = type || 'string'; 31 | options.defaultValue = (options.defaultValue === undefined) ? null : options.defaultValue; 32 | options.operations = (options.operations === undefined) ? ['put', 'post'] : options.operations; 33 | options.validationRules = (options.validationRules === undefined) ? {} : options.validationRules; 34 | 35 | return _computedPropery.property().meta(options); 36 | }; 37 | 38 | /** 39 | @namespace Milo 40 | @module milo-dsl 41 | @class collection 42 | */ 43 | Milo.collection = function (type, options) { 44 | options = options || {}; 45 | options.occurrences = "many"; 46 | options.embedded = options.embedded || false ? true : false; 47 | options.type = type || 'string'; 48 | options.defaultValue = (options.defaultValue === undefined) ? null : options.defaultValue; 49 | options.operations = (options.operations === undefined) ? ['put', 'post'] : options.operations; 50 | options.validationRules = (options.validationRules === undefined) ? {} : options.validationRules; 51 | 52 | if (options.embedded) { 53 | return _computedPropery.property().meta(options); 54 | } else { 55 | return Ember.computed(function (key, value, oldValue) { 56 | var parentName = this.constructor.toString(), 57 | param = '%@Id'.fmt(parentName.substring(parentName.indexOf('.') + 1, parentName.length)).camelize(), 58 | findParams = JSON.parse(JSON.stringify(this.get('anyClause') || {})); 59 | 60 | type = (typeof(type) === 'string') ? Em.get(type) : type; 61 | 62 | findParams[param] = findParams.id || this.get('id'); 63 | delete findParams.id; 64 | 65 | return type.find(findParams); 66 | }).property().volatile().meta(options); 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /website/theme/partials/sidebar.handlebars: -------------------------------------------------------------------------------- 1 | 21 | 22 | 34 | 35 | {{#if methods}} 36 | 48 | {{/if}} 49 | 50 | {{#if events}} 51 | 63 | {{/if}} 64 | 65 | {{#if props}} 66 | 78 | {{/if}} 79 | 80 | {{#if attributes}} 81 | 93 | {{/if}} 94 | 95 | {{#if fileTree}} 96 | 104 | {{/if}} -------------------------------------------------------------------------------- /test/queryable.js: -------------------------------------------------------------------------------- 1 | // XXX Can I order by unexisting fields? Yes, we can't check that 2 | 3 | describe('Support in queries of', function () { 4 | var queryable = Em.Object.extend(Milo.Queryable, {}).create(); 5 | 6 | describe('ordering', function () { 7 | 8 | it('must support order asc', function () { 9 | queryable.orderBy('someField'); 10 | queryable.get('orderByClause').orderBy.should.equal('someField'); 11 | queryable.get('orderByClause').order.should.equal('asc'); 12 | }); 13 | it('must support order desc', function () { 14 | queryable.orderByDescending('someField'); 15 | queryable.get('orderByClause').orderBy.should.equal('someField'); 16 | queryable.get('orderByClause').order.should.equal('desc'); 17 | }); 18 | it('must validate that order fields should be string', function () { 19 | (function() { 20 | queryable.orderByDescending(1); 21 | }).should.Throw(/Ordering field must be a valid string/i); 22 | (function() { 23 | queryable.orderBy(1); 24 | }).should.Throw(/Ordering field must be a valid string/i); 25 | 26 | (function() { 27 | queryable.orderByDescending(''); 28 | }).should.Throw(/Ordering field must be a valid string/i); 29 | (function() { 30 | queryable.orderBy(''); 31 | }).should.Throw(/Ordering field must be a valid string/i); 32 | 33 | (function() { 34 | queryable.orderByDescending(undefined); 35 | }).should.Throw(/Ordering field must be a valid string/i); 36 | (function() { 37 | queryable.orderBy(undefined); 38 | }).should.Throw(/Ordering field must be a valid string/i); 39 | 40 | }); 41 | 42 | }); 43 | describe('take', function () { 44 | it('should generate the clause', function () { 45 | queryable.take(15); 46 | queryable.get('takeClause').limit.should.equal(15); 47 | 48 | }); 49 | it('should work with 0', function () { 50 | queryable.take(0); 51 | queryable.get('takeClause').limit.should.equal(0); 52 | }); 53 | it('should not allow invalid indexes', function () { 54 | (function() { 55 | queryable.take( -14); 56 | }).should.Throw(/invalid index/i); 57 | (function() { 58 | queryable.take( 'hello'); 59 | }).should.Throw(/invalid index/i); 60 | }); 61 | }); 62 | describe('skip', function () { 63 | it('should generate the caluse', function () { 64 | queryable.skip(10); 65 | queryable.get('skipClause').offset.should.equal(10); 66 | }); 67 | it('should allow 0', function () { 68 | queryable.skip(0); 69 | }); 70 | it('should not allow invalid indexes', function () { 71 | (function() { 72 | queryable.skip( -4); 73 | }).should.Throw(/invalid index/i); 74 | (function() { 75 | queryable.skip( 'bar'); 76 | }).should.Throw(/invalid index/i); 77 | }); 78 | }); 79 | }); 80 | 81 | -------------------------------------------------------------------------------- /samples/sample-ui/README.md: -------------------------------------------------------------------------------- 1 | # emberjs-template-with-scaffold 2 | A new and up-to-date [Brunch](http://brunch.io) skeleton for developing [Ember.js](http://emberjs.com) applications based on the template [brunch-with-ember-reloaded](https://github.com/gcollazo/brunch-with-ember-reloaded) created by [Giovanni Collazo](https://github.com/gcollazo). 3 | This skeleton uses JavaScript instead of CoffeScript for scaffolt generators. 4 | 5 | ## Versions 6 | - [Ember v1.0.0-rc.3](http://emberjs.com) 7 | - [Handlebars 1.0.0-rc.3](http://handlebarsjs.com) 8 | - [jQuery v1.9.1](http://jquery.com) 9 | - [HTML5 Boilerplate v4.2.0](http://html5boilerplate.com) 10 | 11 | ## Features 12 | - **CoffeeScript** - a little language that compiles into JavaScript. 13 | - **Stylus** - Expressive, dynamic, robust CSS pre-processor. 14 | - **auto-reload-brunch** - Adds automatic browser reloading support to brunch. 15 | - **uglify-js-brunch** - Adds UglifyJS support to brunch. 16 | 17 | ## Getting started 18 | 19 | ``` 20 | brunch new --skeleton git@github.com:AlejoFernandez/emberjs-template-with-scaffold.git 21 | cd 22 | brunch watch -s 23 | ``` 24 | Open [http://localhost:3333](http://localhost:3333) on your browser. 25 | 26 | ### Generators 27 | This skeleton makes use of [scaffolt](https://github.com/paulmillr/scaffolt#readme) generators to help you create common files quicker. To use first install scaffolt globally with `npm install -g scaffolt`. Then you can use the following command to generate files. 28 | 29 | ``` 30 | scaffolt model → app/models/name.js NameModel class 31 | scaffolt view → app/views/name.js NameView class 32 | scaffolt controller → app/controllers/name.js NameController class 33 | scaffolt route → app/routes/name.js NameRoute class 34 | scaffolt template → app/templates/name.hbs 35 | ``` 36 | It also adds a require to the newly created file in the app root folder inside the correspondent js file (models.js, routes.js, etc.). 37 | 38 | There's a few more commands you can use with scaffolt and also instruction on how to create your own generators, so make sure you check out the [docs](https://github.com/paulmillr/scaffolt#readme). 39 | 40 | 41 | ## License 42 | All of emberjs-template-with-scaffold is licensed under the MIT license. 43 | 44 | Copyright (c) 2013 Alejo Fernandez 45 | 46 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 47 | 48 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 49 | 50 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 51 | -------------------------------------------------------------------------------- /website/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Milojs 7 | 8 | 9 | 10 | 11 |
12 |

13 | 18 |
19 |

MiloJS

20 |
21 |
22 |
23 |
24 |

25 |      The JavaScript framework
26 |         for building clients to
27 | RESTful interfaces
28 |

29 | 43 | 44 |
 45 |         
 46 | Hollywood.Movie = Milo.Model.extend({
 47 |   uriTemplate: Milo.UriTemplate('/movies/:id'),
 48 |   name: Milo.property('string')
 49 | })
 50 | 
 51 | Hollywood.Actor = Milo.Model.extend({
 52 |   uriTemplate: Milo.UriTemplate('/actors/:id'),
 53 |   firstName: Milo.property('string'),
 54 |   lastName: Milo.property('string'),
 55 |   movies: Milo.collection('Hollywood.Movie')
 56 | });
 57 | 
 58 | // retrieve Robert De Niro
 59 | var actor = Hollywood.Actor
 60 |        .find({ 'id': ‘robert-de-niro’ })
 61 |        .single();
 62 | 
 63 | // retrieve all movies with Robert De Niro
 64 | var movies = Hollywood.Actor
 65 |        .find({ 'id': ‘robert-de-niro’ })
 66 |        .get('movies').find().toArray();      
 67 |         
 68 |       
69 |
70 |
71 |
72 |
73 |

74 | 75 | 76 | Declarative Model
Describe your APIs 77 |
78 |

79 |

Milejs features a declarative model for describing interactions and data models in APIs in a very natural way

80 |
81 |
82 |

83 | 84 | 85 | Fluent Interface
Query your APIs 86 |
87 |

88 |

Milejs features a declarative model for describing interactions and data models in APIs in a very natural way

89 |
90 |
91 |

92 | 93 | 94 | Made for Emberjs
KVC model 95 |
96 |

97 |

Milejs features a declarative model for describing interactions and data models in APIs in a very natural way

98 |
99 |
100 |
101 |
102 | 103 | -------------------------------------------------------------------------------- /website/less/yuidoc.less: -------------------------------------------------------------------------------- 1 | body#contrast { 2 | background-color: #4a3b6a; 3 | } 4 | 5 | body#contrast header nav a { 6 | color: white; 7 | } 8 | 9 | body header h1 { 10 | display: inline-block; 11 | float: left; 12 | font-family: Ubuntu; 13 | font-size: 32px; 14 | letter-spacing: -3px; 15 | margin: 0px; 16 | } 17 | 18 | body header h1 a { 19 | color: white; 20 | text-decoration: none; 21 | line-height: 90px; 22 | } 23 | 24 | body div#content { 25 | .make-row(); 26 | margin: auto; 27 | width: 960px; 28 | max-width: 960px; 29 | } 30 | 31 | body div#content aside { 32 | .make-column(4); 33 | } 34 | 35 | body div#content article { 36 | .make-column(8); 37 | } 38 | 39 | body div#content aside > div { 40 | border-left: solid 2px #8872b6; 41 | border-right: solid 2px #8872b6; 42 | border-bottom: solid 2px #8872b6; 43 | background-color: #362f45; 44 | } 45 | 46 | body div#content aside > div:nth-of-type(1) { 47 | border: solid 2px #8872b6; 48 | } 49 | 50 | body div#content aside div.hd { 51 | .gradient(); 52 | border-bottom: solid 2px #8872b6; 53 | } 54 | 55 | body div#content aside div.hd h2 { 56 | margin: 0px; 57 | color: white; 58 | text-transform: uppercase; 59 | font-size: 13px; 60 | font-weight: 500; 61 | color: #8872b6; 62 | height: 30px; 63 | line-height: 30px; 64 | padding-left: 15px; 65 | } 66 | 67 | body div#content ul { 68 | padding-left: 15px; 69 | } 70 | 71 | body div#content ul li { 72 | color: white; 73 | list-style: none; 74 | line-height: 25px; 75 | } 76 | 77 | body div#content ul li a { 78 | color: #8872b6; 79 | font-size: 13px; 80 | text-decoration: none; 81 | } 82 | 83 | body div#content article h2 { 84 | font-size: 20px; 85 | text-transform: uppercase; 86 | color: #8872b6; 87 | margin-top: 7px; 88 | border-bottom: solid 2px #8872b6; 89 | } 90 | 91 | body div#content article h3 { 92 | font-size: 14px; 93 | font-weight: 400; 94 | text-transform: uppercase; 95 | color: #8872b6; 96 | margin-top: 40px; 97 | } 98 | 99 | body div#content div.method:nth-of-type(1) { 100 | border-top: none; 101 | } 102 | 103 | body div#content div.method { 104 | border-top: solid 1px #8872b6; 105 | padding-top: 20px; 106 | } 107 | 108 | body div#content div.method p, 109 | body div#content div.intro p { 110 | color: white; 111 | font-weight: 300; 112 | font-size: 14px; 113 | } 114 | 115 | body div#content div.method.private { 116 | visibility: hidden; 117 | height: 0px; 118 | } 119 | 120 | body div#content div.method strong.name { 121 | color: white; 122 | font-weight: 700; 123 | font-size: 18px; 124 | } 125 | 126 | 127 | body div#content div.method strong { 128 | color: #b6a7d4; 129 | font-weight: 500; 130 | font-size: 16px; 131 | } 132 | 133 | body div#content span.method-parameters { 134 | color: white; 135 | font-style: italic; 136 | font-weight: 300; 137 | } 138 | 139 | body div#classdocs > ul { 140 | padding: 0px; 141 | height: 40px; 142 | line-height: 40px; 143 | .gradient(); 144 | border: solid 2px #8872b6; 145 | } 146 | 147 | body div#classdocs > ul li { 148 | padding: 0px; 149 | height: 40px; 150 | line-height: 40px; 151 | padding-left: 15px; 152 | padding-right: 15px; 153 | border-right: solid 2px #8872b6; 154 | } 155 | 156 | body div#classdocs > ul li a { 157 | padding: 0px; 158 | height: 40px; 159 | line-height: 40px; 160 | text-transform: uppercase; 161 | } -------------------------------------------------------------------------------- /src/core/api.js: -------------------------------------------------------------------------------- 1 | var _mapProperty = function (property, key, value) { 2 | if ('undefined' === typeof key) { 3 | return this.get(property); 4 | } else if ('undefined' === typeof value && 'string' === typeof key) { 5 | return this.get(property)[key]; 6 | } else if ('undefined' === typeof value && 'object' === typeof key) { 7 | if (key.baseUrl) { 8 | _baseUrlValidation(key.baseUrl); 9 | } 10 | this.set(property, $.extend({}, this.get(property), key)); 11 | } else { 12 | if ('baseUrl' === key) { 13 | _baseUrlValidation(value); 14 | } 15 | this.get(property)[key] = value; 16 | return value; 17 | } 18 | }, 19 | _propertyWrapper = function (property, value) { 20 | if ('undefined' === typeof value) { 21 | return this.get(property); 22 | } else { 23 | this.set(property, value); 24 | return value; 25 | } 26 | }, 27 | _baseUrlValidation = function (value) { 28 | var validScheme = ['http://', 'https://'].map(function (e) { 29 | if (value) { 30 | return value.indexOf(e) === 0; 31 | } 32 | return false; 33 | }).reduce(function (x,y) { 34 | return x || y; 35 | }, false); 36 | 37 | if (value && typeof value === 'string' && validScheme) { 38 | _baseUrl = value; 39 | return value; 40 | } 41 | 42 | throw 'Protocol "' + value + '" not supported.'; 43 | }; 44 | 45 | /** 46 | @namespace Milo 47 | @module milo-core 48 | @class API 49 | @extends {Ember.Namespace} 50 | */ 51 | Milo.API = Em.Namespace.extend({ 52 | _options: null, 53 | _headers: null, 54 | _queryParams: null, 55 | _adapter: null, 56 | _serializer: null, 57 | 58 | init: function () { 59 | this._super(); 60 | this.set('_options', {}); 61 | this.set('_headers', {}); 62 | this.set('_queryParams', {}); 63 | this.set('_adapter', Milo.DefaultAdapter.create()); 64 | this.set('_serializer', Milo.DefaultSerializer.create()); 65 | }, 66 | 67 | /** 68 | @method options 69 | @param {String} key 70 | @param {String} value 71 | */ 72 | options: function (key, value) { 73 | return _mapProperty.bind(this)('_options', key, value); 74 | }, 75 | 76 | /** 77 | @method headers 78 | @param {String} key 79 | @param {String} value 80 | */ 81 | headers: function (key, value) { 82 | return _mapProperty.bind(this)('_headers', key, value); 83 | }, 84 | 85 | /** 86 | @method queryParams 87 | @param {String} key 88 | @param {String} value 89 | */ 90 | queryParams: function (key, value) { 91 | return _mapProperty.bind(this)('_queryParams', key, value); 92 | }, 93 | 94 | /** 95 | @method adapter 96 | @param {String} value 97 | */ 98 | adapter: function (value) { 99 | return _propertyWrapper.bind(this)('_adapter', value); 100 | }, 101 | 102 | /** 103 | @method serializer 104 | @param {String} value 105 | */ 106 | serializer: function (value) { 107 | return _propertyWrapper.bind(this)('_serializer', value); 108 | } 109 | }); 110 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readJSON('package.json'), 4 | 5 | dirs: { 6 | src: ['src/namespace.js', 7 | 'src/core/options.js', 8 | 'src/dsl/uriTemplates.js', 9 | 'src/dsl/property.js', 10 | 'src/core/deferred.js', 11 | 'src/core/proxy.js', 12 | 'src/core/arrayProxy.js', 13 | 'src/adapters/defaultAdapter.js', 14 | 'src/adapters/defaultSerializer.js', 15 | 'src/core/queryable.js', 16 | 'src/model.js', 17 | 'src/core/api.js'] 18 | }, 19 | 20 | clean: ['website/stylesheets', 'website/api', 'dist', 'sample-ui/vendor/scripts/<%= pkg.name %>.js'], 21 | 22 | concat: { 23 | dist: { 24 | src: '<%= dirs.src %>', 25 | dest: 'dist/<%= pkg.name %>.js' 26 | }, 27 | sentToExample: { 28 | src: '<%= dirs.src %>', 29 | dest: 'sample-ui/vendor/scripts/<%= pkg.name %>.js' 30 | } 31 | }, 32 | 33 | uglify: { 34 | distMin: { 35 | options: { 36 | report: 'gzip' 37 | }, 38 | files: { 39 | 'dist/<%= pkg.name %>.min.js': '<%= dirs.src %>' 40 | } 41 | } 42 | }, 43 | less: { 44 | development: { 45 | options: { 46 | paths: ['website/less'] 47 | 48 | }, 49 | files: { 50 | "website/stylesheets/milo.css": "website/less/milo.less" 51 | } 52 | } 53 | }, 54 | yuidoc: { 55 | compile: { 56 | name: '<%= pkg.name %>', 57 | version: '<%= pkg.version %>', 58 | options: { 59 | paths: 'src', 60 | outdir: 'website/api', 61 | themedir: 'website/theme', 62 | "no-code": true 63 | } 64 | } 65 | }, 66 | jshint: { 67 | all: ['src/**/*.js'] 68 | }, 69 | mocha_phantomjs: { 70 | all: ['test/**/*.html'] 71 | }, 72 | watch: { 73 | javascript: { 74 | files: ['src/**/*.js', 'test/*.js'], 75 | tasks: ['concat', 'jshint', 'mocha_phantomjs'] 76 | }, 77 | stylesheets: { 78 | files: ['website/less/*.less'], 79 | tasks: ['less'] 80 | }, 81 | theme: { 82 | files: ['website/theme/layouts/*.handlebars', 'website/theme/partials/*.handlebars'], 83 | tasks: ['yuidoc', 'string-replace'] 84 | } 85 | }, 86 | connect: { 87 | server: { 88 | options: { 89 | port: 9001, 90 | base: 'website' 91 | } 92 | } 93 | }, 94 | zip: { 95 | dist: { 96 | src: ['dist/<%= pkg.name %>.min.js'], 97 | dest: 'dist/<%= pkg.name %> - <%= pkg.version %>.zip' 98 | } 99 | }, 100 | 'string-replace': { 101 | prettyPrintWithRainbow: { 102 | options: { 103 | replacements: [{ 104 | pattern: /\
/ig,
105 |             replacement: '
'
106 |           }, {
107 |             pattern: /\/ig,
108 |             replacement: ''
109 |           }]
110 |         },
111 |         files: {
112 |           './': 'website/api/classes/*.html'
113 |         }
114 |       }
115 |     }
116 |   });
117 | 
118 |   grunt.loadNpmTasks('grunt-mocha-phantomjs');
119 |   grunt.loadNpmTasks('grunt-contrib-watch');
120 |   grunt.loadNpmTasks('grunt-contrib-concat');
121 |   grunt.loadNpmTasks('grunt-contrib-jshint');
122 |   grunt.loadNpmTasks('grunt-contrib-uglify');
123 |   grunt.loadNpmTasks('grunt-contrib-less');
124 |   grunt.loadNpmTasks('grunt-contrib-yuidoc');
125 |   grunt.loadNpmTasks('grunt-contrib-clean');
126 |   grunt.loadNpmTasks('grunt-contrib-connect');
127 |   grunt.loadNpmTasks('grunt-string-replace');
128 |   grunt.loadNpmTasks('grunt-zip');
129 | 
130 |   grunt.registerTask('dist', ['clean', 'concat', 'uglify', 'zip']);
131 |   grunt.registerTask('test', ['clean', 'jshint', 'concat', 'mocha_phantomjs']);
132 |   grunt.registerTask('doc', ['clean', 'less', 'yuidoc']);
133 |   grunt.registerTask('doc-server', ['clean', 'less', 'yuidoc', 'string-replace', 'connect', 'watch']);
134 |   grunt.registerTask('ci', ['test', 'dist', 'doc']);
135 | };


--------------------------------------------------------------------------------
/samples/sample-ui/test/vendor/scripts/sinon-chai-2.2.0.js:
--------------------------------------------------------------------------------
  1 | (function (sinonChai) {
  2 |     "use strict";
  3 | 
  4 |     // Module systems magic dance.
  5 | 
  6 |     if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
  7 |         // NodeJS
  8 |         module.exports = sinonChai;
  9 |     } else if (typeof define === "function" && define.amd) {
 10 |         // AMD
 11 |         define(function () {
 12 |             return sinonChai;
 13 |         });
 14 |     } else {
 15 |         // Other environment (usually 
154 |             
155 |         
156 |     
157 | 
158 | 


--------------------------------------------------------------------------------
/src/adapters/defaultSerializer.js:
--------------------------------------------------------------------------------
  1 | var _defaultSerializer = {
  2 |         serialize: function (value) {
  3 |             return value;
  4 |         },
  5 | 
  6 |         deserialize: function (value) {
  7 |             return value;
  8 |         }
  9 |     },
 10 |     _booleanSerializer = {
 11 |         serialize: function (value) {
 12 |             return value ? true : false;
 13 |         },
 14 | 
 15 |         deserialize: function (value) {
 16 |             return value ? true : false;
 17 |         }
 18 |     },
 19 |     _numberSerializer = {
 20 |         serialize: function (value) {
 21 |             var numeric = parseFloat(value);
 22 |             return isNaN(numeric) ? 0 : numeric;
 23 |         },
 24 | 
 25 |         deserialize: function (value) {
 26 |             var numeric = parseFloat(value);
 27 |             return isNaN(numeric) ? 0 : numeric;
 28 |         }
 29 |     };
 30 | 
 31 | /**
 32 |     @namespace Milo
 33 |     @module milo-adapters
 34 |     @class DefaultSerializer
 35 |     @extends {Ember.Object}
 36 | */
 37 | Milo.DefaultSerializer = Em.Object.extend({
 38 |     serializerCache: null,
 39 | 
 40 |     init: function () {
 41 |         var cache = Em.Map.create();
 42 | 
 43 |         cache.set('string', _defaultSerializer);
 44 |         cache.set('object', _defaultSerializer);
 45 |         cache.set('boolean', _booleanSerializer);
 46 |         cache.set('array', _defaultSerializer);
 47 |         cache.set('number', _numberSerializer);
 48 | 
 49 |         this.set('serializerCache', cache);
 50 |     },
 51 | 
 52 |     /**
 53 |         @method serializeFor
 54 |         @param {String} modelClass
 55 |     */
 56 |     serializerFor: function (modelClass) {
 57 |         var cache = this.get('serializerCache'),
 58 |             serializer = cache.get(modelClass);
 59 | 
 60 |         if (!serializer) {
 61 |             modelClass = (typeof(modelClass) === 'string') ? Em.get(modelClass) : modelClass;
 62 |             serializer = this._buildSerializer(modelClass);
 63 |             cache.set(modelClass, serializer);
 64 |         }
 65 | 
 66 |         return serializer;
 67 |     },
 68 | 
 69 |     /**
 70 |         @method _buildSerializer
 71 |         @private
 72 |     */
 73 |     _buildSerializer: function (modelClass) {
 74 |         var properties = [],
 75 |             propertyNamesForPost = [],
 76 |             propertyNamesForPut = [],
 77 |             serializer = {},
 78 |             that = this;
 79 | 
 80 |         modelClass.eachComputedProperty(function (propertyName) {
 81 |             var propertyMetadata = modelClass.metaForProperty(propertyName);
 82 | 
 83 |             if (propertyMetadata.type && propertyMetadata.embedded) {
 84 |                 properties.push($.extend({ name: propertyName }, propertyMetadata));
 85 |             }
 86 |         });
 87 | 
 88 |         properties.forEach(function (property) {
 89 |             if (property.operations.contains('post')) {
 90 |                 propertyNamesForPost.push(property.name);
 91 |             }
 92 | 
 93 |             if (property.operations.contains('put')) {
 94 |                 propertyNamesForPut.push(property.name);
 95 |             }
 96 |         });
 97 | 
 98 |         serializer.serialize = modelClass.serialize || function (model, method) {
 99 |             var serialized = (method || 'post').toLower() === 'post' ?
100 |                 model.getProperties(propertyNamesForPost) : model.getProperties(propertyNamesForPut);
101 | 
102 |             properties.forEach(function (property) {
103 |                 if (serialized[property.name] === undefined) {
104 |                     serialized[property.name] = property.defaultValue;
105 |                 } else {
106 |                     if (property.occurrences === "one") {
107 |                         serialized[property.name] = that.serializerFor(property.type).serialize(model.get(property.name), method || 'post');
108 |                     } else {
109 |                         serialized[property.name] = Em.A();
110 |                         (model.get(property.name) || []).forEach(function (item) {
111 |                             serialized[property.name].pushObject(that.serializerFor(property.type).serialize(item, method || 'post'));
112 |                         });
113 |                     }
114 |                 }
115 |             });
116 | 
117 |             return serialized;
118 |         };
119 | 
120 |         serializer.deserialize = modelClass.deserialize || function (json) {
121 |             var model = modelClass.create();
122 | 
123 |             properties.forEach(function (property) {
124 |                 if (json[property.name] === undefined) {
125 |                     model.set(property.name, property.defaultValue);
126 |                 } else {
127 |                     if (property.occurrences === 'one') {
128 |                         model.set(property.name, that.serializerFor(property.type).deserialize(json[property.name]));
129 |                     } else {
130 |                         model.set(property.name, Em.A());
131 |                         (json[property.name] || []).forEach(function (item) {
132 |                             model.get(property.name).pushObject(that.serializerFor(property.type).deserialize(item));
133 |                         });
134 |                     }
135 |                 }
136 |             });
137 | 
138 |             return model;
139 |         };
140 | 
141 |         return serializer;
142 |     }
143 | });
144 | 


--------------------------------------------------------------------------------
/website/less/milo.less:
--------------------------------------------------------------------------------
  1 | // Base Imports
  2 | @import "normalize.less";
  3 | @import "preboot.less";
  4 | @import "ubuntu.less";
  5 | @import "batch.less";
  6 | @import "colors.less";
  7 | @import "syntax.less";
  8 | @import "yuidoc.less";
  9 | 
 10 | .gradient() {
 11 |   // 0%   c1bfea
 12 |   // 31%  c4c1ea
 13 |   // 49%  cec7ec
 14 |   // 50%  d8d0ef
 15 |   // 79%  e2def5
 16 |   // 100% ebe9f9
 17 | 
 18 |   background-image: linear-gradient(bottom, rgb(193,191,234) 0%, rgb(195,193,234) 31%, rgb(206,199,236) 49%, rgb(216,208,239) 50%, rgb(226,222,245) 79%, rgb(235,233,249) 100%);
 19 |   background-image: -o-linear-gradient(bottom, rgb(193,191,234) 0%, rgb(195,193,234) 31%, rgb(206,199,236) 49%, rgb(216,208,239) 50%, rgb(226,222,245) 79%, rgb(235,233,249) 100%);
 20 |   background-image: -moz-linear-gradient(bottom, rgb(193,191,234) 0%, rgb(195,193,234) 31%, rgb(206,199,236) 49%, rgb(216,208,239) 50%, rgb(226,222,245) 79%, rgb(235,233,249) 100%);
 21 |   background-image: -webkit-linear-gradient(bottom, rgb(193,191,234) 0%, rgb(195,193,234) 31%, rgb(206,199,236) 49%, rgb(216,208,239) 50%, rgb(226,222,245) 79%, rgb(235,233,249) 100%);
 22 |   background-image: -ms-linear-gradient(bottom, rgb(193,191,234) 0%, rgb(195,193,234) 31%, rgb(206,199,236) 49%, rgb(216,208,239) 50%, rgb(226,222,245) 79%, rgb(235,233,249) 100%);
 23 | 
 24 |   background-image: -webkit-gradient(
 25 |   	linear,
 26 |   	left bottom,
 27 |   	left top,
 28 |   	color-stop(0, rgb(193,191,234)),
 29 |   	color-stop(0.31, rgb(195,193,234)),
 30 |   	color-stop(0.49, rgb(206,199,236)),
 31 |   	color-stop(0.5, rgb(216,208,239)),
 32 |   	color-stop(0.79, rgb(226,222,245)),
 33 |   	color-stop(1, rgb(235,233,249))
 34 |   );  
 35 | }
 36 | 
 37 | body {
 38 |   font-family: Ubuntu;
 39 |   margin-top: 60px;
 40 | }
 41 | 
 42 | body header {
 43 |   .make-row();
 44 |   margin: auto;
 45 |   width: 960px;
 46 |   max-width: 960px;
 47 | }
 48 | 
 49 | body header h1 {
 50 |   .make-column(6);
 51 | }
 52 | 
 53 | body header nav {
 54 |   .make-column(6);
 55 |   text-align: center;
 56 | }
 57 | 
 58 | body header nav a {
 59 |   color: @violet;
 60 |   font-size: 22px;
 61 |   text-decoration: none;
 62 |   text-transform: uppercase;
 63 |   font-weight: 500;
 64 |   line-height: 90px;
 65 |   padding-right: 40px;
 66 | }
 67 | 
 68 | body header nav a:nth-last-of-type(1) {
 69 |   padding-right: 0px;
 70 | }
 71 | 
 72 | body header div#title {
 73 |   .make-row();
 74 | }
 75 | 
 76 | body header div#title h1 {
 77 |   .make-column(6);
 78 |   .make-column-offset(6);
 79 |   padding-top: 30px;
 80 |   text-align: center;
 81 |   font-size: 65px;
 82 |   text-decoration: none;
 83 |   letter-spacing: -3px;
 84 |   margin-bottom: 10px;
 85 | }
 86 | 
 87 | body div#slogan {
 88 |   .make-row();
 89 |   height: 370px;
 90 |   background-color: @violet;
 91 | }
 92 | 
 93 | body div#slogan > div {
 94 |   .make-row();
 95 |   margin: auto;
 96 |   width: 960px;
 97 |   max-width: 960px;  
 98 | }
 99 | 
100 | body div#slogan > div pre {
101 |   position: absolute;
102 |   top: 60px;
103 |   background-color: @darkViolet;  
104 |   width: 440px;
105 |   height: 700px;
106 |   .border-top-radius(10px);
107 |   .border-bottom-radius(10px);
108 | }
109 | 
110 | body div#slogan > div img {
111 |   position: absolute;
112 |   top: 180px;
113 |   margin-left: 440px;
114 |   z-index: 1;
115 | }
116 | 
117 | body div#slogan > div h2 {
118 |   color: white;
119 |   text-align: center;
120 |   font-weight: 200;
121 |   font-style: italic;
122 |   line-height: 55px;
123 |   font-size: 28px;
124 |   .make-column(6);
125 |   .make-column-offset(6);  
126 | }
127 | 
128 | body div#slogan > div > div {
129 |   .make-column(6);
130 |   .make-column-offset(6);
131 |   text-align: center;
132 | }
133 | 
134 | body div#slogan > div > div a:nth-last-of-type(1) {
135 |   margin-right: 0px;
136 | }
137 | 
138 | body div#slogan > div > div a {
139 |   height: 55px;
140 |   display: inline-block;
141 |   border: solid 3px #8872b6;
142 |   text-decoration: none;
143 |   padding-top: 5px;
144 |   padding-bottom: 5px;
145 |   padding-left: 15px;
146 |   padding-right: 15px;
147 |   margin-right: 20px;
148 |   margin-top: 30px;
149 |   .border-top-radius(15px);
150 |   .border-bottom-radius(15px);
151 |   .gradient();
152 | }
153 | 
154 | body div#slogan > div > div a i {
155 |   height: 55px;
156 |   line-height: 55px;
157 |   display: inline-block;
158 |   color: #4a3b6a;
159 | }
160 | 
161 | body div#slogan > div > div a span {
162 |   height: 55px;
163 |   line-height: 15px;
164 |   letter-spacing: -1px;
165 |   padding-top: 10px;
166 |   text-transform: uppercase;
167 |   display: inline-block;
168 |   color: #4a3b6a;
169 |   font-size: 23px;
170 |   font-weight: 500;
171 |   padding-left: 10px;
172 |   text-align: left;
173 | }
174 | 
175 | body div#slogan > div > div a span em {
176 |   font-size: 12px;
177 |   font-style: normal;
178 |   color: #8971bc;
179 |   letter-spacing: normal;
180 |   text-transform: lowercase;
181 | }
182 | 
183 | body article#main {
184 |   .make-row();
185 |   margin-left: auto;
186 |   margin-right: auto;
187 |   margin-top: 170px;
188 |   width: 960px;
189 |   max-width: 960px;  
190 | }
191 | 
192 | body article section {
193 |   .make-column(4); 
194 | }
195 | 
196 | body article section h1 i {
197 |   color: @violet;
198 |   display: inline-block;
199 | }
200 | 
201 | body article section h1 span {
202 |   font-size: 22px;
203 |   line-height: 22px;
204 |   font-weight: 500;
205 |   display: inline-block;
206 |   vertical-align: bottom;
207 | }
208 | 
209 | body article section h1 span em {
210 |   font-size: 18px;
211 |   font-weight: 300;
212 |   font-style: normal;
213 |   color: @violet;
214 | }


--------------------------------------------------------------------------------
/src/core/queryable.js:
--------------------------------------------------------------------------------
  1 | /**
  2 | Some details about milo-adapters
  3 | 
  4 | @module milo-core
  5 | */
  6 | 
  7 | var _validateNumber = function (count) {
  8 |     if (typeof count !== 'number' || count < 0) {
  9 |         throw 'Invalid index "' + count + '"';
 10 |     }
 11 | };
 12 | 
 13 | var _validateString = function (fieldName) {
 14 |     if (typeof fieldName !== 'string' || fieldName === '') {
 15 |         throw 'Ordering field must be a valid string';
 16 |     }
 17 | };
 18 | 
 19 | 
 20 | /**
 21 |     Some details about Milo.Queryable
 22 | 
 23 |     @namespace Milo
 24 |     @module milo-core
 25 |     @class Queryable
 26 |     @extends {Ember.Mixin}
 27 | */
 28 | Milo.Queryable = Em.Mixin.create({
 29 |     /**
 30 |         Adds 'asc' param to the request url
 31 |         @method orderBy
 32 |         @param {String} fieldname
 33 |         @chainable
 34 |         @return {Milo.Queryable}
 35 |         @example
 36 |             Hollywood.Actor.orderBy('name').toArray();
 37 |     */
 38 |     orderBy: function (field) {
 39 |         _validateString(field);
 40 |         this.set('orderByClause', {
 41 |             orderBy: field,
 42 |             order: 'asc'
 43 |         });
 44 | 
 45 |         return this;
 46 |     },
 47 | 
 48 |     /**
 49 |         Adds 'desc' param to the request url
 50 | 
 51 |         @method orderByDescending 
 52 |         @param {String} fieldname
 53 |         @chainable
 54 |         @return {Milo.Queryable}
 55 |         @example
 56 |             Hollywood.Actor.orderByDescending('name').toArray();
 57 |     */
 58 |     orderByDescending: function (field) {
 59 |         _validateString(field);
 60 |         this.set('orderByClause', {
 61 |             orderBy: field,
 62 |             order: 'desc'
 63 |         });
 64 | 
 65 |         return this;
 66 |     },
 67 | 
 68 |     /** 
 69 |         Adds 'limit' param to the request url
 70 | 
 71 |         @method take
 72 |         @param {Number} count
 73 |         @chainable
 74 |         @return {Milo.Queryable}
 75 |         @example
 76 |             Hollywood.Actor.take(3).toArray();
 77 |     */
 78 |     take: function (count) {
 79 |         _validateNumber(count);
 80 |         this.set('takeClause', {
 81 |             limit: count
 82 |         });
 83 | 
 84 |         return this;
 85 |     },
 86 | 
 87 |     /** 
 88 |         Adds 'offset' param to the request url
 89 | 
 90 |         @method skip
 91 |         @param {Number} count
 92 |         @chainable
 93 |         @return {Milo.Queryable}
 94 |         @example
 95 |             Hollywood.Actor.skip(3).toArray();
 96 |     */
 97 |     skip: function (count) {
 98 |         _validateNumber(count);
 99 |         this.set('skipClause', {
100 |             offset: count
101 |         });
102 | 
103 |         return this;
104 |     },
105 | 
106 |     /** 
107 |         Adds the filter params to the request url
108 | 
109 |         @method find 
110 |         @param {Object} [clause]
111 |         @chainable
112 |         @return {Milo.Queryable}
113 |         @example
114 |             Hollywood.Actor.find({name: 'Robert De Niro'}).single();
115 |         @example
116 |             Hollywood.Actor.find().toArray();
117 |     */
118 |     find: function (clause) {
119 |         this.set('anyClause', $.extend({}, this.get('anyClause'), clause));
120 | 
121 |         return this;
122 |     },
123 | 
124 |     /** 
125 |         Executes a query expecting to get a single element as a result, if not it will throw an exception
126 | 
127 |         @method single
128 |         @return {Milo.Queryable}
129 |         @example
130 |             Hollywood.Actor.find().single();
131 |     */
132 |     single: function () {
133 |         return this._materialize(this.constructor, Milo.Proxy, function (deserialized) {
134 |             return Em.isArray(deserialized) ? deserialized[0] : deserialized;
135 |         });
136 |     },
137 | 
138 |     /** 
139 |         Executes a query expecting to get an array of element as a result
140 | 
141 |         @method toArray 
142 |         @return {Milo.Queryable}
143 |         @example
144 |             Hollywood.Actor.find().toArray();
145 |     */
146 |     toArray: function () {
147 |         return this._materialize(this.constructor, Milo.ArrayProxy, function (deserialized) {
148 |             return Em.isArray(deserialized) ? deserialized : Em.A([deserialized]);
149 |         });
150 |     },
151 | 
152 |     _getModelClass: function () {
153 |         // XXX Hack that assumes that this is a Model Class
154 |         return this._getAPI(this);
155 |     },
156 | 
157 |     /**
158 |     @method _extractParameters
159 |     @private
160 |     */
161 |     _extractParameters: function () {
162 |         var params = [];
163 |         
164 |         // TODO Fail earlier if Model Class is invalid
165 |         if (!this._getModelClass() || !this._getModelClass().options) {
166 |             throw 'Entity was created from a Milo.API instance not registered as a global';
167 |         }
168 | 
169 |         params.push(this.get('anyClause'));
170 |         params.push(this.get('orderByClause'));
171 |         params.push(this.get('takeClause'));
172 |         params.push(this.get('skipClause'));
173 | 
174 |         
175 |         params.push(this._getModelClass().options('auth'));
176 | 
177 |         return $.extend.apply(null, [{}].concat(params));
178 |     },
179 | 
180 |     /**
181 |     @method _materialize
182 |     @private
183 |     */
184 |     _materialize: function (modelClass, proxyClass, extractContentFromDeserialized) {
185 |         var params = this._extractParameters(),
186 |             proxy = proxyClass.create({
187 |                 isLoading: true,
188 |                 errors: null,
189 |                 deferred: $.Deferred()
190 |             }), apiFromModelClass = this._getModelClass();
191 | 
192 |         apiFromModelClass.adapter().query(modelClass, params)
193 |             .always(function () {
194 |             proxy.set('isLoading', false);
195 |         })
196 |             .fail(function (errors) {
197 |             proxy.set('errors', errors);
198 |             proxy.set('isError', true);
199 |             proxy.get('deferred').reject(errors);
200 |         })
201 |             .done(function (deserialized) {
202 |             proxy.set('content', extractContentFromDeserialized(deserialized));
203 |             proxy.get('deferred').resolve(proxy);
204 |         });
205 | 
206 |         return proxy;
207 |     }
208 | });
209 | 


--------------------------------------------------------------------------------
/test/core.js:
--------------------------------------------------------------------------------
  1 | chai.should();
  2 | var expect = chai.expect;
  3 | 
  4 | // XXX Behaviour when trying to do queries without setting baseUrl
  5 | 
  6 | describe('Core', function () {
  7 |     describe('initialization', function () {
  8 |         var API;
  9 | 
 10 |         beforeEach(function () {
 11 |         });
 12 | 
 13 |         afterEach(function () {
 14 |             API = undefined;
 15 |         });
 16 | 
 17 |         it('should fail if destiny variable is not global (window)', function () {
 18 |             var API2 = Milo.API.create();
 19 | 
 20 |             API2.Dog = Milo.Model.extend({
 21 |                 name: Milo.property('string')
 22 |             });
 23 | 
 24 |             (function () {
 25 |                 API2.Dog.find().single();
 26 |             }).should.Throw(/Milo.API instance not registered as a global/i);
 27 |         });
 28 | 
 29 |         it('should create valid milo namespace', function () {
 30 |             // Act
 31 |             API = Milo.API.create();
 32 | 
 33 |             // Assert
 34 |             API.should.be.ok;
 35 |             API.should.have.property('options');
 36 |             API.should.have.property('headers');
 37 |             API.should.have.property('queryParams');
 38 |             API.isNamespace.should.be.true;
 39 |         });
 40 | 
 41 |         it('should allow to setup baseUrl as property', function () {
 42 |             // Arrange
 43 |             var baseUrlV1 = 'http://myserver/api/v1',
 44 |                 baseUrlV2 = 'http://myserver/api/v2';
 45 | 
 46 |             API = Milo.API.create();
 47 | 
 48 |             // Act
 49 |             API.options('baseUrl', baseUrlV1);
 50 | 
 51 |             // Assert
 52 |             API.options('baseUrl').should.be.equal(baseUrlV1);
 53 |         });
 54 | 
 55 |         it('should allow to setup baseUrl as hashmap', function () {
 56 |             // Arrange
 57 |             var baseUrlV1 = 'http://myserver/api/v1',
 58 |                 baseUrlV2 = 'http://myserver/api/v2';
 59 | 
 60 |             API = Milo.API.create();
 61 | 
 62 |             // Act
 63 |             API.options({ baseUrl: baseUrlV2 });
 64 | 
 65 |             // Assert
 66 |             API.options('baseUrl').should.be.equal(baseUrlV2);
 67 |         });
 68 | 
 69 | 
 70 |         it('should allow to setup an apiKey as property', function () {
 71 |             // Arrange
 72 |             var apiKeyV1 = 'my_apu_key_v1',
 73 |                 apiKeyV2 = 'my_apu_key_v2';
 74 | 
 75 |             API = Milo.API.create();
 76 | 
 77 |             // Act
 78 |             API.queryParams('apiKey', apiKeyV1);
 79 | 
 80 |             // Assert
 81 |             API.queryParams('apiKey').should.be.equal(apiKeyV1);
 82 |         });
 83 | 
 84 |         it('should allow to setup an apiKey as hashmap', function () {
 85 |             // Arrange
 86 |             var apiKeyV1 = 'my_apu_key_v1',
 87 |                 apiKeyV2 = 'my_apu_key_v2';
 88 | 
 89 |             API = Milo.API.create();
 90 | 
 91 |             // Act
 92 |             API.queryParams({ apiKey: apiKeyV2 });
 93 | 
 94 |             // Assert
 95 |             API.queryParams('apiKey').should.be.equal(apiKeyV2);
 96 |         });
 97 | 
 98 |         it('should return all query params', function () {
 99 |             // Arrange
100 |             var param1 = 'param1',
101 |                 param2 = 'param2';
102 | 
103 |             API = Milo.API.create();
104 | 
105 |             // Act
106 |             API.queryParams({ param1: param1, param2: param2 });
107 | 
108 |             // Assert
109 |             API.queryParams().param1.should.be.equal(param1);
110 |             API.queryParams().param2.should.be.equal(param2);
111 |         });
112 | 
113 |         it('should have a default adapter', function () {
114 |             // Act
115 |             API = Milo.API.create();
116 | 
117 |             // Assert
118 |             API.adapter().should.be.ok;
119 |         });
120 | 
121 |         it('should have a default serializer', function () {
122 |             // Act
123 |             API = Milo.API.create();
124 | 
125 |             // Assert
126 |             API.serializer().should.be.ok;
127 |         });
128 | 
129 |         it('should allow to setup an adapter', function () {
130 |             // Arrange
131 |             var mockAdapter = { mock: true };
132 | 
133 |             API = Milo.API.create();
134 | 
135 |             // Act
136 |             API.adapter(mockAdapter);
137 | 
138 |             // Assert
139 |             API.adapter().should.be.equal(mockAdapter);
140 |         });
141 | 
142 |         it('should allow to setup a serializer', function () {
143 |             // Arrange
144 |             var mockSerializer = { mock: true };
145 | 
146 |             API = Milo.API.create();
147 | 
148 |             // Act
149 |             API.serializer(mockSerializer);
150 | 
151 |             // Assert
152 |             API.serializer().should.be.equal(mockSerializer);
153 |         });
154 |     });
155 |     
156 | 
157 |     describe('Options validation', function () {
158 |         var API, API2;
159 | 
160 |         beforeEach(function () {
161 |             API = Milo.API.create(); 
162 |             API2 = Milo.API.create();
163 |         });
164 | 
165 |         afterEach(function () {
166 |             API = undefined;
167 |             API2 = undefined;
168 |         });
169 | 
170 |         it.skip('should fail if baseUrl is set to undefined', function () {
171 |             // Arrange
172 |             var setToUndefined = function () { 
173 |                 API.options('baseUrl', undefined);
174 |             };
175 | 
176 |             // Act 
177 |             setToUndefined.
178 |             
179 |             // Assert
180 |             should.Throw(/not supported/i);
181 |         });
182 | 
183 |         it('should fail if protocol set to smtp', function () {
184 |             // Arrange
185 |             var smtp = function () {
186 |                 API.options('baseUrl', 'smtp://hello');
187 |             };
188 |             
189 |             // Act
190 |             smtp.
191 |             
192 |             // Assert
193 |             should.Throw(/smtp.*not supported/i);
194 |         });
195 |         
196 |         it('should fail if protocol set to unknown protocol', function () {
197 |             // Arrange
198 |             var unknown = function () {
199 |                 API.options('baseUrl', 'bye://hello');
200 |             };
201 |             
202 |             // Act
203 |             unknown.
204 |             
205 |             // Assert
206 |             should.Throw(/bye.* not supported/i);
207 |         });
208 | 
209 |         it('should work with http and https', function (done) {
210 |             // Arrange
211 |             var http = 'http://hello',  https = 'https://hello';
212 | 
213 |             // Act
214 |             API.options('baseUrl', http);
215 |             API2.options('baseUrl', https);
216 |             
217 |             // Assert
218 | 
219 |             // No exception should be thrown :)
220 |             done();
221 |         });
222 | 
223 |         it('should fail if baseUrl is set to undefined using a dictionary', function () {
224 |             // Arrange
225 |             var usingDictionary = function () {
226 |                 API.options({baseUrl: 'lala://', auth: 'my-token'});
227 |             };
228 | 
229 |             // Act 
230 |             usingDictionary.
231 |             
232 |             // Assert
233 |             should.Throw(/not supported/i);
234 |         });
235 |     });
236 | 
237 | });
238 | 


--------------------------------------------------------------------------------
/src/adapters/defaultAdapter.js:
--------------------------------------------------------------------------------
  1 | var _apiFromModelClass = function (modelClass) {
  2 |         var modelClassName = modelClass.toString();
  3 | 
  4 |         return Em.get(modelClassName.substring(0, modelClassName.indexOf('.')));
  5 |     };
  6 | 
  7 | /**
  8 |     @namespace Milo
  9 |     @module milo-adapters
 10 |     @class DefaultAdapter
 11 |     @extends {Ember.Object}
 12 | */
 13 | Milo.DefaultAdapter = Em.Object.extend({
 14 |     /**
 15 |         @method query
 16 |         @param {String} modelClass
 17 |         @param {Array} params
 18 |     */
 19 |     query: function (modelClass, params) {
 20 |         var api = _apiFromModelClass(modelClass),
 21 |             urlAndQueryParams = this._splitUrlAndDataParams(modelClass, params),
 22 |             resourceUrl = this._buildResourceUrl(modelClass, urlAndQueryParams.urlParams),
 23 |             url = api.options('baseUrl') + resourceUrl,
 24 |             queryParams = $.param(urlAndQueryParams.dataParams),
 25 |             method = 'GET',
 26 |             deferred = $.Deferred(),
 27 |             meta = { meta: urlAndQueryParams.urlParams },
 28 |             rootElement = modelClass.create().get('rootElement'),
 29 |             that = this;
 30 | 
 31 |         this._ajax(api, method, url + '?' + queryParams)
 32 |             .done(function (data) {
 33 |                 var root = data[rootElement],
 34 |                     deserialized;
 35 | 
 36 |                 if (root && Em.isArray(root)) {
 37 |                     deserialized = Em.A(root.map(function (dataRow) {
 38 |                         return that._deserialize(modelClass, $.extend({}, meta, { id: dataRow.id }, dataRow));
 39 |                     }));
 40 |                 } else {
 41 |                     deserialized = that._deserialize(modelClass, $.extend({}, meta, { id: data.id }, data));
 42 |                 }
 43 | 
 44 |                 deferred.resolve(deserialized);
 45 |             })
 46 |             .fail(function () {
 47 |                 console.log('failed');
 48 |                 deferred.reject(arguments);
 49 |             });
 50 | 
 51 |         return deferred.promise();
 52 |     },
 53 | 
 54 |     /**
 55 |         @method save
 56 |         @param {String} modelClass
 57 |         @param {Array} params
 58 |     */
 59 |     save: function (modelClass, model) {
 60 |         var api = _apiFromModelClass(modelClass),
 61 |             urlAndQueryParams = this._splitUrlAndDataParams(modelClass, model.get('meta')),
 62 |             resourceUrl = this._buildResourceUrl(modelClass, urlAndQueryParams.urlParams),
 63 |             url = api.options('baseUrl') + resourceUrl,
 64 |             queryParams = $.param($.extend({}, urlAndQueryParams.dataParams, api.queryParams())),
 65 |             method = model.get('isNew') ? 'post' : 'put',
 66 |             deferred = $.Deferred(),
 67 |             serialized;
 68 | 
 69 |         model.set('isSaving', true);
 70 |         serialized = this._serialize(modelClass, model, method);
 71 | 
 72 |         this._ajax(api, method, url + '?' + queryParams, serialized)
 73 |             .done(function (data) {
 74 |                 console.log('saved');
 75 |                 model.set('isDirty', false);
 76 |                 model.set('isNew', false);
 77 |                 deferred.resolve(model);
 78 |             })
 79 |             .fail(function () {
 80 |                 console.log('failed');
 81 |                 model.set('errors', arguments);
 82 |                 deferred.reject(arguments);
 83 |             })
 84 |             .always(function () {
 85 |                 model.set('isSaving', false);
 86 |             });
 87 | 
 88 |         return deferred.promise();
 89 |     },
 90 | 
 91 |     /**
 92 |         @method remove
 93 |         @param {String} modelClass
 94 |         @param {Array} params
 95 |     */
 96 |     remove: function (modelClass, model) {
 97 |         var api = _apiFromModelClass(modelClass),
 98 |             urlAndQueryParams = this._splitUrlAndDataParams(modelClass, model.get('meta')),
 99 |             resourceUrl = this._buildResourceUrl(modelClass, urlAndQueryParams.urlParams),
100 |             url = api.options('baseUrl') + resourceUrl,
101 |             queryParams = $.param($.extend({}, urlAndQueryParams.dataParams, api.queryParams())),
102 |             method = 'delete',
103 |             deferred = $.Deferred();
104 | 
105 |         model.set('isDeleting', true);
106 | 
107 |         this._ajax(api, method, url + '?' + queryParams)
108 |             .done(function (data) {
109 |                 console.log('saved');
110 |                 model.set('isDirty', false);
111 |                 model.set('isDeleted', true);
112 |                 deferred.resolve(model);
113 |             })
114 |             .fail(function () {
115 |                 console.log('failed');
116 |                 model.set('errors', arguments);
117 |                 deferred.reject(arguments);
118 |             })
119 |             .always(function () {
120 |                 model.set('isDeleting', false);
121 |             });
122 | 
123 |         return deferred.promise();
124 |     },
125 | 
126 |     /**
127 |         @method _serialize
128 |         @private
129 |     */
130 |     _serialize: function (modelClass, model, method) {
131 |         var api = _apiFromModelClass(modelClass),
132 |             serializer = api.serializer().serializerFor(modelClass);
133 | 
134 |         return serializer.serialize(model, method);
135 |     },
136 | 
137 |     /**
138 |         @method _deserialize
139 |         @private
140 |     */
141 |     _deserialize: function (modelClass, json) {
142 |         var api = _apiFromModelClass(modelClass),
143 |             serializer = api.serializer().serializerFor(modelClass);
144 | 
145 |         return serializer.deserialize(json);
146 |     },
147 | 
148 |     /**
149 |         @method _buildResourceUrl
150 |         @private
151 |     */
152 |     _buildResourceUrl: function (modelClass, urlParams) {
153 |         var uriTemplate = modelClass.create().get('uriTemplate');
154 |         if (!uriTemplate) {
155 |             throw 'Mandatory parameter uriTemplate not set in model "' + modelClass.toString() + '"';
156 |         }
157 | 
158 | 
159 |         var urlTerms = uriTemplate.split('/');
160 |             resourceUrl = uriTemplate;
161 | 
162 |         urlTerms.forEach(function (uriTerm) {
163 |             var fieldName = uriTerm.replace(':', '');
164 | 
165 |             if (uriTerm.indexOf(':') === 0) {
166 |                 resourceUrl = resourceUrl.replace(uriTerm, urlParams[fieldName] || '');
167 |             }
168 |         });
169 | 
170 |         return resourceUrl.replace(/\/$/g, '');
171 |     },
172 | 
173 |     /**
174 |         @method _splitUrlAndDataParams
175 |         @private
176 |     */
177 |     _splitUrlAndDataParams: function (modelClass, data) {
178 |         var uriTemplate = modelClass.create().get('uriTemplate');
179 |         if (!uriTemplate) {
180 |             throw 'Mandatory parameter uriTemplate not set in model "' + modelClass.toString() + '"';
181 |         }
182 | 
183 |         var urlTerms = uriTemplate.split('/'),
184 |             modelClassName = modelClass.toString(),
185 |             modelIdField = modelClassName.substring(modelClassName.indexOf('.') + 1).camelize() + 'Id',
186 |             urlParams = {},
187 |             dataParams = JSON.parse(JSON.stringify(data));
188 | 
189 |         urlTerms.forEach(function (uriTerm) {
190 |             var fieldName = uriTerm.replace(':', '');
191 | 
192 |             if (uriTerm.indexOf(':') === 0) {
193 | 
194 |                 if (dataParams[fieldName] !== undefined) {
195 |                     urlParams[fieldName] = dataParams[fieldName];
196 |                     delete dataParams[fieldName];
197 |                 }
198 | 
199 |                 if (fieldName === modelIdField && dataParams.id !== undefined) {
200 |                     urlParams[fieldName] = dataParams.id;
201 |                     delete dataParams.id;
202 |                 }
203 |             }
204 |         });
205 | 
206 |         return { urlParams: urlParams, dataParams: dataParams };
207 |     },
208 | 
209 |     /**
210 |         @method _ajax
211 |         @private
212 |     */
213 |     _ajax: function (api, method, url, data, cache) {
214 |         var cacheFlag = (method || 'GET') === 'GET' ? cache : true,
215 |             contentType = api.headers('Content-Type') || 'application/json';
216 | 
217 |         return jQuery.ajax({
218 |             contentType: contentType,
219 |             type: method || 'GET',
220 |             dataType: (method || 'GET') === 'GET' ? 'json' : 'text',
221 |             data: data ? JSON.stringify(data) : '',
222 |             url: url,
223 |             headers: {
224 |                 // XXX Unhardcode
225 |                 accept: 'application/vnd.mulesoft.habitat+json'
226 |             },
227 |             cache: cacheFlag
228 |         });
229 |     }
230 | });
231 | 


--------------------------------------------------------------------------------
/samples/sample/public/js/milo.js:
--------------------------------------------------------------------------------
  1 | window.Milo = Em.Namespace.create({
  2 |     revision: 1
  3 | });
  4 | /**
  5 | @module milo-core
  6 | */
  7 | 
  8 | 
  9 | var OptionsClass = Em.Object.extend({
 10 |     baseUrl: (function () {
 11 |         var _baseUrl;
 12 |         return function (key, value) {
 13 |             var validScheme = ['http://', 'https://'].map(function (e) {
 14 |                 if (value) {
 15 |                     return value.indexOf(e) === 0;
 16 |                 }
 17 |                 return false;
 18 |             }).reduce(function (x,y) {
 19 |                 return x || y;
 20 |             }, false);
 21 |             if (value && typeof value === 'string' && validScheme) {
 22 |                 _baseUrl = value;
 23 |                 return value;
 24 |             }
 25 | 
 26 |             throw 'Protocol "' + value + '" not supported.';
 27 |         }.property();
 28 |     })()
 29 | });
 30 | 
 31 | /**
 32 | @class Options
 33 | @namespace Milo
 34 | */
 35 | Milo.Options = OptionsClass.create({
 36 |     auth: null
 37 | });
 38 | 
 39 | Milo.UriTemplate = function (template, options) {
 40 |     options = options || {};
 41 | 
 42 |     return function () {
 43 |         return template;
 44 |     }.property().meta(options);
 45 | };
 46 | Milo.collection = function (type, options) {
 47 |     options = options || {};
 48 | 
 49 |     return Ember.computed(function (key, value, oldValue) {
 50 |         var parentName = this.constructor.toString(),
 51 |             periodIndex = parentName.indexOf('.'),
 52 |             param = '%@Id'.fmt(parentName.substring(periodIndex + 1, parentName.length)).camelize(),
 53 |             findParams = {};
 54 | 
 55 |         findParams[param] = this.get('id');
 56 | 
 57 |         return type.find(findParams).toArray();
 58 |     }).property().volatile().meta(options);
 59 | };
 60 | Milo.property = function (type, options) {
 61 |     options = options || {};
 62 | 
 63 |     return Ember.computed(function (key, value, oldValue) {
 64 |         var temp = this.get('data').findProperty('key', key);
 65 | 
 66 |         if (!temp) {
 67 |             temp = this.get('data').pushObject(Em.Object.create({
 68 |                 key: key,
 69 |                 value: value,
 70 |                 orig: value
 71 |             }));
 72 |         } else {
 73 |             temp.value = value;
 74 |         }
 75 | 
 76 |         if (oldValue) {
 77 |             this.set('isDirty', true);
 78 |         }
 79 | 
 80 |         return temp.value;
 81 |     }).property().meta(options);
 82 | };
 83 | /**
 84 | @module milo-core
 85 | */
 86 | 
 87 | /**
 88 | @class Deferred
 89 | @namespace Milo
 90 | */
 91 | Milo.Deferred = Em.Mixin.create({
 92 |   /**
 93 |     This method will be called when the  ...
 94 | 
 95 |     @method done
 96 |     @return {Milo.Deferred}
 97 |   */
 98 |   done: function (callback) {
 99 |     this.get('deferred').done(callback);
100 |     return this;
101 |   },
102 | 
103 |   /**
104 |     This method will be called when the  ...
105 | 
106 |     @method fail
107 |     @return {Milo.Deferred}
108 |   */
109 |   fail: function (callback) {
110 |     this.get('deferred').fail(callback);
111 |     return this;
112 |   }
113 | });
114 | /**
115 | @module milo-core
116 | */
117 | 
118 | /**
119 | @class Proxy
120 | @namespace Milo
121 | */
122 | Milo.Proxy = Em.ObjectProxy.extend(Milo.Deferred, {
123 | 	isLoading: null,
124 | 	rollback: function () {
125 | 		this.content.rollback();
126 | 	}
127 | });
128 | /**
129 | @module milo-core
130 | */
131 | 
132 | /**
133 | @class ArrayProxy
134 | @namespace Milo
135 | */
136 | Milo.ArrayProxy = Em.ArrayProxy.extend(Milo.Deferred, {
137 | 	isLoading: null
138 | });
139 | Milo.DefaultAdapter = Em.Mixin.create({
140 |     execQuery: function (proxy) {
141 |         var params,
142 |         url = Milo.Options.get('baseUrl').fmt(this.get('resourceUrl'));
143 | 
144 |         proxy.set('isLoading', true);
145 | 
146 |         params = $.param($.extend({},
147 |         this.get('orderByClause'),
148 |         this.get('anyClause'),
149 |         this.get('takeClause'),
150 |         Milo.Options.get('auth')));
151 | 
152 |         return $.get(url + '?' + params).always(function () {
153 |             proxy.set('isLoading', false);
154 |         });
155 |     },
156 | 
157 |     resourceUrl: function () {
158 |         var metaMap = {};
159 |         this.constructor.eachComputedProperty(function (key, value) {
160 |             metaMap[key] = value;
161 |         });
162 | 
163 |         if (!metaMap.uriTemplate) {
164 |             throw "Error: uriTemplate not set in model " + this.constructor;
165 |         }
166 | 
167 |         var meta = metaMap.uriTemplate, resourceUrl;
168 | 
169 |         if (meta.namedParams) {
170 |             var params = [];
171 | 
172 |             meta.namedParams.forEach(function (param) {
173 |                 if (this.get('anyClause')[param]) {
174 |                     params.push(this.get('anyClause')[param]);
175 | 
176 |                     this.get('meta')[param] = this.get('anyClause')[param];
177 | 
178 |                     delete this.get('anyClause')[param];
179 |                 }
180 |             }.bind(this));
181 | 
182 |             resourceUrl = Em.String.fmt(this.get('uriTemplate'), params);
183 |         } else if (this.get('anyClause').id) {
184 |             resourceUrl = this.get('uriTemplate').fmt(this.get('anyClause').id);
185 |             delete this.get('anyClause').id;
186 |         } else {
187 |             resourceUrl = this.get('uriTemplate').fmt();
188 |         }
189 | 
190 |         if (resourceUrl.charAt(resourceUrl.length - 1) === '/') {
191 |             resourceUrl = resourceUrl.substring(resourceUrl.length - 1, 0);
192 |         }
193 | 
194 |         return resourceUrl;
195 |     }.property().volatile()
196 | });
197 | 
198 | /**
199 | @module milo-core
200 | */
201 | 
202 | var _validateNumber = function (count) {
203 |     if (typeof count !== 'number' || count < 0) {
204 |         throw 'Invalid index "' + count + '"';
205 |     }
206 | };
207 | 
208 | var _validateString = function (fieldName) {
209 |     if (typeof fieldName !== 'string') {
210 |         throw 'Ordering field must be a valid string';
211 |     }
212 | };
213 | 
214 | 
215 | /**
216 | @class Queryable
217 | @namespace Milo
218 | */
219 | Milo.Queryable = Em.Mixin.create({
220 |     orderBy: function (field) {
221 |         _validateString(field);
222 |         this.set('orderByClause', {
223 |             orderBy: field,
224 |             order: 'asc'
225 |         });
226 | 
227 |         return this;
228 |     },
229 | 
230 |     orderByDescending: function (field) {
231 |         _validateString(field);
232 |         this.set('orderByClause', {
233 |             orderBy: field,
234 |             order: 'desc'
235 |         });
236 | 
237 |         return this;
238 |     },
239 | 
240 |     take: function (count) {
241 |         _validateNumber(count);
242 |         this.set('takeClause', {
243 |             limit: count
244 |         });
245 | 
246 |         return this;
247 |     },
248 | 
249 |     skip: function (count) {
250 |         _validateNumber(count);
251 |         this.set('skipClause', {
252 |             offset: count
253 |         });
254 | 
255 |         return this;
256 |     },
257 | 
258 |     find: function (clause) {
259 |         this.set('anyClause', $.extend({}, this.get('anyClause'), clause));
260 | 
261 |         return this;
262 |     },
263 | 
264 |     single: function () {
265 |         var proxy = Milo.Proxy.create({
266 |             deferred: $.Deferred()
267 |         });
268 | 
269 |         this.execQuery(proxy).done(function (data) {
270 |             var content = this.constructor.create($.extend({}, this.get('meta'), data));
271 |             //// TODO: Throw an exception if data.lenght > 1
272 |             proxy.set('content', content);
273 | 
274 |             proxy.get('deferred').resolve(proxy);
275 |         }.bind(this));
276 | 
277 |         return proxy;
278 |     },
279 | 
280 |     toArray: function () {
281 |         var results = Em.A(),
282 |             proxy = Milo.ArrayProxy.create({
283 |                 deferred: $.Deferred()
284 |             });
285 | 
286 |         this.execQuery(proxy).done(function (data) {
287 |             data[this.get('rootElement')].forEach(function (value) {
288 |                 var result = this.constructor.create($.extend({}, this.get('meta'), value));
289 | 
290 |                 results.pushObject(result);
291 |             }.bind(this));
292 | 
293 |             proxy.set('content', results);
294 | 
295 |             proxy.get('deferred').resolve(proxy);
296 |         }.bind(this));
297 | 
298 |         return proxy;
299 |     }
300 | });
301 | 
302 | Milo.Model = Em.Object.extend(Milo.Queryable, Milo.DefaultAdapter, {
303 |     meta: {},
304 | 
305 |     data: function () {
306 |         if (!this.get('_data')) {
307 |             this.set('_data', Em.A());
308 |         }
309 | 
310 |         return this.get('_data');
311 |     }.property(),
312 | 
313 |     rollback: function () {
314 |         this.get('data').forEach(function (element) {
315 |             this.set(element.key, element.orig);
316 |         }.bind(this));
317 |     }
318 | });
319 | 
320 | Milo.Model.reopenClass({
321 |     find: function (clause) {
322 |         return this.create().find(clause);
323 |     }
324 | });


--------------------------------------------------------------------------------
/website/less/normalize.less:
--------------------------------------------------------------------------------
  1 | /*! normalize.css v2.1.0 | MIT License | git.io/normalize */
  2 | 
  3 | /* ==========================================================================
  4 |    HTML5 display definitions
  5 |    ========================================================================== */
  6 | 
  7 | /**
  8 |  * Correct `block` display not defined in IE 8/9.
  9 |  */
 10 | 
 11 | article,
 12 | aside,
 13 | details,
 14 | figcaption,
 15 | figure,
 16 | footer,
 17 | header,
 18 | hgroup,
 19 | main,
 20 | nav,
 21 | section,
 22 | summary {
 23 |     display: block;
 24 | }
 25 | 
 26 | /**
 27 |  * Correct `inline-block` display not defined in IE 8/9.
 28 |  */
 29 | 
 30 | audio,
 31 | canvas,
 32 | video {
 33 |     display: inline-block;
 34 | }
 35 | 
 36 | /**
 37 |  * Prevent modern browsers from displaying `audio` without controls.
 38 |  * Remove excess height in iOS 5 devices.
 39 |  */
 40 | 
 41 | audio:not([controls]) {
 42 |     display: none;
 43 |     height: 0;
 44 | }
 45 | 
 46 | /**
 47 |  * Address styling not present in IE 8/9.
 48 |  */
 49 | 
 50 | [hidden] {
 51 |     display: none;
 52 | }
 53 | 
 54 | /* ==========================================================================
 55 |    Base
 56 |    ========================================================================== */
 57 | 
 58 | /**
 59 |  * 1. Set default font family to sans-serif.
 60 |  * 2. Prevent iOS text size adjust after orientation change, without disabling
 61 |  *    user zoom.
 62 |  */
 63 | 
 64 | html {
 65 |     font-family: sans-serif; /* 1 */
 66 |     -webkit-text-size-adjust: 100%; /* 2 */
 67 |     -ms-text-size-adjust: 100%; /* 2 */
 68 | }
 69 | 
 70 | /**
 71 |  * Remove default margin.
 72 |  */
 73 | 
 74 | body {
 75 |     margin: 0;
 76 | }
 77 | 
 78 | /* ==========================================================================
 79 |    Links
 80 |    ========================================================================== */
 81 | 
 82 | /**
 83 |  * Address `outline` inconsistency between Chrome and other browsers.
 84 |  */
 85 | 
 86 | a:focus {
 87 |     outline: thin dotted;
 88 | }
 89 | 
 90 | /**
 91 |  * Improve readability when focused and also mouse hovered in all browsers.
 92 |  */
 93 | 
 94 | a:active,
 95 | a:hover {
 96 |     outline: 0;
 97 | }
 98 | 
 99 | /* ==========================================================================
100 |    Typography
101 |    ========================================================================== */
102 | 
103 | /**
104 |  * Address variable `h1` font-size and margin within `section` and `article`
105 |  * contexts in Firefox 4+, Safari 5, and Chrome.
106 |  */
107 | 
108 | h1 {
109 |     font-size: 2em;
110 |     margin: 0.67em 0;
111 | }
112 | 
113 | /**
114 |  * Address styling not present in IE 8/9, Safari 5, and Chrome.
115 |  */
116 | 
117 | abbr[title] {
118 |     border-bottom: 1px dotted;
119 | }
120 | 
121 | /**
122 |  * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
123 |  */
124 | 
125 | b,
126 | strong {
127 |     font-weight: bold;
128 | }
129 | 
130 | /**
131 |  * Address styling not present in Safari 5 and Chrome.
132 |  */
133 | 
134 | dfn {
135 |     font-style: italic;
136 | }
137 | 
138 | /**
139 |  * Address differences between Firefox and other browsers.
140 |  */
141 | 
142 | hr {
143 |     -moz-box-sizing: content-box;
144 |     box-sizing: content-box;
145 |     height: 0;
146 | }
147 | 
148 | /**
149 |  * Address styling not present in IE 8/9.
150 |  */
151 | 
152 | mark {
153 |     background: #ff0;
154 |     color: #000;
155 | }
156 | 
157 | /**
158 |  * Correct font family set oddly in Safari 5 and Chrome.
159 |  */
160 | 
161 | code,
162 | kbd,
163 | pre,
164 | samp {
165 |     font-family: monospace, serif;
166 |     font-size: 1em;
167 | }
168 | 
169 | /**
170 |  * Improve readability of pre-formatted text in all browsers.
171 |  */
172 | 
173 | pre {
174 |     white-space: pre-wrap;
175 | }
176 | 
177 | /**
178 |  * Set consistent quote types.
179 |  */
180 | 
181 | q {
182 |     quotes: "\201C" "\201D" "\2018" "\2019";
183 | }
184 | 
185 | /**
186 |  * Address inconsistent and variable font size in all browsers.
187 |  */
188 | 
189 | small {
190 |     font-size: 80%;
191 | }
192 | 
193 | /**
194 |  * Prevent `sub` and `sup` affecting `line-height` in all browsers.
195 |  */
196 | 
197 | sub,
198 | sup {
199 |     font-size: 75%;
200 |     line-height: 0;
201 |     position: relative;
202 |     vertical-align: baseline;
203 | }
204 | 
205 | sup {
206 |     top: -0.5em;
207 | }
208 | 
209 | sub {
210 |     bottom: -0.25em;
211 | }
212 | 
213 | /* ==========================================================================
214 |    Embedded content
215 |    ========================================================================== */
216 | 
217 | /**
218 |  * Remove border when inside `a` element in IE 8/9.
219 |  */
220 | 
221 | img {
222 |     border: 0;
223 | }
224 | 
225 | /**
226 |  * Correct overflow displayed oddly in IE 9.
227 |  */
228 | 
229 | svg:not(:root) {
230 |     overflow: hidden;
231 | }
232 | 
233 | /* ==========================================================================
234 |    Figures
235 |    ========================================================================== */
236 | 
237 | /**
238 |  * Address margin not present in IE 8/9 and Safari 5.
239 |  */
240 | 
241 | figure {
242 |     margin: 0;
243 | }
244 | 
245 | /* ==========================================================================
246 |    Forms
247 |    ========================================================================== */
248 | 
249 | /**
250 |  * Define consistent border, margin, and padding.
251 |  */
252 | 
253 | fieldset {
254 |     border: 1px solid #c0c0c0;
255 |     margin: 0 2px;
256 |     padding: 0.35em 0.625em 0.75em;
257 | }
258 | 
259 | /**
260 |  * 1. Correct `color` not being inherited in IE 8/9.
261 |  * 2. Remove padding so people aren't caught out if they zero out fieldsets.
262 |  */
263 | 
264 | legend {
265 |     border: 0; /* 1 */
266 |     padding: 0; /* 2 */
267 | }
268 | 
269 | /**
270 |  * 1. Correct font family not being inherited in all browsers.
271 |  * 2. Correct font size not being inherited in all browsers.
272 |  * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
273 |  */
274 | 
275 | button,
276 | input,
277 | select,
278 | textarea {
279 |     font-family: inherit; /* 1 */
280 |     font-size: 100%; /* 2 */
281 |     margin: 0; /* 3 */
282 | }
283 | 
284 | /**
285 |  * Address Firefox 4+ setting `line-height` on `input` using `!important` in
286 |  * the UA stylesheet.
287 |  */
288 | 
289 | button,
290 | input {
291 |     line-height: normal;
292 | }
293 | 
294 | /**
295 |  * Address inconsistent `text-transform` inheritance for `button` and `select`.
296 |  * All other form control elements do not inherit `text-transform` values.
297 |  * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
298 |  * Correct `select` style inheritance in Firefox 4+ and Opera.
299 |  */
300 | 
301 | button,
302 | select {
303 |     text-transform: none;
304 | }
305 | 
306 | /**
307 |  * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
308 |  *    and `video` controls.
309 |  * 2. Correct inability to style clickable `input` types in iOS.
310 |  * 3. Improve usability and consistency of cursor style between image-type
311 |  *    `input` and others.
312 |  */
313 | 
314 | button,
315 | html input[type="button"], /* 1 */
316 | input[type="reset"],
317 | input[type="submit"] {
318 |     -webkit-appearance: button; /* 2 */
319 |     cursor: pointer; /* 3 */
320 | }
321 | 
322 | /**
323 |  * Re-set default cursor for disabled elements.
324 |  */
325 | 
326 | button[disabled],
327 | html input[disabled] {
328 |     cursor: default;
329 | }
330 | 
331 | /**
332 |  * 1. Address box sizing set to `content-box` in IE 8/9.
333 |  * 2. Remove excess padding in IE 8/9.
334 |  */
335 | 
336 | input[type="checkbox"],
337 | input[type="radio"] {
338 |     box-sizing: border-box; /* 1 */
339 |     padding: 0; /* 2 */
340 | }
341 | 
342 | /**
343 |  * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
344 |  * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
345 |  *    (include `-moz` to future-proof).
346 |  */
347 | 
348 | input[type="search"] {
349 |     -webkit-appearance: textfield; /* 1 */
350 |     -moz-box-sizing: content-box;
351 |     -webkit-box-sizing: content-box; /* 2 */
352 |     box-sizing: content-box;
353 | }
354 | 
355 | /**
356 |  * Remove inner padding and search cancel button in Safari 5 and Chrome
357 |  * on OS X.
358 |  */
359 | 
360 | input[type="search"]::-webkit-search-cancel-button,
361 | input[type="search"]::-webkit-search-decoration {
362 |     -webkit-appearance: none;
363 | }
364 | 
365 | /**
366 |  * Remove inner padding and border in Firefox 4+.
367 |  */
368 | 
369 | button::-moz-focus-inner,
370 | input::-moz-focus-inner {
371 |     border: 0;
372 |     padding: 0;
373 | }
374 | 
375 | /**
376 |  * 1. Remove default vertical scrollbar in IE 8/9.
377 |  * 2. Improve readability and alignment in all browsers.
378 |  */
379 | 
380 | textarea {
381 |     overflow: auto; /* 1 */
382 |     vertical-align: top; /* 2 */
383 | }
384 | 
385 | /* ==========================================================================
386 |    Tables
387 |    ========================================================================== */
388 | 
389 | /**
390 |  * Remove most spacing between table cells.
391 |  */
392 | 
393 | table {
394 |     border-collapse: collapse;
395 |     border-spacing: 0;
396 | }


--------------------------------------------------------------------------------
/test/find.js:
--------------------------------------------------------------------------------
  1 | chai.should();
  2 | var expect = chai.expect;
  3 | 
  4 | // TODO What happens if there is no element returned in the query and I do .single?
  5 | // TODO Test always returning a proxy with deferred
  6 | // XXX Why can't we remove operations array from the model?
  7 | // XXX .fail() does not work
  8 | // XXX What happens if JSON sent does not match the schema? Ans: There is not validation
  9 | // XXX Assert that an entity can't use array if rootElement is not set
 10 | // First done parameter is foo
 11 | 
 12 | 
 13 | describe('when called find', function () {
 14 |     var server;
 15 | 
 16 |     beforeEach(function () {
 17 |         // API
 18 |         window.API = Milo.API.create({});
 19 |         API.options('baseUrl', 'https://myapi.com/api');
 20 |         
 21 |         // Fake Server
 22 |         server = sinon.fakeServer.create();
 23 |     });
 24 | 
 25 |     afterEach(function () {
 26 |         window.API = undefined;
 27 |         server.restore();
 28 |     });
 29 | 
 30 |     it('should fail if it does not contain properties', function () {
 31 |         // Act
 32 |         window.API.Foo = Milo.Model.extend({});
 33 | 
 34 |         // Assert
 35 |         (function () {
 36 |             window.API.Foo.find({ 'id': 42 }).single();
 37 |         }).should.Throw(/uriTemplate not set in model/i);
 38 |     });
 39 | 
 40 |     it('should make an ajax call', function (done) {
 41 |         // Arrange
 42 |         API.Foo = Milo.Model.extend({
 43 |             uriTemplate: '/foo/:id',
 44 |             id: Milo.property('number'),
 45 |             name: Milo.property('string', {
 46 |                 defaultValue: '',
 47 |                 operations: ['put', 'post'],
 48 |                 validationRules: {
 49 |                     required: true
 50 |                 }
 51 |             })
 52 |         });
 53 |         
 54 |         // Act 
 55 |         var foo = API.Foo.find({'id':42}).single();
 56 | 
 57 |         // Assert
 58 |         foo.should.should.be.ok;
 59 |         foo.should.have.property('isLoading', true);
 60 |         foo.should.have.property('done');
 61 |         foo.done(function (data) {
 62 |             foo.should.be.equal(data);
 63 |             foo.should.have.property('isLoading', false);
 64 |             // XXX Can't have a property named content, throw exception if found
 65 |             foo.get('id').should.be.equal(42);
 66 |             foo.get('name').should.be.equal('Catch 22');
 67 |             
 68 |             server.requests.length.should.be.equal(1);
 69 |             server.requests[0].url.should.be.equal("https://myapi.com/api/foo/42?");
 70 | 
 71 |             
 72 |             done();
 73 |         });
 74 |         
 75 |         server.requests[0].respond(200, { "Content-Type": "application/json" }, 
 76 |                 JSON.stringify({id: 42, name: 'Catch 22'}));
 77 |         
 78 |     });
 79 | 
 80 |     it('should handle nested entities', function (done) {
 81 |         // Arrange
 82 |         API.Author = Milo.Model.extend({
 83 |             uriTemplate: '/book/:bookId/authors',
 84 |             rootElement: 'authors',
 85 |             id: Milo.property('number'),
 86 |             name: Milo.property('string')
 87 |         });
 88 | 
 89 |         API.Book = Milo.Model.extend({
 90 |             uriTemplate: '/book/:id',
 91 |             id: Milo.property('number'),
 92 |             name: Milo.property('string', {
 93 |                 defaultValue: '',
 94 |                 operations: ['put', 'post'],
 95 |                 validationRules: {
 96 |                     required: true
 97 |                 }
 98 |             }),
 99 |             authors: Milo.collection('API.Author')
100 |         });
101 | 
102 |         // Act
103 |         var author = API.Book.find({id: 42})
104 |             .get('authors')
105 |             .find()
106 |             .single()
107 |             .done(function (data) {
108 |                 // Assert
109 |                 author.should.be.equal(data);
110 |                 author.should.have.property('isLoading', false);
111 | 
112 |                 author.get('id').should.be.equal(23);
113 |                 author.get('name').should.be.equal('Joseph Heller');
114 | 
115 |                 server.requests.length.should.be.equal(1);
116 |                 server.requests[0].url.should.be.equal("https://myapi.com/api/book/42/authors?");
117 | 
118 |                 done();
119 |             });
120 |         author.should.be.ok;
121 |         author.should.have.property('isLoading', true);
122 |         author.should.have.property('done');
123 | 
124 |         server.requests[0].respond(200, { "Content-Type": "application/json" }, 
125 |                 JSON.stringify({authors: [{id: 23, name: 'Joseph Heller'}]}));
126 |     });
127 | 
128 | 
129 |     it('single must not throw an exception and pick the first element of the array if query returns more than one element', function (done) {
130 |         // Arrange
131 |         API.Author = Milo.Model.extend({
132 |             uriTemplate: '/book/:bookId/authors',
133 |             rootElement: 'authors',
134 |             id: Milo.property('number'),
135 |             name: Milo.property('string')
136 |         });
137 | 
138 |         API.Book = Milo.Model.extend({
139 |             uriTemplate: '/book/:id',
140 |             id: Milo.property('number'),
141 |             name: Milo.property('string', {
142 |                 defaultValue: '',
143 |                 operations: ['put', 'post'],
144 |                 validationRules: {
145 |                     required: true
146 |                 }
147 |             }),
148 |             authors: Milo.collection('API.Author')
149 |         });
150 | 
151 |         // Act
152 |         var author = API.Book.find({id: 42})
153 |             .get('authors')
154 |             .find()
155 |             .single()
156 |             .done(function (data) {
157 |                 // Assert
158 |                 author.should.be.equal(data);
159 |                 author.should.have.property('isLoading', false);
160 | 
161 |                 author.get('id').should.be.equal(23);
162 |                 author.get('name').should.be.equal('Joseph Heller');
163 | 
164 |                 server.requests.length.should.be.equal(1);
165 |                 server.requests[0].url.should.be.equal("https://myapi.com/api/book/42/authors?");
166 | 
167 |                 done();
168 |             });
169 |         author.should.be.ok;
170 |         author.should.have.property('isLoading', true);
171 |         author.should.have.property('done');
172 | 
173 |         server.requests[0].respond(200, { "Content-Type": "application/json" }, 
174 |                 JSON.stringify({authors: [{id: 23, name: 'Joseph Heller'}, {id: 24, name: 'Julio Cortázar'}]}));
175 |     });
176 | 
177 |     it('should work and return collection if using to array', function (done) {
178 |         // Arrange
179 |         API.Author = Milo.Model.extend({
180 |             uriTemplate: '/book/:bookId/authors',
181 |             rootElement: 'authors',
182 |             id: Milo.property('number'),
183 |             name: Milo.property('string')
184 |         });
185 | 
186 |         API.Book = Milo.Model.extend({
187 |             uriTemplate: '/book/:id',
188 |             id: Milo.property('number'),
189 |             name: Milo.property('string'),
190 |             authors: Milo.collection('API.Author')
191 |         });
192 | 
193 |         // Act
194 |         var author = API.Book.find({id: 42})
195 |             .get('authors')
196 |             .find()
197 |             .toArray()
198 |             .done(function (data) {
199 |                 // Assert
200 |                 author.should.be.equal(data);
201 |                 author.should.have.property('isLoading', false);
202 | 
203 |                 author.objectAt(0).get('id').should.be.equal(23);
204 |                 author.objectAt(0).get('name').should.be.equal('Joseph Heller');
205 | 
206 |                 author.objectAt(1).get('id').should.be.equal(24);
207 |                 author.objectAt(1).get('name').should.be.equal('Julio Cortázar');
208 | 
209 |                 server.requests.length.should.be.equal(1);
210 |                 server.requests[0].url.should.be.equal("https://myapi.com/api/book/42/authors?");
211 | 
212 |                 done();
213 |             });
214 |         author.should.be.ok;
215 |         author.should.have.property('isLoading', true);
216 |         author.should.have.property('done');
217 | 
218 |         server.requests[0].respond(200, { "Content-Type": "application/json" }, 
219 |                 JSON.stringify({authors: [{id: 23, name: 'Joseph Heller'}, {id: 24, name: 'Julio Cortázar'}]}));
220 |     });
221 | 
222 |     it('should add the access token/api key it to the request', function (done) {
223 |         // Arrange
224 |         API.Foo = Milo.Model.extend({
225 |             uriTemplate: '/foo/:id',
226 |             id: Milo.property('number'),
227 |             name: Milo.property('string', {
228 |                 defaultValue: '',
229 |                 operations: ['put', 'post'],
230 |                 validationRules: {
231 |                     required: true
232 |                 }
233 |             })
234 |         });
235 |         API.options('auth', { api_key: 'XXX' });
236 |         
237 |         // Act 
238 |         var foo = API.Foo.find({'id':42}).single();
239 | 
240 |         // Assert
241 |         foo.should.should.be.ok;
242 |         foo.done(function (data) {
243 |             server.requests.length.should.be.equal(1);
244 |             server.requests[0].url.should.be.equal("https://myapi.com/api/foo/42?api_key=XXX");
245 | 
246 |             done();
247 |         });
248 |         
249 |         server.requests[0].respond(200, { "Content-Type": "application/json" }, 
250 |                 JSON.stringify({id: 42, name: 'Catch 22'}));
251 |         
252 |     });
253 | 
254 |     it('must add content type (when set) to the requests', function (done) {
255 |         // Arrange
256 |         API.Foo = Milo.Model.extend({
257 |             uriTemplate: '/foo/:id',
258 |             id: Milo.property('number'),
259 |             name: Milo.property('string')
260 |         });
261 |         API.headers('Content-Type', 'application/cool');
262 |         
263 |         // Act 
264 |         var foo = API.Foo.find({'id':42}).single();
265 | 
266 |         // Assert
267 |         foo.should.should.be.ok;
268 |         foo.done(function (data) {
269 |             server.requests.length.should.be.equal(1);
270 |             server.requests[0].url.should.be.equal("https://myapi.com/api/foo/42?");
271 |             server.requests[0].requestHeaders['Content-Type'].should.be.ok;
272 |             server.requests[0].requestHeaders['Content-Type'].should.be.equal('application/cool');
273 | 
274 |             done();
275 |         });
276 |         
277 |         server.requests[0].respond(200, { "Content-Type": "application/json" }, 
278 |                 JSON.stringify({id: 42, name: 'Catch 22'}));
279 |         
280 | 
281 |     });
282 | });
283 | 
284 | 
285 | 


--------------------------------------------------------------------------------
/samples/sample-ui/vendor/styles/normalize.css:
--------------------------------------------------------------------------------
  1 | /*! normalize.css v1.1.1 | MIT License | git.io/normalize */
  2 | 
  3 | /* ==========================================================================
  4 |    HTML5 display definitions
  5 |    ========================================================================== */
  6 | 
  7 | /**
  8 |  * Correct `block` display not defined in IE 6/7/8/9 and Firefox 3.
  9 |  */
 10 | 
 11 | article,
 12 | aside,
 13 | details,
 14 | figcaption,
 15 | figure,
 16 | footer,
 17 | header,
 18 | hgroup,
 19 | main,
 20 | nav,
 21 | section,
 22 | summary {
 23 |     display: block;
 24 | }
 25 | 
 26 | /**
 27 |  * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
 28 |  */
 29 | 
 30 | audio,
 31 | canvas,
 32 | video {
 33 |     display: inline-block;
 34 |     *display: inline;
 35 |     *zoom: 1;
 36 | }
 37 | 
 38 | /**
 39 |  * Prevent modern browsers from displaying `audio` without controls.
 40 |  * Remove excess height in iOS 5 devices.
 41 |  */
 42 | 
 43 | audio:not([controls]) {
 44 |     display: none;
 45 |     height: 0;
 46 | }
 47 | 
 48 | /**
 49 |  * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
 50 |  * Known issue: no IE 6 support.
 51 |  */
 52 | 
 53 | [hidden] {
 54 |     display: none;
 55 | }
 56 | 
 57 | /* ==========================================================================
 58 |    Base
 59 |    ========================================================================== */
 60 | 
 61 | /**
 62 |  * 1. Prevent system color scheme's background color being used in Firefox, IE,
 63 |  *    and Opera.
 64 |  * 2. Prevent system color scheme's text color being used in Firefox, IE, and
 65 |  *    Opera.
 66 |  * 3. Correct text resizing oddly in IE 6/7 when body `font-size` is set using
 67 |  *    `em` units.
 68 |  * 4. Prevent iOS text size adjust after orientation change, without disabling
 69 |  *    user zoom.
 70 |  */
 71 | 
 72 | html {
 73 |     background: #fff; /* 1 */
 74 |     color: #000; /* 2 */
 75 |     font-size: 100%; /* 3 */
 76 |     -webkit-text-size-adjust: 100%; /* 4 */
 77 |     -ms-text-size-adjust: 100%; /* 4 */
 78 | }
 79 | 
 80 | /**
 81 |  * Address `font-family` inconsistency between `textarea` and other form
 82 |  * elements.
 83 |  */
 84 | 
 85 | html,
 86 | button,
 87 | input,
 88 | select,
 89 | textarea {
 90 |     font-family: sans-serif;
 91 | }
 92 | 
 93 | /**
 94 |  * Address margins handled incorrectly in IE 6/7.
 95 |  */
 96 | 
 97 | body {
 98 |     margin: 0;
 99 | }
100 | 
101 | /* ==========================================================================
102 |    Links
103 |    ========================================================================== */
104 | 
105 | /**
106 |  * Address `outline` inconsistency between Chrome and other browsers.
107 |  */
108 | 
109 | a:focus {
110 |     outline: thin dotted;
111 | }
112 | 
113 | /**
114 |  * Improve readability when focused and also mouse hovered in all browsers.
115 |  */
116 | 
117 | a:active,
118 | a:hover {
119 |     outline: 0;
120 | }
121 | 
122 | /* ==========================================================================
123 |    Typography
124 |    ========================================================================== */
125 | 
126 | /**
127 |  * Address font sizes and margins set differently in IE 6/7.
128 |  * Address font sizes within `section` and `article` in Firefox 4+, Safari 5,
129 |  * and Chrome.
130 |  */
131 | 
132 | h1 {
133 |     font-size: 2em;
134 |     margin: 0.67em 0;
135 | }
136 | 
137 | h2 {
138 |     font-size: 1.5em;
139 |     margin: 0.83em 0;
140 | }
141 | 
142 | h3 {
143 |     font-size: 1.17em;
144 |     margin: 1em 0;
145 | }
146 | 
147 | h4 {
148 |     font-size: 1em;
149 |     margin: 1.33em 0;
150 | }
151 | 
152 | h5 {
153 |     font-size: 0.83em;
154 |     margin: 1.67em 0;
155 | }
156 | 
157 | h6 {
158 |     font-size: 0.67em;
159 |     margin: 2.33em 0;
160 | }
161 | 
162 | /**
163 |  * Address styling not present in IE 7/8/9, Safari 5, and Chrome.
164 |  */
165 | 
166 | abbr[title] {
167 |     border-bottom: 1px dotted;
168 | }
169 | 
170 | /**
171 |  * Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome.
172 |  */
173 | 
174 | b,
175 | strong {
176 |     font-weight: bold;
177 | }
178 | 
179 | blockquote {
180 |     margin: 1em 40px;
181 | }
182 | 
183 | /**
184 |  * Address styling not present in Safari 5 and Chrome.
185 |  */
186 | 
187 | dfn {
188 |     font-style: italic;
189 | }
190 | 
191 | /**
192 |  * Address differences between Firefox and other browsers.
193 |  * Known issue: no IE 6/7 normalization.
194 |  */
195 | 
196 | hr {
197 |     -moz-box-sizing: content-box;
198 |     box-sizing: content-box;
199 |     height: 0;
200 | }
201 | 
202 | /**
203 |  * Address styling not present in IE 6/7/8/9.
204 |  */
205 | 
206 | mark {
207 |     background: #ff0;
208 |     color: #000;
209 | }
210 | 
211 | /**
212 |  * Address margins set differently in IE 6/7.
213 |  */
214 | 
215 | p,
216 | pre {
217 |     margin: 1em 0;
218 | }
219 | 
220 | /**
221 |  * Correct font family set oddly in IE 6, Safari 4/5, and Chrome.
222 |  */
223 | 
224 | code,
225 | kbd,
226 | pre,
227 | samp {
228 |     font-family: monospace, serif;
229 |     _font-family: 'courier new', monospace;
230 |     font-size: 1em;
231 | }
232 | 
233 | /**
234 |  * Improve readability of pre-formatted text in all browsers.
235 |  */
236 | 
237 | pre {
238 |     white-space: pre;
239 |     white-space: pre-wrap;
240 |     word-wrap: break-word;
241 | }
242 | 
243 | /**
244 |  * Address CSS quotes not supported in IE 6/7.
245 |  */
246 | 
247 | q {
248 |     quotes: none;
249 | }
250 | 
251 | /**
252 |  * Address `quotes` property not supported in Safari 4.
253 |  */
254 | 
255 | q:before,
256 | q:after {
257 |     content: '';
258 |     content: none;
259 | }
260 | 
261 | /**
262 |  * Address inconsistent and variable font size in all browsers.
263 |  */
264 | 
265 | small {
266 |     font-size: 80%;
267 | }
268 | 
269 | /**
270 |  * Prevent `sub` and `sup` affecting `line-height` in all browsers.
271 |  */
272 | 
273 | sub,
274 | sup {
275 |     font-size: 75%;
276 |     line-height: 0;
277 |     position: relative;
278 |     vertical-align: baseline;
279 | }
280 | 
281 | sup {
282 |     top: -0.5em;
283 | }
284 | 
285 | sub {
286 |     bottom: -0.25em;
287 | }
288 | 
289 | /* ==========================================================================
290 |    Lists
291 |    ========================================================================== */
292 | 
293 | /**
294 |  * Address margins set differently in IE 6/7.
295 |  */
296 | 
297 | dl,
298 | menu,
299 | ol,
300 | ul {
301 |     margin: 1em 0;
302 | }
303 | 
304 | dd {
305 |     margin: 0 0 0 40px;
306 | }
307 | 
308 | /**
309 |  * Address paddings set differently in IE 6/7.
310 |  */
311 | 
312 | menu,
313 | ol,
314 | ul {
315 |     padding: 0 0 0 40px;
316 | }
317 | 
318 | /**
319 |  * Correct list images handled incorrectly in IE 7.
320 |  */
321 | 
322 | nav ul,
323 | nav ol {
324 |     list-style: none;
325 |     list-style-image: none;
326 | }
327 | 
328 | /* ==========================================================================
329 |    Embedded content
330 |    ========================================================================== */
331 | 
332 | /**
333 |  * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3.
334 |  * 2. Improve image quality when scaled in IE 7.
335 |  */
336 | 
337 | img {
338 |     border: 0; /* 1 */
339 |     -ms-interpolation-mode: bicubic; /* 2 */
340 | }
341 | 
342 | /**
343 |  * Correct overflow displayed oddly in IE 9.
344 |  */
345 | 
346 | svg:not(:root) {
347 |     overflow: hidden;
348 | }
349 | 
350 | /* ==========================================================================
351 |    Figures
352 |    ========================================================================== */
353 | 
354 | /**
355 |  * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
356 |  */
357 | 
358 | figure {
359 |     margin: 0;
360 | }
361 | 
362 | /* ==========================================================================
363 |    Forms
364 |    ========================================================================== */
365 | 
366 | /**
367 |  * Correct margin displayed oddly in IE 6/7.
368 |  */
369 | 
370 | form {
371 |     margin: 0;
372 | }
373 | 
374 | /**
375 |  * Define consistent border, margin, and padding.
376 |  */
377 | 
378 | fieldset {
379 |     border: 1px solid #c0c0c0;
380 |     margin: 0 2px;
381 |     padding: 0.35em 0.625em 0.75em;
382 | }
383 | 
384 | /**
385 |  * 1. Correct color not being inherited in IE 6/7/8/9.
386 |  * 2. Correct text not wrapping in Firefox 3.
387 |  * 3. Correct alignment displayed oddly in IE 6/7.
388 |  */
389 | 
390 | legend {
391 |     border: 0; /* 1 */
392 |     padding: 0;
393 |     white-space: normal; /* 2 */
394 |     *margin-left: -7px; /* 3 */
395 | }
396 | 
397 | /**
398 |  * 1. Correct font size not being inherited in all browsers.
399 |  * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5,
400 |  *    and Chrome.
401 |  * 3. Improve appearance and consistency in all browsers.
402 |  */
403 | 
404 | button,
405 | input,
406 | select,
407 | textarea {
408 |     font-size: 100%; /* 1 */
409 |     margin: 0; /* 2 */
410 |     vertical-align: baseline; /* 3 */
411 |     *vertical-align: middle; /* 3 */
412 | }
413 | 
414 | /**
415 |  * Address Firefox 3+ setting `line-height` on `input` using `!important` in
416 |  * the UA stylesheet.
417 |  */
418 | 
419 | button,
420 | input {
421 |     line-height: normal;
422 | }
423 | 
424 | /**
425 |  * Address inconsistent `text-transform` inheritance for `button` and `select`.
426 |  * All other form control elements do not inherit `text-transform` values.
427 |  * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
428 |  * Correct `select` style inheritance in Firefox 4+ and Opera.
429 |  */
430 | 
431 | button,
432 | select {
433 |     text-transform: none;
434 | }
435 | 
436 | /**
437 |  * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
438 |  *    and `video` controls.
439 |  * 2. Correct inability to style clickable `input` types in iOS.
440 |  * 3. Improve usability and consistency of cursor style between image-type
441 |  *    `input` and others.
442 |  * 4. Remove inner spacing in IE 7 without affecting normal text inputs.
443 |  *    Known issue: inner spacing remains in IE 6.
444 |  */
445 | 
446 | button,
447 | html input[type="button"], /* 1 */
448 | input[type="reset"],
449 | input[type="submit"] {
450 |     -webkit-appearance: button; /* 2 */
451 |     cursor: pointer; /* 3 */
452 |     *overflow: visible;  /* 4 */
453 | }
454 | 
455 | /**
456 |  * Re-set default cursor for disabled elements.
457 |  */
458 | 
459 | button[disabled],
460 | html input[disabled] {
461 |     cursor: default;
462 | }
463 | 
464 | /**
465 |  * 1. Address box sizing set to content-box in IE 8/9.
466 |  * 2. Remove excess padding in IE 8/9.
467 |  * 3. Remove excess padding in IE 7.
468 |  *    Known issue: excess padding remains in IE 6.
469 |  */
470 | 
471 | input[type="checkbox"],
472 | input[type="radio"] {
473 |     box-sizing: border-box; /* 1 */
474 |     padding: 0; /* 2 */
475 |     *height: 13px; /* 3 */
476 |     *width: 13px; /* 3 */
477 | }
478 | 
479 | /**
480 |  * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
481 |  * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
482 |  *    (include `-moz` to future-proof).
483 |  */
484 | 
485 | input[type="search"] {
486 |     -webkit-appearance: textfield; /* 1 */
487 |     -moz-box-sizing: content-box;
488 |     -webkit-box-sizing: content-box; /* 2 */
489 |     box-sizing: content-box;
490 | }
491 | 
492 | /**
493 |  * Remove inner padding and search cancel button in Safari 5 and Chrome
494 |  * on OS X.
495 |  */
496 | 
497 | input[type="search"]::-webkit-search-cancel-button,
498 | input[type="search"]::-webkit-search-decoration {
499 |     -webkit-appearance: none;
500 | }
501 | 
502 | /**
503 |  * Remove inner padding and border in Firefox 3+.
504 |  */
505 | 
506 | button::-moz-focus-inner,
507 | input::-moz-focus-inner {
508 |     border: 0;
509 |     padding: 0;
510 | }
511 | 
512 | /**
513 |  * 1. Remove default vertical scrollbar in IE 6/7/8/9.
514 |  * 2. Improve readability and alignment in all browsers.
515 |  */
516 | 
517 | textarea {
518 |     overflow: auto; /* 1 */
519 |     vertical-align: top; /* 2 */
520 | }
521 | 
522 | /* ==========================================================================
523 |    Tables
524 |    ========================================================================== */
525 | 
526 | /**
527 |  * Remove most spacing between table cells.
528 |  */
529 | 
530 | table {
531 |     border-collapse: collapse;
532 |     border-spacing: 0;
533 | }
534 | 


--------------------------------------------------------------------------------
/samples/sample-ui/generators/index.js:
--------------------------------------------------------------------------------
  1 | 'use strict';
  2 | 
  3 | var async = require('async');
  4 | var fs = require('fs');
  5 | var Handlebars = require('handlebars');
  6 | var inflection = require('inflection');
  7 | var mkdirp = require('mkdirp');
  8 | var sysPath = require('path');
  9 | var logger = require('loggy');
 10 | 
 11 | exports.formatTemplate = function(template, templateData) {
 12 |   var key = '__TEMPLATE_FORMATTER';
 13 |   var compiled = Handlebars.compile(template.replace(/\\\{/, key));
 14 |   return compiled(templateData).toString().replace(key, '\\');
 15 | };
 16 | 
 17 | Handlebars.registerHelper('camelize', (function() {
 18 |   var camelize = function(string) {
 19 |     var regexp = /[-_]([a-z])/g;
 20 |     var rest = string.replace(regexp, function(match, char) {
 21 |       return char.toUpperCase();
 22 |     });
 23 |     return rest[0].toUpperCase() + rest.slice(1);
 24 |   };
 25 |   return function(options) {
 26 |     return new Handlebars.SafeString(camelize(options.fn(this)));
 27 |   };
 28 | })());
 29 | 
 30 | exports.loadHelpers = function(helpersPath)
 31 | {
 32 |   var path = sysPath.resolve(helpersPath);
 33 |   var helpers = require(path);
 34 |   helpers(Handlebars);
 35 | }
 36 | 
 37 | exports.generateFile = function(path, data, method, callback) {
 38 |   fs.exists(path, function(exists) {
 39 |     if (exists && method !== 'overwrite' && method !== 'append') {
 40 |       logger.info("skipping " + path + " (already exists)");
 41 |       if (callback != null) {
 42 |         return callback();
 43 |       }
 44 |     } else {
 45 |       var parentDir = sysPath.dirname(path);
 46 |       var write = function() {
 47 |         if (method === 'create' || method === 'overwrite') {
 48 |           logger.info("create " + path);
 49 |           fs.writeFile(path, data, callback);
 50 |         } else if (method === 'append') {
 51 |           logger.info("appending to " + path);
 52 |           fs.appendFile(path, data, callback);
 53 |         }
 54 |       };
 55 |       fs.exists(parentDir, function(exists) {
 56 |         if (exists) return write();
 57 |         logger.info("init " + parentDir);
 58 |         // chmod 755.
 59 |         mkdirp(parentDir, 0x1ed, function(error) {
 60 |           if (error != null) return logger.error(error);
 61 |           write();
 62 |         });
 63 |       });
 64 |     }
 65 |   });
 66 | };
 67 | 
 68 | exports.destroyFile = function(path, callback) {
 69 |   fs.unlink(path, function(error) {
 70 |     if (error != null) {
 71 |       callback(error);
 72 |       return logger.error("" + error);
 73 |     }
 74 |     logger.info("destroy " + path);
 75 |     callback();
 76 |   });
 77 | };
 78 | 
 79 | exports.amendFile = function(path, contents, callback) {
 80 |   fs.readFile(path, 'utf8', function(error, existingContents) {
 81 |     fs.writeFile(path, existingContents.replace(contents, ''), function(error) {
 82 |       if (error != null) {
 83 |         callback(error);
 84 |         return logger.error("" + error);
 85 |       }
 86 |       logger.info("editing contents of " + path);
 87 |       callback();
 88 |     });
 89 |   });
 90 | };
 91 | 
 92 | exports.scaffoldFile = function(revert, from, to, method, templateData, parentPath, callback) {
 93 |   if (parentPath) {
 94 |     to = sysPath.join(parentPath, sysPath.basename(to));
 95 |   }
 96 |   if (revert && method !== 'append') {
 97 |     exports.destroyFile(to, callback);
 98 |   } else {
 99 |     fs.readFile(from, 'utf8', function(error, contents) {
100 |       var formatted = (function() {
101 |         try {
102 |           return exports.formatTemplate(contents, templateData);
103 |         } catch (error) {
104 |           return contents;
105 |         }
106 |       })();
107 |       if (revert && method === 'append') {
108 |         exports.amendFile(to, formatted, callback);
109 |       } else {
110 |         exports.generateFile(to, formatted, method, callback);
111 |       }
112 |     });
113 |   }
114 | };
115 | 
116 | exports.scaffoldFiles = function(revert, templateData, parentPath) {
117 |   return function(generator, callback) {
118 |     if (generator.helpers) {
119 |       exports.loadHelpers( generator.helpers );
120 |     }
121 |     async.forEach(generator.files, function(args, next) {
122 |       exports.scaffoldFile(
123 |         revert, args.from, args.to, args.method, templateData, parentPath, next
124 |       );
125 |     }, callback);
126 |   };
127 | };
128 | 
129 | exports.isDirectory = function(generatorsPath) {
130 |   return function(path, callback) {
131 |     fs.stat(sysPath.join(generatorsPath, path), function(error, stats) {
132 |       if (error != null) logger.error(error);
133 |       callback(stats.isDirectory());
134 |     });
135 |   };
136 | };
137 | 
138 | exports.readGeneratorConfig = function(generatorsPath) {
139 |   return function(name, callback) {
140 |     var path = sysPath.resolve(sysPath.join(generatorsPath, name, 'generator.json'));
141 |     var json = require(path);
142 |     json.name = name;
143 | 
144 |     var helpersPath = sysPath.join(generatorsPath, name, 'helpers.js');
145 |     fs.stat(sysPath.resolve(helpersPath), function(error, stats) {
146 |       if (error == null && stats.isFile()) {
147 |         json.helpers = helpersPath;
148 |       }
149 |       callback(null, json);
150 |     });
151 |   };
152 | };
153 | 
154 | exports.formatGeneratorConfig = function(path, json, templateData) {
155 |   var join = function(file) {
156 |     return sysPath.join(path, file);
157 |   };
158 |   var replaceSlashes = function(string) {
159 |     if (sysPath.sep === '\\') {
160 |       return string.replace(/\//g, '\\');
161 |     } else {
162 |       return string;
163 |     }
164 |   };
165 | 
166 |   json.files = json.files.map(function(object) {
167 |     return {
168 |       method: object.method || 'create',
169 |       from: join(replaceSlashes(object.from)),
170 |       to: replaceSlashes(exports.formatTemplate(object.to, templateData))
171 |     };
172 |   });
173 | 
174 |   json.dependencies = json.dependencies.map(function(object) {
175 |     return {
176 |       method: object.method || 'create',
177 |       name: object.name,
178 |       params: exports.formatTemplate(object.params, templateData)
179 |     };
180 |   });
181 | 
182 |   return Object.freeze(json);
183 | };
184 | 
185 | exports.getDependencyTree = function(generators, generatorName, memo) {
186 |   if (memo == null) memo = [];
187 |   var generator = generators.filter(function(gen) {
188 |     return gen.name === generatorName;
189 |   })[0];
190 |   if (generator == null) {
191 |     throw new Error("Invalid generator " + generatorName);
192 |   }
193 |   (generator.dependencies || []).forEach(function(dependency) {
194 |     exports.getDependencyTree(generators, dependency.name, memo);
195 |   });
196 |   memo.push(generator);
197 |   return memo;
198 | };
199 | 
200 | exports.generateFiles = function(revert, generatorsPath, type, templateData, parentPath, callback) {
201 |   fs.readdir(generatorsPath, function(error, files) {
202 |     if (error != null) throw new Error(error);
203 | 
204 |     // Get directories from generators directory.
205 |     async.filter(files, exports.isDirectory(generatorsPath), function(directories) {
206 | 
207 |       // Read all generator configs.
208 |       async.map(directories, exports.readGeneratorConfig(generatorsPath), function(error, configs) {
209 |         if (error != null) throw new Error(error);
210 |         var generators = directories.map(function(directory, index) {
211 |           var path = sysPath.join(generatorsPath, directory);
212 |           return exports.formatGeneratorConfig(path, configs[index], templateData);
213 |         });
214 | 
215 |         // Calculate dependency trees, do the scaffolding.
216 |         var tree = exports.getDependencyTree(generators, type);
217 |         async.forEach(tree, exports.scaffoldFiles(revert, templateData, parentPath), function(error) {
218 |           if (error != null) return callback(error);
219 |           callback();
220 |         });
221 |       });
222 |     });
223 |   });
224 | };
225 | 
226 | exports.listGenerators = function(generatorsPath, callback) {
227 |   fs.readdir(generatorsPath, function(error, files) {
228 |     if (error != null) throw new Error(error);
229 | 
230 |     // Get directories from generators directory.
231 |     async.filter(files, exports.isDirectory(generatorsPath), function(directories) {
232 |       console.log("List of available generators in " + generatorsPath);
233 | 
234 |       directories.map(function(directory, index) {
235 |         console.log(" * " + directory);
236 |       });
237 |     });
238 |   });
239 | };
240 | 
241 | exports.helpGenerator = function(generatorsPath, type, templateData) {
242 |   fs.readdir(generatorsPath, function(error, files) {
243 |     if (error != null) throw new Error(error);
244 | 
245 |     // Get directories from generators directory.
246 |     async.filter(files, exports.isDirectory(generatorsPath), function(directories) {
247 | 
248 |       // Read all generator configs.
249 |       async.map(directories, exports.readGeneratorConfig(generatorsPath), function(error, configs) {
250 |         if (error != null) throw new Error(error);
251 |         var generators = directories.map(function(directory, index) {
252 |           var path = sysPath.join(generatorsPath, directory);
253 |           return exports.formatGeneratorConfig(path, configs[index], templateData);
254 |         });
255 | 
256 |         // Calculate dependency trees
257 |         var tree = exports.getDependencyTree(generators, type);
258 |         tree.reverse();
259 |         tree.map(function(generator, index) {
260 |           if (index == 0) {
261 |             console.log("Documentation for '" + type + "' generator:");
262 |             console.log(" 'scaffolt " + type + " name'");
263 |           } else {
264 |             console.log(" * " + generator.name);
265 |           }
266 |           async.forEach(generator.files, function(args) {
267 |             console.log("   will " + args.method + " " + args.to);
268 |           });
269 |           if (index === 0 && tree.length > 1) {
270 |             console.log("");
271 |             console.log("Dependencies:");
272 |           }
273 |         });
274 |       });
275 |     });
276 |   });
277 | };
278 | 
279 | var checkIfExists = function(generatorsPath, callback) {
280 |   fs.exists(generatorsPath, function(exists) {
281 |     if (!exists) {
282 |       var msg = 'Generators directory "' + generatorsPath + '" does not exist';
283 |       logger.error(msg);
284 |       return callback(new Error(msg));
285 |     }
286 | 
287 |     callback();
288 |   });
289 | };
290 | 
291 | var scaffolt = module.exports = function(type, name, options, callback) {
292 |   // Set some default params.
293 |   if (options == null) options = {};
294 |   if (callback == null) callback = function() {};
295 | 
296 |   var pluralName = options.pluralName;
297 |   var generatorsPath = options.generatorsPath;
298 |   var revert = options.revert;
299 |   var parentPath = options.parentPath;
300 |   var path = parentPath ? parentPath.replace('app/', '') : inflection.pluralize(type);
301 | 
302 |   if (pluralName == null) pluralName = inflection.pluralize(name);
303 |   if (generatorsPath == null) generatorsPath = 'generators';
304 |   if (revert == null) revert = false;
305 |   var templateData = {name: name, pluralName: pluralName, path: path};
306 | 
307 |   checkIfExists(generatorsPath, function(exists) {
308 |     exports.generateFiles(revert, generatorsPath, type, templateData, undefined, function(error) {
309 |       if (error != null) {
310 |         logger.error(error);
311 |         return callback(error);
312 |       }
313 |       callback();
314 |     });
315 |   });
316 | };
317 | 
318 | 
319 | scaffolt.list = function(options, callback) {
320 |   // Set some default params
321 |   if (options == null) options = {};
322 |   if (callback == null) callback = function() {};
323 |   var generatorsPath = options.generatorsPath;
324 |   if (generatorsPath == null) generatorsPath = 'generators';
325 | 
326 |   checkIfExists(generatorsPath, function() {
327 |     exports.listGenerators(generatorsPath, function(error) {
328 |       if (error != null) {
329 |         logger.error(error);
330 |         return callback(error);
331 |       }
332 |       callback();
333 |     });
334 |   });
335 | };
336 | 
337 | scaffolt.help = function(type, options) {
338 |   // Set some default params
339 |   if (options == null) options = {};
340 |   var generatorsPath = options.generatorsPath;
341 |   if (generatorsPath == null) generatorsPath = 'generators';
342 |   var templateData = {name: "name", pluralName: "names"};
343 | 
344 |   checkIfExists(generatorsPath, function() {
345 |     exports.helpGenerator(generatorsPath, type, templateData);
346 |   });
347 | };


--------------------------------------------------------------------------------
/samples/sample-ui/app/assets/javascripts/modernizr-2.6.2.min.js:
--------------------------------------------------------------------------------
1 | /* Modernizr 2.6.2 (Custom Build) | MIT & BSD
2 |  * Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-mq-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load
3 |  */
4 | ;window.Modernizr=function(a,b,c){function D(a){j.cssText=a}function E(a,b){return D(n.join(a+";")+(b||""))}function F(a,b){return typeof a===b}function G(a,b){return!!~(""+a).indexOf(b)}function H(a,b){for(var d in a){var e=a[d];if(!G(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function I(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:F(f,"function")?f.bind(d||b):f}return!1}function J(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+p.join(d+" ")+d).split(" ");return F(b,"string")||F(b,"undefined")?H(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),I(e,b,c))}function K(){e.input=function(c){for(var d=0,e=c.length;d',a,""].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.flexbox=function(){return J("flexWrap")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){return!!a.WebGLRenderingContext},s.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:y(["@media (",n.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},s.geolocation=function(){return"geolocation"in navigator},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.rgba=function(){return D("background-color:rgba(150,255,150,.5)"),G(j.backgroundColor,"rgba")},s.hsla=function(){return D("background-color:hsla(120,40%,100%,.5)"),G(j.backgroundColor,"rgba")||G(j.backgroundColor,"hsla")},s.multiplebgs=function(){return D("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},s.backgroundsize=function(){return J("backgroundSize")},s.borderimage=function(){return J("borderImage")},s.borderradius=function(){return J("borderRadius")},s.boxshadow=function(){return J("boxShadow")},s.textshadow=function(){return b.createElement("div").style.textShadow===""},s.opacity=function(){return E("opacity:.55"),/^0.55$/.test(j.opacity)},s.cssanimations=function(){return J("animationName")},s.csscolumns=function(){return J("columnCount")},s.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return D((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),G(j.backgroundImage,"gradient")},s.cssreflections=function(){return J("boxReflect")},s.csstransforms=function(){return!!J("transform")},s.csstransforms3d=function(){var a=!!J("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return J("transition")},s.fontface=function(){var a;return y('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},s.generatedcontent=function(){var a;return y(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},s.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},s.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},s.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},s.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},s.webworkers=function(){return!!a.Worker},s.applicationcache=function(){return!!a.applicationCache},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect},s.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==r.svg},s.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(m.call(b.createElementNS(r.svg,"animate")))},s.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(m.call(b.createElementNS(r.svg,"clipPath")))};for(var L in s)C(s,L)&&(x=L.toLowerCase(),e[x]=s[L](),v.push((e[x]?"":"no-")+x));return e.input||K(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)C(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},D(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f