├── .gitignore ├── LICENSE ├── README.md ├── cli ├── cli.js └── templates │ ├── component │ ├── directive.js.template │ ├── style.scss.template │ └── template.html.template │ └── page │ ├── controller.js.template │ ├── style.scss.template │ └── template.html.template ├── core ├── generate.js ├── initializers │ └── angular_spa_initializer.js └── module.js ├── index.js ├── package.json └── src ├── angular ├── app │ ├── components │ │ └── test-component │ │ │ ├── directive.js │ │ │ ├── style.scss │ │ │ └── template.html │ ├── factories │ │ └── api.js │ ├── pages │ │ └── root │ │ │ ├── controller.js │ │ │ ├── index │ │ │ ├── controller.js │ │ │ ├── style.scss │ │ │ └── template.html │ │ │ ├── style.scss │ │ │ └── template.html │ └── style │ │ ├── base.scss │ │ └── import.scss ├── application.js ├── index.html ├── router.js └── vendor │ ├── script │ └── sample.js │ └── style │ └── sample.css └── app ├── controllers └── index_controller.js └── router.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Keith William Horwood 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 of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nodal-angular 2 | 3 | Welcome to the Angular SPA plugin for [Nodal](https://github.com/keithwhor/nodal)! 4 | 5 | This module contains some simple CLI tools for working with a new Angular project, 6 | as well as an initializer that compiles your Angular SPA for you. 7 | 8 | ## Installation 9 | 10 | Installation is simple. 11 | 12 | 1. Begin a new Nodal project with `nodal new` 13 | 2. In your new project directory, type `npm install nodal-angular --save` 14 | 3. Now type `nodal-angular init` 15 | 4. Next, go to `app/app.js` and find the following line: 16 | 17 | ```javascript 18 | /* Import Initializers */ 19 | const StaticAssetInitializer = Nodal.require('initializers/static_asset_initializer.js'); 20 | ``` 21 | 22 | Add the following line: 23 | 24 | ```javascript 25 | const AngularInitializer = require('nodal-angular').Initializer; 26 | ``` 27 | 28 | 5. In the same file, find: 29 | 30 | ```javascript 31 | /* Initializers */ 32 | this.initializers.use(StaticAssetInitializer); 33 | ``` 34 | 35 | And change it to: 36 | 37 | ```javascript 38 | /* Initializers */ 39 | this.initializers.use(AngularInitializer); 40 | this.initializers.use(StaticAssetInitializer); 41 | ``` 42 | 43 | Make sure you assign your AngularInitializer *first*! It copies files to be 44 | served by your StaticAssetInitializer. 45 | 46 | All Done! 47 | -------------------------------------------------------------------------------- /cli/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | (function() { 4 | 5 | 'use strict'; 6 | 7 | const colors = require('colors/safe'); 8 | const fs = require('fs-extra'); 9 | const inflect = require('i')(); 10 | const dot = require('dot'); 11 | dot.templateSettings.strip = false; 12 | 13 | let command = process.argv.slice(2, 3).pop(); 14 | 15 | command = command ? command : '_'; 16 | command = {name: command.split(':')[0], value: command.split(':')[1] || '_'}; 17 | 18 | let args = []; 19 | let flags = {}; 20 | 21 | process.argv.slice(3).forEach(function(v) { 22 | let values = v.split(':'); 23 | if (v.substr(0, 2) === '--') { 24 | values[0] = values[0].substr(2); 25 | flags[values[0]] = values[1]; 26 | } else { 27 | args.push(values); 28 | } 29 | }); 30 | 31 | let fnError = function(str) { 32 | console.error(colors.red.bold('Error: ') + str); 33 | process.exit(1); 34 | }; 35 | if (!fs.existsSync(process.cwd() + '/.nodal')) { 36 | 37 | fnError('Cannot use nodal-ng: No Nodal project in this directory.'); 38 | 39 | } 40 | 41 | let commands = { 42 | init: { 43 | _: (args, flags) => { 44 | 45 | if (fs.existsSync(process.cwd() + '/angular')) { 46 | fnError('Cannot initialize angular project --- seems you already have an angular directory present!'); 47 | } 48 | 49 | fs.copySync(__dirname + '/../src/angular', process.cwd() + '/angular'); 50 | fs.copySync(__dirname + '/../src/app/router.js', process.cwd() + '/app/router.js', {clobber: true}); 51 | fs.copySync(__dirname + '/../src/app/controllers/index_controller.js', process.cwd() + '/app/controllers/index_controller.js', {clobber: true}); 52 | 53 | console.log(colors.green.bold('Complete: ') + 'Angular SPA initialized successfully! Please make sure to include the angular initializer in app.js.'); 54 | 55 | } 56 | }, 57 | g: { 58 | page: (args, flags) => { 59 | 60 | let fArg = args[0] && args[0][0] || ''; 61 | 62 | if (!fArg) { 63 | fnError('Must supply a page path.'); 64 | } 65 | 66 | let path = ['angular', 'app', 'pages'].concat(fArg.split('/')) 67 | .map(v => inflect.underscore(v)) 68 | .map(v => inflect.dasherize(v)); 69 | 70 | let pagePath = process.cwd() + '/' + path.join('/'); 71 | 72 | if (fs.existsSync(pagePath)) { 73 | fnError('This page already exists.'); 74 | } 75 | 76 | for (let i = 0; i < path.length; i++) { 77 | let dirPath = process.cwd() + '/' + path.slice(0, i + 1).join('/'); 78 | if (!fs.existsSync(dirPath)) { 79 | fs.mkdirSync(dirPath); 80 | } 81 | } 82 | 83 | let data = {}; 84 | data.controllerName = fArg.split('/') 85 | .map(v => inflect.underscore(v)) 86 | .map(v => inflect.camelize(v)) 87 | .join('') + 'Controller'; 88 | 89 | [ 90 | 'controller.js', 91 | 'style.scss', 92 | 'template.html' 93 | ].forEach(f => { 94 | 95 | let template = dot.template(fs.readFileSync(__dirname + `/templates/page/${f}.template`).toString()); 96 | fs.writeFileSync([pagePath, f].join('/'), template(data)); 97 | 98 | console.log(colors.green.bold('Create: ') + [pagePath, f].join('/')); 99 | 100 | }); 101 | 102 | }, 103 | component: (args, flags) => { 104 | 105 | let fArg = args[0] && args[0][0] || ''; 106 | 107 | if (!fArg) { 108 | fnError('Must supply a component path.'); 109 | } 110 | 111 | if (fArg.indexOf('/') !== -1) { 112 | fnError('Components can not be nested within other component directories'); 113 | } 114 | 115 | let data = {}; 116 | data.componentName = inflect.camelize(inflect.underscore(fArg)); 117 | data.componentNameDash = inflect.dasherize(inflect.underscore(fArg)); 118 | 119 | let path = ['angular', 'app', 'components', data.componentNameDash]; 120 | let componentPath = process.cwd() + '/' + path.join('/'); 121 | 122 | if (fs.existsSync(componentPath)) { 123 | fnError('This component already exists.'); 124 | } 125 | 126 | for (let i = 0; i < path.length; i++) { 127 | let dirPath = process.cwd() + '/' + path.slice(0, i + 1).join('/'); 128 | if (!fs.existsSync(dirPath)) { 129 | fs.mkdirSync(dirPath); 130 | } 131 | } 132 | 133 | [ 134 | 'directive.js', 135 | 'style.scss', 136 | 'template.html' 137 | ].forEach(f => { 138 | 139 | let template = dot.template(fs.readFileSync(__dirname + `/templates/component/${f}.template`).toString()); 140 | fs.writeFileSync([componentPath, f].join('/'), template(data)); 141 | 142 | console.log(colors.green.bold('Create: ') + [componentPath, f].join('/')); 143 | 144 | }); 145 | 146 | } 147 | } 148 | }; 149 | 150 | let execFn = commands[command.name] && 151 | commands[command.name][command.value] || 152 | () => { fnError('Invalid command'); }; 153 | 154 | execFn(args, flags); 155 | 156 | process.exit(0); 157 | 158 | })(); 159 | -------------------------------------------------------------------------------- /cli/templates/component/directive.js.template: -------------------------------------------------------------------------------- 1 | app.directive('{{= it.componentName }}', function() { 2 | 3 | return { 4 | templateUrl: 'components/{{= it.componentNameDash }}/template.html' 5 | }; 6 | 7 | }); 8 | -------------------------------------------------------------------------------- /cli/templates/component/style.scss.template: -------------------------------------------------------------------------------- 1 | {{= it.componentNameDash }} { 2 | 3 | /* style your components here */ 4 | 5 | } 6 | -------------------------------------------------------------------------------- /cli/templates/component/template.html.template: -------------------------------------------------------------------------------- 1 |
2 | New Component: {{= it.componentName }} 3 |
4 | -------------------------------------------------------------------------------- /cli/templates/page/controller.js.template: -------------------------------------------------------------------------------- 1 | app.controller('{{= it.controllerName }}', ['$scope', 'API', function($scope, API) { 2 | 3 | // Controller Code 4 | 5 | }]); 6 | -------------------------------------------------------------------------------- /cli/templates/page/style.scss.template: -------------------------------------------------------------------------------- 1 | [ng-controller="{{= it.controllerName }}"] { 2 | 3 | /* style your controller here */ 4 | 5 | } 6 | -------------------------------------------------------------------------------- /cli/templates/page/template.html.template: -------------------------------------------------------------------------------- 1 |
2 | My New Page ({{= it.controllerName }}) 3 |
4 | -------------------------------------------------------------------------------- /core/generate.js: -------------------------------------------------------------------------------- 1 | module.exports = (() => { 2 | 3 | const AngularSPAInitializer = require('./initializers/angular_spa_initializer.js'); 4 | const html = AngularSPAInitializer.prototype.compileHTML(AngularSPAInitializer.PATHS.script); 5 | 6 | return (globals) => { 7 | globals = globals && typeof globals === 'object' ? globals : {}; 8 | return `${html}`; 9 | }; 10 | 11 | })(); 12 | -------------------------------------------------------------------------------- /core/initializers/angular_spa_initializer.js: -------------------------------------------------------------------------------- 1 | module.exports = (function() { 2 | 3 | 'use strict'; 4 | 5 | const fs = require('fs-extra'); 6 | const sass = require('node-sass'); 7 | 8 | class AngularSPAInitializer { 9 | 10 | exec(callback) { 11 | 12 | // Compile JavaScript, with minify flag 13 | let script = this.compileJavaScript(process.env.NODE_ENV === 'production'); 14 | fs.outputFileSync(`static/${this.constructor.PATHS.script}`, script); 15 | 16 | // Compile css 17 | let css = this.compileCSS(); 18 | fs.outputFileSync(`static/${this.constructor.PATHS.css}`, css); 19 | 20 | // Copy all vendor references 21 | fs.copySync('angular/vendor', `static/${this.constructor.PATHS.vendor}`); 22 | 23 | callback(null); 24 | 25 | } 26 | 27 | compileJavaScript(minify) { 28 | 29 | let files = [ 30 | fs.readFileSync('angular/application.js').toString(), 31 | fs.readFileSync('angular/router.js').toString() 32 | ]; 33 | 34 | this.readDir( 35 | 'angular/app/factories', 36 | /^.*\.js$/, 37 | (path, filename) => files.push(fs.readFileSync(path).toString()) 38 | ); 39 | 40 | this.readDir( 41 | 'angular/app/pages', 42 | /^.*\.js$/, 43 | (path, filename) => files.push(fs.readFileSync(path).toString()) 44 | ); 45 | 46 | this.readDir( 47 | 'angular/app/components', 48 | /^.*\.js$/, 49 | (path, filename) => files.push(fs.readFileSync(path).toString()) 50 | ); 51 | 52 | return files.join(''); 53 | 54 | } 55 | 56 | compileCSS() { 57 | 58 | let css = []; 59 | 60 | this.readDir( 61 | 'angular/app/pages', 62 | /^.*\.scss$/, 63 | (path, filename) => css.push(fs.readFileSync(path).toString()) 64 | ); 65 | 66 | this.readDir( 67 | 'angular/app/components', 68 | /^.*\.scss$/, 69 | (path, filename) => css.push(fs.readFileSync(path).toString()) 70 | ); 71 | 72 | let result = sass.renderSync({ 73 | data: `@import 'import'; ${css.join('')}`, 74 | outputStyle: 'compressed', 75 | includePaths: ['angular/app/style'] 76 | }); 77 | 78 | return result.css.toString(); 79 | 80 | }; 81 | 82 | compileHTML(scriptPath) { 83 | 84 | let templates = [ 85 | fs.readFileSync('angular/index.html').toString() 86 | ]; 87 | 88 | this.readDir( 89 | 'angular/app/pages', 90 | /^.*\.html$/, 91 | (path, filename) => { 92 | templates.push( 93 | `' 96 | ); 97 | } 98 | ); 99 | 100 | this.readDir( 101 | 'angular/app/components', 102 | /^.*\.html$/, 103 | (path, filename) => { 104 | templates.push( 105 | `' 108 | ); 109 | } 110 | ); 111 | 112 | templates.push(``); 113 | 114 | return templates.join(''); 115 | 116 | } 117 | 118 | readDir(dirname, match, fnMatch) { 119 | 120 | let cwd = process.cwd(); 121 | 122 | let files = fs.readdirSync([cwd, dirname].join('/')); 123 | 124 | files.forEach(filename => { 125 | 126 | let relPath = [dirname, filename].join('/'); 127 | let fullPath = [cwd, relPath].join('/'); 128 | 129 | let stat = fs.statSync(fullPath); 130 | 131 | if (stat.isDirectory()) { 132 | this.readDir(relPath, match, fnMatch); 133 | return; 134 | } 135 | 136 | if (filename.match(match)) { 137 | 138 | fnMatch(relPath, filename, fullPath); 139 | 140 | } 141 | 142 | }); 143 | 144 | } 145 | 146 | } 147 | 148 | AngularSPAInitializer.PATHS = { 149 | script: 'compiled/app.js', 150 | css: 'compiled/style.css', 151 | vendor: 'compiled/vendor' 152 | }; 153 | 154 | return AngularSPAInitializer; 155 | 156 | })(); 157 | -------------------------------------------------------------------------------- /core/module.js: -------------------------------------------------------------------------------- 1 | module.exports = (function() { 2 | 3 | return { 4 | Initializer: require('./initializers/angular_spa_initializer.js'), 5 | generate: require('./generate.js') 6 | }; 7 | 8 | })(); 9 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/keithwhor/nodal-angular/a68103aef8fe193e0b581556235fd317d2102f35/index.js -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodal-angular", 3 | "version": "0.0.4", 4 | "description": "Angular SPA tools for Nodal", 5 | "author": "Keith Horwood", 6 | "main": "core/module.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [ 11 | "nodal", 12 | "angular", 13 | "spa" 14 | ], 15 | "dependencies": { 16 | "colors": "^1.1.2", 17 | "dot": "^1.0.3", 18 | "fs-extra": "^0.26.3", 19 | "i": "^0.3.3", 20 | "node-sass": "^3.4.2" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/keithwhor/nodal-angular.git" 25 | }, 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/keithwhor/nodal-angular/issues" 29 | }, 30 | "bin": { 31 | "nodal-angular": "cli/cli.js" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/angular/app/components/test-component/directive.js: -------------------------------------------------------------------------------- 1 | app.directive('testComponent', function() { 2 | 3 | return { 4 | templateUrl: 'components/test-component/template.html' 5 | }; 6 | 7 | }); 8 | -------------------------------------------------------------------------------- /src/angular/app/components/test-component/style.scss: -------------------------------------------------------------------------------- 1 | test-component { 2 | 3 | color: #000000; 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/angular/app/components/test-component/template.html: -------------------------------------------------------------------------------- 1 |
2 | Test Component 3 |
4 | -------------------------------------------------------------------------------- /src/angular/app/factories/api.js: -------------------------------------------------------------------------------- 1 | app.factory('API', ['$rootScope', function($rootScope) { 2 | 3 | function copyAdd(obj, field, value) { 4 | 5 | var newObj = {}; 6 | value && (newObj[field] = value); 7 | 8 | Object.keys(obj).forEach(function(k) { 9 | newObj[k] = obj[k]; 10 | }); 11 | 12 | return newObj; 13 | 14 | } 15 | 16 | function serializeParameters(obj) { 17 | 18 | var fnConvert = function(keys, isArray, v) { 19 | isArray = ['', '[]'][isArray | 0]; 20 | return (keys.length < 2) ? ( 21 | [keys[0], isArray, '=', v].join('') 22 | ) : ( 23 | [keys[0], '[' + keys.slice(1).join(']['), ']', isArray, '=', v].join('') 24 | ); 25 | }; 26 | 27 | var fnSerialize = function(keys, key, i) { 28 | 29 | keys = keys.concat([key]); 30 | var datum = obj; 31 | 32 | keys.forEach(function(key) { 33 | datum = datum[key]; 34 | }); 35 | 36 | if (datum instanceof Date) { 37 | 38 | datum = [datum.getFullYear(), datum.getMonth() + 1, datum.getDate()].join('-'); 39 | 40 | } 41 | 42 | if (datum instanceof Array) { 43 | 44 | return datum.map(fnConvert.bind(null, keys, true)).join('&'); 45 | 46 | } else if (typeof(datum) === 'object' && datum !== null) { 47 | 48 | return Object.keys(datum).map(fnSerialize.bind(null, keys)).join('&'); 49 | 50 | } 51 | 52 | return fnConvert(keys, false, datum); 53 | 54 | }; 55 | 56 | return Object.keys(obj).map(fnSerialize.bind(null, [])).join('&'); 57 | 58 | } 59 | 60 | function APIConnect(domain) { 61 | 62 | if (domain[domain.length - 1] === '/') { 63 | domain = domain.substr(0, domain.length - 1); 64 | } 65 | 66 | this._domain = domain; 67 | this._accessToken = localStorage.getItem(this._domain + ':accessToken') || undefined; 68 | 69 | } 70 | 71 | APIConnect.prototype.request = function(path) { 72 | 73 | if (path[0] === '/') { 74 | path = path.substr(1); 75 | } 76 | 77 | return new APIRequest(this._domain, path, this._accessToken); 78 | 79 | }; 80 | 81 | APIConnect.prototype.open = function(path, params) { 82 | 83 | window.location = [ 84 | [this._domain, path].join('/'), 85 | '?', 86 | serializeParameters(copyAdd(params, 'access_token', this._accessToken)) 87 | ].join(''); 88 | 89 | }; 90 | 91 | APIConnect.prototype.setAccessToken = function(accessToken) { 92 | if (accessToken) { 93 | localStorage.setItem(this._domain + ':accessToken', accessToken); 94 | this._accessToken = accessToken 95 | } else { 96 | this.clearAccessToken(); 97 | } 98 | }; 99 | 100 | APIConnect.prototype.clearAccessToken = function() { 101 | localStorage.removeItem(this._domain + ':accessToken'); 102 | this._accessToken = undefined; 103 | }; 104 | 105 | function APIRequest(domain, path, accessToken) { 106 | this._url = [domain, path].join('/'); 107 | this._accessToken = accessToken || undefined; 108 | } 109 | 110 | APIRequest.prototype.addAccessToken = function(obj) { 111 | 112 | return copyAdd(obj, 'access_token', this._accessToken); 113 | 114 | }; 115 | 116 | APIRequest.prototype.addAccessTokenQueryString = function() { 117 | 118 | return (this._accessToken ? '?access_token=' + this._accessToken : ''); 119 | 120 | }; 121 | 122 | APIRequest.prototype.index = function(params, callback) { 123 | 124 | return new APIXHR(this._url, callback).get(this.addAccessToken(params)); 125 | 126 | }; 127 | 128 | APIRequest.prototype.show = function(id, params, callback) { 129 | 130 | return new APIXHR(this._url + (id ? '/' + id : ''), callback).get(this.addAccessToken(params)); 131 | 132 | }; 133 | 134 | APIRequest.prototype.destroy = function(id, params, callback) { 135 | 136 | return new APIXHR(this._url + (id ? '/' + id : ''), callback).del(this.addAccessToken(params)); 137 | 138 | }; 139 | 140 | APIRequest.prototype.create = function(params, callback) { 141 | 142 | return new APIXHR(this._url + this.addAccessTokenQueryString(), callback).post(params); 143 | 144 | }; 145 | 146 | APIRequest.prototype.update = function(id, params, callback) { 147 | 148 | return new APIXHR(this._url + (id ? '/' + id : '') + this.addAccessTokenQueryString(), callback).put(params); 149 | 150 | }; 151 | 152 | APIRequest.prototype.upload = function(file, callback) { 153 | 154 | return new APIXHR(this._url + this.addAccessTokenQueryString(), callback).upload(file); 155 | 156 | }; 157 | 158 | function APIXHR(url, callback) { 159 | 160 | this._url = url; 161 | this._active = false; 162 | this._complete = false; 163 | 164 | var self = this; 165 | var xhr = new XMLHttpRequest(); 166 | this._xhr = xhr; 167 | 168 | var cb = callback; 169 | callback = function() { 170 | 171 | self._complete = true; 172 | cb.apply(this, arguments); 173 | $rootScope.$digest(); 174 | 175 | }; 176 | 177 | this._callback = callback; 178 | 179 | xhr.addEventListener('readystatechange', function() { 180 | 181 | var obj; 182 | 183 | if (xhr.readyState === 0) { 184 | callback.call(self, new Error('Request aborted'), null, []); 185 | return; 186 | } 187 | 188 | if (xhr.readyState === 4) { 189 | 190 | if (xhr.status === 0) { 191 | callback.call(self, new Error('Request aborted'), null, []); 192 | return; 193 | } 194 | 195 | try { 196 | obj = JSON.parse(xhr.responseText); 197 | } catch(e) { 198 | callback.call(self, new Error('Expected JSON, could not parse response'), null, []); 199 | return; 200 | } 201 | 202 | if (obj.meta && obj.meta.error) { 203 | callback.call(self, obj.meta.error, obj, obj.data || []); 204 | return; 205 | } 206 | 207 | callback.call(self, null, obj, obj.data || []); 208 | return; 209 | 210 | } 211 | 212 | }); 213 | 214 | xhr.addEventListener('error', function(err) { 215 | 216 | callback.call(self, err, null, []); 217 | 218 | }); 219 | 220 | return this; 221 | 222 | } 223 | 224 | APIXHR.prototype.__checkActiveState__ = function() { 225 | if (this._active) { 226 | throw new Error('APIXHR is already active, can only be aborted.'); 227 | } 228 | return true; 229 | }; 230 | 231 | APIXHR.prototype.__setActiveState__ = function() { 232 | this._active = true; 233 | }; 234 | 235 | APIXHR.prototype.abort = function() { 236 | 237 | if (!this._active) { 238 | throw new Error('Cannot abort APIXHR that is not active'); 239 | } 240 | 241 | if (!this._complete) { 242 | this._xhr.abort(); 243 | } 244 | 245 | return this; 246 | 247 | }; 248 | 249 | APIXHR.prototype.get = function(params) { 250 | this.__checkActiveState__(); 251 | var xhr = this._xhr; 252 | xhr.open('GET', [this._url, serializeParameters(params)].join('?')); 253 | xhr.send(); 254 | this.__setActiveState__(); 255 | return this; 256 | }; 257 | 258 | APIXHR.prototype.del = function(params) { 259 | this.__checkActiveState__(); 260 | var xhr = this._xhr; 261 | xhr.open('DELETE', [this._url, serializeParameters(params)].join('?')); 262 | xhr.send(); 263 | this.__setActiveState__(); 264 | return this; 265 | }; 266 | 267 | APIXHR.prototype.post = function(params) { 268 | this.__checkActiveState__(); 269 | var xhr = this._xhr; 270 | xhr.open('POST', this._url); 271 | xhr.setRequestHeader('Content-Type', 'application/json'); 272 | xhr.send(JSON.stringify(params)); 273 | this.__setActiveState__(); 274 | return this; 275 | }; 276 | 277 | APIXHR.prototype.put = function(params) { 278 | this.__checkActiveState__(); 279 | var xhr = this._xhr; 280 | xhr.open('PUT', this._url); 281 | xhr.setRequestHeader('Content-Type', 'application/json'); 282 | xhr.send(JSON.stringify(params)); 283 | this.__setActiveState__(); 284 | return this; 285 | }; 286 | 287 | APIXHR.prototype.upload = function(file) { 288 | this.__checkActiveState__(); 289 | var xhr = this._xhr; 290 | xhr.open('POST', this._url); 291 | xhr.send(file); 292 | this.__setActiveState__(); 293 | return this; 294 | }; 295 | 296 | return new APIConnect((window.globals && window.globals.api_url) || ''); 297 | 298 | }]); 299 | -------------------------------------------------------------------------------- /src/angular/app/pages/root/controller.js: -------------------------------------------------------------------------------- 1 | app.controller('RootController', ['$scope', '$http', function($scope, $http) { 2 | 3 | $scope.greeting = 'Hello, world.'; 4 | 5 | }]); 6 | -------------------------------------------------------------------------------- /src/angular/app/pages/root/index/controller.js: -------------------------------------------------------------------------------- 1 | app.controller('RootIndexController', ['$scope', 'API', function ($scope, API) { 2 | 3 | $scope.name = 'Jo'; 4 | 5 | }]); 6 | -------------------------------------------------------------------------------- /src/angular/app/pages/root/index/style.scss: -------------------------------------------------------------------------------- 1 | [ng-controller="RootIndexController"] { 2 | 3 | /* style your controller here */ 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/angular/app/pages/root/index/template.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | Hello, {{ name }}. 4 | 5 |
6 | -------------------------------------------------------------------------------- /src/angular/app/pages/root/style.scss: -------------------------------------------------------------------------------- 1 | [ng-controller="RootController"] { 2 | 3 | /* style your controller here */ 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/angular/app/pages/root/template.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
{{ greeting }}
6 | 7 |
8 | 9 |
10 | -------------------------------------------------------------------------------- /src/angular/app/style/base.scss: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: 'Arial'; 3 | margin: 0px; 4 | padding: 0px; 5 | } 6 | -------------------------------------------------------------------------------- /src/angular/app/style/import.scss: -------------------------------------------------------------------------------- 1 | @import 'base'; 2 | -------------------------------------------------------------------------------- /src/angular/application.js: -------------------------------------------------------------------------------- 1 | var app = angular.module('App', ['ui.router']); 2 | -------------------------------------------------------------------------------- /src/angular/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Nodal Angular SPA 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/angular/router.js: -------------------------------------------------------------------------------- 1 | app.config(function($stateProvider, $locationProvider, $urlRouterProvider) { 2 | 3 | $locationProvider.html5Mode(true); 4 | 5 | $stateProvider 6 | 7 | .state( 8 | 'root', 9 | { 10 | templateUrl: 'pages/root/template.html', 11 | } 12 | ) 13 | 14 | .state( 15 | 'root.index', 16 | { 17 | url: '/', 18 | templateUrl: 'pages/root/index/template.html' 19 | } 20 | ) 21 | 22 | $urlRouterProvider.otherwise('/'); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /src/angular/vendor/script/sample.js: -------------------------------------------------------------------------------- 1 | console.log('This is a sample vendor script'); 2 | -------------------------------------------------------------------------------- /src/angular/vendor/style/sample.css: -------------------------------------------------------------------------------- 1 | a { 2 | color: #0000ff; 3 | text-decoration: none; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/controllers/index_controller.js: -------------------------------------------------------------------------------- 1 | module.exports = (function() { 2 | 3 | 'use strict'; 4 | 5 | const Nodal = require('nodal'); 6 | 7 | class IndexController extends Nodal.Controller { 8 | 9 | get() { 10 | 11 | this.render( 12 | this.app.angularSPA({ 13 | api_url: Nodal.my.Config.secrets.api_url 14 | }) 15 | ); 16 | 17 | } 18 | 19 | } 20 | 21 | return IndexController; 22 | 23 | })(); 24 | -------------------------------------------------------------------------------- /src/app/router.js: -------------------------------------------------------------------------------- 1 | module.exports = (function() { 2 | 3 | 'use strict'; 4 | 5 | const Nodal = require('nodal'); 6 | const router = new Nodal.Router(); 7 | 8 | /* Middleware */ 9 | /* executed *before* Controller-specific middleware */ 10 | 11 | const CORSMiddleware = Nodal.require('middleware/cors_middleware.js'); 12 | // const ForceWWWMiddleware = Nodal.require('middleware/force_www_middleware.js'); 13 | // const ForceHTTPSMiddleware = Nodal.require('middleware/force_https_middleware.js'); 14 | 15 | router.middleware.use(CORSMiddleware); 16 | // router.middleware.use(ForceWWWMiddleware); 17 | // router.middleware.use(ForceHTTPSMiddleware); 18 | 19 | /* Renderware */ 20 | /* executed *after* Controller-specific renderware */ 21 | 22 | const GzipRenderware = Nodal.require('renderware/gzip_renderware.js') 23 | 24 | router.renderware.use(GzipRenderware); 25 | 26 | /* Routes */ 27 | 28 | const IndexController = Nodal.require('app/controllers/index_controller.js'); 29 | const StaticController = Nodal.require('app/controllers/static_controller.js'); 30 | const Error404Controller = Nodal.require('app/controllers/error/404_controller.js'); 31 | 32 | /* generator: begin imports */ 33 | 34 | const V1UsersController = Nodal.require('app/controllers/v1/users_controller.js'); 35 | const V1AccessTokensController = Nodal.require('app/controllers/v1/access_tokens_controller.js'); 36 | 37 | /* generator: end imports */ 38 | 39 | router.route('/static/*').use(StaticController); 40 | 41 | /* generator: begin routes */ 42 | 43 | router.route('/v1/users/{id}').use(V1UsersController); 44 | router.route('/v1/access_tokens/{id}').use(V1AccessTokensController); 45 | 46 | /* generator: end routes */ 47 | 48 | router.route('/*').use(IndexController); 49 | 50 | return router; 51 | 52 | })(); 53 | --------------------------------------------------------------------------------