├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── .travis.yml ├── .yo-rc.json ├── README.md ├── app ├── index.js └── templates │ ├── _README.md │ ├── _bower.json │ ├── _gulp.config.js │ ├── _gulpfile.js │ ├── _karma.conf.js │ ├── _package.json │ ├── bowerrc │ ├── editorconfig │ ├── gitignore │ ├── gulp.png │ ├── jscsrc │ ├── jshintrc │ └── src │ ├── client │ ├── _index.html │ ├── _specs.html │ ├── app │ │ ├── app.module.js │ │ ├── blocks │ │ │ ├── exception │ │ │ │ ├── exception-handler.provider.js │ │ │ │ ├── exception-handler.provider.spec.js │ │ │ │ ├── exception.js │ │ │ │ └── exception.module.js │ │ │ ├── logger │ │ │ │ ├── logger.js │ │ │ │ └── logger.module.js │ │ │ └── router │ │ │ │ ├── router-helper.provider.js │ │ │ │ └── router.module.js │ │ ├── core │ │ │ ├── 404.html │ │ │ ├── config.js │ │ │ ├── constants.js │ │ │ ├── core.module.js │ │ │ ├── core.route.js │ │ │ ├── core.route.spec.js │ │ │ ├── dataservice.js │ │ │ ├── directives │ │ │ │ └── ng-really-click.directive.js │ │ │ └── services │ │ │ │ └── table.settings.service.js │ │ ├── dashboard │ │ │ ├── dashboard.controller.js │ │ │ ├── dashboard.controller.spec.js │ │ │ ├── dashboard.html │ │ │ ├── dashboard.module.js │ │ │ ├── dashboard.route.js │ │ │ └── dashboard.route.spec.js │ │ ├── layout │ │ │ ├── ht-sidebar.directive.js │ │ │ ├── ht-sidebar.directive.spec.js │ │ │ ├── ht-top-nav.directive.js │ │ │ ├── ht-top-nav.html │ │ │ ├── layout.module.js │ │ │ ├── shell.controller.js │ │ │ ├── shell.controller.spec.js │ │ │ ├── shell.html │ │ │ ├── sidebar.controller.js │ │ │ ├── sidebar.controller.spec.js │ │ │ └── sidebar.html │ │ └── widgets │ │ │ ├── ht-img-person.directive.js │ │ │ ├── ht-widget-header.directive.js │ │ │ ├── widget-header.html │ │ │ └── widgets.module.js │ ├── images │ │ ├── AngularJS-small.png │ │ ├── busy.gif │ │ ├── gulp-tiny.png │ │ └── icon.png │ ├── styles │ │ └── styles.less │ └── test-helpers │ │ ├── bind-polyfill.js │ │ └── mock-data.js │ └── server │ ├── _app.js │ ├── _data.js │ ├── _routes.js │ ├── favicon.ico │ └── utils │ └── 404.js ├── feature ├── index.js └── templates │ ├── _.client.module.js │ ├── config │ └── _.client.routes.js │ ├── controllers │ └── _.client.controller.js │ ├── services │ ├── _.client.service.js │ └── _.form.client.service.js │ └── views │ ├── _.create.client.view.html │ ├── _.edit.client.view.html │ ├── _.list.client.view.html │ └── _.view.client.view.html ├── package.json ├── sails └── api │ └── blueprints │ └── find.js └── test └── test-app.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "undef": true, 15 | "unused": true, 16 | "strict": true 17 | } 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 'iojs' 5 | - '0.12' 6 | - '0.10' 7 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-generator": {} 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## generator-angular-crud 2 | 3 | generator-angular-crud is a yeoman generator based on the great John Papa's [hottowel generator](https://github.com/johnpapa/generator-hottowel). This generator generates the code using the [Angular Style Guide](https://github.com/johnpapa/angular-styleguide) written by John Papa. The generated code is based in a proven structure and conventions for developing Angular applications. 4 | 5 | **generator-angular-crud** allows creating entities and CRUD operations very productively. 6 | 7 | Currently, this generator is adapted for working with a Sails backend, although it's very easy to adapt it for working with whatever backend. 8 | 9 | The generator allows creating entities automatically in a table form from where you can create, read, update and remove each database record. 10 | 11 | ## Demo Application 12 | 13 | You can see a demo application developed with generator-angular-crud at [Angular CRUD Demo](http://45.55.146.80:8080/) 14 | 15 | ## Video Tutorial 16 | 17 | [![Angular CRUD](http://www.jlmonteagudo.com/wp-content/uploads/2015/04/generator-angular-crud-readme.png)](http://youtu.be/O_AqjE7e_As) 18 | 19 | 20 | ## Backend with Sails 21 | 22 | At first place, you will have to create your API with Sails. However, when you get a list of records, Sails won't give you information regarding the total records, so you can't paginate properly. For example, if I request this: http://localhost:1337/customer, I get the following information: 23 | 24 | ``` 25 | [ 26 | { 27 | code: 'customer 1' 28 | }, 29 | { 30 | code: 'customer 2' 31 | }, 32 | { 33 | code: 'customer 3' 34 | } 35 | ] 36 | ``` 37 | 38 | But if I have to paginate I need information regarding the total number of records. So, I have to override the **find** blueprint. To do this, you have to copy from this project the file [sails/api/blueprints/find.js](https://raw.githubusercontent.com/jlmonteagudo/generator-angular-crud/master/sails/api/blueprints/find.js) into your **ROOT_PROJECT/api/blueprints/find.js**. Now, if I request this: http://localhost:1337/customer, I will get the following information: 39 | 40 | ``` 41 | { 42 | total: 1000, 43 | results: [ 44 | { 45 | code: 'customer 1' 46 | }, 47 | { 48 | code: 'customer 2' 49 | }, 50 | { 51 | code: 'customer 3' 52 | } 53 | ] 54 | } 55 | ``` 56 | 57 | With this object we have all the information that we need to paginate. 58 | 59 | Next steps that you have to do with Sails are: 60 | 61 | * npm install lodash --save 62 | * update config/models to enable the following parameter: **migrate: 'alter'** 63 | * update config/cors to set **allRoutes: true** and **origin: '*'** 64 | * generate your api with **sails generate api ** 65 | 66 | ## Frontend Quickstart 67 | 68 | You will need to install generator-angular-crud: 69 | 70 | ``` 71 | $ npm install -g generator-angular-crud 72 | ``` 73 | 74 | You have to create a new folder for your project and from this folder you will generate your application: 75 | 76 | ``` 77 | $ yo angular-crud 78 | ``` 79 | 80 | Next, you will create a new feature, a customer, for example: 81 | 82 | ``` 83 | $ yo angular-crud:feature 84 | ``` 85 | 86 | This will create an AngularJS application supporting full CRUD functionality. 87 | 88 | This subgenerator will create an entity with two properties called 'name' and 'street'. If we want to add new properties to our entity, we need to follow these steps: 89 | 90 | * Add new properties to the angular-formly array properties in src/client/app/**feature-name**/services/**feature-name**.form.client.service.js 91 | * Add new columns for the new properties in the HTML table in src/client/app/**feature-name**/views/list.html 92 | 93 | 94 | ## TODO 95 | 96 | * Improve validation errors returned by the server 97 | * Websockets to get realtime notifications 98 | * Adaptors for different backends (Firebase, Backand, ...) 99 | * Testing 100 | 101 | ## License 102 | 103 | MIT 104 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var yeoman = require('yeoman-generator'); 3 | var chalk = require('chalk'); 4 | var yosay = require('yosay'); 5 | 6 | module.exports = yeoman.generators.Base.extend({ 7 | initializing: function () { 8 | this.pkg = require('../package.json'); 9 | }, 10 | 11 | prompting: function () { 12 | var done = this.async(); 13 | 14 | // Have Yeoman greet the user. 15 | this.log(yosay( 16 | 'Welcome to the unreal ' + chalk.red('AngularCrud') + ' generator!\n' + 17 | 'Generating ' + this.appname + ' Application...' 18 | )); 19 | 20 | 21 | var prompts = [{ 22 | type: 'input', 23 | name: 'name', 24 | message: 'Your project name', 25 | default: this.appname 26 | }]; 27 | 28 | this.prompt(prompts, function (props) { 29 | this.props = props; 30 | // To access props later use this.props.someOption; 31 | 32 | this.appName = this.props.name; 33 | 34 | done(); 35 | }.bind(this)); 36 | 37 | }, 38 | 39 | 40 | packageFiles: function () { 41 | this.copy('_package.json', 'package.json'); 42 | this.template('_bower.json', 'bower.json'); 43 | this.template('_gulpfile.js', 'gulpfile.js'); 44 | this.template('_gulp.config.js', 'gulp.config.js'); 45 | this.template('_karma.conf.js', 'karma.conf.js'); 46 | this.template('_README.md', 'README.md'); 47 | }, 48 | 49 | assets: function () { 50 | this.copy('gulp.png', 'gulp.png'); 51 | }, 52 | 53 | testRunnerFiles: function () { 54 | this.template('src/client/_specs.html', 'src/client/specs.html'); 55 | }, 56 | 57 | appFiles: function () { 58 | this.directory('src/client/app'); 59 | this.directory('src/client/images'); 60 | this.directory('src/client/styles'); 61 | this.directory('src/client/test-helpers'); 62 | 63 | this.template('src/client/_index.html', 'src/client/index.html'); 64 | 65 | this.template('src/server/_app.js', 'src/server/app.js'); 66 | this.template('src/server/_data.js', 'src/server/data.js'); 67 | this.template('src/server/_routes.js', 'src/server/routes.js'); 68 | this.directory('src/server/utils'); 69 | this.copy('src/server/favicon.ico'); 70 | }, 71 | 72 | projectfiles: function () { 73 | this.copy('editorconfig', '.editorconfig'); 74 | this.copy('jshintrc', '.jshintrc'); 75 | this.copy('jscsrc', '.jscsrc'); 76 | this.copy('bowerrc', '.bowerrc'); 77 | this.copy('gitignore', '.gitignore'); 78 | }, 79 | 80 | install: function () { 81 | this.installDependencies(); 82 | } 83 | 84 | }); 85 | -------------------------------------------------------------------------------- /app/templates/_README.md: -------------------------------------------------------------------------------- 1 | # <%= appName %> 2 | 3 | **Generated from HotTowel Angular** 4 | 5 | >*Opinionated AngularJS style guide for teams by [@john_papa](//twitter.com/john_papa)* 6 | 7 | >More details about the styles and patterns used in this app can be found in my [AngularJS Style Guide](https://github.com/johnpapa/angularjs-styleguide) and my [AngularJS Patterns: Clean Code](http://jpapa.me/ngclean) course at [Pluralsight](http://pluralsight.com/training/Authors/Details/john-papa) and working in teams. 8 | 9 | ## Prerequisites 10 | 11 | 1. Install [Node.js](http://nodejs.org) 12 | - on OSX use [homebrew](http://brew.sh) `brew install node` 13 | - on Windows use [chocolatey](https://chocolatey.org/) `choco install nodejs` 14 | 15 | 2. Install Yeoman `npm install -g yo` 16 | 17 | 3. Install these NPM packages globally 18 | 19 | ```bash 20 | npm install -g bower gulp nodemon` 21 | ``` 22 | 23 | >Refer to these [instructions on how to not require sudo](https://github.com/sindresorhus/guides/blob/master/npm-global-without-sudo.md) 24 | 25 | ## Running HotTowel 26 | 27 | ### Linting 28 | - Run code analysis using `gulp vet`. This runs jshint, jscs, and plato. 29 | 30 | ### Tests 31 | - Run the unit tests using `gulp test` (via karma, mocha, sinon). 32 | 33 | ### Running in dev mode 34 | - Run the project with `gulp serve-dev --sync` 35 | 36 | - `--sync` opens it in a browser and updates the browser with any files changes. 37 | 38 | ### Building the project 39 | - Build the optimized project using `gulp build` 40 | - This create the optimized code for the project and puts it in the build folder 41 | 42 | ### Running the optimized code 43 | - Run the optimize project from the build folder with `gulp serve-build` 44 | 45 | ## Exploring HotTowel 46 | HotTowel Angular starter project 47 | 48 | ### Structure 49 | The structure also contains a gulpfile.js and a server folder. The server is there just so we can serve the app using node. Feel free to use any server you wish. 50 | 51 | /src 52 | /client 53 | /app 54 | /content 55 | 56 | ### Installing Packages 57 | When you generate the project it should run these commands, but if you notice missing pavkages, run these again: 58 | 59 | - `npm install` 60 | - `bower install` 61 | 62 | ### The Modules 63 | The app has 4 feature modules and depends on a series of external modules and custom but cross-app modules 64 | 65 | ``` 66 | app --> [ 67 | app.admin, 68 | app.dashboard, 69 | app.layout, 70 | app.widgets, 71 | app.core --> [ 72 | ngAnimate, 73 | ngSanitize, 74 | ui.router, 75 | blocks.exception, 76 | blocks.logger, 77 | blocks.router 78 | ] 79 | ] 80 | ``` 81 | 82 | #### core Module 83 | Core modules are ones that are shared throughout the entire application and may be customized for the specific application. Example might be common data services. 84 | 85 | This is an aggregator of modules that the application will need. The `core` module takes the blocks, common, and Angular sub-modules as dependencies. 86 | 87 | #### blocks Modules 88 | Block modules are reusable blocks of code that can be used across projects simply by including them as dependencies. 89 | 90 | ##### blocks.logger Module 91 | The `blocks.logger` module handles logging across the Angular app. 92 | 93 | ##### blocks.exception Module 94 | The `blocks.exception` module handles exceptions across the Angular app. 95 | 96 | It depends on the `blocks.logger` module, because the implementation logs the exceptions. 97 | 98 | ##### blocks.router Module 99 | The `blocks.router` module contains a routing helper module that assists in adding routes to the $routeProvider. 100 | -------------------------------------------------------------------------------- /app/templates/_bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= appName %>", 3 | "version": "0.0.1", 4 | "description": "<%= appName %>", 5 | "authors": [], 6 | "license": "MIT", 7 | "ignore": [ 8 | "**/.*", 9 | "node_modules", 10 | "bower_components", 11 | "test", 12 | "tests" 13 | ], 14 | "devDependencies": { 15 | "angular-mocks": "~1.3.8", 16 | "sinon": "http://sinonjs.org/releases/sinon-1.12.1.js", 17 | "bardjs": "~0.0.3" 18 | }, 19 | "dependencies": { 20 | "jquery": "~2.1.0", 21 | "angular": "~1.3.8", 22 | "angular-sanitize": "~1.3.8", 23 | "angular-resource": "~1.3.8", 24 | "bootstrap": "~3.2.0", 25 | "extras.angular.plus": "~0.9.2", 26 | "font-awesome": "~4.2.0", 27 | "moment": "~2.6.0", 28 | "angular-ui-router": "~0.2.12", 29 | "toastr": "~2.1.0", 30 | "angular-animate": "~1.3.8", 31 | "angular-bootstrap": "~0.11.2", 32 | "ng-table": "~0.3.3", 33 | "api-check": "~6.0.10", 34 | "angular-formly": "~4.0.11", 35 | "angular-formly-templates-bootstrap": "~4.0.3" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/templates/_gulp.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | var client = './src/client/'; 3 | var server = './src/server/'; 4 | var clientApp = client + 'app/'; 5 | var report = './report/'; 6 | var root = './'; 7 | var specRunnerFile = 'specs.html'; 8 | var temp = './.tmp/'; 9 | var wiredep = require('wiredep'); 10 | var bowerFiles = wiredep({devDependencies: true})['js']; 11 | var bower = { 12 | json: require('./bower.json'), 13 | directory: './bower_components/', 14 | ignorePath: '../..' 15 | }; 16 | var nodeModules = 'node_modules'; 17 | 18 | var config = { 19 | /** 20 | * File paths 21 | */ 22 | // all javascript that we want to vet 23 | alljs: [ 24 | './src/**/*.js', 25 | './*.js' 26 | ], 27 | build: './build/', 28 | client: client, 29 | css: temp + 'styles.css', 30 | fonts: [bower.directory + 'font-awesome/fonts/**/*.*', 31 | bower.directory + 'bootstrap/fonts/**/*.*'], 32 | html: client + '**/*.html', 33 | htmltemplates: clientApp + '**/*.html', 34 | images: client + 'images/**/*.*', 35 | index: client + 'index.html', 36 | // app js, with no specs 37 | js: [ 38 | clientApp + '**/*.module.js', 39 | clientApp + '**/*.js', 40 | '!' + clientApp + '**/*.spec.js' 41 | ], 42 | jsOrder: [ 43 | '**/app.module.js', 44 | '**/*.module.js', 45 | '**/*.js' 46 | ], 47 | less: client + 'styles/styles.less', 48 | report: report, 49 | root: root, 50 | server: server, 51 | source: 'src/', 52 | stubsjs: [ 53 | bower.directory + 'angular-mocks/angular-mocks.js', 54 | client + 'stubs/**/*.js' 55 | ], 56 | temp: temp, 57 | 58 | /** 59 | * optimized files 60 | */ 61 | optimized: { 62 | app: 'app.js', 63 | lib: 'lib.js' 64 | }, 65 | 66 | /** 67 | * plato 68 | */ 69 | plato: {js: clientApp + '**/*.js'}, 70 | 71 | /** 72 | * browser sync 73 | */ 74 | browserReloadDelay: 1000, 75 | 76 | /** 77 | * template cache 78 | */ 79 | templateCache: { 80 | file: 'templates.js', 81 | options: { 82 | module: 'app.core', 83 | root: 'app/', 84 | standAlone: false 85 | } 86 | }, 87 | 88 | /** 89 | * Bower and NPM files 90 | */ 91 | bower: bower, 92 | packages: [ 93 | './package.json', 94 | './bower.json' 95 | ], 96 | 97 | /** 98 | * specs.html, our HTML spec runner 99 | */ 100 | specRunner: client + specRunnerFile, 101 | specRunnerFile: specRunnerFile, 102 | 103 | /** 104 | * The sequence of the injections into specs.html: 105 | * 1 testlibraries 106 | * mocha setup 107 | * 2 bower 108 | * 3 js 109 | * 4 spechelpers 110 | * 5 specs 111 | * 6 templates 112 | */ 113 | testlibraries: [ 114 | nodeModules + '/mocha/mocha.js', 115 | nodeModules + '/chai/chai.js', 116 | nodeModules + '/mocha-clean/index.js', 117 | nodeModules + '/sinon-chai/lib/sinon-chai.js' 118 | ], 119 | specHelpers: [client + 'test-helpers/*.js'], 120 | specs: [clientApp + '**/*.spec.js'], 121 | serverIntegrationSpecs: [client + '/tests/server-integration/**/*.spec.js'], 122 | 123 | /** 124 | * Node settings 125 | */ 126 | nodeServer: './src/server/app.js', 127 | defaultPort: '8001' 128 | }; 129 | 130 | /** 131 | * wiredep and bower settings 132 | */ 133 | config.getWiredepDefaultOptions = function() { 134 | var options = { 135 | bowerJson: config.bower.json, 136 | directory: config.bower.directory, 137 | ignorePath: config.bower.ignorePath 138 | }; 139 | return options; 140 | }; 141 | 142 | /** 143 | * karma settings 144 | */ 145 | config.karma = getKarmaOptions(); 146 | 147 | return config; 148 | 149 | //////////////// 150 | 151 | function getKarmaOptions() { 152 | var options = { 153 | files: [].concat( 154 | bowerFiles, 155 | config.specHelpers, 156 | clientApp + '**/*.module.js', 157 | clientApp + '**/*.js', 158 | temp + config.templateCache.file, 159 | config.serverIntegrationSpecs 160 | ), 161 | exclude: [], 162 | coverage: { 163 | dir: report + 'coverage', 164 | reporters: [ 165 | // reporters not supporting the `file` property 166 | {type: 'html', subdir: 'report-html'}, 167 | {type: 'lcov', subdir: 'report-lcov'}, 168 | {type: 'text-summary'} //, subdir: '.', file: 'text-summary.txt'} 169 | ] 170 | }, 171 | preprocessors: {} 172 | }; 173 | options.preprocessors[clientApp + '**/!(*.spec)+(.js)'] = ['coverage']; 174 | return options; 175 | } 176 | }; 177 | -------------------------------------------------------------------------------- /app/templates/_gulpfile.js: -------------------------------------------------------------------------------- 1 | var args = require('yargs').argv; 2 | var browserSync = require('browser-sync'); 3 | var config = require('./gulp.config')(); 4 | var del = require('del'); 5 | var glob = require('glob'); 6 | var gulp = require('gulp'); 7 | var path = require('path'); 8 | var _ = require('lodash'); 9 | var $ = require('gulp-load-plugins')({lazy: true}); 10 | 11 | var colors = $.util.colors; 12 | var envenv = $.util.env; 13 | var port = process.env.PORT || config.defaultPort; 14 | 15 | /** 16 | * yargs variables can be passed in to alter the behavior, when present. 17 | * Example: gulp serve-dev 18 | * 19 | * --verbose : Various tasks will produce more output to the console. 20 | * --nosync : Don't launch the browser with browser-sync when serving code. 21 | * --debug : Launch debugger with node-inspector. 22 | * --debug-brk: Launch debugger and break on 1st line with node-inspector. 23 | * --startServers: Will start servers for midway tests on the test task. 24 | */ 25 | 26 | /** 27 | * List the available gulp tasks 28 | */ 29 | gulp.task('help', $.taskListing); 30 | gulp.task('default', ['help']); 31 | 32 | /** 33 | * vet the code and create coverage report 34 | * @return {Stream} 35 | */ 36 | gulp.task('vet', function() { 37 | log('Analyzing source with JSHint and JSCS'); 38 | 39 | return gulp 40 | .src(config.alljs) 41 | .pipe($.if(args.verbose, $.print())) 42 | .pipe($.jshint()) 43 | .pipe($.jshint.reporter('jshint-stylish', {verbose: true})) 44 | .pipe($.jshint.reporter('fail')) 45 | .pipe($.jscs()); 46 | }); 47 | 48 | /** 49 | * Create a visualizer report 50 | */ 51 | gulp.task('plato', function(done) { 52 | log('Analyzing source with Plato'); 53 | log('Browse to /report/plato/index.html to see Plato results'); 54 | 55 | startPlatoVisualizer(done); 56 | }); 57 | 58 | /** 59 | * Compile less to css 60 | * @return {Stream} 61 | */ 62 | gulp.task('styles', ['clean-styles'], function() { 63 | log('Compiling Less --> CSS'); 64 | 65 | return gulp 66 | .src(config.less) 67 | .pipe($.plumber()) // exit gracefully if something fails after this 68 | .pipe($.less()) 69 | // .on('error', errorLogger) // more verbose and dupe output. requires emit. 70 | .pipe($.autoprefixer({browsers: ['last 2 version', '> 5%']})) 71 | .pipe(gulp.dest(config.temp)); 72 | }); 73 | 74 | /** 75 | * Copy fonts 76 | * @return {Stream} 77 | */ 78 | gulp.task('fonts', ['clean-fonts'], function() { 79 | log('Copying fonts'); 80 | 81 | return gulp 82 | .src(config.fonts, {bower: config.bower.directory}) 83 | .pipe(gulp.dest(config.build + 'fonts')); 84 | }); 85 | 86 | /** 87 | * Compress images 88 | * @return {Stream} 89 | */ 90 | gulp.task('images', ['clean-images'], function() { 91 | log('Compressing and copying images'); 92 | 93 | return gulp 94 | .src(config.images) 95 | .pipe($.imagemin({optimizationLevel: 4})) 96 | .pipe(gulp.dest(config.build + 'images')); 97 | }); 98 | 99 | gulp.task('less-watcher', function() { 100 | gulp.watch([config.less], ['styles']); 101 | }); 102 | 103 | /** 104 | * Create $templateCache from the html templates 105 | * @return {Stream} 106 | */ 107 | gulp.task('templatecache', ['clean-code'], function() { 108 | log('Creating an AngularJS $templateCache'); 109 | 110 | return gulp 111 | .src(config.htmltemplates) 112 | .pipe($.if(args.verbose, $.bytediff.start())) 113 | .pipe($.minifyHtml({empty: true})) 114 | .pipe($.if(args.verbose, $.bytediff.stop(bytediffFormatter))) 115 | .pipe($.angularTemplatecache( 116 | config.templateCache.file, 117 | config.templateCache.options 118 | )) 119 | .pipe(gulp.dest(config.temp)); 120 | }); 121 | 122 | /** 123 | * Wire-up the bower dependencies 124 | * @return {Stream} 125 | */ 126 | gulp.task('wiredep', function() { 127 | log('Wiring the bower dependencies into the html'); 128 | 129 | var wiredep = require('wiredep').stream; 130 | var options = config.getWiredepDefaultOptions(); 131 | 132 | // Only include stubs if flag is enabled 133 | var js = args.stubs ? [].concat(config.js, config.stubsjs) : config.js; 134 | 135 | return gulp 136 | .src(config.index) 137 | .pipe(wiredep(options)) 138 | .pipe(inject(js, '', config.jsOrder)) 139 | .pipe(gulp.dest(config.client)); 140 | }); 141 | 142 | gulp.task('inject', ['wiredep', 'styles', 'templatecache'], function() { 143 | log('Wire up css into the html, after files are ready'); 144 | 145 | return gulp 146 | .src(config.index) 147 | .pipe(inject(config.css)) 148 | .pipe(gulp.dest(config.client)); 149 | }); 150 | 151 | /** 152 | * Run the spec runner 153 | * @return {Stream} 154 | */ 155 | gulp.task('serve-specs', ['build-specs'], function(done) { 156 | log('run the spec runner'); 157 | serve(true /* isDev */, true /* specRunner */); 158 | done(); 159 | }); 160 | 161 | /** 162 | * Inject all the spec files into the specs.html 163 | * @return {Stream} 164 | */ 165 | gulp.task('build-specs', ['templatecache'], function(done) { 166 | log('building the spec runner'); 167 | 168 | var wiredep = require('wiredep').stream; 169 | var templateCache = config.temp + config.templateCache.file; 170 | var options = config.getWiredepDefaultOptions(); 171 | var specs = config.specs; 172 | 173 | if (args.startServers) { 174 | specs = [].concat(specs, config.serverIntegrationSpecs); 175 | } 176 | options.devDependencies = true; 177 | 178 | return gulp 179 | .src(config.specRunner) 180 | .pipe(wiredep(options)) 181 | .pipe(inject(config.js, '', config.jsOrder)) 182 | .pipe(inject(config.testlibraries, 'testlibraries')) 183 | .pipe(inject(config.specHelpers, 'spechelpers')) 184 | .pipe(inject(specs, 'specs', ['**/*'])) 185 | .pipe(inject(templateCache, 'templates')) 186 | .pipe(gulp.dest(config.client)); 187 | }); 188 | 189 | /** 190 | * Build everything 191 | * This is separate so we can run tests on 192 | * optimize before handling image or fonts 193 | */ 194 | gulp.task('build', ['optimize', 'images', 'fonts'], function() { 195 | log('Building everything'); 196 | 197 | var msg = { 198 | title: 'gulp build', 199 | subtitle: 'Deployed to the build folder', 200 | message: 'Running `gulp serve-build`' 201 | }; 202 | del(config.temp); 203 | log(msg); 204 | notify(msg); 205 | }); 206 | 207 | /** 208 | * Optimize all files, move to a build folder, 209 | * and inject them into the new index.html 210 | * @return {Stream} 211 | */ 212 | gulp.task('optimize', ['inject', 'vet'], function() { 213 | log('Optimizing the js, css, and html'); 214 | 215 | var assets = $.useref.assets({searchPath: './'}); 216 | // Filters are named for the gulp-useref path 217 | var cssFilter = $.filter('**/*.css'); 218 | var jsAppFilter = $.filter('**/' + config.optimized.app); 219 | var jslibFilter = $.filter('**/' + config.optimized.lib); 220 | 221 | var templateCache = config.temp + config.templateCache.file; 222 | 223 | return gulp 224 | .src(config.index) 225 | .pipe($.plumber()) 226 | .pipe(inject(templateCache, 'templates')) 227 | .pipe(assets) // Gather all assets from the html with useref 228 | // Get the css 229 | .pipe(cssFilter) 230 | .pipe($.csso()) 231 | .pipe(cssFilter.restore()) 232 | // Get the custom javascript 233 | .pipe(jsAppFilter) 234 | .pipe($.ngAnnotate({add: true})) 235 | .pipe($.uglify()) 236 | .pipe(getHeader()) 237 | .pipe(jsAppFilter.restore()) 238 | // Get the vendor javascript 239 | .pipe(jslibFilter) 240 | .pipe($.uglify()) // another option is to override wiredep to use min files 241 | .pipe(jslibFilter.restore()) 242 | // Take inventory of the file names for future rev numbers 243 | .pipe($.rev()) 244 | // Apply the concat and file replacement with useref 245 | .pipe(assets.restore()) 246 | .pipe($.useref()) 247 | // Replace the file names in the html with rev numbers 248 | .pipe($.revReplace()) 249 | .pipe(gulp.dest(config.build)); 250 | }); 251 | 252 | /** 253 | * Remove all files from the build, temp, and reports folders 254 | * @param {Function} done - callback when complete 255 | */ 256 | gulp.task('clean', function(done) { 257 | var delconfig = [].concat(config.build, config.temp, config.report); 258 | log('Cleaning: ' + $.util.colors.blue(delconfig)); 259 | del(delconfig, done); 260 | }); 261 | 262 | /** 263 | * Remove all fonts from the build folder 264 | * @param {Function} done - callback when complete 265 | */ 266 | gulp.task('clean-fonts', function(done) { 267 | clean(config.build + 'fonts/**/*.*', done); 268 | }); 269 | 270 | /** 271 | * Remove all images from the build folder 272 | * @param {Function} done - callback when complete 273 | */ 274 | gulp.task('clean-images', function(done) { 275 | clean(config.build + 'images/**/*.*', done); 276 | }); 277 | 278 | /** 279 | * Remove all styles from the build and temp folders 280 | * @param {Function} done - callback when complete 281 | */ 282 | gulp.task('clean-styles', function(done) { 283 | var files = [].concat( 284 | config.temp + '**/*.css', 285 | config.build + 'styles/**/*.css' 286 | ); 287 | clean(files, done); 288 | }); 289 | 290 | /** 291 | * Remove all js and html from the build and temp folders 292 | * @param {Function} done - callback when complete 293 | */ 294 | gulp.task('clean-code', function(done) { 295 | var files = [].concat( 296 | config.temp + '**/*.js', 297 | config.build + 'js/**/*.js', 298 | config.build + '**/*.html' 299 | ); 300 | clean(files, done); 301 | }); 302 | 303 | /** 304 | * Run specs once and exit 305 | * To start servers and run midway specs as well: 306 | * gulp test --startServers 307 | * @return {Stream} 308 | */ 309 | gulp.task('test', ['vet', 'templatecache'], function(done) { 310 | startTests(true /*singleRun*/ , done); 311 | }); 312 | 313 | /** 314 | * Run specs and wait. 315 | * Watch for file changes and re-run tests on each change 316 | * To start servers and run midway specs as well: 317 | * gulp autotest --startServers 318 | */ 319 | gulp.task('autotest', function(done) { 320 | startTests(false /*singleRun*/ , done); 321 | }); 322 | 323 | /** 324 | * serve the dev environment 325 | * --debug-brk or --debug 326 | * --nosync 327 | */ 328 | gulp.task('serve-dev', ['inject'], function() { 329 | serve(true /*isDev*/); 330 | }); 331 | 332 | /** 333 | * serve the build environment 334 | * --debug-brk or --debug 335 | * --nosync 336 | */ 337 | gulp.task('serve-build', ['build'], function() { 338 | serve(false /*isDev*/); 339 | }); 340 | 341 | /** 342 | * Bump the version 343 | * --type=pre will bump the prerelease version *.*.*-x 344 | * --type=patch or no flag will bump the patch version *.*.x 345 | * --type=minor will bump the minor version *.x.* 346 | * --type=major will bump the major version x.*.* 347 | * --version=1.2.3 will bump to a specific version and ignore other flags 348 | */ 349 | gulp.task('bump', function() { 350 | var msg = 'Bumping versions'; 351 | var type = args.type; 352 | var version = args.ver; 353 | var options = {}; 354 | if (version) { 355 | options.version = version; 356 | msg += ' to ' + version; 357 | } else { 358 | options.type = type; 359 | msg += ' for a ' + type; 360 | } 361 | log(msg); 362 | 363 | return gulp 364 | .src(config.packages) 365 | .pipe($.print()) 366 | .pipe($.bump(options)) 367 | .pipe(gulp.dest(config.root)); 368 | }); 369 | 370 | //////////////// 371 | 372 | /** 373 | * When files change, log it 374 | * @param {Object} event - event that fired 375 | */ 376 | function changeEvent(event) { 377 | var srcPattern = new RegExp('/.*(?=/' + config.source + ')/'); 378 | log('File ' + event.path.replace(srcPattern, '') + ' ' + event.type); 379 | } 380 | 381 | /** 382 | * Delete all files in a given path 383 | * @param {Array} path - array of paths to delete 384 | * @param {Function} done - callback when complete 385 | */ 386 | function clean(path, done) { 387 | log('Cleaning: ' + $.util.colors.blue(path)); 388 | del(path, done); 389 | } 390 | 391 | /** 392 | * Inject files in a sorted sequence at a specified inject label 393 | * @param {Array} src glob pattern for source files 394 | * @param {String} label The label name 395 | * @param {Array} order glob pattern for sort order of the files 396 | * @returns {Stream} The stream 397 | */ 398 | function inject(src, label, order) { 399 | var options = {read: false}; 400 | if (label) { 401 | options.name = 'inject:' + label; 402 | } 403 | 404 | return $.inject(orderSrc(src, order), options); 405 | } 406 | 407 | /** 408 | * Order a stream 409 | * @param {Stream} src The gulp.src stream 410 | * @param {Array} order Glob array pattern 411 | * @returns {Stream} The ordered stream 412 | */ 413 | function orderSrc (src, order) { 414 | //order = order || ['**/*']; 415 | return gulp 416 | .src(src) 417 | .pipe($.if(order, $.order(order))); 418 | } 419 | 420 | /** 421 | * serve the code 422 | * --debug-brk or --debug 423 | * --nosync 424 | * @param {Boolean} isDev - dev or build mode 425 | * @param {Boolean} specRunner - server spec runner html 426 | */ 427 | function serve(isDev, specRunner) { 428 | var debug = args.debug || args.debugBrk; 429 | var debugMode = args.debug ? '--debug' : args.debugBrk ? '--debug-brk' : ''; 430 | var nodeOptions = getNodeOptions(isDev); 431 | 432 | if (debug) { 433 | runNodeInspector(); 434 | nodeOptions.nodeArgs = [debugMode + '=5858']; 435 | } 436 | 437 | if (args.verbose) { 438 | console.log(nodeOptions); 439 | } 440 | 441 | return $.nodemon(nodeOptions) 442 | .on('restart', ['vet'], function(ev) { 443 | log('*** nodemon restarted'); 444 | log('files changed:\n' + ev); 445 | setTimeout(function() { 446 | browserSync.notify('reloading now ...'); 447 | browserSync.reload({stream: false}); 448 | }, config.browserReloadDelay); 449 | }) 450 | .on('start', function () { 451 | log('*** nodemon started'); 452 | startBrowserSync(isDev, specRunner); 453 | }) 454 | .on('crash', function () { 455 | log('*** nodemon crashed: script crashed for some reason'); 456 | }) 457 | .on('exit', function () { 458 | log('*** nodemon exited cleanly'); 459 | }); 460 | } 461 | 462 | function getNodeOptions(isDev) { 463 | return { 464 | script: config.nodeServer, 465 | delayTime: 1, 466 | env: { 467 | 'PORT': port, 468 | 'NODE_ENV': isDev ? 'dev' : 'build' 469 | }, 470 | watch: [config.server] 471 | }; 472 | } 473 | 474 | function runNodeInspector() { 475 | log('Running node-inspector.'); 476 | log('Browse to http://localhost:8080/debug?port=5858'); 477 | var exec = require('child_process').exec; 478 | exec('node-inspector'); 479 | } 480 | 481 | /** 482 | * Start BrowserSync 483 | * --nosync will avoid browserSync 484 | */ 485 | function startBrowserSync(isDev, specRunner) { 486 | if (args.nosync || browserSync.active) { 487 | return; 488 | } 489 | 490 | log('Starting BrowserSync on port ' + port); 491 | 492 | // If build: watches the files, builds, and restarts browser-sync. 493 | // If dev: watches less, compiles it to css, browser-sync handles reload 494 | if (isDev) { 495 | gulp.watch([config.less], ['styles']) 496 | .on('change', changeEvent); 497 | } else { 498 | gulp.watch([config.less, config.js, config.html], ['optimize', browserSync.reload]) 499 | .on('change', changeEvent); 500 | } 501 | 502 | var options = { 503 | proxy: 'localhost:' + port, 504 | port: 3000, 505 | files: isDev ? [ 506 | config.client + '**/*.*', 507 | '!' + config.less, 508 | config.temp + '**/*.css' 509 | ] : [], 510 | ghostMode: { // these are the defaults t,f,t,t 511 | clicks: true, 512 | location: false, 513 | forms: true, 514 | scroll: true 515 | }, 516 | injectChanges: true, 517 | logFileChanges: true, 518 | logLevel: 'debug', 519 | logPrefix: 'gulp-patterns', 520 | notify: true, 521 | reloadDelay: 0 //1000 522 | } ; 523 | if (specRunner) { 524 | options.startPath = config.specRunnerFile; 525 | } 526 | 527 | browserSync(options); 528 | } 529 | 530 | /** 531 | * Start Plato inspector and visualizer 532 | */ 533 | function startPlatoVisualizer(done) { 534 | log('Running Plato'); 535 | 536 | var files = glob.sync(config.plato.js); 537 | var excludeFiles = /.*\.spec\.js/; 538 | var plato = require('plato'); 539 | 540 | var options = { 541 | title: 'Plato Inspections Report', 542 | exclude: excludeFiles 543 | }; 544 | var outputDir = config.report + '/plato'; 545 | 546 | plato.inspect(files, outputDir, options, platoCompleted); 547 | 548 | function platoCompleted(report) { 549 | var overview = plato.getOverviewReport(report); 550 | if (args.verbose) { 551 | log(overview.summary); 552 | } 553 | if (done) { done(); } 554 | } 555 | } 556 | 557 | /** 558 | * Start the tests using karma. 559 | * @param {boolean} singleRun - True means run once and end (CI), or keep running (dev) 560 | * @param {Function} done - Callback to fire when karma is done 561 | * @return {undefined} 562 | */ 563 | function startTests(singleRun, done) { 564 | var child; 565 | var excludeFiles = []; 566 | var fork = require('child_process').fork; 567 | var karma = require('karma').server; 568 | var serverSpecs = config.serverIntegrationSpecs; 569 | 570 | if (args.startServers) { 571 | log('Starting servers'); 572 | var savedEnv = process.env; 573 | savedEnv.NODE_ENV = 'dev'; 574 | savedEnv.PORT = 8888; 575 | child = fork(config.nodeServer); 576 | } else { 577 | if (serverSpecs && serverSpecs.length) { 578 | excludeFiles = serverSpecs; 579 | } 580 | } 581 | 582 | karma.start({ 583 | configFile: __dirname + '/karma.conf.js', 584 | exclude: excludeFiles, 585 | singleRun: !!singleRun 586 | }, karmaCompleted); 587 | 588 | //////////////// 589 | 590 | function karmaCompleted(karmaResult) { 591 | log('Karma completed'); 592 | if (child) { 593 | log('shutting down the child process'); 594 | child.kill(); 595 | } 596 | if (karmaResult === 1) { 597 | done('karma: tests failed with code ' + karmaResult); 598 | } else { 599 | done(); 600 | } 601 | } 602 | } 603 | 604 | /** 605 | * Formatter for bytediff to display the size changes after processing 606 | * @param {Object} data - byte data 607 | * @return {String} Difference in bytes, formatted 608 | */ 609 | function bytediffFormatter(data) { 610 | var difference = (data.savings > 0) ? ' smaller.' : ' larger.'; 611 | return data.fileName + ' went from ' + 612 | (data.startSize / 1000).toFixed(2) + ' kB to ' + 613 | (data.endSize / 1000).toFixed(2) + ' kB and is ' + 614 | formatPercent(1 - data.percent, 2) + '%' + difference; 615 | } 616 | 617 | /** 618 | * Log an error message and emit the end of a task 619 | */ 620 | function errorLogger(error) { 621 | log('*** Start of Error ***'); 622 | log(error); 623 | log('*** End of Error ***'); 624 | this.emit('end'); 625 | } 626 | 627 | /** 628 | * Format a number as a percentage 629 | * @param {Number} num Number to format as a percent 630 | * @param {Number} precision Precision of the decimal 631 | * @return {String} Formatted perentage 632 | */ 633 | function formatPercent(num, precision) { 634 | return (num * 100).toFixed(precision); 635 | } 636 | 637 | /** 638 | * Format and return the header for files 639 | * @return {String} Formatted file header 640 | */ 641 | function getHeader() { 642 | var pkg = require('./package.json'); 643 | var template = ['/**', 644 | ' * <%%= pkg.name %> - <%%= pkg.description %>', 645 | ' * @authors <%%= pkg.authors %>', 646 | ' * @version v<%%= pkg.version %>', 647 | ' * @link <%%= pkg.homepage %>', 648 | ' * @license <%%= pkg.license %>', 649 | ' */', 650 | '' 651 | ].join('\n'); 652 | return $.header(template, { 653 | pkg: pkg 654 | }); 655 | } 656 | 657 | /** 658 | * Log a message or series of messages using chalk's blue color. 659 | * Can pass in a string, object or array. 660 | */ 661 | function log(msg) { 662 | if (typeof(msg) === 'object') { 663 | for (var item in msg) { 664 | if (msg.hasOwnProperty(item)) { 665 | $.util.log($.util.colors.blue(msg[item])); 666 | } 667 | } 668 | } else { 669 | $.util.log($.util.colors.blue(msg)); 670 | } 671 | } 672 | 673 | /** 674 | * Show OS level notification using node-notifier 675 | */ 676 | function notify(options) { 677 | var notifier = require('node-notifier'); 678 | var notifyOptions = { 679 | sound: 'Bottle', 680 | contentImage: path.join(__dirname, 'gulp.png'), 681 | icon: path.join(__dirname, 'gulp.png') 682 | }; 683 | _.assign(notifyOptions, options); 684 | notifier.notify(notifyOptions); 685 | } 686 | 687 | module.exports = gulp; 688 | -------------------------------------------------------------------------------- /app/templates/_karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | var gulpConfig = require('./gulp.config')(); 3 | 4 | config.set({ 5 | // base path that will be used to resolve all patterns (eg. files, exclude) 6 | basePath: './', 7 | 8 | // frameworks to use 9 | // some available frameworks: https://npmjs.org/browse/keyword/karma-adapter 10 | frameworks: ['mocha', 'chai', 'sinon', 'chai-sinon'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: gulpConfig.karma.files, 14 | 15 | // list of files to exclude 16 | exclude: gulpConfig.karma.exclude, 17 | 18 | proxies: { 19 | '/': 'http://localhost:8888/' 20 | }, 21 | 22 | // preprocess matching files before serving them to the browser 23 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 24 | preprocessors: gulpConfig.karma.preprocessors, 25 | 26 | // test results reporter to use 27 | // possible values: 'dots', 'progress', 'coverage' 28 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 29 | reporters: ['progress', 'coverage'], 30 | 31 | coverageReporter: { 32 | dir: gulpConfig.karma.coverage.dir, 33 | reporters: gulpConfig.karma.coverage.reporters 34 | }, 35 | 36 | // web server port 37 | port: 9876, 38 | 39 | // enable / disable colors in the output (reporters and logs) 40 | colors: true, 41 | 42 | // level of logging 43 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || 44 | // config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 45 | logLevel: config.LOG_INFO, 46 | 47 | // enable / disable watching file and executing tests whenever any file changes 48 | autoWatch: true, 49 | 50 | // start these browsers 51 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 52 | // browsers: ['Chrome', 'ChromeCanary', 'FirefoxAurora', 'Safari', 'PhantomJS'], 53 | browsers: ['PhantomJS'], 54 | 55 | // Continuous Integration mode 56 | // if true, Karma captures browsers, runs the tests and exits 57 | singleRun: false 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /app/templates/_package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= appName %>", 3 | "description": "<%= appName %> Project Generated from HotTowel Angular", 4 | "version": "0.0.0", 5 | "scripts": { 6 | "init": "npm install", 7 | "install": "bower install", 8 | "start": "node src/server/app.js", 9 | "test": "gulp test" 10 | }, 11 | "dependencies": { 12 | "body-parser": "^1.8.2", 13 | "express": "^4.9.3", 14 | "morgan": "^1.1.1", 15 | "serve-favicon": "^2.0.1" 16 | }, 17 | "devDependencies": { 18 | "browser-sync": "^1.5.8", 19 | "chalk": "^0.5.1", 20 | "dateformat": "^1.0.8-1.2.3", 21 | "debug": "^2.0.0", 22 | "del": "^0.1.3", 23 | "glob": "^4.0.5", 24 | "gulp": "^3.8.10", 25 | "gulp-angular-templatecache": "^1.4.2", 26 | "gulp-autoprefixer": "^1.0.1", 27 | "gulp-bump": "^0.1.11", 28 | "gulp-bytediff": "^0.2.0", 29 | "gulp-concat": "^2.3.3", 30 | "gulp-csso": "^0.2.9", 31 | "gulp-filter": "^1.0.2", 32 | "gulp-header": "^1.2.2", 33 | "gulp-if": "^1.2.5", 34 | "gulp-imagemin": "^1.0.1", 35 | "gulp-inject": "^1.0.1", 36 | "gulp-jscs": "^1.1.2", 37 | "gulp-jshint": "^1.7.1", 38 | "gulp-less": "^3.0.1", 39 | "gulp-load-plugins": "^0.7.0", 40 | "gulp-minify-html": "^0.1.7", 41 | "gulp-ng-annotate": "^0.5.2", 42 | "gulp-nodemon": "^1.0.4", 43 | "gulp-order": "^1.1.1", 44 | "gulp-plumber": "^0.6.6", 45 | "gulp-print": "^1.1.0", 46 | "gulp-rev": "^1.1.0", 47 | "gulp-rev-replace": "^0.3.1", 48 | "gulp-sourcemaps": "^1.1.5", 49 | "gulp-task-listing": "^1.0.0", 50 | "gulp-uglify": "^1.0.1", 51 | "gulp-useref": "^1.0.2", 52 | "gulp-util": "^3.0.1", 53 | "jshint-stylish": "^1.0.0", 54 | "lodash": "^2.4.1", 55 | "method-override": "^1.0.2", 56 | "minimist": "^1.1.0", 57 | "node-notifier": "^4.0.3", 58 | "plato": "^1.2.0", 59 | "q": "^1.0.1", 60 | "wiredep": "^2.1.0", 61 | "yargs": "^1.3.3" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/templates/bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "scripts": { 4 | "postinstall": "gulp wiredep" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /app/templates/editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /app/templates/gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | bower_components 30 | 31 | build 32 | 33 | .tmp 34 | -------------------------------------------------------------------------------- /app/templates/gulp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jlmonteagudo/generator-angular-crud/780716d313248b44fd6aece0091a0dcb757fb684/app/templates/gulp.png -------------------------------------------------------------------------------- /app/templates/jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "excludeFiles": ["node_modules/**", "bower_components/**"], 3 | 4 | "requireCurlyBraces": [ 5 | "if", 6 | "else", 7 | "for", 8 | "while", 9 | "do", 10 | "try", 11 | "catch" 12 | ], 13 | "requireOperatorBeforeLineBreak": true, 14 | "requireCamelCaseOrUpperCaseIdentifiers": true, 15 | "maximumLineLength": { 16 | "value": 100, 17 | "allowComments": true, 18 | "allowRegex": true 19 | }, 20 | "validateIndentation": 4, 21 | "validateQuoteMarks": "'", 22 | 23 | "disallowMultipleLineStrings": true, 24 | "disallowMixedSpacesAndTabs": true, 25 | "disallowTrailingWhitespace": true, 26 | "disallowSpaceAfterPrefixUnaryOperators": true, 27 | "disallowMultipleVarDecl": null, 28 | 29 | "requireSpaceAfterKeywords": [ 30 | "if", 31 | "else", 32 | "for", 33 | "while", 34 | "do", 35 | "switch", 36 | "return", 37 | "try", 38 | "catch" 39 | ], 40 | "requireSpaceBeforeBinaryOperators": [ 41 | "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", 42 | "&=", "|=", "^=", "+=", 43 | 44 | "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&", 45 | "|", "^", "&&", "||", "===", "==", ">=", 46 | "<=", "<", ">", "!=", "!==" 47 | ], 48 | "requireSpaceAfterBinaryOperators": true, 49 | "requireSpacesInConditionalExpression": true, 50 | "requireSpaceBeforeBlockStatements": true, 51 | "requireLineFeedAtFileEnd": true, 52 | "disallowSpacesInsideObjectBrackets": "all", 53 | "disallowSpacesInsideArrayBrackets": "all", 54 | "disallowSpacesInsideParentheses": true, 55 | 56 | "validateJSDoc": { 57 | "checkParamNames": true, 58 | "requireParamTypes": true 59 | }, 60 | 61 | "disallowMultipleLineBreaks": true, 62 | 63 | "disallowCommaBeforeLineBreak": null, 64 | "disallowDanglingUnderscores": null, 65 | "disallowEmptyBlocks": null, 66 | "disallowMultipleLineStrings": null, 67 | "disallowTrailingComma": null, 68 | "requireCommaBeforeLineBreak": null, 69 | "requireDotNotation": null, 70 | "requireMultipleVarDecl": null, 71 | "requireParenthesesAroundIIFE": true 72 | } 73 | -------------------------------------------------------------------------------- /app/templates/jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "bitwise": true, 3 | "camelcase": true, 4 | "curly": true, 5 | "eqeqeq": true, 6 | "es3": false, 7 | "forin": true, 8 | "freeze": true, 9 | "immed": true, 10 | "indent": 4, 11 | "latedef": "nofunc", 12 | "newcap": true, 13 | "noarg": true, 14 | "noempty": true, 15 | "nonbsp": true, 16 | "nonew": true, 17 | "plusplus": false, 18 | "quotmark": "single", 19 | "undef": true, 20 | "unused": false, 21 | "strict": false, 22 | "maxparams": 10, 23 | "maxdepth": 5, 24 | "maxstatements": 40, 25 | "maxcomplexity": 8, 26 | "maxlen": 120, 27 | 28 | "asi": false, 29 | "boss": false, 30 | "debug": false, 31 | "eqnull": true, 32 | "esnext": false, 33 | "evil": false, 34 | "expr": false, 35 | "funcscope": false, 36 | "globalstrict": false, 37 | "iterator": false, 38 | "lastsemic": false, 39 | "laxbreak": false, 40 | "laxcomma": false, 41 | "loopfunc": true, 42 | "maxerr": 50, 43 | "moz": false, 44 | "multistr": false, 45 | "notypeof": false, 46 | "proto": false, 47 | "scripturl": false, 48 | "shadow": false, 49 | "sub": true, 50 | "supernew": false, 51 | "validthis": false, 52 | "noyield": false, 53 | 54 | "browser": true, 55 | "node": true, 56 | 57 | "globals": { 58 | "angular": false 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/templates/src/client/_index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | <%= appName %> 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 |
29 |
30 |
31 | <%= appName %> 32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /app/templates/src/client/_specs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Spec Runner 8 | 9 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

Spec Runner

24 |

Make sure the REMOTE server is running
25 | Click on a description title to narrow the scope to just its specs 26 | (see " 27 | ?grep" in address bar).
28 | Click on a spec title to see the test implementation.
29 | Click on page title to start over. 30 |

31 | 32 |
33 | 34 | 35 | 36 | 37 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /app/templates/src/client/app/app.module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('app', [ 5 | 'app.core', 6 | 'app.widgets', 7 | 'app.dashboard', 8 | 'app.layout' 9 | ]); 10 | 11 | })(); 12 | -------------------------------------------------------------------------------- /app/templates/src/client/app/blocks/exception/exception-handler.provider.js: -------------------------------------------------------------------------------- 1 | // Include in index.html so that app level exceptions are handled. 2 | // Exclude from testRunner.html which should run exactly what it wants to run 3 | (function() { 4 | 'use strict'; 5 | 6 | angular 7 | .module('blocks.exception') 8 | .provider('exceptionHandler', exceptionHandlerProvider) 9 | .config(config); 10 | 11 | /** 12 | * Must configure the exception handling 13 | * @return {[type]} 14 | */ 15 | function exceptionHandlerProvider() { 16 | /* jshint validthis:true */ 17 | this.config = { 18 | appErrorPrefix: undefined 19 | }; 20 | 21 | this.configure = function (appErrorPrefix) { 22 | this.config.appErrorPrefix = appErrorPrefix; 23 | }; 24 | 25 | this.$get = function() { 26 | return {config: this.config}; 27 | }; 28 | } 29 | 30 | config.$inject = ['$provide']; 31 | 32 | /** 33 | * Configure by setting an optional string value for appErrorPrefix. 34 | * Accessible via config.appErrorPrefix (via config value). 35 | * @param {[type]} $provide 36 | * @return {[type]} 37 | * @ngInject 38 | */ 39 | function config($provide) { 40 | $provide.decorator('$exceptionHandler', extendExceptionHandler); 41 | } 42 | 43 | extendExceptionHandler.$inject = ['$delegate', 'exceptionHandler', 'logger']; 44 | 45 | /** 46 | * Extend the $exceptionHandler service to also display a toast. 47 | * @param {Object} $delegate 48 | * @param {Object} exceptionHandler 49 | * @param {Object} logger 50 | * @return {Function} the decorated $exceptionHandler service 51 | */ 52 | function extendExceptionHandler($delegate, exceptionHandler, logger) { 53 | return function(exception, cause) { 54 | var appErrorPrefix = exceptionHandler.config.appErrorPrefix || ''; 55 | var errorData = {exception: exception, cause: cause}; 56 | exception.message = appErrorPrefix + exception.message; 57 | $delegate(exception, cause); 58 | /** 59 | * Could add the error to a service's collection, 60 | * add errors to $rootScope, log errors to remote web server, 61 | * or log locally. Or throw hard. It is entirely up to you. 62 | * throw exception; 63 | * 64 | * @example 65 | * throw { message: 'error message we added' }; 66 | */ 67 | logger.error(exception.message, errorData); 68 | }; 69 | } 70 | })(); 71 | -------------------------------------------------------------------------------- /app/templates/src/client/app/blocks/exception/exception-handler.provider.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('blocks.exception', function() { 3 | var exceptionHandlerProvider; 4 | var mocks = { 5 | errorMessage: 'fake error', 6 | prefix: '[TEST]: ' 7 | }; 8 | 9 | beforeEach(function() { 10 | bard.appModule('blocks.exception', function(_exceptionHandlerProvider_) { 11 | exceptionHandlerProvider = _exceptionHandlerProvider_; 12 | }); 13 | bard.inject('$rootScope'); 14 | }); 15 | 16 | bard.verifyNoOutstandingHttpRequests(); 17 | 18 | describe('$exceptionHandler', function() { 19 | it('should have a dummy test', inject(function() { 20 | expect(true).to.equal(true); 21 | })); 22 | 23 | it('should be defined', inject(function($exceptionHandler) { 24 | expect($exceptionHandler).to.be.defined; 25 | })); 26 | 27 | it('should have configuration', inject(function($exceptionHandler) { 28 | expect($exceptionHandler.config).to.be.defined; 29 | })); 30 | 31 | describe('with appErrorPrefix', function() { 32 | beforeEach(function() { 33 | exceptionHandlerProvider.configure(mocks.prefix); 34 | }); 35 | 36 | it('should have exceptionHandlerProvider defined', inject(function() { 37 | expect(exceptionHandlerProvider).to.be.defined; 38 | })); 39 | 40 | it('should have appErrorPrefix defined', inject(function() { 41 | expect(exceptionHandlerProvider.$get().config.appErrorPrefix).to.be.defined; 42 | })); 43 | 44 | it('should have appErrorPrefix set properly', inject(function() { 45 | expect(exceptionHandlerProvider.$get().config.appErrorPrefix) 46 | .to.equal(mocks.prefix); 47 | })); 48 | 49 | it('should throw an error when forced', inject(function() { 50 | expect(functionThatWillThrow).to.throw(); 51 | })); 52 | 53 | it('manual error is handled by decorator', function() { 54 | var exception; 55 | exceptionHandlerProvider.configure(mocks.prefix); 56 | try { 57 | $rootScope.$apply(functionThatWillThrow); 58 | } 59 | catch (ex) { 60 | exception = ex; 61 | expect(ex.message).to.equal(mocks.prefix + mocks.errorMessage); 62 | } 63 | }); 64 | }); 65 | }); 66 | 67 | function functionThatWillThrow() { 68 | throw new Error(mocks.errorMessage); 69 | } 70 | }); 71 | -------------------------------------------------------------------------------- /app/templates/src/client/app/blocks/exception/exception.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('blocks.exception') 6 | .factory('exception', exception); 7 | 8 | exception.$inject = ['logger']; 9 | 10 | /* @ngInject */ 11 | function exception(logger) { 12 | var service = { 13 | catcher: catcher 14 | }; 15 | return service; 16 | 17 | function catcher(message) { 18 | return function(reason) { 19 | logger.error(message, reason); 20 | }; 21 | } 22 | } 23 | })(); 24 | -------------------------------------------------------------------------------- /app/templates/src/client/app/blocks/exception/exception.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('blocks.exception', ['blocks.logger']); 5 | })(); 6 | -------------------------------------------------------------------------------- /app/templates/src/client/app/blocks/logger/logger.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('blocks.logger') 6 | .factory('logger', logger); 7 | 8 | logger.$inject = ['$log', 'toastr']; 9 | 10 | /* @ngInject */ 11 | function logger($log, toastr) { 12 | var service = { 13 | showToasts: true, 14 | 15 | error : error, 16 | info : info, 17 | success : success, 18 | warning : warning, 19 | 20 | // straight to console; bypass toastr 21 | log : $log.log 22 | }; 23 | 24 | return service; 25 | ///////////////////// 26 | 27 | function error(message, data, title) { 28 | toastr.error(message, title); 29 | $log.error('Error: ' + message, data); 30 | } 31 | 32 | function info(message, data, title) { 33 | toastr.info(message, title); 34 | $log.info('Info: ' + message, data); 35 | } 36 | 37 | function success(message, data, title) { 38 | toastr.success(message, title); 39 | $log.info('Success: ' + message, data); 40 | } 41 | 42 | function warning(message, data, title) { 43 | toastr.warning(message, title); 44 | $log.warn('Warning: ' + message, data); 45 | } 46 | } 47 | }()); 48 | -------------------------------------------------------------------------------- /app/templates/src/client/app/blocks/logger/logger.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('blocks.logger', []); 5 | })(); 6 | -------------------------------------------------------------------------------- /app/templates/src/client/app/blocks/router/router-helper.provider.js: -------------------------------------------------------------------------------- 1 | /* Help configure the state-base ui.router */ 2 | (function() { 3 | 'use strict'; 4 | 5 | angular 6 | .module('blocks.router') 7 | .provider('routerHelper', routerHelperProvider); 8 | 9 | routerHelperProvider.$inject = ['$locationProvider', '$stateProvider', '$urlRouterProvider']; 10 | /* @ngInject */ 11 | function routerHelperProvider($locationProvider, $stateProvider, $urlRouterProvider) { 12 | /* jshint validthis:true */ 13 | var config = { 14 | docTitle: undefined, 15 | resolveAlways: {} 16 | }; 17 | 18 | $locationProvider.html5Mode(true); 19 | 20 | this.configure = function(cfg) { 21 | angular.extend(config, cfg); 22 | }; 23 | 24 | this.$get = RouterHelper; 25 | RouterHelper.$inject = ['$location', '$rootScope', '$state', 'logger']; 26 | /* @ngInject */ 27 | function RouterHelper($location, $rootScope, $state, logger) { 28 | var handlingStateChangeError = false; 29 | var hasOtherwise = false; 30 | var stateCounts = { 31 | errors: 0, 32 | changes: 0 33 | }; 34 | 35 | var service = { 36 | configureStates: configureStates, 37 | getStates: getStates, 38 | stateCounts: stateCounts 39 | }; 40 | 41 | init(); 42 | 43 | return service; 44 | 45 | /////////////// 46 | 47 | function configureStates(states, otherwisePath) { 48 | states.forEach(function(state) { 49 | state.config.resolve = 50 | angular.extend(state.config.resolve || {}, config.resolveAlways); 51 | $stateProvider.state(state.state, state.config); 52 | }); 53 | if (otherwisePath && !hasOtherwise) { 54 | hasOtherwise = true; 55 | $urlRouterProvider.otherwise(otherwisePath); 56 | } 57 | } 58 | 59 | function handleRoutingErrors() { 60 | // Route cancellation: 61 | // On routing error, go to the dashboard. 62 | // Provide an exit clause if it tries to do it twice. 63 | $rootScope.$on('$stateChangeError', 64 | function(event, toState, toParams, fromState, fromParams, error) { 65 | if (handlingStateChangeError) { 66 | return; 67 | } 68 | stateCounts.errors++; 69 | handlingStateChangeError = true; 70 | var destination = (toState && 71 | (toState.title || toState.name || toState.loadedTemplateUrl)) || 72 | 'unknown target'; 73 | var msg = 'Error routing to ' + destination + '. ' + 74 | (error.data || '') + '.
' + (error.statusText || '') + 75 | ': ' + (error.status || ''); 76 | logger.warning(msg, [toState]); 77 | $location.path('/'); 78 | } 79 | ); 80 | } 81 | 82 | function init() { 83 | handleRoutingErrors(); 84 | updateDocTitle(); 85 | } 86 | 87 | function getStates() { return $state.get(); } 88 | 89 | function updateDocTitle() { 90 | $rootScope.$on('$stateChangeSuccess', 91 | function(event, toState, toParams, fromState, fromParams) { 92 | stateCounts.changes++; 93 | handlingStateChangeError = false; 94 | var title = config.docTitle + ' ' + (toState.title || ''); 95 | $rootScope.title = title; // data bind to 96 | } 97 | ); 98 | } 99 | } 100 | } 101 | })(); 102 | -------------------------------------------------------------------------------- /app/templates/src/client/app/blocks/router/router.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('blocks.router', [ 5 | 'ui.router', 6 | 'blocks.logger' 7 | ]); 8 | })(); 9 | -------------------------------------------------------------------------------- /app/templates/src/client/app/core/404.html: -------------------------------------------------------------------------------- 1 | <section id="dashboard-view" class="mainbar"> 2 | <section class="matter"> 3 | <div class="container"> 4 | <div class="row"> 5 | <div class="col-md-12"> 6 | <ul class="today-datas"> 7 | <li class="bred"> 8 | <div class="pull-left"><i class="fa fa-warning"></i></div> 9 | <div class="datas-text pull-right"> 10 | <a><span class="bold">404</span></a>Page Not Found 11 | </div> 12 | <div class="clearfix"></div> 13 | </li> 14 | </ul> 15 | </div> 16 | </div> 17 | <div class="row"> 18 | <div class="widget wblue"> 19 | <div ht-widget-header title="Page Not Found" 20 | allow-collapse="true"></div> 21 | <div class="widget-content text-center text-info"> 22 | <div class="container"> 23 | No soup for you! 24 | </div> 25 | </div> 26 | <div class="widget-foot"> 27 | <div class="clearfix"></div> 28 | </div> 29 | </div> 30 | </div> 31 | </div> 32 | </div> 33 | </section> 34 | </section> 35 | -------------------------------------------------------------------------------- /app/templates/src/client/app/core/config.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | var core = angular.module('app.core'); 5 | 6 | core.config(toastrConfig); 7 | 8 | toastrConfig.$inject = ['toastr']; 9 | /* @ngInject */ 10 | function toastrConfig(toastr) { 11 | toastr.options.timeOut = 4000; 12 | toastr.options.positionClass = 'toast-bottom-right'; 13 | } 14 | 15 | var config = { 16 | appErrorPrefix: '[<%= appName %> Error] ', 17 | appTitle: '<%= appName %>' 18 | }; 19 | 20 | core.value('config', config); 21 | 22 | core.config(configure); 23 | 24 | configure.$inject = ['$logProvider', 'routerHelperProvider', 'exceptionHandlerProvider']; 25 | /* @ngInject */ 26 | function configure($logProvider, routerHelperProvider, exceptionHandlerProvider) { 27 | if ($logProvider.debugEnabled) { 28 | $logProvider.debugEnabled(true); 29 | } 30 | exceptionHandlerProvider.configure(config.appErrorPrefix); 31 | routerHelperProvider.configure({docTitle: config.appTitle + ': '}); 32 | } 33 | 34 | })(); 35 | -------------------------------------------------------------------------------- /app/templates/src/client/app/core/constants.js: -------------------------------------------------------------------------------- 1 | /* global toastr:false, moment:false */ 2 | (function() { 3 | 'use strict'; 4 | 5 | angular 6 | .module('app.core') 7 | .constant('toastr', toastr) 8 | .constant('moment', moment) 9 | .constant('API_BASE_URL', 'http://localhost:1337'); 10 | })(); 11 | -------------------------------------------------------------------------------- /app/templates/src/client/app/core/core.module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.core', [ 6 | 'ngAnimate', 7 | 'ngSanitize', 8 | 'blocks.exception', 9 | 'blocks.logger', 10 | 'blocks.router', 11 | 'ui.router', 12 | 'ngplus', 13 | 'ngResource', 14 | 'ui.bootstrap', 15 | 'ngTable', 16 | 'formly', 17 | 'formlyBootstrap' 18 | ]); 19 | })(); 20 | -------------------------------------------------------------------------------- /app/templates/src/client/app/core/core.route.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.core') 6 | .run(appRun); 7 | 8 | /* @ngInject */ 9 | function appRun(routerHelper) { 10 | var otherwise = '/404'; 11 | routerHelper.configureStates(getStates(), otherwise); 12 | } 13 | 14 | function getStates() { 15 | return [ 16 | { 17 | state: '404', 18 | config: { 19 | url: '/404', 20 | templateUrl: 'app/core/404.html', 21 | title: '404' 22 | } 23 | } 24 | ]; 25 | } 26 | })(); 27 | -------------------------------------------------------------------------------- /app/templates/src/client/app/core/core.route.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('core', function() { 3 | describe('state', function() { 4 | var controller; 5 | var views = { 6 | four0four: 'app/core/404.html' 7 | }; 8 | 9 | beforeEach(function() { 10 | module('app.core', bard.fakeToastr); 11 | bard.inject('$location', '$rootScope', '$state', '$templateCache'); 12 | $templateCache.put(views.core, ''); 13 | }); 14 | 15 | it('should map /404 route to 404 View template', function() { 16 | expect($state.get('404').templateUrl).to.equal(views.four0four); 17 | }); 18 | 19 | it('of dashboard should work with $state.go', function() { 20 | $state.go('404'); 21 | $rootScope.$apply(); 22 | expect($state.is('404')); 23 | }); 24 | 25 | it('should route /invalid to the otherwise (404) route', function() { 26 | $location.path('/invalid'); 27 | $rootScope.$apply(); 28 | expect($state.current.templateUrl).to.equal(views.four0four); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /app/templates/src/client/app/core/dataservice.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.core') 6 | .factory('dataservice', dataservice); 7 | 8 | dataservice.$inject = ['$http', '$q', 'logger']; 9 | /* @ngInject */ 10 | function dataservice($http, $q, logger) { 11 | var service = { 12 | getPeople: getPeople, 13 | getMessageCount: getMessageCount 14 | }; 15 | 16 | return service; 17 | 18 | function getMessageCount() { return $q.when(72); } 19 | 20 | function getPeople() { 21 | return $http.get('/api/people') 22 | .then(success) 23 | .catch(fail); 24 | 25 | function success(response) { 26 | return response.data; 27 | } 28 | 29 | function fail(error) { 30 | var msg = 'query for people failed. ' + error.data.description; 31 | logger.error(msg); 32 | return $q.reject(msg); 33 | } 34 | } 35 | } 36 | })(); 37 | -------------------------------------------------------------------------------- /app/templates/src/client/app/core/directives/ng-really-click.directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular.module('app.core') 4 | .directive('ngReallyClick', ['$modal', function($modal) { 5 | 6 | var ModalInstanceCtrl = function($scope, $modalInstance) { 7 | $scope.ok = function() { 8 | $modalInstance.close(); 9 | }; 10 | 11 | $scope.cancel = function() { 12 | $modalInstance.dismiss('cancel'); 13 | }; 14 | }; 15 | 16 | return { 17 | restrict: 'A', 18 | scope: { 19 | ngReallyClick: '&' 20 | }, 21 | link: function(scope, element, attrs) { 22 | 23 | element.bind('click', function() { 24 | 25 | var message = attrs.ngReallyMessage || 'Are you sure ?'; 26 | var modalHtml = '<div class="modal-body">' + message + '</div>'; 27 | modalHtml += '<div class="modal-footer">'; 28 | modalHtml += '<button class="btn btn-primary" ng-click="ok()">OK</button>'; 29 | modalHtml += '<button class="btn btn-warning" ng-click="cancel()">'; 30 | modalHtml += 'Cancel</button>'; 31 | modalHtml += '</div>'; 32 | 33 | var modalInstance = $modal.open({ 34 | template: modalHtml, 35 | controller: ModalInstanceCtrl 36 | }); 37 | 38 | modalInstance.result.then(function() { 39 | scope.ngReallyClick(); 40 | }, function() { 41 | //Modal dismissed 42 | }); 43 | 44 | }); 45 | 46 | } 47 | 48 | }; 49 | 50 | } 51 | 52 | ]); 53 | -------------------------------------------------------------------------------- /app/templates/src/client/app/core/services/table.settings.service.js: -------------------------------------------------------------------------------- 1 | /* jshint ignore:start */ 2 | 3 | (function() { 4 | 'use strict'; 5 | 6 | angular 7 | .module('app.core') 8 | .factory('TableSettings', factory); 9 | 10 | factory.$inject = ['ngTableParams']; 11 | /* @ngInject */ 12 | function factory(ngTableParams) { 13 | 14 | var previousEntity; 15 | 16 | var getData = function(Entity) { 17 | return function($defer, params) { 18 | 19 | var requestParams = convertToServerRequestParams(params.$params); 20 | 21 | if (previousEntity !== Entity) { 22 | previousEntity = Entity; 23 | tableParams.$params.filter = {}; 24 | } 25 | 26 | Entity.get(requestParams, function(response) { 27 | params.total(response.total); 28 | $defer.resolve(response.results); 29 | }); 30 | }; 31 | }; 32 | 33 | var params = { 34 | page: 1, 35 | count: 5 36 | }; 37 | 38 | var settings = { 39 | total: 0, 40 | counts: [5, 10, 15], 41 | filterDelay: 0 42 | }; 43 | 44 | var tableParams = new ngTableParams(params, settings); 45 | 46 | var getParams = function(Entity) { 47 | tableParams.settings({getData: getData(Entity)}); 48 | return tableParams; 49 | }; 50 | 51 | var service = { 52 | getParams: getParams 53 | }; 54 | 55 | return service; 56 | 57 | } 58 | 59 | function convertToServerRequestParams(tableParams) { 60 | var serverParams = {}; 61 | var filterServerParams = getFilterServerParams(tableParams); 62 | var keySort = Object.keys(tableParams.sorting)[0]; 63 | 64 | if (filterServerParams) { 65 | serverParams.where = filterServerParams; 66 | }; 67 | 68 | if (keySort) { 69 | serverParams.sort = keySort + ' ' + tableParams.sorting[keySort]; 70 | }; 71 | 72 | serverParams.limit = tableParams.count; 73 | serverParams.skip = (tableParams.page - 1) * tableParams.count; 74 | 75 | return serverParams; 76 | } 77 | 78 | function getFilterServerParams(tableParams) { 79 | var tableFilters = tableParams.filter; 80 | var tableFiltersNumber = Object.keys(tableFilters).length; 81 | var filterServerParams; 82 | 83 | if (tableFiltersNumber === 1) { 84 | var key = Object.keys(tableFilters)[0]; 85 | filterServerParams = getFilterServer(key, tableFilters); 86 | } 87 | else if (tableFiltersNumber > 1) { 88 | var arrayFilters = []; 89 | var keys = Object.keys(tableFilters); 90 | var filter; 91 | 92 | keys.forEach(function(key) { 93 | filter = getFilterServer(key, tableFilters); 94 | arrayFilters.push(filter); 95 | }); 96 | 97 | filterServerParams = { 98 | 'and': arrayFilters 99 | }; 100 | } 101 | 102 | return filterServerParams; 103 | } 104 | 105 | function getFilterServer(key, tableFilters) { 106 | var filterServer = {}; 107 | 108 | filterServer[key] = { 109 | 'contains':tableFilters[key] 110 | }; 111 | 112 | return filterServer; 113 | } 114 | 115 | })(); 116 | -------------------------------------------------------------------------------- /app/templates/src/client/app/dashboard/dashboard.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.dashboard') 6 | .controller('DashboardController', DashboardController); 7 | 8 | DashboardController.$inject = ['$q', 'dataservice', 'logger']; 9 | /* @ngInject */ 10 | function DashboardController($q, dataservice, logger) { 11 | var vm = this; 12 | vm.news = { 13 | title: '<%= appName %>', 14 | description: 'generator-angular-crud allows creating entities ' + 15 | 'and CRUD operations very productively for AngularJS applications' 16 | }; 17 | vm.messageCount = 0; 18 | vm.people = []; 19 | vm.title = 'Dashboard'; 20 | 21 | activate(); 22 | 23 | function activate() { 24 | var promises = [getMessageCount(), getPeople()]; 25 | return $q.all(promises).then(function() { 26 | //logger.info('Activated Dashboard View'); 27 | }); 28 | } 29 | 30 | function getMessageCount() { 31 | return dataservice.getMessageCount().then(function (data) { 32 | vm.messageCount = data; 33 | return vm.messageCount; 34 | }); 35 | } 36 | 37 | function getPeople() { 38 | return dataservice.getPeople().then(function (data) { 39 | vm.people = data; 40 | return vm.people; 41 | }); 42 | } 43 | } 44 | })(); 45 | -------------------------------------------------------------------------------- /app/templates/src/client/app/dashboard/dashboard.controller.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('DashboardController', function() { 3 | var controller; 4 | var people = mockData.getMockPeople(); 5 | 6 | beforeEach(function() { 7 | bard.appModule('app.dashboard'); 8 | bard.inject('$controller', '$log', '$q', '$rootScope', 'dataservice'); 9 | }); 10 | 11 | beforeEach(function () { 12 | sinon.stub(dataservice, 'getPeople').returns($q.when(people)); 13 | controller = $controller('DashboardController'); 14 | $rootScope.$apply(); 15 | }); 16 | 17 | bard.verifyNoOutstandingHttpRequests(); 18 | 19 | describe('Dashboard controller', function() { 20 | it('should be created successfully', function () { 21 | expect(controller).to.be.defined; 22 | }); 23 | 24 | describe('after activate', function() { 25 | it('should have title of Dashboard', function () { 26 | expect(controller.title).to.equal('Dashboard'); 27 | }); 28 | 29 | it('should have logged "Activated"', function() { 30 | expect($log.info.logs).to.match(/Activated/); 31 | }); 32 | 33 | it('should have news', function () { 34 | expect(controller.news).to.not.be.empty; 35 | }); 36 | 37 | it('should have at least 1 person', function () { 38 | expect(controller.people).to.have.length.above(0); 39 | }); 40 | 41 | it('should have people count of 5', function () { 42 | expect(controller.people).to.have.length(7); 43 | }); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /app/templates/src/client/app/dashboard/dashboard.html: -------------------------------------------------------------------------------- 1 | <section id="dashboard-view" class="mainbar"> 2 | <section class="matter"> 3 | <div class="container"> 4 | <div class="row"> 5 | <div class="col-md-12"> 6 | <ul class="today-datas"> 7 | <li class="blightblue"> 8 | <div class="pull-left"><i class="fa fa-plane"></i></div> 9 | <div class="datas-text pull-right"> 10 | <span class="bold">May 18 - 19, 2015</span> Castle Resort, Neverland 11 | </div> 12 | <div class="clearfix"></div> 13 | </li> 14 | 15 | <li class="borange"> 16 | <div class="pull-left"><i class="fa fa-envelope"></i></div> 17 | <div class="datas-text pull-right"> 18 | <span class="bold">{{vm.messageCount}}</span> Messages 19 | </div> 20 | <div class="clearfix"></div> 21 | </li> 22 | 23 | </ul> 24 | </div> 25 | </div> 26 | <div class="row"> 27 | <div class="col-md-6"> 28 | <div class="widget wviolet"> 29 | <div ht-widget-header title="People" 30 | allow-collapse="true"></div> 31 | <div class="widget-content text-center text-info"> 32 | <table class="table table-condensed table-striped"> 33 | <thead> 34 | <tr> 35 | <th>First Name</th> 36 | <th>Last Name</th> 37 | <th>Age</th> 38 | <th>Location</th> 39 | </tr> 40 | </thead> 41 | <tbody> 42 | <tr ng-repeat="p in vm.people"> 43 | <td>{{p.firstName}}</td> 44 | <td>{{p.lastName}}</td> 45 | <td>{{p.age}}</td> 46 | <td>{{p.location}}</td> 47 | </tr> 48 | </tbody> 49 | </table> 50 | </div> 51 | <div class="widget-foot"> 52 | <div class="clearfix"></div> 53 | </div> 54 | </div> 55 | </div> 56 | <div class="col-md-6"> 57 | <div class="widget wgreen"> 58 | <div ht-widget-header title="{{vm.news.title}}" 59 | allow-collapse="true"></div> 60 | <div class="widget-content text-center text-info"> 61 | <small>{{vm.news.description}}</small> 62 | </div> 63 | <div class="widget-foot"> 64 | <div class="clearfix"></div> 65 | </div> 66 | </div> 67 | </div> 68 | </div> 69 | </div> 70 | </section> 71 | </section> -------------------------------------------------------------------------------- /app/templates/src/client/app/dashboard/dashboard.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('app.dashboard', [ 5 | 'app.core', 6 | 'app.widgets' 7 | ]); 8 | })(); 9 | -------------------------------------------------------------------------------- /app/templates/src/client/app/dashboard/dashboard.route.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.dashboard') 6 | .run(appRun); 7 | 8 | appRun.$inject = ['routerHelper']; 9 | /* @ngInject */ 10 | function appRun(routerHelper) { 11 | routerHelper.configureStates(getStates()); 12 | } 13 | 14 | function getStates() { 15 | return [ 16 | { 17 | state: 'dashboard', 18 | config: { 19 | url: '/', 20 | templateUrl: 'app/dashboard/dashboard.html', 21 | controller: 'DashboardController', 22 | controllerAs: 'vm', 23 | title: 'dashboard', 24 | settings: { 25 | nav: 1, 26 | content: '<i class="fa fa-dashboard"></i> Dashboard' 27 | } 28 | } 29 | } 30 | ]; 31 | } 32 | })(); 33 | -------------------------------------------------------------------------------- /app/templates/src/client/app/dashboard/dashboard.route.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('dashboard routes', function () { 3 | describe('state', function () { 4 | var controller; 5 | var view = 'app/dashboard/dashboard.html'; 6 | 7 | beforeEach(function() { 8 | module('app.dashboard', bard.fakeToastr); 9 | bard.inject('$httpBackend', '$location', '$rootScope', '$state', '$templateCache'); 10 | }); 11 | 12 | beforeEach(function() { 13 | $templateCache.put(view, ''); 14 | }); 15 | 16 | bard.verifyNoOutstandingHttpRequests(); 17 | 18 | it('should map state dashboard to url / ', function() { 19 | expect($state.href('dashboard', {})).to.equal('/'); 20 | }); 21 | 22 | it('should map /dashboard route to dashboard View template', function () { 23 | expect($state.get('dashboard').templateUrl).to.equal(view); 24 | }); 25 | 26 | it('of dashboard should work with $state.go', function () { 27 | $state.go('dashboard'); 28 | $rootScope.$apply(); 29 | expect($state.is('dashboard')); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /app/templates/src/client/app/layout/ht-sidebar.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.layout') 6 | .directive('htSidebar', htSidebar); 7 | 8 | /* @ngInject */ 9 | function htSidebar () { 10 | // Opens and closes the sidebar menu. 11 | // Usage: 12 | // <div ht-sidebar"> 13 | // <div ht-sidebar whenDoneAnimating="vm.sidebarReady()"> 14 | // Creates: 15 | // <div ht-sidebar class="sidebar"> 16 | var directive = { 17 | bindToController: true, 18 | link: link, 19 | restrict: 'EA', 20 | scope: { 21 | whenDoneAnimating: '&?' 22 | } 23 | }; 24 | return directive; 25 | 26 | function link(scope, element, attrs) { 27 | var $sidebarInner = element.find('.sidebar-inner'); 28 | var $dropdownElement = element.find('.sidebar-dropdown a'); 29 | element.addClass('sidebar'); 30 | $dropdownElement.click(dropdown); 31 | 32 | function dropdown(e) { 33 | var dropClass = 'dropy'; 34 | e.preventDefault(); 35 | if (!$dropdownElement.hasClass(dropClass)) { 36 | $sidebarInner.slideDown(350, scope.whenDoneAnimating); 37 | $dropdownElement.addClass(dropClass); 38 | } else if ($dropdownElement.hasClass(dropClass)) { 39 | $dropdownElement.removeClass(dropClass); 40 | $sidebarInner.slideUp(350, scope.whenDoneAnimating); 41 | } 42 | } 43 | } 44 | } 45 | })(); 46 | -------------------------------------------------------------------------------- /app/templates/src/client/app/layout/ht-sidebar.directive.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | /* jshint multistr:true */ 3 | describe('htSidebar directive: ', function () { 4 | var dropdownElement; 5 | var el; 6 | var innerElement; 7 | var isOpenClass = 'dropy'; 8 | var scope; 9 | 10 | beforeEach(module('app.layout')); 11 | 12 | beforeEach(inject(function($compile, $rootScope) { 13 | // The minimum necessary template HTML for this spec. 14 | // Simulates a menu link that opens and closes a dropdown of menu items 15 | // The `when-done-animating` attribute is optional (as is the vm's implementation) 16 | // 17 | // N.B.: the attribute value is supposed to be an expression that invokes a $scope method 18 | // so make sure the expression includes '()', e.g., "vm.sidebarReady(42)" 19 | // no harm if the expression fails ... but then scope.sidebarReady will be undefined. 20 | // All parameters in the expression are passed to vm.sidebarReady ... if it exists 21 | // 22 | // N.B.: We do NOT add this element to the browser DOM (although we could). 23 | // spec runs faster if we don't touch the DOM (even the PhantomJS DOM). 24 | el = angular.element( 25 | '<ht-sidebar when-done-animating="vm.sidebarReady(42)" > \ 26 | <div class="sidebar-dropdown"><a href="">Menu</a></div> \ 27 | <div class="sidebar-inner" style="display: none"></div> \ 28 | </ht-sidebar>'); 29 | 30 | // The spec examines changes to these template parts 31 | dropdownElement = el.find('.sidebar-dropdown a'); // the link to click 32 | innerElement = el.find('.sidebar-inner'); // container of menu items 33 | 34 | // ng's $compile service resolves nested directives (there are none in this example) 35 | // and binds the element to the scope (which must be a real ng scope) 36 | scope = $rootScope; 37 | $compile(el)(scope); 38 | 39 | // tell angular to look at the scope values right now 40 | scope.$digest(); 41 | })); 42 | 43 | /// tests /// 44 | describe('the isOpenClass', function () { 45 | it('is absent for a closed menu', function () { 46 | hasIsOpenClass(false); 47 | }); 48 | 49 | it('is added to a closed menu after clicking', function () { 50 | clickIt(); 51 | hasIsOpenClass(true); 52 | }); 53 | 54 | it('is present for an open menu', function () { 55 | openDropdown(); 56 | hasIsOpenClass(true); 57 | }); 58 | 59 | it('is removed from a closed menu after clicking', function () { 60 | openDropdown(); 61 | clickIt(); 62 | hasIsOpenClass(false); 63 | }); 64 | }); 65 | 66 | describe('when animating w/ jQuery fx off', function () { 67 | beforeEach(function () { 68 | // remember current state of jQuery's global FX duration switch 69 | this.oldFxOff = $.fx.off; 70 | // when jQuery fx are of, there is zero animation time; no waiting for animation to complete 71 | $.fx.off = true; 72 | // must add to DOM when testing jQuery animation result 73 | el.appendTo(document.body); 74 | }); 75 | 76 | afterEach(function () { 77 | $.fx.off = this.oldFxOff; 78 | el.remove(); 79 | }); 80 | 81 | it('dropdown is visible after opening a closed menu', function () { 82 | dropdownIsVisible(false); // hidden before click 83 | clickIt(); 84 | dropdownIsVisible(true); // visible after click 85 | }); 86 | 87 | it('dropdown is hidden after closing an open menu', function () { 88 | openDropdown(); 89 | dropdownIsVisible(true); // visible before click 90 | clickIt(); 91 | dropdownIsVisible(false); // hidden after click 92 | }); 93 | 94 | it('click triggers "when-done-animating" expression', function () { 95 | 96 | // spy on directive's callback when the animation is done 97 | var spy = sinon.spy(); 98 | 99 | // Recall the pertinent tag in the template ... 100 | // ' <div ht-sidebar when-done-animating="vm.sidebarReady(42)" > 101 | // therefore, the directive looks for scope.vm.sidebarReady 102 | // and should call that method with the value '42' 103 | scope.vm = {sidebarReady: spy}; 104 | 105 | // tell angular to look again for that vm.sidebarReady property 106 | scope.$digest(); 107 | 108 | // spy not called until after click which triggers the animation 109 | expect(spy).not.to.have.been.called; 110 | 111 | // this click triggers an animation 112 | clickIt(); 113 | 114 | // verify that the vm's method (sidebarReady) was called with '42' 115 | // FYI: spy.args[0] is the array of args passed to sidebarReady() 116 | expect(spy).to.have.been.calledWith(42); 117 | }); 118 | }); 119 | 120 | /////// helpers ////// 121 | 122 | // put the dropdown in the 'menu open' state 123 | function openDropdown() { 124 | dropdownElement.addClass(isOpenClass); 125 | innerElement.css('display', 'block'); 126 | } 127 | 128 | // click the "menu" link 129 | function clickIt() { 130 | dropdownElement.trigger('click'); 131 | } 132 | 133 | // assert whether the "menu" link has the class that means 'is open' 134 | function hasIsOpenClass(isTrue) { 135 | var hasClass = dropdownElement.hasClass(isOpenClass); 136 | expect(hasClass).equal(!!isTrue, 137 | 'dropdown has the "is open" class is ' + hasClass); 138 | } 139 | 140 | // assert whether the dropdown container is 'block' (visible) or 'none' (hidden) 141 | function dropdownIsVisible(isTrue) { 142 | var display = innerElement.css('display'); 143 | expect(display).to.equal(isTrue ? 'block' : 'none', 144 | 'innerElement display value is ' + display); 145 | } 146 | }); 147 | -------------------------------------------------------------------------------- /app/templates/src/client/app/layout/ht-top-nav.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.layout') 6 | .directive('htTopNav', htTopNav); 7 | 8 | /* @ngInject */ 9 | function htTopNav () { 10 | var directive = { 11 | bindToController: true, 12 | controller: TopNavController, 13 | controllerAs: 'vm', 14 | restrict: 'EA', 15 | scope: { 16 | 'navline': '=' 17 | }, 18 | templateUrl: 'app/layout/ht-top-nav.html' 19 | }; 20 | 21 | /* @ngInject */ 22 | function TopNavController() { 23 | var vm = this; 24 | } 25 | 26 | return directive; 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /app/templates/src/client/app/layout/ht-top-nav.html: -------------------------------------------------------------------------------- 1 | <nav class="navbar navbar-fixed-top navbar-inverse"> 2 | <div class="navbar-header"> 3 | <a href="/" class="navbar-brand"><span class="brand-title">{{vm.navline.title}}</span></a> 4 | <a class="btn navbar-btn navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> 5 | <span class="icon-bar"></span> 6 | <span class="icon-bar"></span> 7 | <span class="icon-bar"></span> 8 | </a> 9 | </div> 10 | <div class="navbar-collapse collapse"> 11 | <div class="pull-right navbar-logo"> 12 | <ul class="nav navbar-nav pull-right"> 13 | <li> 14 | <a ng-href="{{vm.navline.link}}" target="_blank"> 15 | {{vm.navline.text}} 16 | </a> 17 | </li> 18 | <li class="dropdown dropdown-big"> 19 | <a href="http://www.angularjs.org" target="_blank"> 20 | <img src="images/AngularJS-small.png" /> 21 | </a> 22 | </li> 23 | <li> 24 | <a href="http://www.gulpjs.com/" target="_blank"> 25 | <img src="images/gulp-tiny.png" /> 26 | </a> 27 | </li> 28 | </ul> 29 | </div> 30 | </div> 31 | </nav> 32 | -------------------------------------------------------------------------------- /app/templates/src/client/app/layout/layout.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('app.layout', ['app.core']); 5 | })(); 6 | -------------------------------------------------------------------------------- /app/templates/src/client/app/layout/shell.controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.layout') 6 | .controller('ShellController', ShellController); 7 | 8 | ShellController.$inject = ['$rootScope', '$timeout', 'config', 'logger']; 9 | /* @ngInject */ 10 | function ShellController($rootScope, $timeout, config, logger) { 11 | var vm = this; 12 | vm.busyMessage = 'Please wait ...'; 13 | vm.isBusy = true; 14 | $rootScope.showSplash = true; 15 | vm.navline = { 16 | title: config.appTitle, 17 | text: 'Developed with generator-angular-crud', 18 | link: 'https://github.com/jlmonteagudo/generator-angular-crud' 19 | }; 20 | 21 | activate(); 22 | 23 | function activate() { 24 | logger.success(config.appTitle + ' loaded!', null); 25 | hideSplash(); 26 | } 27 | 28 | function hideSplash() { 29 | //Force a 1 second delay so we can see the splash. 30 | $timeout(function() { 31 | $rootScope.showSplash = false; 32 | }, 1000); 33 | } 34 | } 35 | })(); 36 | -------------------------------------------------------------------------------- /app/templates/src/client/app/layout/shell.controller.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('ShellController', function() { 3 | var controller; 4 | 5 | beforeEach(function() { 6 | bard.appModule('app.layout'); 7 | bard.inject('$controller', '$q', '$rootScope', '$timeout', 'dataservice'); 8 | }); 9 | 10 | beforeEach(function () { 11 | controller = $controller('ShellController'); 12 | $rootScope.$apply(); 13 | }); 14 | 15 | bard.verifyNoOutstandingHttpRequests(); 16 | 17 | describe('Shell controller', function() { 18 | it('should be created successfully', function () { 19 | expect(controller).to.be.defined; 20 | }); 21 | 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /app/templates/src/client/app/layout/shell.html: -------------------------------------------------------------------------------- 1 | <div ng-controller="ShellController as vm"> 2 | <header class="clearfix"> 3 | <ht-top-nav navline="vm.navline"></ht-top-nav> 4 | </header> 5 | <section id="content" class="content"> 6 | <div ng-include="'app/layout/sidebar.html'"></div> 7 | 8 | <div ui-view class="shuffle-animation"></div> 9 | 10 | <div ngplus-overlay 11 | ngplus-overlay-delay-in="50" 12 | ngplus-overlay-delay-out="700" 13 | ngplus-overlay-animation="dissolve-animation"> 14 | <img src="images/busy.gif"/> 15 | 16 | <div class="page-spinner-message overlay-message">{{vm.busyMessage}}</div> 17 | </div> 18 | </section> 19 | </div> 20 | -------------------------------------------------------------------------------- /app/templates/src/client/app/layout/sidebar.controller.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.layout') 6 | .controller('SidebarController', SidebarController); 7 | 8 | SidebarController.$inject = ['$state', 'routerHelper']; 9 | /* @ngInject */ 10 | function SidebarController($state, routerHelper) { 11 | var vm = this; 12 | var states = routerHelper.getStates(); 13 | vm.isCurrent = isCurrent; 14 | 15 | activate(); 16 | 17 | function activate() { getNavRoutes(); } 18 | 19 | function getNavRoutes() { 20 | vm.navRoutes = states.filter(function(r) { 21 | return r.settings && r.settings.nav; 22 | }).sort(function(r1, r2) { 23 | return r1.settings.nav - r2.settings.nav; 24 | }); 25 | } 26 | 27 | function isCurrent(route) { 28 | if (!route.title || !$state.current || !$state.current.title) { 29 | return ''; 30 | } 31 | var menuName = route.title; 32 | return $state.current.title.substr(0, menuName.length) === menuName ? 'current' : ''; 33 | } 34 | } 35 | })(); 36 | -------------------------------------------------------------------------------- /app/templates/src/client/app/layout/sidebar.controller.spec.js: -------------------------------------------------------------------------------- 1 | /* jshint -W117, -W030 */ 2 | describe('layout', function() { 3 | describe('sidebar', function() { 4 | var controller; 5 | var views = { 6 | dashboard: 'app/dashboard/dashboard.html', 7 | customers: 'app/customers/customers.html' 8 | }; 9 | 10 | beforeEach(function() { 11 | module('app.layout', bard.fakeToastr); 12 | bard.inject('$controller', '$httpBackend', '$location', 13 | '$rootScope', '$state', 'routerHelper'); 14 | }); 15 | 16 | beforeEach(function() { 17 | routerHelper.configureStates(mockData.getMockStates(), '/'); 18 | controller = $controller('SidebarController'); 19 | $rootScope.$apply(); 20 | }); 21 | 22 | bard.verifyNoOutstandingHttpRequests(); 23 | 24 | it('should have isCurrent() for / to return `current`', function() { 25 | $location.path('/'); 26 | expect(controller.isCurrent($state.current)).to.equal('current'); 27 | }); 28 | 29 | it('should have isCurrent() for /customers to return `current`', function() { 30 | $location.path('/customers'); 31 | expect(controller.isCurrent($state.current)).to.equal('current'); 32 | }); 33 | 34 | it('should have isCurrent() for non route not return `current`', function() { 35 | $location.path('/invalid'); 36 | expect(controller.isCurrent({title: 'invalid'})).not.to.equal('current'); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /app/templates/src/client/app/layout/sidebar.html: -------------------------------------------------------------------------------- 1 | <div ng-controller="SidebarController as vm"> 2 | <ht-sidebar when-done-animating="vm.sidebarReady()"> 3 | <div class="sidebar-filler"></div> 4 | <div class="sidebar-dropdown"><a href="#">Menu</a></div> 5 | <div class="sidebar-inner"> 6 | <div class="sidebar-widget"></div> 7 | <ul class="navi"> 8 | <li class="nlightblue fade-selection-animation" ng-class="vm.isCurrent(r)" 9 | ng-repeat="r in vm.navRoutes"> 10 | <a ui-sref="{{r.name}}" 11 | ng-bind-html="r.settings.content"></a> 12 | </li> 13 | </ul> 14 | </div> 15 | </ht-sidebar> 16 | </div> 17 | -------------------------------------------------------------------------------- /app/templates/src/client/app/widgets/ht-img-person.directive.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.widgets') 6 | .directive('htImgPerson', htImgPerson); 7 | 8 | htImgPerson.$inject = ['config']; 9 | /* @ngInject */ 10 | function htImgPerson (config) { 11 | //Usage: 12 | //<img ht-img-person="{{person.imageSource}}"/> 13 | var basePath = config.imageBasePath; 14 | var unknownImage = config.unknownPersonImageSource; 15 | var directive = { 16 | link: link, 17 | restrict: 'A' 18 | }; 19 | return directive; 20 | 21 | function link(scope, element, attrs) { 22 | attrs.$observe('htImgPerson', function (value) { 23 | value = basePath + (value || unknownImage); 24 | attrs.$set('src', value); 25 | }); 26 | } 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /app/templates/src/client/app/widgets/ht-widget-header.directive.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.widgets') 6 | .directive('htWidgetHeader', htWidgetHeader); 7 | 8 | /* @ngInject */ 9 | function htWidgetHeader() { 10 | //Usage: 11 | //<div ht-widget-header title="vm.map.title"></div> 12 | // Creates: 13 | // <div ht-widget-header="" 14 | // title="Movie" 15 | // allow-collapse="true" </div> 16 | var directive = { 17 | scope: { 18 | 'title': '@', 19 | 'subtitle': '@', 20 | 'rightText': '@', 21 | 'allowCollapse': '@' 22 | }, 23 | templateUrl: 'app/widgets/widget-header.html', 24 | restrict: 'EA' 25 | }; 26 | return directive; 27 | } 28 | })(); 29 | -------------------------------------------------------------------------------- /app/templates/src/client/app/widgets/widget-header.html: -------------------------------------------------------------------------------- 1 | <div class="widget-head"> 2 | <div class="page-title pull-left">{{title}}</div> 3 | <small class="page-title-subtle" ng-show="subtitle">({{subtitle}})</small> 4 | <div class="widget-icons pull-right"></div> 5 | <small class="pull-right page-title-subtle" ng-show="rightText">{{rightText}}</small> 6 | <div class="clearfix"></div> 7 | </div> -------------------------------------------------------------------------------- /app/templates/src/client/app/widgets/widgets.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('app.widgets', []); 5 | })(); 6 | -------------------------------------------------------------------------------- /app/templates/src/client/images/AngularJS-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jlmonteagudo/generator-angular-crud/780716d313248b44fd6aece0091a0dcb757fb684/app/templates/src/client/images/AngularJS-small.png -------------------------------------------------------------------------------- /app/templates/src/client/images/busy.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jlmonteagudo/generator-angular-crud/780716d313248b44fd6aece0091a0dcb757fb684/app/templates/src/client/images/busy.gif -------------------------------------------------------------------------------- /app/templates/src/client/images/gulp-tiny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jlmonteagudo/generator-angular-crud/780716d313248b44fd6aece0091a0dcb757fb684/app/templates/src/client/images/gulp-tiny.png -------------------------------------------------------------------------------- /app/templates/src/client/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jlmonteagudo/generator-angular-crud/780716d313248b44fd6aece0091a0dcb757fb684/app/templates/src/client/images/icon.png -------------------------------------------------------------------------------- /app/templates/src/client/styles/styles.less: -------------------------------------------------------------------------------- 1 | @color_lightblue: #5bc0de; 2 | @color_blue: #1171a3; 3 | @color_success: #43c83c; 4 | @color_warning: #f88529; 5 | @color_important: #fa3031; 6 | @color_violet: #932ab6; 7 | 8 | 9 | * { 10 | /*-webkit-box-shadow: none !important; 11 | -ms-box-shadow: none !important; 12 | box-shadow: none !important; 13 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false) !important; 14 | -webkit-text-shadow: none !important; 15 | -ms-text-shadow: none !important; 16 | text-shadow: none !important;*/ 17 | font-family: "Segoe UI", Arial, Helvetica, sans-serif; 18 | -webkit-font-smoothing: antialiased; 19 | /*font-weight: 400;*/ 20 | } 21 | 22 | 23 | .ng-table th.filter .input-filter { 24 | font-weight: normal; 25 | margin-top: 10px; 26 | margin-bottom: 10px; 27 | } 28 | 29 | .ng-table-pager { 30 | margin-top: 20px; 31 | margin-bottom: 20px; 32 | } 33 | 34 | .ng-table-pagination { 35 | font-size: 14px; 36 | } 37 | 38 | .table-actions { 39 | width: 160px; 40 | } 41 | 42 | /* 43 | .btn, .badge, .modal, .modal-dialog, .modal-content, 44 | input, select { 45 | -ms-border-radius: 0 !important; 46 | border-radius: 0 !important; 47 | } 48 | */ 49 | 50 | input, select { 51 | padding: 4px !important; 52 | /*width: 65% !important;*/ 53 | display: inline-block !important; 54 | } 55 | 56 | 57 | .navbar * { 58 | background-image: none !important; 59 | -webkit-text-shadow: none !important; 60 | -ms-text-shadow: none !important; 61 | text-shadow: none !important; 62 | } 63 | 64 | form * { 65 | font-family: "Segoe UI", Arial, Helvetica, sans-serif; 66 | } 67 | 68 | input, textarea { 69 | background-image: none !important; 70 | border: 1px solid #ccc !important; 71 | font-family: "Segoe UI", Arial, Helvetica, sans-serif; 72 | } 73 | 74 | select { 75 | font-size: 13px !important; 76 | font-family: "Segoe UI", Arial, Helvetica, sans-serif; 77 | } 78 | 79 | input[type="radio"], input[type="checkbox"] { 80 | margin: 0 0 0 !important; 81 | margin-right: 5px !important; 82 | line-height: 23px !important; 83 | border: 0 !important; 84 | } 85 | 86 | .btn { 87 | background-image: none !important; 88 | font-family: "Segoe UI", Arial, Helvetica, sans-serif; 89 | font-size: 13px !important; 90 | } 91 | .btn.btn-form-md { 92 | margin: 0 .5em .2em .5em; 93 | width: 9em; 94 | } 95 | .btn.btn-mini { 96 | font-size: 11px !important; 97 | } 98 | 99 | body { 100 | font-size: 13px; 101 | line-height: 23px; 102 | color: #666; 103 | background: #111; 104 | padding-top: 45px; 105 | -webkit-font-smoothing: antialiased; 106 | } 107 | 108 | body .container { 109 | width: 97%; 110 | padding-left: 1.5%; 111 | padding-right: 1.5%; 112 | } 113 | 114 | h1, h2, h3, h4, h5, h6 { 115 | padding: 2px 0; 116 | margin: 2px 0; 117 | color: #777; 118 | font-weight: 400; 119 | } 120 | 121 | h2 { 122 | font-size: 30px; 123 | line-height: 40px; 124 | } 125 | 126 | h3 { 127 | font-size: 23px; 128 | line-height: 33px; 129 | } 130 | 131 | h4 { 132 | font-size: 20px; 133 | line-height: 30px; 134 | } 135 | 136 | h5 { 137 | font-size: 18px; 138 | line-height: 28px; 139 | } 140 | 141 | h6 { 142 | font-size: 15px; 143 | line-height: 25px; 144 | } 145 | 146 | p { 147 | padding: 1px 0 !important; 148 | margin: 1px 0 !important; 149 | } 150 | 151 | a { 152 | color: #333; 153 | text-decoration: none !important; 154 | } 155 | 156 | a:hover { 157 | color: #888; 158 | text-decoration: none; 159 | } 160 | 161 | a:hover, a:focus, a:active { 162 | outline: 0; 163 | } 164 | 165 | .bold { 166 | font-weight: 600; 167 | } 168 | 169 | hr { 170 | margin: 8px 0 8px 0 !important; 171 | padding: 0 !important; 172 | border-top: 0; 173 | border-bottom: 1px solid #ddd !important; 174 | height: 0; 175 | } 176 | 177 | :focus { 178 | outline: none; 179 | } 180 | 181 | ::-moz-focus-inner { 182 | border: 0; 183 | } 184 | 185 | .well { 186 | padding: 10px 15px; 187 | } 188 | 189 | a.btn { 190 | font-size: 15px !important; 191 | } 192 | 193 | .label { 194 | font-weight: 400; 195 | padding: 3px 6px !important; 196 | font-size: 13px; 197 | } 198 | 199 | .control-label{ 200 | font-weight: 400 !important; 201 | width: 100%; 202 | } 203 | 204 | .badge { 205 | font-weight: 400; 206 | padding: 3px 8px; 207 | font-size: 13px; 208 | } 209 | 210 | .progress { 211 | height: 17px; 212 | line-height: 17px; 213 | margin: 5px 0 !important; 214 | border: 0; 215 | } 216 | 217 | .progress .bar { 218 | font-size: 12px; 219 | } 220 | 221 | /* Button colors */ 222 | 223 | .btn.btn-primary { 224 | background: @color_blue !important; 225 | } 226 | 227 | .btn.btn-primary:hover { 228 | background: #0f608b !important; 229 | } 230 | 231 | .btn.btn-info { 232 | background: @color_lightblue !important; 233 | } 234 | 235 | .btn.btn-info:hover { 236 | background: #459fc9 !important; 237 | } 238 | 239 | .btn.btn-success { 240 | background: @color_success !important; 241 | } 242 | 243 | .btn.btn-success:hover { 244 | background: #36a530 !important; 245 | } 246 | 247 | .btn.btn-warning { 248 | background: @color_warning !important; 249 | } 250 | 251 | .btn.btn-warning:hover { 252 | background: #d67323 !important; 253 | } 254 | 255 | .btn.btn-danger { 256 | background: @color_important !important; 257 | } 258 | 259 | .btn.btn-danger:hover { 260 | background: #d82829 !important; 261 | } 262 | 263 | /* Label colors */ 264 | 265 | .label.label-success, 266 | .badge.badge-success { 267 | background: @color_success !important; 268 | } 269 | 270 | .label.label-warning, 271 | .badge.badge-warning { 272 | background: @color_warning !important; 273 | } 274 | 275 | .label.label-important, 276 | .badge.badge-important { 277 | background: @color_important !important; 278 | } 279 | 280 | .label.label-info, 281 | .badge.badge-info { 282 | background: @color_lightblue !important; 283 | } 284 | 285 | /* Background colors */ 286 | 287 | 288 | .blightblue { 289 | background: @color_lightblue !important; 290 | color: #fff !important; 291 | border: 0 !important; 292 | } 293 | 294 | .bblue { 295 | background: @color_blue !important; 296 | color: #fff !important; 297 | border: 0 !important; 298 | } 299 | 300 | .bgreen { 301 | background: @color_success !important; 302 | color: #fff !important; 303 | border: 0 !important; 304 | } 305 | 306 | .borange { 307 | background: @color_warning !important; 308 | color: #fff !important; 309 | border: 0 !important; 310 | } 311 | 312 | .bred { 313 | background: @color_important !important; 314 | color: #fff !important; 315 | border: 0 !important; 316 | } 317 | 318 | .bviolet { 319 | background: @color_violet !important; 320 | color: #fff !important; 321 | border: 0 !important; 322 | } 323 | 324 | .blightblue h2, .blightblue h3, .blightblue h3, .blightblue h4, .blightblue h5, .blightblue h6, 325 | .bblue h2, .bblue h3, .bblue h3, .bblue h4, .bblue h5, .bblue h6, 326 | .bgreen h2, .bgreen h3, .bgreen h3, .bgreen h4, .bgreen h5, .bgreen h6, 327 | .bred h2, .bred h3, .bred h3, .bred h4, .bred h5, .bred h6, 328 | .bviolet h2, .bviolet h3, .bviolet h3, .bviolet h4, .bviolet h5, .bviolet h6, 329 | .borange h2, .borange h3, .borange h3, .borange h4, .borange h5, .borange h6 { 330 | color: #fff !important; 331 | } 332 | 333 | .blightblue a, 334 | .bblue a, 335 | .bgreen a, 336 | .bred a, 337 | .borange a, 338 | .bviolet a { 339 | color: #eee !important; 340 | } 341 | 342 | .blightblue a:hover, 343 | .bblue a:hover, 344 | .bgreen a:hover, 345 | .bred a:hover, 346 | .borange a:hover, 347 | .bviolet a:hover { 348 | color: #ddd !important; 349 | } 350 | 351 | /* Text colors */ 352 | 353 | .lightblue { 354 | color: @color_lightblue !important; 355 | } 356 | 357 | .blue { 358 | color: @color_blue !important; 359 | } 360 | 361 | .green { 362 | color: @color_success !important; 363 | } 364 | 365 | .orange { 366 | color: @color_warning !important; 367 | } 368 | 369 | .red { 370 | color: @color_important !important; 371 | } 372 | 373 | .violet { 374 | color: @color_violet !important; 375 | } 376 | 377 | 378 | /* Modal */ 379 | 380 | .modal-header { 381 | padding-top: 7px; 382 | padding-bottom: 7px; 383 | } 384 | 385 | /* Form */ 386 | 387 | form { 388 | margin: 10px; 389 | } 390 | 391 | form input, form button, form textarea, form select { 392 | font-size: 16px !important; 393 | } 394 | 395 | 396 | form label { 397 | /* 398 | font-size: 13px; 399 | line-height: 13px; 400 | */ 401 | } 402 | 403 | .form-inline button { 404 | margin-left: 15px; 405 | } 406 | 407 | .form-horizontal .control-label { 408 | width: 90px; 409 | } 410 | 411 | .form-horizontal .controls { 412 | margin-left: 110px; 413 | } 414 | 415 | .form-horizontal .controls:first-child { 416 | *padding-left: 100px; 417 | } 418 | 419 | .form-horizontal .form-actions { 420 | padding-left: 110px; 421 | } 422 | 423 | .form-actions { 424 | padding: 5px 20px 5px; 425 | background: transparent; 426 | border-top: 0; 427 | } 428 | 429 | /* Back to top */ 430 | 431 | .totop { 432 | position: fixed; 433 | bottom: 0; 434 | left: 0; 435 | z-index: 104400; 436 | background: @color_important; 437 | display: none; 438 | } 439 | 440 | .totop a, .totop a:visited { 441 | display: block; 442 | width: 30px; 443 | height: 30px; 444 | color: #fff; 445 | text-align: center; 446 | line-height: 30px; 447 | } 448 | 449 | .totop a:hover { 450 | color: #eee; 451 | text-decoration: none; 452 | } 453 | 454 | /* Half column - CHECK LATER */ 455 | 456 | .col-left { 457 | width: 48%; 458 | float: left; 459 | } 460 | 461 | .col-right { 462 | width: 48%; 463 | float: right; 464 | } 465 | 466 | /* Dropdown menu */ 467 | 468 | .dropdown-big .dropdown-menu { 469 | -moz-min-width: 250px; 470 | -ms-min-width: 250px; 471 | -o-min-width: 250px; 472 | -webkit-min-width: 250px; 473 | min-width: 250px; 474 | padding: 8px 10px; 475 | background: #fff; 476 | } 477 | 478 | .dropdown-big .dropdown-menu p { 479 | margin: 1px 0; 480 | padding: 1px 0; 481 | font-size: 12px; 482 | line-height: 18px; 483 | } 484 | 485 | .dropdown-big .dropdown-menu a { 486 | font-size: 13px; 487 | line-height: 23px; 488 | background: transparent; 489 | padding: 0; 490 | color: #444 !important; 491 | display: inline; 492 | } 493 | 494 | .dropdown-big .dropdown-menu a:hover { 495 | color: #777 !important; 496 | background: transparent !important; 497 | } 498 | 499 | .dropdown-big .dropdown-menu .drop-foot { 500 | text-align: center; 501 | } 502 | 503 | .dropdown-big .dropdown-menu .drop-foot a { 504 | font-size: 12px !important; 505 | } 506 | 507 | .dropdown-big .dropdown-menu hr { 508 | padding: 0; 509 | margin: 8px 0; 510 | border-top: 0 solid #aaa; 511 | border-bottom: 1px solid #eee; 512 | } 513 | 514 | .dropdown-big .dropdown-menu h5 { 515 | color: #666 !important; 516 | line-height: 18px; 517 | font-weight: bold; 518 | font-size: 13px; 519 | } 520 | 521 | .dropdown-menu { 522 | background: #fff; 523 | -ms-border-radius: 0; 524 | border-radius: 0; 525 | border: 1px solid #ddd; 526 | border-bottom: 1px solid #ddd; 527 | } 528 | 529 | .dropdown-menu li { 530 | color: #777; 531 | font-size: 13px; 532 | line-height: 18px; 533 | } 534 | 535 | .dropdown-menu li > a { 536 | color: #555; 537 | line-height: 23px !important; 538 | } 539 | 540 | .dropdown-menu li > a:hover { 541 | background: #f9f9f9 !important; 542 | filter: none; 543 | color: #888; 544 | } 545 | 546 | .dropdown-menu a:hover, .dropdown-menu a:focus { 547 | filter: none !important; 548 | background: #fff !important; 549 | } 550 | 551 | .dropdown-menu::after, .dropdown-menu::before { 552 | border: none !important; 553 | } 554 | 555 | /* Navbar */ 556 | 557 | .navbar { 558 | background: #000; 559 | } 560 | 561 | .navbar .container { 562 | width: 97% !important; 563 | } 564 | 565 | .navbar .nav > li > a { 566 | font-size: 13px !important; 567 | color: #fff !important; 568 | } 569 | 570 | .navbar i { 571 | margin-right: 3px; 572 | } 573 | 574 | .navbar .caret { 575 | border-top-color: #fff !important; 576 | border-bottom-color: #fff !important; 577 | } 578 | 579 | .navbar .nav-user-pic { 580 | width: 20px; 581 | margin-right: 10px; 582 | } 583 | 584 | .navbar .badge { 585 | margin-left: 5px; 586 | } 587 | 588 | 589 | /* Sidebar */ 590 | 591 | .sidebar { 592 | width: 230px; 593 | float: left; 594 | display: block; 595 | background: #111; 596 | color: #eee; 597 | position: relative; 598 | } 599 | 600 | .sidebar hr { 601 | border-bottom: 1px solid #333 !important; 602 | } 603 | 604 | .sidebar ul { 605 | padding: 0; 606 | margin: 0; 607 | list-style-type: none; 608 | } 609 | 610 | .sidebar ul li { 611 | list-style-type: none; 612 | } 613 | 614 | .sidebar .sidebar-inner { 615 | display: block; 616 | width: 100%; 617 | margin: 0 auto; 618 | position: absolute; 619 | z-index: 60; 620 | background: #111; 621 | } 622 | 623 | .sidebar .navi li i { 624 | margin-right: 5px; 625 | } 626 | 627 | .sidebar .navi li span i { 628 | margin: 0; 629 | } 630 | 631 | .sidebar .navi > li > a { 632 | display: block; 633 | padding: 12px 20px; 634 | font-size: 15px; 635 | line-height: 25px; 636 | color: #fff; 637 | text-decoration: none; 638 | border-bottom: 1px solid #222; 639 | background-color: #111; 640 | } 641 | 642 | .sidebar .navi > li > a:hover, .sidebar .navi > li.open > a { 643 | border-bottom: 1px solid #222; 644 | background-color: #222; 645 | color: #fff; 646 | } 647 | 648 | .sidebar .navi li ul { 649 | display: none; 650 | background: #181818; 651 | } 652 | 653 | .sidebar .navi li.open ul { 654 | display: block; 655 | } 656 | 657 | .sidebar .navi li ul li a { 658 | display: block; 659 | background: none; 660 | padding: 10px 0; 661 | padding-left: 30px; 662 | text-decoration: none; 663 | color: #999; 664 | border-bottom: 1px solid #222; 665 | } 666 | 667 | .sidebar .navi li ul li.active a { 668 | background: #131313; 669 | border-bottom: 1px solid #222; 670 | } 671 | 672 | .sidebar .navi li ul li a:hover { 673 | background: #131313; 674 | border-bottom: 1px solid #222; 675 | } 676 | 677 | /* Sidebar colors */ 678 | 679 | .sidebar .navi > li.nlightblue > a:hover, 680 | .sidebar .navi > li.open.nlightblue > a, 681 | .sidebar .navi > li.current.nlightblue > a { 682 | background: @color_lightblue !important; 683 | -webkit-transition: background 0.5s ease; 684 | -moz-transition: background 0.5s ease; 685 | -o-transition: background 0.5s ease; 686 | transition: background 0.5s ease; 687 | } 688 | 689 | .sidebar .navi > li.nblue > a:hover, 690 | .sidebar .navi > li.open.nblue > a, 691 | .sidebar .navi > li.current.nblue > a { 692 | background: @color_blue !important; 693 | -webkit-transition: background 0.5s ease; 694 | -moz-transition: background 0.5s ease; 695 | -o-transition: background 0.5s ease; 696 | transition: background 0.5s ease; 697 | } 698 | 699 | .sidebar .navi > li.ngreen > a:hover, 700 | .sidebar .navi > li.open.ngreen > a, 701 | .sidebar .navi > li.current.ngreen > a { 702 | background: @color_success !important; 703 | -webkit-transition: background 0.5s ease; 704 | -moz-transition: background 0.5s ease; 705 | -o-transition: background 0.5s ease; 706 | transition: background 0.5s ease; 707 | } 708 | 709 | .sidebar .navi > li.norange > a:hover, 710 | .sidebar .navi > li.open.norange > a, 711 | .sidebar .navi > li.current.norange > a { 712 | background: @color_warning !important; 713 | -webkit-transition: background 0.5s ease; 714 | -moz-transition: background 0.5s ease; 715 | -o-transition: background 0.5s ease; 716 | transition: background 0.5s ease; 717 | } 718 | 719 | .sidebar .navi > li.nred > a:hover, 720 | .sidebar .navi > li.open.nred > a, 721 | .sidebar .navi > li.current.nred > a { 722 | background: @color_important !important; 723 | -webkit-transition: background 0.5s ease; 724 | -moz-transition: background 0.5s ease; 725 | -o-transition: background 0.5s ease; 726 | transition: background 0.5s ease; 727 | } 728 | 729 | .sidebar .navi > li.nviolet > a:hover, 730 | .sidebar .navi > li.open.nviolet > a, 731 | .sidebar .navi > li.current.nviolet > a { 732 | background: @color_violet !important; 733 | -webkit-transition: background 0.5s ease; 734 | -moz-transition: background 0.5s ease; 735 | -o-transition: background 0.5s ease; 736 | transition: background 0.5s ease; 737 | } 738 | 739 | /* Sidebar dropdown */ 740 | 741 | .sidebar .sidebar-dropdown* { 742 | text-decoration: none; 743 | } 744 | 745 | .sidebar .sidebar-dropdown { 746 | display: none; 747 | } 748 | 749 | .sidebar .sidebar-dropdown a { 750 | color: #ddd; 751 | background-color: #343434; 752 | padding: 6px; 753 | text-transform: uppercase; 754 | text-align: center; 755 | font-size: 13px; 756 | display: block; 757 | border-top: 1px solid #666; 758 | border-bottom: 1px solid #333; 759 | } 760 | 761 | .sidebar .sidebar-dropdown a:hover { 762 | text-decoration: none; 763 | } 764 | 765 | /* Sidebar widget */ 766 | 767 | .sidebar .sidebar-widget { 768 | padding: 10px 5px; 769 | } 770 | 771 | .sidebar .ui-datepicker { 772 | width: 95%; 773 | margin: 0 auto; 774 | background: #111; 775 | color: #888; 776 | border: 0; 777 | padding: 0; 778 | } 779 | 780 | .sidebar .ui-datepicker-header { 781 | background: #222; 782 | border: 1px solid #212121; 783 | } 784 | 785 | .sidebar .ui-datepicker-prev:hover { 786 | background: transparent; 787 | border: 0; 788 | top: 2px !important; 789 | left: 2px !important; 790 | } 791 | 792 | .sidebar .ui-datepicker-next:hover { 793 | background: transparent; 794 | border: 0; 795 | top: 2px !important; 796 | right: 2px !important; 797 | } 798 | 799 | .sidebar .ui-state-default { 800 | background: #222; 801 | border: 0; 802 | text-align: center; 803 | color: #ccc; 804 | } 805 | 806 | .sidebar .ui-state-default:hover { 807 | background: #282828; 808 | color: #999; 809 | } 810 | 811 | .sidebar .ui-state-hightlight, .sidebar .ui-state-active { 812 | background: #444; 813 | } 814 | 815 | 816 | /* Main */ 817 | 818 | .mainbar { 819 | position: relative; 820 | margin-left: 230px; 821 | margin-right: 0; 822 | width: auto; 823 | background: #fff; 824 | min-height: 900px; 825 | padding: 30px; 826 | font-size: 16px; 827 | } 828 | 829 | .mainbar .container { 830 | width: 100%; 831 | padding: 0 !important; 832 | } 833 | 834 | /* Pagination*/ 835 | 836 | .pagination { 837 | margin: 10px 0 5px 0; 838 | } 839 | 840 | .pagination ul > li > a, .pagination ul > li > span { 841 | border: 1px solid #ccc; 842 | margin-right: 3px; 843 | padding: 3px 8px; 844 | background-color: #fff; 845 | color: #666; 846 | } 847 | 848 | .pagination ul > li > a:hover { 849 | color: #333; 850 | background: #fafafa; 851 | } 852 | 853 | /* Page head */ 854 | 855 | .mainbar .page-head { 856 | padding: 15px 20px; 857 | border-bottom: 1px solid #fff; 858 | } 859 | 860 | .mainbar .page-meta { 861 | font-size: 13px; 862 | line-height: 15px; 863 | margin-left: 2px; 864 | display: block; 865 | } 866 | 867 | 868 | /* Matter */ 869 | 870 | .mainbar .matter { 871 | border-top: 0px solid #ddd; 872 | margin: 2em; 873 | } 874 | 875 | /* Widget */ 876 | 877 | .widget { 878 | margin-top: 10px; 879 | margin-bottom: 20px; 880 | background: #fff; 881 | } 882 | 883 | .widget hr { 884 | margin: 4px 0; 885 | padding: 4px 0; 886 | border-top: 0; 887 | border-bottom: 1px solid #ddd; 888 | } 889 | 890 | .widget .table { 891 | margin: 0; 892 | width: 100%; 893 | } 894 | 895 | .widget .table-bordered { 896 | border: 0; 897 | } 898 | 899 | .widget .table-bordered th { 900 | border-bottom: 1px solid #ccc !important; 901 | } 902 | 903 | .widget .table-bordered td { 904 | border-top: 0 !important; 905 | border-bottom: 1px solid #ccc !important; 906 | } 907 | 908 | .widget .table-bordered td:first-child, .widget .table-bordered th:first-child { 909 | border-left: 0; 910 | } 911 | 912 | .widget .padd { 913 | padding: 15px; 914 | } 915 | 916 | .widget .widget-head { 917 | background-color: #f5f5f5; 918 | border: 1px solid #ddd; 919 | color: #777; 920 | font-size: 18px; 921 | padding: 12px 15px; 922 | } 923 | 924 | .widget .widget-head .widget-icons i { 925 | font-size: 14px; 926 | margin: 0 4px; 927 | } 928 | 929 | .widget .widget-head .widget-icons a { 930 | color: #aaa; 931 | } 932 | 933 | .widget .widget-head .widget-icons a:hover { 934 | color: #888; 935 | } 936 | 937 | .widget .widget-content { 938 | border-left: 1px solid #ddd; 939 | border-right: 1px solid #ddd; 940 | border-bottom: 1px solid #ddd; 941 | } 942 | 943 | .widget .widget-foot { 944 | background-color: #f9f9f9; 945 | border: 1px solid #ddd; 946 | border-top: 0; 947 | padding: 8px 15px; 948 | font-size: 13px; 949 | color: #555; 950 | } 951 | 952 | /* Widget colors */ 953 | 954 | .widget.wred .widget-head { 955 | background-color: @color_important; 956 | border: 1px solid @color_important; 957 | color: #fff; 958 | } 959 | 960 | .widget.wlightblue .widget-head { 961 | background-color: @color_lightblue; 962 | border: 1px solid @color_lightblue; 963 | color: #fff; 964 | } 965 | 966 | .widget.wblue .widget-head { 967 | background-color: @color_blue; 968 | border: 1px solid @color_blue; 969 | color: #fff; 970 | } 971 | 972 | .widget.wgreen .widget-head { 973 | background-color: @color_success; 974 | border: 1px solid @color_success; 975 | color: #fff; 976 | } 977 | 978 | .widget.worange .widget-head { 979 | background-color: @color_warning; 980 | border: 1px solid @color_warning; 981 | color: #fff; 982 | } 983 | 984 | .widget.wviolet .widget-head { 985 | background-color: @color_violet; 986 | border: 1px solid @color_violet; 987 | color: #fff; 988 | } 989 | 990 | .widget.wred .widget-head .widget-icons a, 991 | .widget.wblue .widget-head .widget-icons a, 992 | .widget.wlightblue .widget-head .widget-icons a, 993 | .widget.worange .widget-head .widget-icons a, 994 | .widget.wgreen .widget-head .widget-icons a, 995 | .widget.wviolet .widget-head .widget-icons a { 996 | color: #fff; 997 | } 998 | 999 | .widget.wred .widget-head .widget-icons a:hover, 1000 | .widget.wblue .widget-head .widget-icons a:hover, 1001 | .widget.wlightblue .widget-head .widget-icons a:hover, 1002 | .widget.worange .widget-head .widget-icons a:hover, 1003 | .widget.wgreen .widget-head .widget-icons a:hover, 1004 | .widget.wviolet .widget-head .widget-icons a:hover { 1005 | color: #eee; 1006 | } 1007 | 1008 | /* Widget white extras */ 1009 | 1010 | .widget .nav-tabs > li a { 1011 | padding: 5px 10px; 1012 | } 1013 | 1014 | .widget .nav-tabs { 1015 | margin-bottom: 5px; 1016 | } 1017 | 1018 | .widget .tab-content { 1019 | margin-bottom: 10px; 1020 | } 1021 | 1022 | /* Today datas */ 1023 | 1024 | .today-datas { 1025 | list-style-type: none; 1026 | padding: 0; 1027 | margin: 10px 0; 1028 | } 1029 | 1030 | .today-datas li { 1031 | display: inline-block; 1032 | margin-bottom: 5px; 1033 | margin-right: 10px; 1034 | padding: 1.5em 1em; 1035 | background-color: #f8f8f8; 1036 | background: #f8f8f8; 1037 | border: 1px solid #ccc; 1038 | max-width: 100%; 1039 | text-align: center; 1040 | } 1041 | 1042 | .today-datas li .spark { 1043 | margin-right: 10px; 1044 | } 1045 | 1046 | .today-datas li .datas-text { 1047 | font-size: 13px; 1048 | padding: 7px 0 0 0; 1049 | font-weight: normal; 1050 | } 1051 | 1052 | .today-datas li .datas-text span { 1053 | display: block; 1054 | font-size: 24px; 1055 | margin-bottom: 5px; 1056 | } 1057 | 1058 | .today-datas li i { 1059 | font-size: 50px; 1060 | margin-right: 10px; 1061 | } 1062 | 1063 | .today-datas li .dial { 1064 | margin-right: 10px !important; 1065 | } 1066 | 1067 | /* Toggle button */ 1068 | 1069 | .toggle-button span { 1070 | font-size: 13px !important; 1071 | } 1072 | 1073 | /* Gallery */ 1074 | 1075 | .gallery img { 1076 | max-width: 170px; 1077 | margin: 5px; 1078 | } 1079 | 1080 | /* Responsive */ 1081 | 1082 | @media (max-width: 480px) { 1083 | .mainbar .page-head h2 { 1084 | float: none; 1085 | } 1086 | 1087 | .mainbar .bread-crumb { 1088 | float: none; 1089 | margin-top: 10px; 1090 | } 1091 | 1092 | .col-left { 1093 | width: 100%; 1094 | float: none; 1095 | margin-right: 0; 1096 | } 1097 | 1098 | .col-right { 1099 | width: 100%; 1100 | float: none; 1101 | } 1102 | } 1103 | 1104 | @media (max-width: 767px) { 1105 | body { 1106 | margin: 0 auto; 1107 | } 1108 | 1109 | body .container { 1110 | width: 95%; 1111 | padding-left: 2.5%; 1112 | padding-right: 2.5%; 1113 | } 1114 | 1115 | .content { 1116 | margin-left: -20px; 1117 | margin-right: -20px; 1118 | } 1119 | 1120 | .mainbar .matter { 1121 | padding-left: 20px; 1122 | padding-right: 20px; 1123 | } 1124 | 1125 | .form-inline button { 1126 | margin-left: 0; 1127 | } 1128 | 1129 | .navbar { 1130 | margin-top: 0 !important; 1131 | margin-bottom: 0 !important; 1132 | } 1133 | 1134 | .nav-collapse .dropdown-big .dropdown-menu { 1135 | color: #bbb !important; 1136 | } 1137 | 1138 | .nav-collapse .dropdown-big .dropdown-menu a { 1139 | color: #ccc !important; 1140 | padding-left: 0 !important; 1141 | padding-right: 0 !important; 1142 | } 1143 | 1144 | .nav-collapse .dropdown-big .dropdown-menu a:hover { 1145 | color: #aaa !important; 1146 | } 1147 | 1148 | .nav-collapse .dropdown-big .dropdown-menu h5 { 1149 | color: #eee !important; 1150 | } 1151 | 1152 | .nav-collapse .dropdown-menu { 1153 | padding: 10px 10px !important; 1154 | } 1155 | 1156 | .nav-collapse .dropdown-menu a { 1157 | color: #fff !important; 1158 | } 1159 | 1160 | .nav-collapse .dropdown-menu a:hover { 1161 | background: transparent !important; 1162 | } 1163 | 1164 | .nav-collapse .dropdown-menu hr { 1165 | border-top: 0 solid #eee; 1166 | border-bottom: 1px solid #333; 1167 | } 1168 | 1169 | .sidebar { 1170 | float: none; 1171 | width: 100%; 1172 | } 1173 | 1174 | .sidebar .sidebar-dropdown { 1175 | display: block; 1176 | } 1177 | 1178 | .sidebar .sidebar-inner { 1179 | display: none; 1180 | } 1181 | 1182 | .sidebar .sidebar-widget { 1183 | text-align: center; 1184 | } 1185 | 1186 | .mainbar { 1187 | margin: 0; 1188 | float: none; 1189 | } 1190 | 1191 | .today-datas { 1192 | text-align: center; 1193 | } 1194 | } 1195 | 1196 | @media (min-width: 768px) and (max-width: 979px) { 1197 | .form { 1198 | margin: 0 !important; 1199 | } 1200 | 1201 | form .control-group { 1202 | margin: 0 !important; 1203 | } 1204 | 1205 | form .control-label { 1206 | float: none !important; 1207 | width: auto !important; 1208 | text-align: left !important; 1209 | } 1210 | 1211 | form .controls { 1212 | float: none !important; 1213 | margin-left: 0 !important; 1214 | } 1215 | 1216 | form .form-actions { 1217 | padding-left: 0 !important; 1218 | } 1219 | 1220 | .navbar { 1221 | margin-top: 0 !important; 1222 | margin-bottom: 0 !important; 1223 | } 1224 | 1225 | .nav-collapse .dropdown-big .dropdown-menu { 1226 | color: #bbb !important; 1227 | } 1228 | 1229 | .nav-collapse .dropdown-big .dropdown-menu a { 1230 | color: #ccc !important; 1231 | padding-left: 0 !important; 1232 | padding-right: 0 !important; 1233 | } 1234 | 1235 | .nav-collapse .dropdown-big .dropdown-menu a:hover { 1236 | color: #aaa !important; 1237 | } 1238 | 1239 | .nav-collapse .dropdown-big .dropdown-menu h5 { 1240 | color: #eee !important; 1241 | } 1242 | 1243 | .nav-collapse .dropdown-menu { 1244 | padding: 10px 10px !important; 1245 | } 1246 | 1247 | .nav-collapse .dropdown-menu a { 1248 | color: #fff !important; 1249 | } 1250 | 1251 | .nav-collapse .dropdown-menu a:hover { 1252 | background: transparent !important; 1253 | } 1254 | 1255 | .nav-collapse .dropdown-menu hr { 1256 | border-top: 0 solid #eee; 1257 | border-bottom: 1px solid #333; 1258 | } 1259 | 1260 | .sidebar { 1261 | width: 200px; 1262 | } 1263 | 1264 | .mainbar { 1265 | margin-left: 200px; 1266 | } 1267 | } 1268 | 1269 | /*#region Widgets */ 1270 | 1271 | /* maps */ 1272 | 1273 | .map iframe { 1274 | width: 100%; 1275 | margin: 0 !important; 1276 | padding: 0 !important; 1277 | } 1278 | 1279 | /* maps ends */ 1280 | 1281 | /* Users starts */ 1282 | 1283 | .user h6 { 1284 | line-height: 17px !important; 1285 | } 1286 | 1287 | .user { 1288 | font-size: 16px !important; 1289 | line-height: 20px !important; 1290 | } 1291 | 1292 | .user img { 1293 | max-width: 70px; 1294 | margin-top: 10px; 1295 | cursor: pointer; 1296 | } 1297 | 1298 | .user .user-pic { 1299 | float: left; 1300 | width: 80px; 1301 | } 1302 | 1303 | .user .user-details { 1304 | margin-left: 85px; 1305 | } 1306 | 1307 | .user .btn { 1308 | font-size: 14px !important; 1309 | } 1310 | 1311 | /* Users ends */ 1312 | 1313 | 1314 | /*#endregion */ 1315 | 1316 | 1317 | 1318 | 1319 | 1320 | 1321 | 1322 | 1323 | 1324 | 1325 | 1326 | 1327 | 1328 | 1329 | 1330 | 1331 | 1332 | 1333 | 1334 | /*#region Override background color theme */ 1335 | 1336 | /*For Bootstrap 3*/ 1337 | .nav, .pagination, .carousel, .panel-title a { cursor: pointer; } 1338 | 1339 | body { 1340 | background-color: #FFF; 1341 | } 1342 | 1343 | .navbar-inverse { 1344 | background-color: #333; 1345 | } 1346 | 1347 | .sidebar-widget form { 1348 | margin: 0em -1em; 1349 | } 1350 | 1351 | .sidebar .navi > li > a { 1352 | background-color: #333 !important; 1353 | } 1354 | 1355 | .sidebar .navi li ul li a { 1356 | color: #fff; 1357 | } 1358 | 1359 | .sidebar .navi li ul li a:hover { 1360 | color: @color_lightblue; 1361 | } 1362 | 1363 | .sidebar .navi li ul li a:disabled { 1364 | color: #999; 1365 | } 1366 | 1367 | .navbar-inverse .btn-navbar { 1368 | background-color: #333; 1369 | } 1370 | 1371 | .navbar-inverse .btn-navbar:hover { 1372 | background-color: #333; 1373 | } 1374 | 1375 | .sidebar { 1376 | margin-top: -2px; 1377 | } 1378 | 1379 | .sidebar .sidebar-inner { 1380 | background-color: #333; 1381 | } 1382 | 1383 | .sidebar-filler { 1384 | z-index: -1; 1385 | position: fixed; 1386 | top: 0; 1387 | left: 0; 1388 | height: 100%; 1389 | background-color: #333; 1390 | width: 230px; 1391 | } 1392 | 1393 | /*#endregion */ 1394 | 1395 | /* #region Main Styles */ 1396 | 1397 | ul.image-group { 1398 | padding:0 0 0 0; 1399 | margin:0 0 0 0; 1400 | } 1401 | ul.image-group li { 1402 | list-style:none; 1403 | margin-bottom:25px; 1404 | } 1405 | 1406 | .ng-cloak { 1407 | display: none !important; 1408 | } 1409 | 1410 | select > option { 1411 | color: black; 1412 | } 1413 | 1414 | input.form-control, 1415 | select.form-control, 1416 | textarea.form-control { 1417 | /*width: 205px;*/ 1418 | display: inline; 1419 | display: block; 1420 | width: 50%; 1421 | max-width: 30em; 1422 | } 1423 | 1424 | .widget .padd { 1425 | /*margin-bottom: 0;*/ 1426 | /*padding: 1em 0;*/ 1427 | height: 12em; 1428 | } 1429 | 1430 | .widget .padd.padd-tight { 1431 | padding: 6px; 1432 | } 1433 | 1434 | .list-flow.list-flow-tight { 1435 | width: 90px; 1436 | } 1437 | 1438 | 1439 | .map { 1440 | height: 228px; 1441 | } 1442 | 1443 | .btn > i { 1444 | margin-right: 8px; 1445 | } 1446 | 1447 | .btn { 1448 | margin-right: 4px; 1449 | margin-left: 4px; 1450 | } 1451 | 1452 | .btn.btn-notext > i { 1453 | margin-right: 0; 1454 | } 1455 | 1456 | .btn-group{ 1457 | margin: .2em; 1458 | } 1459 | 1460 | 1461 | small { 1462 | font-size: 14px; 1463 | } 1464 | 1465 | .text-subtle { 1466 | color: #b7b7b7; 1467 | } 1468 | 1469 | .page-title-subtle { 1470 | color: white; 1471 | } 1472 | 1473 | .page-title { 1474 | color: #EEE; 1475 | margin-right: 12px; 1476 | } 1477 | 1478 | .right { 1479 | clear: right; 1480 | float: right; 1481 | } 1482 | 1483 | .widget-content .padd:hover { 1484 | cursor: pointer; 1485 | color: white; 1486 | background-color: @color_lightblue; 1487 | } 1488 | 1489 | .widget-content .padd:hover * { 1490 | color: white; 1491 | } 1492 | 1493 | .list-flow { 1494 | margin: 6px; 1495 | float: left; 1496 | width: 110px; 1497 | height: 120px; 1498 | border-bottom: solid 0 transparent; 1499 | padding: 1.5% .5%; 1500 | } 1501 | 1502 | .name-stack h5 { 1503 | text-align: center; 1504 | padding: 0; 1505 | margin: 0; 1506 | line-height: 18px !important; 1507 | } 1508 | 1509 | 1510 | .search-query { 1511 | margin: 2px auto 8px auto !important; 1512 | } 1513 | 1514 | .fa-search { 1515 | color: #FFFFFF; 1516 | } 1517 | 1518 | .form-search { 1519 | min-height: 2em; 1520 | } 1521 | 1522 | .input-group[class*="col-"] { 1523 | width: 80%; 1524 | margin: 0 1.5em;} 1525 | 1526 | .user .user-pic { 1527 | display: block; 1528 | margin: auto; 1529 | } 1530 | 1531 | .user .user-details { 1532 | margin-left: 100px; 1533 | margin-right: 10px; 1534 | } 1535 | 1536 | .widget-content { 1537 | padding: 1em; 1538 | margin: 0; 1539 | } 1540 | 1541 | .stacked { 1542 | float: none !important; 1543 | display: block; 1544 | margin: auto; 1545 | } 1546 | 1547 | .navbar .navbar-brand { 1548 | /*background: url(../images/gg.png) no-repeat left top !important;*/ 1549 | margin: 0 1em; 1550 | height: 45px; 1551 | } 1552 | 1553 | .navbar .brand-title { 1554 | /*margin-left: 4em;*/ 1555 | color: #FFFFFF; 1556 | } 1557 | 1558 | .navbar-logo img { 1559 | height: 20px; 1560 | } 1561 | 1562 | .today-datas li { 1563 | padding: 20px 14px; 1564 | height: 9em; 1565 | } 1566 | 1567 | /*#region Splash */ 1568 | #splash-page { 1569 | z-index: 99999 !important; 1570 | } 1571 | 1572 | #splash-page .bar { 1573 | width: 100%; 1574 | } 1575 | 1576 | .page-splash { 1577 | z-index: 99999 !important; 1578 | position: fixed !important; 1579 | top: 0; 1580 | left: 0; 1581 | width: 100%; 1582 | height: 100%; 1583 | background-color: #333; 1584 | opacity: .9; 1585 | pointer-events: auto; 1586 | -webkit-backface-visibility: hidden; 1587 | -moz-backface-visibility: hidden; 1588 | -ms-backface-visibility: hidden; 1589 | -o-backface-visibility: hidden; 1590 | backface-visibility: hidden; 1591 | -webkit-transition: opacity 0.3s linear; 1592 | -moz-transition: opacity 0.3s linear; 1593 | -o-transition: opacity 0.3s linear; 1594 | transition: opacity 0.3s linear; 1595 | } 1596 | 1597 | .page-splash-message { 1598 | text-align: center; 1599 | margin: 20% auto 0 auto; 1600 | font-size: 400%; 1601 | font-family: "Segoe UI", Arial, Helvetica, sans-serif; 1602 | font-weight: normal; 1603 | -webkit-text-shadow: 2px 2px #000000; 1604 | text-shadow: 2px 2px #000000; 1605 | text-shadow: 2px 2px rgba(0, 0, 0, 0.15); 1606 | text-transform: uppercase; 1607 | text-decoration: none; 1608 | color: #F58A00; 1609 | padding: 0; 1610 | } 1611 | 1612 | .page-splash-message.page-splash-message-subtle { 1613 | margin: 30% auto 0 auto; 1614 | font-size: 200%; 1615 | } 1616 | 1617 | .flag-haschanges { 1618 | position: fixed !important; 1619 | display: inline; 1620 | } 1621 | 1622 | .fa-asterisk.fa-asterisk-large { 1623 | font-size: 180%; 1624 | vertical-align: middle; 1625 | color: #F58A00; 1626 | } 1627 | 1628 | .fa-asterisk.fa-asterisk-alert { 1629 | color: #F58A00; 1630 | } 1631 | 1632 | .fa-asterisk-inline { 1633 | padding: 0 4px 0 0; 1634 | } 1635 | 1636 | 1637 | .progress, 1638 | .page-progress-bar { 1639 | margin: 30px 10% !important; 1640 | } 1641 | 1642 | .ngplus-overlay-background { 1643 | top: 0px; 1644 | left: 0px; 1645 | padding-left: 100px; 1646 | position: absolute; 1647 | z-index: 10000; 1648 | height: 100%; 1649 | width: 100%; 1650 | background-color: #808080; 1651 | opacity: 0.2; 1652 | } 1653 | 1654 | .ngplus-overlay-content { 1655 | position: absolute; 1656 | /*border: 1px solid #000;*/ 1657 | /*background-color: #fff;*/ 1658 | font-weight: bold; 1659 | height: 100px; 1660 | width: 300px; 1661 | height: 15em; 1662 | width: 20em; 1663 | z-index: 10000; 1664 | text-align: center; 1665 | } 1666 | 1667 | .page-spinner-message { 1668 | text-align: center; 1669 | font-size: 400%; 1670 | font-family: "Segoe UI", Arial, Helvetica, sans-serif; 1671 | font-weight: normal; 1672 | -webkit-text-shadow: 2px 2px #000000; 1673 | text-shadow: 2px 2px #000000; 1674 | text-shadow: 2px 2px rgba(0, 0, 0, 0.15); 1675 | text-transform: uppercase; 1676 | text-decoration: none; 1677 | color: #F58A00; 1678 | padding: 0; 1679 | } 1680 | 1681 | .page-spinner-message.page-spinner-message-subtle { 1682 | margin: 30% auto 0 auto; 1683 | font-size: 200%; 1684 | } 1685 | 1686 | .overlay-message { 1687 | font-size: 200%; 1688 | } 1689 | .spinner { 1690 | margin: 20% auto 0 auto; 1691 | left: auto; 1692 | top: auto !important; 1693 | } 1694 | /*#endregion*/ 1695 | 1696 | table th > a { 1697 | font-weight: bold; 1698 | } 1699 | 1700 | table th, 1701 | table td { 1702 | text-align: left; 1703 | vertical-align: middle; 1704 | } 1705 | 1706 | .table-hover tbody tr:hover > td, 1707 | .table-hover tbody tr:hover > th { 1708 | cursor: pointer; 1709 | } 1710 | 1711 | 1712 | .widget-content.referrer { 1713 | border-width: 0; 1714 | } 1715 | 1716 | a { 1717 | cursor: pointer; 1718 | } 1719 | 1720 | /* #endregion */ 1721 | 1722 | 1723 | 1724 | .customer-name { 1725 | font-size: 1.2em; 1726 | } 1727 | .customer-thumb { 1728 | margin: 0.1em !important; 1729 | padding: 0.1em; 1730 | } 1731 | 1732 | /*#region wrapper for angular ng-include and ng-view animations*/ 1733 | .view-container { 1734 | position: relative; 1735 | overflow: hidden; 1736 | } 1737 | /*#endregion */ 1738 | 1739 | /*#region Angular ng-include, ng-view, ng-repeat shuffle animations*/ 1740 | 1741 | .shuffle-animation.ng-enter, 1742 | .shuffle-animation.ng-leave { 1743 | position: relative; 1744 | } 1745 | 1746 | .shuffle-animation.ng-enter { 1747 | -moz-transition: ease-out all 0.3s 0.4s; 1748 | -o-transition: ease-out all 0.3s 0.4s; 1749 | -webkit-transition: ease-out all 0.3s 0.4s; 1750 | transition: ease-out all 0.3s 0.4s; 1751 | left: 2em; 1752 | opacity: 0; 1753 | } 1754 | 1755 | .shuffle-animation.ng-enter.ng-enter-active { 1756 | left: 0; 1757 | opacity: 1; 1758 | } 1759 | 1760 | .shuffle-animation.ng-leave { 1761 | -moz-transition: 0.3s ease-out all; 1762 | -o-transition: 0.3s ease-out all; 1763 | -webkit-transition: 0.3s ease-out all; 1764 | transition: 0.3s ease-out all; 1765 | left: 0; 1766 | opacity: 1; 1767 | } 1768 | 1769 | .shuffle-animation.ng-leave.ng-leave-active { 1770 | left: 2em; 1771 | opacity: 0; 1772 | } 1773 | /*#endregion*/ 1774 | 1775 | /*#region Angular ng-include, ng-view, ng-repeat fader animation */ 1776 | .fader-animation.ng-enter, 1777 | .fader-animation.ng-leave, 1778 | .fader-animation.ng-move { 1779 | position: relative; 1780 | } 1781 | 1782 | .fader-animation.ng-enter, 1783 | .fader-animation.ng-leave { 1784 | -webkit-transition: cubic-bezier(0.250, 0.460, 0.450, 0.940) all 0.5s; 1785 | -moz-transition: cubic-bezier(0.250, 0.460, 0.450, 0.940) all 0.5s; 1786 | -o-transition: cubic-bezier(0.250, 0.460, 0.450, 0.940) all 0.5s; 1787 | transition: cubic-bezier(0.250, 0.460, 0.450, 0.940) all 0.5s; 1788 | opacity: 1; 1789 | } 1790 | 1791 | .fader-animation.ng-enter, 1792 | .fader-animation.ng-leave.ng-leave-active { 1793 | opacity: 0; 1794 | } 1795 | 1796 | .fader-animation.ng-enter.ng-enter-active { 1797 | opacity: 1; 1798 | } 1799 | 1800 | .fader-animation.ng-move { 1801 | opacity:0.5; 1802 | } 1803 | .fader-animation.ng-move.ng-move-active { 1804 | opacity:1; 1805 | } 1806 | 1807 | /*#endregion*/ 1808 | 1809 | /*#region Angular ng-show dissolve animation */ 1810 | .dissolve-animation.ng-hide-remove, 1811 | .dissolve-animation.ng-hide-add { 1812 | position: fixed !important; 1813 | display: inline !important; 1814 | -webkit-transition: 0.5s linear all; 1815 | -moz-transition: 0.5s linear all; 1816 | -o-transition: 0.5s linear all; 1817 | transition: 0.5s linear all; 1818 | } 1819 | 1820 | .dissolve-animation.ng-hide-remove.ng-hide-remove-active, 1821 | .dissolve-animation.ng-hide-add { 1822 | opacity: 1; 1823 | } 1824 | 1825 | .dissolve-animation.ng-hide-add.ng-hide-add-active, 1826 | .dissolve-animation.ng-hide-remove { 1827 | opacity: 0; 1828 | } 1829 | /*#endregion */ 1830 | 1831 | /*#region toastr */ 1832 | #toast-container.toast-top-full-width > div, #toast-container.toast-bottom-full-width > div { 1833 | margin: 4px auto; 1834 | } 1835 | /*#endregion */ 1836 | 1837 | /*#region Responsive */ 1838 | 1839 | @media (max-width: 979px) { 1840 | .sidebar-filler { 1841 | width: 200px; 1842 | } 1843 | 1844 | .nav-collapse { 1845 | clear: none; 1846 | } 1847 | 1848 | .nav-collapse .nav > li { 1849 | float: left; 1850 | } 1851 | 1852 | .navbar .btn-navbar { 1853 | display: none; 1854 | } 1855 | 1856 | .nav-collapse, 1857 | .nav-collapse.collapse { 1858 | height: inherit; 1859 | overflow: inherit; 1860 | } 1861 | 1862 | .page-splash-message { 1863 | font-size: 300%; 1864 | } 1865 | 1866 | .btn-group.pull-right { 1867 | float: none !important; 1868 | display: block; 1869 | } 1870 | } 1871 | 1872 | @media (min-width: 768px) { 1873 | .sidebar .sidebar-inner { 1874 | display: block !important; 1875 | } 1876 | } 1877 | 1878 | @media (max-width: 767px) { 1879 | .sidebar-filler { 1880 | display: none; 1881 | } 1882 | 1883 | .nav-collapse { 1884 | clear: both; 1885 | } 1886 | 1887 | .nav-collapse .nav > li { 1888 | float: none; 1889 | } 1890 | 1891 | .navbar .btn-navbar { 1892 | display: block; 1893 | } 1894 | 1895 | .nav-collapse, 1896 | .nav-collapse.collapse { 1897 | height: 0; 1898 | overflow: hidden; 1899 | } 1900 | 1901 | .page-splash-message { 1902 | font-size: 200%; 1903 | margin: 40% auto 0 auto; 1904 | } 1905 | 1906 | .page-splash-message.page-splash-message-subtle { 1907 | font-size: 150%; 1908 | } 1909 | 1910 | .sidebar .sidebar-inner { 1911 | height: inherit; 1912 | } 1913 | } 1914 | 1915 | @media (max-width: 480px) { 1916 | input.form-control, select.form-control, textarea.form-control { 1917 | width: 70%; 1918 | } 1919 | } 1920 | 1921 | @media (max-width: 320px) { 1922 | .today-datas li { 1923 | padding: 1em .5em; 1924 | line-height: 1em; 1925 | } 1926 | 1927 | .today-datas li i { 1928 | font-size: 2em; 1929 | } 1930 | 1931 | .today-datas li .datas-text span { 1932 | font-size: 1em; 1933 | } 1934 | 1935 | .btn { 1936 | margin: .2em; 1937 | width: 7em; 1938 | } 1939 | 1940 | .btn-group > .btn { 1941 | display: block; 1942 | width: 7em; 1943 | } 1944 | 1945 | .btn-group.pull-right { 1946 | margin: .2em 0; 1947 | } 1948 | 1949 | input, 1950 | select { 1951 | width: 85% !important; 1952 | } 1953 | 1954 | textarea { 1955 | width: 80%; 1956 | } 1957 | 1958 | .img-thumbnail.user-pic { 1959 | width: 5em; 1960 | } 1961 | 1962 | .user .user-details { 1963 | margin-left: 7em; 1964 | margin-right: .5em; 1965 | } 1966 | 1967 | h3 { 1968 | font-size: 1.5em !important; 1969 | line-height: 1.2em !important; 1970 | } 1971 | } 1972 | 1973 | /*#endregion */ 1974 | -------------------------------------------------------------------------------- /app/templates/src/client/test-helpers/bind-polyfill.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Phantom.js does not support Function.prototype.bind (at least not before v.2.0 3 | * That's just crazy. Everybody supports bind. 4 | * Read about it here: https://groups.google.com/forum/#!msg/phantomjs/r0hPOmnCUpc/uxusqsl2LNoJ 5 | * This polyfill is copied directly from MDN 6 | * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility 7 | */ 8 | if (!Function.prototype.bind) { 9 | /*jshint freeze: false */ 10 | Function.prototype.bind = function (oThis) { 11 | if (typeof this !== 'function') { 12 | // closest thing possible to the ECMAScript 5 13 | // internal IsCallable function 14 | var msg = 'Function.prototype.bind - what is trying to be bound is not callable'; 15 | throw new TypeError(msg); 16 | } 17 | 18 | var aArgs = Array.prototype.slice.call(arguments, 1), 19 | fToBind = this, 20 | FuncNoOp = function () {}, 21 | fBound = function () { 22 | return fToBind.apply(this instanceof FuncNoOp && oThis ? this : oThis, 23 | aArgs.concat(Array.prototype.slice.call(arguments))); 24 | }; 25 | 26 | FuncNoOp.prototype = this.prototype; 27 | fBound.prototype = new FuncNoOp(); 28 | 29 | return fBound; 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /app/templates/src/client/test-helpers/mock-data.js: -------------------------------------------------------------------------------- 1 | /* jshint -W079 */ 2 | var mockData = (function() { 3 | return { 4 | getMockPeople: getMockPeople, 5 | getMockStates: getMockStates 6 | }; 7 | 8 | function getMockStates() { 9 | return [ 10 | { 11 | state: 'dashboard', 12 | config: { 13 | url: '/', 14 | templateUrl: 'app/dashboard/dashboard.html', 15 | title: 'dashboard', 16 | settings: { 17 | nav: 1, 18 | content: '<i class="fa fa-dashboard"></i> Dashboard' 19 | } 20 | } 21 | } 22 | ]; 23 | } 24 | 25 | function getMockPeople() { 26 | return [ 27 | {firstName: 'John', lastName: 'Papa', age: 25, location: 'Florida'}, 28 | {firstName: 'Ward', lastName: 'Bell', age: 31, location: 'California'}, 29 | {firstName: 'Colleen', lastName: 'Jones', age: 21, location: 'New York'}, 30 | {firstName: 'Madelyn', lastName: 'Green', age: 18, location: 'North Dakota'}, 31 | {firstName: 'Ella', lastName: 'Jobs', age: 18, location: 'South Dakota'}, 32 | {firstName: 'Landon', lastName: 'Gates', age: 11, location: 'South Carolina'}, 33 | {firstName: 'Haley', lastName: 'Guthrie', age: 35, location: 'Wyoming'} 34 | ]; 35 | } 36 | })(); 37 | -------------------------------------------------------------------------------- /app/templates/src/server/_app.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | 'use strict'; 3 | 4 | var express = require('express'); 5 | var app = express(); 6 | var bodyParser = require('body-parser'); 7 | var favicon = require('serve-favicon'); 8 | var logger = require('morgan'); 9 | var port = process.env.PORT || 8001; 10 | var four0four = require('./utils/404')(); 11 | 12 | var environment = process.env.NODE_ENV; 13 | 14 | app.use(favicon(__dirname + '/favicon.ico')); 15 | app.use(bodyParser.urlencoded({extended: true})); 16 | app.use(bodyParser.json()); 17 | app.use(logger('dev')); 18 | 19 | app.use('/api', require('./routes')); 20 | 21 | console.log('About to crank up node'); 22 | console.log('PORT=' + port); 23 | console.log('NODE_ENV=' + environment); 24 | 25 | switch (environment){ 26 | case 'build': 27 | console.log('** BUILD **'); 28 | app.use(express.static('./build/')); 29 | // Any invalid calls for templateUrls are under app/* and should return 404 30 | app.use('/app/*', function(req, res, next) { 31 | four0four.send404(req, res); 32 | }); 33 | // Any deep link calls should return index.html 34 | app.use('/*', express.static('./build/index.html')); 35 | break; 36 | default: 37 | console.log('** DEV **'); 38 | app.use(express.static('./src/client/')); 39 | app.use(express.static('./')); 40 | app.use(express.static('./tmp')); 41 | // Any invalid calls for templateUrls are under app/* and should return 404 42 | app.use('/app/*', function(req, res, next) { 43 | four0four.send404(req, res); 44 | }); 45 | // Any deep link calls should return index.html 46 | app.use('/*', express.static('./src/client/index.html')); 47 | break; 48 | } 49 | 50 | app.listen(port, function() { 51 | console.log('Express server listening on port ' + port); 52 | console.log('env = ' + app.get('env') + 53 | '\n__dirname = ' + __dirname + 54 | '\nprocess.cwd = ' + process.cwd()); 55 | }); 56 | -------------------------------------------------------------------------------- /app/templates/src/server/_data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | people: getPeople() 3 | }; 4 | 5 | function getPeople() { 6 | return [ 7 | {id: 1, firstName: 'John', lastName: 'Papa', age: 25, location: 'Florida'}, 8 | {id: 2, firstName: 'Ward', lastName: 'Bell', age: 31, location: 'California'}, 9 | {id: 3, firstName: 'Colleen', lastName: 'Jones', age: 21, location: 'New York'}, 10 | {id: 4, firstName: 'Madelyn', lastName: 'Green', age: 18, location: 'North Dakota'}, 11 | {id: 5, firstName: 'Ella', lastName: 'Jobs', age: 18, location: 'South Dakota'}, 12 | {id: 6, firstName: 'Landon', lastName: 'Gates', age: 11, location: 'South Carolina'}, 13 | {id: 7, firstName: 'Haley', lastName: 'Guthrie', age: 35, location: 'Wyoming'}, 14 | {id: 8, firstName: 'Aaron', lastName: 'Jinglehiemer', age: 22, location: 'Utah'} 15 | ]; 16 | } 17 | -------------------------------------------------------------------------------- /app/templates/src/server/_routes.js: -------------------------------------------------------------------------------- 1 | var router = require('express').Router(); 2 | var four0four = require('./utils/404')(); 3 | var data = require('./data'); 4 | 5 | router.get('/people', getPeople); 6 | router.get('/person/:id', getPerson); 7 | router.get('/*', four0four.notFoundMiddleware); 8 | 9 | module.exports = router; 10 | 11 | ////////////// 12 | 13 | function getPeople(req, res, next) { 14 | res.status(200).send(data.people); 15 | } 16 | 17 | function getPerson(req, res, next) { 18 | var id = +req.params.id; 19 | var person = data.people.filter(function(p) { 20 | return p.id === id; 21 | })[0]; 22 | 23 | if (person) { 24 | res.status(200).send(person); 25 | } else { 26 | four0four.send404(req, res, 'person ' + id + ' not found'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/templates/src/server/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jlmonteagudo/generator-angular-crud/780716d313248b44fd6aece0091a0dcb757fb684/app/templates/src/server/favicon.ico -------------------------------------------------------------------------------- /app/templates/src/server/utils/404.js: -------------------------------------------------------------------------------- 1 | module.exports = function (app) { 2 | var service = { 3 | notFoundMiddleware: notFoundMiddleware, 4 | send404: send404 5 | }; 6 | return service; 7 | 8 | function notFoundMiddleware(req, res, next) { 9 | send404(req, res, 'API endpoint not found'); 10 | } 11 | 12 | function send404(req, res, description) { 13 | var data = { 14 | status: 404, 15 | message: 'Not Found', 16 | description: description, 17 | url: req.url 18 | }; 19 | res.status(404) 20 | .send(data) 21 | .end(); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /feature/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var yeoman = require('yeoman-generator'); 3 | var chalk = require('chalk'); 4 | var yosay = require('yosay'); 5 | var _ = require('lodash'); 6 | var inflections = require('underscore.inflections'); 7 | 8 | 9 | module.exports = yeoman.generators.Base.extend({ 10 | 11 | constructor: function () { 12 | 13 | yeoman.generators.Base.apply(this, arguments); 14 | 15 | this.argument('featureName', { type: String, required: true }); 16 | 17 | this.featureName = _.capitalize(this.featureName); 18 | this.featureSingularName = inflections.singularize(this.featureName); 19 | this.featurePluralName = inflections.pluralize(this.featureName); 20 | 21 | this.slugifiedName = _.kebabCase(this.featureName); 22 | 23 | this.camelizedSingularName = _.camelCase(this.featureSingularName); 24 | this.camelizedPluralName = _.camelCase(this.featurePluralName); 25 | 26 | }, 27 | 28 | prompting: function () { 29 | 30 | this.log(yosay( 31 | 'Adding a new feature!' 32 | )); 33 | 34 | }, 35 | 36 | 37 | featureFiles: function () { 38 | 39 | // Define file names 40 | var moduleFile = 'src/client/app/' + this.slugifiedName + '/' + this.slugifiedName + '.client.module.js'; 41 | var configFile = 'src/client/app/' + this.slugifiedName + '/config/' + this.slugifiedName + '.client.routes.js'; 42 | var controllerFile = 'src/client/app/' + this.slugifiedName + '/controllers/' + this.slugifiedName + '.client.controller.js'; 43 | var serviceFile = 'src/client/app/' + this.slugifiedName + '/services/' + this.slugifiedName + '.client.service.js'; 44 | var serviceFormFile = 'src/client/app/' + this.slugifiedName + '/services/' + this.slugifiedName + '.form.client.service.js'; 45 | 46 | // Render angular module files 47 | this.template('_.client.module.js', moduleFile); 48 | this.template('config/_.client.routes.js', configFile); 49 | this.template('controllers/_.client.controller.js', controllerFile); 50 | this.template('services/_.client.service.js', serviceFile); 51 | this.template('services/_.form.client.service.js', serviceFormFile); 52 | 53 | // Render angular module views 54 | this.template('views/_.create.client.view.html', 'src/client/app/' + this.slugifiedName + '/views/create.html'); 55 | this.template('views/_.edit.client.view.html', 'src/client/app/' + this.slugifiedName + '/views/edit.html'); 56 | this.template('views/_.list.client.view.html', 'src/client/app/' + this.slugifiedName + '/views/list.html'); 57 | this.template('views/_.view.client.view.html', 'src/client/app/' + this.slugifiedName + '/views/view.html'); 58 | 59 | } 60 | 61 | }); 62 | -------------------------------------------------------------------------------- /feature/templates/_.client.module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('app.<%= camelizedSingularName %>', []); 5 | angular.module('app').requires.push('app.<%= camelizedSingularName %>'); 6 | 7 | })(); 8 | -------------------------------------------------------------------------------- /feature/templates/config/_.client.routes.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.<%= camelizedSingularName %>') 6 | .run(appRun); 7 | 8 | appRun.$inject = ['routerHelper']; 9 | /* @ngInject */ 10 | function appRun(routerHelper) { 11 | routerHelper.configureStates(getStates()); 12 | } 13 | 14 | function getStates() { 15 | return [ 16 | { 17 | state: 'list<%= featureSingularName %>', 18 | config: { 19 | url: '/<%= slugifiedName %>', 20 | templateUrl: 'app/<%= slugifiedName %>/views/list.html', 21 | controller: '<%= featureSingularName %>Controller', 22 | controllerAs: 'vm', 23 | title: 'List <%= featurePluralName %>', 24 | settings: { 25 | nav: 3, 26 | content: '<i class="fa fa-folder-open"></i> <%= featurePluralName %>' 27 | } 28 | } 29 | }, 30 | { 31 | state: 'create<%= featureSingularName %>', 32 | config: { 33 | url: '/<%= slugifiedName %>/create', 34 | templateUrl: 'app/<%= slugifiedName %>/views/create.html', 35 | controller: '<%= featureSingularName %>Controller', 36 | controllerAs: 'vm', 37 | title: 'Create <%= featureSingularName %>' 38 | } 39 | }, 40 | { 41 | state: 'view<%= featureSingularName %>', 42 | config: { 43 | url: '/<%= slugifiedName %>/:<%= camelizedSingularName %>Id', 44 | templateUrl: 'app/<%= slugifiedName %>/views/view.html', 45 | controller: '<%= featureSingularName %>Controller', 46 | controllerAs: 'vm', 47 | title: 'View <%= featureSingularName %>' 48 | } 49 | }, 50 | { 51 | state: 'edit<%= featureSingularName %>', 52 | config: { 53 | url: '/<%= slugifiedName %>/:<%= camelizedSingularName %>Id/edit', 54 | templateUrl: 'app/<%= slugifiedName %>/views/edit.html', 55 | controller: '<%= featureSingularName %>Controller', 56 | controllerAs: 'vm', 57 | title: 'Edit <%= featureSingularName %>' 58 | } 59 | } 60 | ]; 61 | } 62 | })(); 63 | -------------------------------------------------------------------------------- /feature/templates/controllers/_.client.controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.<%= camelizedSingularName %>') 6 | .controller('<%= featureSingularName %>Controller', <%= featureSingularName %>Controller); 7 | 8 | <%= featureSingularName %>Controller.$inject = ['logger', 9 | '$stateParams', 10 | '$location', 11 | '<%= featureSingularName %>', 12 | 'TableSettings', 13 | '<%= featureSingularName %>Form']; 14 | /* @ngInject */ 15 | function <%= featureSingularName %>Controller(logger, 16 | $stateParams, 17 | $location, 18 | <%= featureSingularName %>, 19 | TableSettings, 20 | <%= featureSingularName %>Form) { 21 | 22 | var vm = this; 23 | 24 | vm.tableParams = TableSettings.getParams(<%= featureSingularName %>); 25 | vm.<%= camelizedSingularName %> = {}; 26 | 27 | vm.setFormFields = function(disabled) { 28 | vm.formFields = <%= featureSingularName %>Form.getFormFields(disabled); 29 | }; 30 | 31 | vm.create = function() { 32 | // Create new <%= featureSingularName %> object 33 | var <%= camelizedSingularName %> = new <%= featureSingularName %>(vm.<%= camelizedSingularName %>); 34 | 35 | // Redirect after save 36 | <%= camelizedSingularName %>.$save(function(response) { 37 | logger.success('<%= featureSingularName %> created'); 38 | $location.path('<%= slugifiedName %>/' + response.id); 39 | }, function(errorResponse) { 40 | vm.error = errorResponse.data.summary; 41 | }); 42 | }; 43 | 44 | // Remove existing <%= featureSingularName %> 45 | vm.remove = function(<%= camelizedSingularName %>) { 46 | 47 | if (<%= camelizedSingularName %>) { 48 | <%= camelizedSingularName %> = <%= featureSingularName %>.get({<%= camelizedSingularName %>Id:<%= camelizedSingularName %>.id}, function() { 49 | <%= camelizedSingularName %>.$remove(function() { 50 | logger.success('<%= featureSingularName %> deleted'); 51 | vm.tableParams.reload(); 52 | }); 53 | }); 54 | } else { 55 | vm.<%= camelizedSingularName %>.$remove(function() { 56 | logger.success('<%= featureSingularName %> deleted'); 57 | $location.path('/<%= slugifiedName %>'); 58 | }); 59 | } 60 | 61 | }; 62 | 63 | // Update existing <%= featureSingularName %> 64 | vm.update = function() { 65 | var <%= camelizedSingularName %> = vm.<%= camelizedSingularName %>; 66 | 67 | <%= camelizedSingularName %>.$update(function() { 68 | logger.success('<%= featureSingularName %> updated'); 69 | $location.path('<%= slugifiedName %>/' + <%= camelizedSingularName %>.id); 70 | }, function(errorResponse) { 71 | vm.error = errorResponse.data.summary; 72 | }); 73 | }; 74 | 75 | vm.toView<%= featureSingularName %> = function() { 76 | vm.<%= camelizedSingularName %> = <%= featureSingularName %>.get({<%= camelizedSingularName %>Id: $stateParams.<%= camelizedSingularName %>Id}); 77 | vm.setFormFields(true); 78 | }; 79 | 80 | vm.toEdit<%= featureSingularName %> = function() { 81 | vm.<%= camelizedSingularName %> = <%= featureSingularName %>.get({<%= camelizedSingularName %>Id: $stateParams.<%= camelizedSingularName %>Id}); 82 | vm.setFormFields(false); 83 | }; 84 | 85 | activate(); 86 | 87 | function activate() { 88 | //logger.info('Activated <%= featureSingularName %> View'); 89 | } 90 | } 91 | 92 | })(); 93 | -------------------------------------------------------------------------------- /feature/templates/services/_.client.service.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.<%= camelizedSingularName %>') 6 | .factory('<%= featureSingularName %>', <%= featureSingularName %>); 7 | 8 | <%= featureSingularName %>.$inject = ['$resource', 'API_BASE_URL']; 9 | /* @ngInject */ 10 | function <%= featureSingularName %>($resource, API_BASE_URL) { 11 | 12 | var params = { 13 | <%= camelizedSingularName %>Id: '@id' 14 | }; 15 | 16 | var actions = { 17 | update: { 18 | method: 'PUT' 19 | } 20 | }; 21 | 22 | var API_URL = API_BASE_URL + '/<%= camelizedSingularName %>/:<%= camelizedSingularName %>Id'; 23 | 24 | return $resource(API_URL, params, actions); 25 | 26 | } 27 | 28 | })(); 29 | -------------------------------------------------------------------------------- /feature/templates/services/_.form.client.service.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('app.<%= camelizedSingularName %>') 6 | .factory('<%= featureSingularName %>Form', factory); 7 | 8 | function factory() { 9 | 10 | var getFormFields = function(disabled) { 11 | 12 | var fields = [ 13 | { 14 | key: 'name', 15 | type: 'input', 16 | templateOptions: { 17 | label: 'Name:', 18 | disabled: disabled, 19 | required: true 20 | } 21 | }, 22 | { 23 | key: 'address', 24 | type: 'input', 25 | templateOptions: { 26 | label: 'Address:', 27 | disabled: disabled 28 | } 29 | } 30 | ]; 31 | 32 | return fields; 33 | 34 | }; 35 | 36 | var service = { 37 | getFormFields: getFormFields 38 | }; 39 | 40 | return service; 41 | 42 | } 43 | 44 | })(); 45 | -------------------------------------------------------------------------------- /feature/templates/views/_.create.client.view.html: -------------------------------------------------------------------------------- 1 | <section class="mainbar" data-ng-init="vm.setFormFields()"> 2 | 3 | <div class="panel panel-primary"> 4 | <div class="panel-heading"> 5 | New <%= featureSingularName %> 6 | </div> 7 | <div class="panel-body"> 8 | 9 | <form data-ng-submit="vm.create()" novalidate> 10 | 11 | <formly-form model="vm.<%= camelizedSingularName %>" fields="vm.formFields" form="vm.form"> 12 | </formly-form> 13 | 14 | <hr/> 15 | <div class="form-group"> 16 | <button type="submit" class="btn btn-success" ng-disabled="vm.form.$invalid"> 17 | <i class="glyphicon glyphicon-ok"></i> Save 18 | </button> 19 | <a class="btn btn-danger" href="/<%= slugifiedName %>"> 20 | <i class="glyphicon glyphicon-remove"></i> Cancel 21 | </a> 22 | </div> 23 | 24 | <div data-ng-show="vm.error" class="alert alert-danger form-group"> 25 | <strong data-ng-bind="vm.error"></strong> 26 | </div> 27 | 28 | </form> 29 | 30 | </div> 31 | </div> 32 | 33 | </section> 34 | -------------------------------------------------------------------------------- /feature/templates/views/_.edit.client.view.html: -------------------------------------------------------------------------------- 1 | <section class="mainbar" data-ng-init="vm.toEdit<%= featureSingularName %>()"> 2 | 3 | <div class="panel panel-primary"> 4 | <div class="panel-heading"> 5 | <%= featureSingularName %> Edit 6 | </div> 7 | <div class="panel-body"> 8 | 9 | <form data-ng-submit="vm.update()" novalidate> 10 | 11 | <formly-form model="vm.<%= camelizedSingularName %>" fields="vm.formFields" form="vm.form"> 12 | </formly-form> 13 | 14 | <hr/> 15 | <div class="form-group"> 16 | <button type="submit" class="btn btn-success" ng-disabled="vm.form.$invalid"> 17 | <i class="glyphicon glyphicon-ok"></i> Save 18 | </button> 19 | <a class="btn btn-danger" href="/<%= slugifiedName %>"> 20 | <i class="glyphicon glyphicon-remove"></i> Cancel 21 | </a> 22 | </div> 23 | 24 | <div data-ng-show="vm.error" class="alert alert-danger form-group"> 25 | <strong data-ng-bind="vm.error"></strong> 26 | </div> 27 | 28 | </form> 29 | 30 | 31 | </div> 32 | </div> 33 | 34 | </section> 35 | -------------------------------------------------------------------------------- /feature/templates/views/_.list.client.view.html: -------------------------------------------------------------------------------- 1 | <section class="mainbar"> 2 | 3 | <div class="panel panel-primary"> 4 | <div class="panel-heading"> 5 | <%= featureSingularName %> List 6 | </div> 7 | <div class="panel-body"> 8 | 9 | <div class="pull-right"> 10 | <a type="button" class="btn btn-success" href="/<%= slugifiedName %>/create"> 11 | <i class="glyphicon glyphicon-open"></i> New <%= featureSingularName %> 12 | </a> 13 | </div> 14 | 15 | <table ng-table="vm.tableParams" show-filter="true" class="table table-condensed table-striped"> 16 | <tr ng-repeat="<%= camelizedSingularName %> in $data"> 17 | <td data-title="'Name'" sortable="'name'" filter="{ 'name': 'text' }">{{<%= camelizedSingularName %>.name}}</td> 18 | <td data-title="'Address'" sortable="'address'" filter="{'address': 'text'}">{{<%= camelizedSingularName %>.address}}</td> 19 | <td> 20 | <div class="table-actions"> 21 | <a class="btn btn-default" title="View" href="/<%= slugifiedName %>/{{<%= camelizedSingularName %>.id}}"> 22 | <span class="glyphicon glyphicon-eye-open"></span> 23 | </a> 24 | <a class="btn btn-default" title="Edit" href="/<%= slugifiedName %>/{{<%= camelizedSingularName %>.id}}/edit"> 25 | <span class="glyphicon glyphicon-edit"></span> 26 | </a> 27 | <a class="btn btn-danger" title="Remove" ng-really-message="Are you sure ?" ng-really-click="vm.remove(<%= camelizedSingularName %>)" item="<%= camelizedSingularName %>"> 28 | <span class="glyphicon glyphicon-remove-circle"></span> 29 | </a> 30 | </div> 31 | </td> 32 | </tr> 33 | </table> 34 | 35 | </div> 36 | </div> 37 | 38 | </section> 39 | -------------------------------------------------------------------------------- /feature/templates/views/_.view.client.view.html: -------------------------------------------------------------------------------- 1 | <section class="mainbar" data-ng-init="vm.toView<%= featureSingularName %>()"> 2 | 3 | <div class="panel panel-primary"> 4 | <div class="panel-heading"> 5 | <%= featureSingularName %> View 6 | </div> 7 | <div class="panel-body"> 8 | 9 | <form novalidate> 10 | <formly-form model="vm.<%= camelizedSingularName %>" fields="vm.formFields"> 11 | </formly-form> 12 | </form> 13 | 14 | <hr/> 15 | <div> 16 | <a class="btn btn-success" href="/<%= slugifiedName %>/{{vm.<%= camelizedSingularName %>.id}}/edit"> 17 | <i class="glyphicon glyphicon-edit"></i> Edit 18 | </a> 19 | <a class="btn btn-danger" title="Remove" ng-really-message="Are you sure ?" ng-really-click="vm.remove()" item="vm.<%= camelizedSingularName %>"> 20 | <i class="glyphicon glyphicon-remove-circle"></i> Delete 21 | </a> 22 | <a class="btn btn-primary" href="/<%= slugifiedName %>"> 23 | <i class="glyphicon glyphicon-list"></i> List <%= featurePluralName %> 24 | </a> 25 | </div> 26 | 27 | </div> 28 | </div> 29 | 30 | </section> 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-angular-crud", 3 | "version": "0.0.4", 4 | "description": "Angular CRUD generator", 5 | "license": "MIT", 6 | "main": "app/index.js", 7 | "repository": "jlmonteagudo/generator-angular-crud", 8 | "author": { 9 | "name": "Jose Luis Monteagudo", 10 | "email": "jl_monteagudo@yahoo.es", 11 | "url": "https://github.com/jlmonteagudo" 12 | }, 13 | "scripts": { 14 | "test": "mocha" 15 | }, 16 | "files": [ 17 | "app", 18 | "feature" 19 | ], 20 | "keywords": [ 21 | "yeoman-generator", 22 | "angularjs", 23 | "angular", 24 | "mean", 25 | "meanjs", 26 | "sails", 27 | "sailsjs", 28 | "crud" 29 | ], 30 | "dependencies": { 31 | "chalk": "^1.0.0", 32 | "lodash": "^3.7.0", 33 | "underscore.inflections": "^0.2.1", 34 | "yeoman-generator": "^0.19.0", 35 | "yosay": "^1.0.2" 36 | }, 37 | "devDependencies": { 38 | "mocha": "*", 39 | "gulp": "^3.8.11", 40 | "gulp-bump": "^0.1.11", 41 | "gulp-load-plugins": "^0.8.0", 42 | "gulp-task-listing": "^1.0.0", 43 | "gulp-util": "^3.0.1", 44 | "yargs": "^1.3.3" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /sails/api/blueprints/find.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies 3 | */ 4 | var actionUtil = require('../../node_modules/sails/lib/hooks/blueprints/actionUtil'), 5 | _ = require('lodash'); 6 | 7 | /** 8 | * Find Records 9 | * 10 | * get /:modelIdentity 11 | * * /:modelIdentity/find 12 | * 13 | * An API call to find and return model instances from the data adapter 14 | * using the specified criteria. If an id was specified, just the instance 15 | * with that unique id will be returned. 16 | * 17 | * Optional: 18 | * @param {Object} where - the find criteria (passed directly to the ORM) 19 | * @param {Integer} limit - the maximum number of records to send back (useful for pagination) 20 | * @param {Integer} skip - the number of records to skip (useful for pagination) 21 | * @param {String} sort - the order of returned records, e.g. `name ASC` or `age DESC` 22 | * @param {String} callback - default jsonp callback param (i.e. the name of the js function returned) 23 | */ 24 | 25 | module.exports = function findRecords (req, res) { 26 | 27 | // Look up the model 28 | var Model = actionUtil.parseModel(req); 29 | 30 | 31 | // If an `id` param was specified, use the findOne blueprint action 32 | // to grab the particular instance with its primary key === the value 33 | // of the `id` param. (mainly here for compatibility for 0.9, where 34 | // there was no separate `findOne` action) 35 | if ( actionUtil.parsePk(req) ) { 36 | return require('./findOne')(req,res); 37 | } 38 | 39 | // Lookup for records that match the specified criteria 40 | 41 | 42 | var query = Model.find() 43 | .where( actionUtil.parseCriteria(req) ) 44 | .limit( actionUtil.parseLimit(req) ) 45 | .skip( actionUtil.parseSkip(req) ) 46 | .sort( actionUtil.parseSort(req) ); 47 | query = actionUtil.populateRequest(query, req); 48 | query.exec(function found(err, matchingRecords) { 49 | if (err) return res.serverError(err); 50 | 51 | // Only `.watch()` for new instances of the model if 52 | // `autoWatch` is enabled. 53 | if (req._sails.hooks.pubsub && req.isSocket) { 54 | Model.subscribe(req, matchingRecords); 55 | if (req.options.autoWatch) { Model.watch(req); } 56 | // Also subscribe to instances of all associated models 57 | _.each(matchingRecords, function (record) { 58 | actionUtil.subscribeDeep(req, record); 59 | }); 60 | } 61 | 62 | Model.count(actionUtil.parseCriteria(req)).exec(function(error, total) { 63 | if (err) return res.serverError(err); 64 | var data = {}; 65 | 66 | data.total = total; 67 | data.results = matchingRecords; 68 | res.ok(data); 69 | }); 70 | 71 | 72 | 73 | }); 74 | }; 75 | -------------------------------------------------------------------------------- /test/test-app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var assert = require('yeoman-generator').assert; 5 | var helpers = require('yeoman-generator').test; 6 | var os = require('os'); 7 | 8 | describe('angular-crud:app', function () { 9 | before(function (done) { 10 | helpers.run(path.join(__dirname, '../app')) 11 | .withOptions({ skipInstall: true }) 12 | .withPrompts({ someOption: true }) 13 | .on('end', done); 14 | }); 15 | 16 | it('creates files', function () { 17 | assert.file([ 18 | 'bower.json', 19 | 'package.json', 20 | '.editorconfig', 21 | '.jshintrc' 22 | ]); 23 | }); 24 | }); 25 | --------------------------------------------------------------------------------