Commit 7f3edf1e authored by Richard Torenvliet's avatar Richard Torenvliet

initial commit

parents
*.sw[a-z]
vendor/*
build/*
bin/*
node_modules/*
CHANGELOG.md
CONTRIBUTING.md
LICENSE
changelog.tpl
tools.md
module.exports = function ( grunt ) {
/**
* Load required Grunt tasks. These are installed based on the versions listed
* in `package.json` when you do `npm install` in this directory.
*/
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-coffee');
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-conventional-changelog');
grunt.loadNpmTasks('grunt-bump');
grunt.loadNpmTasks('grunt-coffeelint');
grunt.loadNpmTasks('grunt-karma');
grunt.loadNpmTasks('grunt-ng-annotate');
grunt.loadNpmTasks('grunt-html2js');
/**
* Load in our build configuration file.
*/
var userConfig = require( './build.config.js' );
/**
* This is the configuration object Grunt uses to give each plugin its
* instructions.
*/
var taskConfig = {
/**
* We read in our `package.json` file so we can access the package name and
* version. It's already there, so we don't repeat ourselves here.
*/
pkg: grunt.file.readJSON("package.json"),
/**
* The banner is the comment that is placed at the top of our compiled
* source files. It is first processed as a Grunt template, where the `<%=`
* pairs are evaluated based on this very configuration object.
*/
meta: {
banner:
'/**\n' +
' * <%= pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' +
' * <%= pkg.homepage %>\n' +
' *\n' +
' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' +
' * Licensed <%= pkg.licenses.type %> <<%= pkg.licenses.url %>>\n' +
' */\n'
},
/**
* Creates a changelog on a new version.
*/
changelog: {
options: {
dest: 'CHANGELOG.md',
template: 'changelog.tpl'
}
},
/**
* Increments the version number, etc.
*/
bump: {
options: {
files: [
"package.json",
"bower.json"
],
commit: false,
commitMessage: 'chore(release): v%VERSION%',
commitFiles: [
"package.json",
"client/bower.json"
],
createTag: false,
tagName: 'v%VERSION%',
tagMessage: 'Version %VERSION%',
push: false,
pushTo: 'origin'
}
},
/**
* The directories to delete when `grunt clean` is executed.
*/
clean: [
'<%= build_dir %>',
'<%= compile_dir %>'
],
/**
* The `copy` task just copies files from A to B. We use it here to copy
* our project assets (images, fonts, etc.) and javascripts into
* `build_dir`, and then to copy the assets to `compile_dir`.
*/
copy: {
build_app_assets: {
files: [
{
src: [ '**' ],
dest: '<%= build_dir %>/assets/',
cwd: 'src/assets',
expand: true
}
]
},
build_vendor_assets: {
files: [
{
src: [ '<%= vendor_files.assets %>' ],
dest: '<%= build_dir %>/assets/',
cwd: '.',
expand: true,
flatten: true
}
]
},
build_appjs: {
files: [
{
src: [ '<%= app_files.js %>' ],
dest: '<%= build_dir %>/',
cwd: '.',
expand: true
}
]
},
build_vendorjs: {
files: [
{
src: [ '<%= vendor_files.js %>' ],
dest: '<%= build_dir %>/',
cwd: '.',
expand: true
}
]
},
build_vendorcss: {
files: [
{
src: [ '<%= vendor_files.css %>' ],
dest: '<%= build_dir %>/',
cwd: '.',
expand: true
}
]
},
compile_assets: {
files: [
{
src: [ '**' ],
dest: '<%= compile_dir %>/assets',
cwd: '<%= build_dir %>/assets',
expand: true
},
{
src: [ '<%= vendor_files.css %>' ],
dest: '<%= compile_dir %>/',
cwd: '.',
expand: true
}
]
}
},
/**
* `grunt concat` concatenates multiple source files into a single file.
*/
concat: {
/**
* The `build_css` target concatenates compiled CSS and vendor CSS
* together.
*/
build_css: {
src: [
'<%= vendor_files.css %>',
'<%= build_dir %>/assets/<%= pkg.name %>-<%= pkg.version %>.css'
],
dest: '<%= build_dir %>/assets/<%= pkg.name %>-<%= pkg.version %>.css'
},
/**
* The `compile_js` target is the concatenation of our application source
* code and all specified vendor source code into a single file.
*/
compile_js: {
options: {
banner: '<%= meta.banner %>'
},
src: [
'<%= vendor_files.js %>',
'module.prefix',
'<%= build_dir %>/src/**/*.js',
'<%= html2js.app.dest %>',
'<%= html2js.common.dest %>',
'module.suffix'
],
dest: '<%= compile_dir %>/assets/<%= pkg.name %>-<%= pkg.version %>.js'
}
},
/**
* `grunt coffee` compiles the CoffeeScript sources. To work well with the
* rest of the build, we have a separate compilation task for sources and
* specs so they can go to different places. For example, we need the
* sources to live with the rest of the copied JavaScript so we can include
* it in the final build, but we don't want to include our specs there.
*/
coffee: {
source: {
options: {
bare: true
},
expand: true,
cwd: '.',
src: [ '<%= app_files.coffee %>' ],
dest: '<%= build_dir %>',
ext: '.js'
}
},
/**
* `ngAnnotate` annotates the sources before minifying. That is, it allows us
* to code without the array syntax.
*/
ngAnnotate: {
compile: {
files: [
{
src: [ '<%= app_files.js %>' ],
cwd: '<%= build_dir %>',
dest: '<%= build_dir %>',
expand: true
}
]
}
},
/**
* Minify the sources!
*/
uglify: {
compile: {
options: {
banner: '<%= meta.banner %>'
},
files: {
'<%= concat.compile_js.dest %>': '<%= concat.compile_js.dest %>'
}
}
},
/**
* `grunt-contrib-less` handles our LESS compilation and uglification automatically.
* Only our `main.less` file is included in compilation; all other files
* must be imported from this file.
*/
less: {
build: {
files: {
'<%= build_dir %>/assets/<%= pkg.name %>-<%= pkg.version %>.css': '<%= app_files.less %>'
}
},
compile: {
files: {
'<%= build_dir %>/assets/<%= pkg.name %>-<%= pkg.version %>.css': '<%= app_files.less %>'
},
options: {
cleancss: true,
compress: true
}
}
},
/**
* `jshint` defines the rules of our linter as well as which files we
* should check. This file, all javascript sources, and all our unit tests
* are linted based on the policies listed in `options`. But we can also
* specify exclusionary patterns by prefixing them with an exclamation
* point (!); this is useful when code comes from a third party but is
* nonetheless inside `src/`.
*/
jshint: {
src: [
'<%= app_files.js %>'
],
test: [
'<%= app_files.jsunit %>'
],
gruntfile: [
'Gruntfile.js'
],
options: {
curly: true,
immed: true,
newcap: true,
noarg: true,
sub: true,
boss: true,
eqnull: true
},
globals: {}
},
/**
* `coffeelint` does the same as `jshint`, but for CoffeeScript.
* CoffeeScript is not the default in ngBoilerplate, so we're just using
* the defaults here.
*/
coffeelint: {
src: {
files: {
src: [ '<%= app_files.coffee %>' ]
}
},
test: {
files: {
src: [ '<%= app_files.coffeeunit %>' ]
}
}
},
/**
* HTML2JS is a Grunt plugin that takes all of your template files and
* places them into JavaScript files as strings that are added to
* AngularJS's template cache. This means that the templates too become
* part of the initial payload as one JavaScript file. Neat!
*/
html2js: {
/**
* These are the templates from `src/app`.
*/
app: {
options: {
base: 'src/app'
},
src: [ '<%= app_files.atpl %>' ],
dest: '<%= build_dir %>/templates-app.js'
},
/**
* These are the templates from `src/common`.
*/
common: {
options: {
base: 'src/common'
},
src: [ '<%= app_files.ctpl %>' ],
dest: '<%= build_dir %>/templates-common.js'
}
},
/**
* The Karma configurations.
*/
karma: {
options: {
configFile: '<%= build_dir %>/karma-unit.js'
},
unit: {
port: 9019,
background: true
},
continuous: {
singleRun: true
}
},
/**
* The `index` task compiles the `index.html` file as a Grunt template. CSS
* and JS files co-exist here but they get split apart later.
*/
index: {
/**
* During development, we don't want to have wait for compilation,
* concatenation, minification, etc. So to avoid these steps, we simply
* add all script files directly to the `<head>` of `index.html`. The
* `src` property contains the list of included files.
*/
build: {
dir: '<%= build_dir %>',
src: [
'<%= vendor_files.js %>',
'<%= build_dir %>/src/**/*.js',
'<%= html2js.common.dest %>',
'<%= html2js.app.dest %>',
'<%= vendor_files.css %>',
'<%= build_dir %>/assets/<%= pkg.name %>-<%= pkg.version %>.css'
]
},
/**
* When it is time to have a completely compiled application, we can
* alter the above to include only a single JavaScript and a single CSS
* file. Now we're back!
*/
compile: {
dir: '<%= compile_dir %>',
src: [
'<%= concat.compile_js.dest %>',
'<%= vendor_files.css %>',
'<%= build_dir %>/assets/<%= pkg.name %>-<%= pkg.version %>.css'
]
}
},
/**
* This task compiles the karma template so that changes to its file array
* don't have to be managed manually.
*/
karmaconfig: {
unit: {
dir: '<%= build_dir %>',
src: [
'<%= vendor_files.js %>',
'<%= html2js.app.dest %>',
'<%= html2js.common.dest %>',
'<%= test_files.js %>'
]
}
},
/**
* And for rapid development, we have a watch set up that checks to see if
* any of the files listed below change, and then to execute the listed
* tasks when they do. This just saves us from having to type "grunt" into
* the command-line every time we want to see what we're working on; we can
* instead just leave "grunt watch" running in a background terminal. Set it
* and forget it, as Ron Popeil used to tell us.
*
* But we don't need the same thing to happen for all the files.
*/
delta: {
/**
* By default, we want the Live Reload to work for all tasks; this is
* overridden in some tasks (like this file) where browser resources are
* unaffected. It runs by default on port 35729, which your browser
* plugin should auto-detect.
*/
options: {
livereload: true
},
/**
* When the Gruntfile changes, we just want to lint it. In fact, when
* your Gruntfile changes, it will automatically be reloaded!
*/
gruntfile: {
files: 'Gruntfile.js',
tasks: [ 'jshint:gruntfile' ],
options: {
livereload: false
}
},
/**
* When our JavaScript source files change, we want to run lint them and
* run our unit tests.
*/
jssrc: {
files: [
'<%= app_files.js %>'
],
tasks: [ 'jshint:src', 'karma:unit:run', 'copy:build_appjs' ]
},
/**
* When our CoffeeScript source files change, we want to run lint them and
* run our unit tests.
*/
coffeesrc: {
files: [
'<%= app_files.coffee %>'
],
tasks: [ 'coffeelint:src', 'coffee:source', 'karma:unit:run', 'copy:build_appjs' ]
},
/**
* When assets are changed, copy them. Note that this will *not* copy new
* files, so this is probably not very useful.
*/
assets: {
files: [
'src/assets/**/*'
],
tasks: [ 'copy:build_app_assets', 'copy:build_vendor_assets' ]
},
/**
* When index.html changes, we need to compile it.
*/
html: {
files: [ '<%= app_files.html %>' ],
tasks: [ 'index:build' ]
},
/**
* When our templates change, we only rewrite the template cache.
*/
tpls: {
files: [
'<%= app_files.atpl %>',
'<%= app_files.ctpl %>'
],
tasks: [ 'html2js' ]
},
/**
* When the CSS files change, we need to compile and minify them.
*/
less: {
files: [ 'src/**/*.less' ],
tasks: [ 'less:build' ]
},
/**
* When a JavaScript unit test file changes, we only want to lint it and
* run the unit tests. We don't want to do any live reloading.
*/
jsunit: {
files: [
'<%= app_files.jsunit %>'
],
tasks: [ 'jshint:test', 'karma:unit:run' ],
options: {
livereload: false
}
},
/**
* When a CoffeeScript unit test file changes, we only want to lint it and
* run the unit tests. We don't want to do any live reloading.
*/
coffeeunit: {
files: [
'<%= app_files.coffeeunit %>'
],
tasks: [ 'coffeelint:test', 'karma:unit:run' ],
options: {
livereload: false
}
}
}
};
grunt.initConfig( grunt.util._.extend( taskConfig, userConfig ) );
/**
* In order to make it safe to just compile or copy *only* what was changed,
* we need to ensure we are starting from a clean, fresh build. So we rename
* the `watch` task to `delta` (that's why the configuration var above is
* `delta`) and then add a new task called `watch` that does a clean build
* before watching for changes.
*/
grunt.renameTask( 'watch', 'delta' );
grunt.registerTask( 'watch', [ 'build', 'karma:unit', 'delta' ] );
/**
* The default task is to build and compile.
*/
grunt.registerTask( 'default', [ 'build', 'compile' ] );
/**
* The `build` task gets your app ready to run for development and testing.
*/
grunt.registerTask( 'build', [
'clean', 'html2js', 'jshint', 'coffeelint', 'coffee', 'less:build',
'concat:build_css', 'copy:build_app_assets', 'copy:build_vendor_assets',
'copy:build_appjs', 'copy:build_vendorjs', 'copy:build_vendorcss', 'index:build', 'karmaconfig',
'karma:continuous'
]);
/**
* The `compile` task gets your app ready for deployment by concatenating and
* minifying your code.
*/
grunt.registerTask( 'compile', [
'less:compile', 'copy:compile_assets', 'ngAnnotate', 'concat:compile_js', 'uglify', 'index:compile'
]);
/**
* A utility function to get all app JavaScript sources.
*/
function filterForJS ( files ) {
return files.filter( function ( file ) {
return file.match( /\.js$/ );
});
}
/**
* A utility function to get all app CSS sources.
*/
function filterForCSS ( files ) {
return files.filter( function ( file ) {
return file.match( /\.css$/ );
});
}
/**
* The index.html template includes the stylesheet and javascript sources
* based on dynamic names calculated in this Gruntfile. This task assembles
* the list into variables for the template to use and then runs the
* compilation.
*/
grunt.registerMultiTask( 'index', 'Process index.html template', function () {
var dirRE = new RegExp( '^('+grunt.config('build_dir')+'|'+grunt.config('compile_dir')+')\/', 'g' );
var jsFiles = filterForJS( this.filesSrc ).map( function ( file ) {
return file.replace( dirRE, '' );
});
var cssFiles = filterForCSS( this.filesSrc ).map( function ( file ) {
return file.replace( dirRE, '' );
});
grunt.file.copy('src/index.html', this.data.dir + '/index.html', {
process: function ( contents, path ) {
return grunt.template.process( contents, {
data: {
scripts: jsFiles,
styles: cssFiles,
version: grunt.config( 'pkg.version' )
}
});
}
});
});
/**
* In order to avoid having to specify manually the files needed for karma to
* run, we use grunt to manage the list for us. The `karma/*` files are
* compiled as grunt templates for use by Karma. Yay!
*/
grunt.registerMultiTask( 'karmaconfig', 'Process karma config templates', function () {
var jsFiles = filterForJS( this.filesSrc );
grunt.file.copy( 'karma/karma-unit.tpl.js', grunt.config( 'build_dir' ) + '/karma-unit.js', {
process: function ( contents, path ) {
return grunt.template.process( contents, {
data: {
scripts: jsFiles
}
});
}
});
});
};
{
"name": "Interactive",
"version": "0.3.2",
"devDependencies": {
"angular": "~1.2",
"angular-mocks": "~1.2",
"bootstrap": "~3.1",
"angular-bootstrap": "~0.10.0",
"angular-ui-router": "~0.2",
"pixi.js": "~2.2.7"
},
"dependencies": {}
}
/**
* This file/module contains all configuration for the build process.
*/
module.exports = {
/**
* The `build_dir` folder is where our projects are compiled during
* development and the `compile_dir` folder is where our app resides once it's
* completely built.
*/
build_dir: 'build',
compile_dir: 'bin',
/**
* This is a collection of file patterns that refer to our app code (the
* stuff in `src/`). These file paths are used in the configuration of
* build tasks. `js` is all project javascript, less tests. `ctpl` contains
* our reusable components' (`src/common`) template HTML files, while
* `atpl` contains the same, but for our app's code. `html` is just our
* main HTML file, `less` is our main stylesheet, and `unit` contains our
* app's unit tests.
*/
app_files: {
js: [ 'src/**/*.js', '!src/**/*.spec.js', '!src/assets/**/*.js' ],
jsunit: [ 'src/**/*.spec.js' ],
coffee: [ 'src/**/*.coffee', '!src/**/*.spec.coffee' ],
coffeeunit: [ 'src/**/*.spec.coffee' ],
atpl: [ 'src/app/**/*.tpl.html' ],
ctpl: [ 'src/common/**/*.tpl.html' ],
html: [ 'src/index.html' ],
less: 'src/less/main.less'
},
/**
* This is a collection of files used during testing only.
*/
test_files: {
js: [
'vendor/angular-mocks/angular-mocks.js'
]
},
/**
* This is the same as `app_files`, except it contains patterns that
* reference vendor code (`vendor/`) that we need to place into the build
* process somewhere. While the `app_files` property ensures all
* standardized files are collected for compilation, it is the user's job
* to ensure non-standardized (i.e. vendor-related) files are handled
* appropriately in `vendor_files.js`.
*
* The `vendor_files.js` property holds files to be automatically
* concatenated and minified with our project source files.
*
* The `vendor_files.css` property holds any CSS files to be automatically
* included in our app.
*
* The `vendor_files.assets` property holds any assets to be copied along
* with our app's assets. This structure is flattened, so it is not
* recommended that you use wildcards.
*/
vendor_files: {
js: [
'vendor/angular/angular.js',
'vendor/angular-bootstrap/ui-bootstrap-tpls.min.js',
'vendor/placeholders/angular-placeholders-0.0.1-SNAPSHOT.min.js',
'vendor/angular-ui-router/release/angular-ui-router.js',
'vendor/angular-ui-utils/modules/route/route.js'
],
css: [
],
assets: [
]
},
};
module.exports = function ( karma ) {
karma.set({
/**
* From where to look for files, starting with the location of this file.
*/
basePath: '../',
/**
* This is the list of file patterns to load into the browser during testing.
*/
files: [
<% scripts.forEach( function ( file ) { %>'<%= file %>',
<% }); %>
'src/**/*.js',
'src/**/*.coffee',
],
exclude: [
'src/assets/**/*.js'
],
frameworks: [ 'jasmine' ],
plugins: [ 'karma-jasmine', 'karma-firefox-launcher', 'karma-coffee-preprocessor' ],
preprocessors: {
'**/*.coffee': 'coffee',
},
/**
* How to report, by default.
*/
reporters: 'dots',
/**
* On which port should the browser connect, on which port is the test runner
* operating, and what is the URL path for the browser to use.
*/
port: 9018,
runnerPort: 9100,
urlRoot: '/',
/**
* Disable file watching by default.
*/
autoWatch: false,
/**
* The list of browsers to launch to test on. This includes only "Firefox" by
* default, but other browser names include:
* Chrome, ChromeCanary, Firefox, Opera, Safari, PhantomJS
*
* Note that you can also use the executable name of the browser, like "chromium"
* or "firefox", but that these vary based on your operating system.
*
* You may also leave this blank and manually navigate your browser to
* http://localhost:9018/ when you're running tests. The window/tab can be left
* open and the tests will automatically occur there during the build. This has
* the aesthetic advantage of not launching a browser every time you save.
*/
browsers: [
'Firefox'
]
});
};
(function ( window, angular, undefined ) {
})( window, window.angular );
{
"author": "Richard Torenvliet",
"name": "Interactive",
"version": "0.3.2",
"dependencies": {},
"devDependencies": {
"grunt": "~0.4.1",
"grunt-bump": "0.0.6",
"grunt-coffeelint": "~0.0.10",
"grunt-contrib-clean": "^0.4.1",
"grunt-contrib-coffee": "^0.7.0",
"grunt-contrib-concat": "^0.3.0",
"grunt-contrib-copy": "^0.4.1",
"grunt-contrib-jshint": "^0.4.3",
"grunt-contrib-less": "~0.11.0",
"grunt-contrib-uglify": "^0.2.7",
"grunt-contrib-watch": "^0.4.4",
"grunt-conventional-changelog": "^0.1.2",
"grunt-html2js": "^0.1.9",
"grunt-karma": "^0.8.2",
"grunt-ng-annotate": "^0.8.0",
"karma": "^0.12.9",
"karma-coffee-preprocessor": "^0.2.1",
"karma-firefox-launcher": "^0.1.3",
"karma-jasmine": "^0.1.5",
}
}
# The `src` Directory
## Overview
The `src/` directory contains all code used in the application along with all
tests of such code.
```
src/
|- app/
| |- about/
| |- home/
| |- app.js
| |- app.spec.js
|- assets/
|- common/
| |- plusOne/
|- less/
| |- main.less
| |- variables.less
|- index.html
```
- `src/app/` - application-specific code, i.e. code not likely to be reused in
another application. [Read more &raquo;](app/README.md)
- `src/assets/` - static files like fonts and images.
[Read more &raquo;](assets/README.md)
- `src/common/` - third-party libraries or components likely to be reused in
another application. [Read more &raquo;](common/README.md)
- `src/less/` - LESS CSS files. [Read more &raquo;](less/README.md)
- `src/index.html` - this is the HTML document of the single-page application.
See below.
See each directory for a detailed explanation.
## `index.html`
The `index.html` file is the HTML document of the single-page application (SPA)
that should contain all markup that applies to everything in the app, such as
the header and footer. It declares with `ngApp` that this is `ngBoilerplate`,
specifies the main `AppCtrl` controller, and contains the `ngView` directive
into which route templates are placed.
Unlike any other HTML document (e.g. the templates), `index.html` is compiled as
a Grunt template, so variables from `Gruntfile.js` and `package.json` can be
referenced from within it. Changing `name` in `package.json` from
"ng-boilerplate" will rename the resultant CSS and JavaScript placed in `build/`,
so this HTML references them by variable for convenience.
# The `src/app` Directory
## Overview
```
src/
|- app/
| |- home/
| |- about/
| |- app.js
| |- app.spec.js
```
The `src/app` directory contains all code specific to this application. Apart
from `app.js` and its accompanying tests (discussed below), this directory is
filled with subdirectories corresponding to high-level sections of the
application, often corresponding to top-level routes. Each directory can have as
many subdirectories as it needs, and the build system will understand what to
do. For example, a top-level route might be "products", which would be a folder
within the `src/app` directory that conceptually corresponds to the top-level
route `/products`, though this is in no way enforced. Products may then have
subdirectories for "create", "view", "search", etc. The "view" submodule may
then define a route of `/products/:id`, ad infinitum.
As `ngBoilerplate` is quite minimal, take a look at the two provided submodules
to gain a better understanding of how these are used as well as to get a
glimpse of how powerful this simple construct can be.
## `app.js`
This is our main app configuration file. It kickstarts the whole process by
requiring all the modules from `src/app` that we need. We must load these now to
ensure the routes are loaded. If as in our "products" example there are
subroutes, we only require the top-level module, and allow the submodules to
require their own submodules.
As a matter of course, we also require the template modules that are generated
during the build.
However, the modules from `src/common` should be required by the app
submodules that need them to ensure proper dependency handling. These are
app-wide dependencies that are required to assemble your app.
```js
angular.module( 'ngBoilerplate', [
'templates-app',
'templates-common',
'ngBoilerplate.home',
'ngBoilerplate.about'
'ui.router',
'ui.route'
])
```
With app modules broken down in this way, all routing is performed by the
submodules we include, as that is where our app's functionality is really
defined. So all we need to do in `app.js` is specify a default route to follow,
which route of course is defined in a submodule. In this case, our `home` module
is where we want to start, which has a defined route for `/home` in
`src/app/home/home.js`.
```js
.config( function myAppConfig ( $stateProvider, $urlRouterProvider ) {
$urlRouterProvider.otherwise( '/home' );
})
```
Use the main applications run method to execute any code after services
have been instantiated.
```js
.run( function run () {
})
```
And then we define our main application controller. This is a good place for logic
not specific to the template or route, such as menu logic or page title wiring.
```js
.controller( 'AppCtrl', function AppCtrl ( $scope, $location ) {
$scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){
if ( angular.isDefined( toState.data.pageTitle ) ) {
$scope.pageTitle = toState.data.pageTitle + ' | ngBoilerplate' ;
}
});
})
```
### Testing
One of the design philosophies of `ngBoilerplate` is that tests should exist
alongside the code they test and that the build system should be smart enough to
know the difference and react accordingly. As such, the unit test for `app.js`
is `app.spec.js`, though it is quite minimal.
angular.module( 'Interactive', [
'templates-app',
'templates-common',
'Interactive.index',
'ui.router'
])
.config( function ( $stateProvider, $urlRouterProvider ) {
$urlRouterProvider.otherwise( '/index' );
})
.run( function run () {
})
.controller( 'AppCtrl', function AppCtrl ( $scope, $location ) {
$scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){
if ( angular.isDefined( toState.data.pageTitle ) ) {
$scope.pageTitle = toState.data.pageTitle;
}
});
})
;
describe( 'AppCtrl', function() {
describe( 'isCurrentUrl', function() {
var AppCtrl, $location, $scope;
beforeEach( module( 'Interactive' ) );
beforeEach( inject( function( $controller, _$location_, $rootScope ) {
$location = _$location_;
$scope = $rootScope.$new();
AppCtrl = $controller( 'AppCtrl', { $location: $location, $scope: $scope });
}));
it( 'should pass a dummy test', inject( function() {
expect( AppCtrl ).toBeTruthy();
}));
});
});
angular.module('Interactive.index', [
'ui.router'
])
.config(function($stateProvider) {
$stateProvider.state( 'index', {
url: '/index',
views: {
'main': {
controller: 'IndexCtrl',
templateUrl: 'index/index.tpl.html'
}
},
data: {pageTitle: 'index'}
});
})
.controller( 'IndexCtrl', function IndexController($scope) {
})
;
<div>
<h1>Index</h1>
</div>
# The `src/assets` Directory
There's really not much to say here. Every file in this directory is recursively transferred to `dist/assets/`.
# The `src/common/` Directory
The `src/common/` directory houses internal and third-party re-usable
components. Essentially, this folder is for everything that isn't completely
specific to this application.
Each component resides in its own directory that may then be structured any way
the developer desires. The build system will read all `*.js` files that do not
end in `.spec.js` as source files to be included in the final build, all
`*.spec.js` files as unit tests to be executed, and all `*.tpl.html` files as
templates to compiled into the `$templateCache`. There is currently no way to
handle components that do not meet this pattern.
```
src/
|- common/
| |- plusOne/
```
- `plusOne` - a simple directive to load a Google +1 Button on an element.
Every component contained here should be drag-and-drop reusable in any other
project; they should depend on no other components that aren't similarly
drag-and-drop reusable.
angular.module( 'plusOne', [] )
.directive( 'plusOne', function() {
return {
link: function( scope, element, attrs ) {
gapi.plusone.render( element[0], {
"size": "medium",
"href": "http://bit.ly/ngBoilerplate"
});
}
};
})
;
<!DOCTYPE html>
<html ng-app="Interactive" ng-controller="AppCtrl">
<head>
<title ng-bind="pageTitle"></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- font awesome from BootstrapCDN -->
<link href="http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<!-- compiled CSS --><% styles.forEach( function ( file ) { %>
<link rel="stylesheet" type="text/css" href="<%= file %>" /><% }); %>
<!-- compiled JavaScript --><% scripts.forEach( function ( file ) { %>
<script type="text/javascript" src="<%= file %>"></script><% }); %>
</head>
<body>
<div class="container" ui-view="main">
<h1>Test</h1>
</div>
</body>
</html>
# The `src/less` Directory
This folder is actually fairly self-explanatory: it contains your LESS/CSS files to be compiled during the build.
The only important thing to note is that *only* `main.less` will be processed during the build, meaning that all
other stylesheets must be *imported* into that one.
This should operate somewhat like the routing; the `main.less` file contains all of the site-wide styles, while
any styles that are route-specific should be imported into here from LESS files kept alongside the JavaScript
and HTML sources of that component. For example, the `home` section of the site has some custom styles, which
are imported like so:
```css
@import '../app/home/home.less';
```
The same principal, though not demonstrated in the code, would also apply to reusable components. CSS or LESS
files from external components would also be imported. If, for example, we had a Twitter feed directive with
an accompanying template and style, we would similarly import it:
```css
@import '../common/twitterFeed/twitterFeedDirective.less';
```
Using this decentralized approach for all our code (JavaScript, HTML, and CSS) creates a framework where a
component's directory can be dragged and dropped into *any other project* and it will "just work".
I would like to eventually automate the importing during the build so that manually importing it here would no
longer be required, but more thought must be put in to whether this is the best approach.
/**
* This is the main application stylesheet. It should include or import all
* stylesheets used throughout the application as this is the only stylesheet in
* the Grunt configuration that is automatically processed.
*/
/**
* First, we include the Twitter Bootstrap LESS files. Only the ones used in the
* project should be imported as the rest are just wasting space.
*/
@import '../../vendor/bootstrap/less/bootstrap.less';
/**
* This is our main variables file. We must include it last so we can overwrite any variable
* definitions in our imported stylesheets.
*/
@import 'variables.less';
/**
* Typography
*/
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
src: local('Roboto Regular'), local('Roboto-Regular'), url(fonts/Roboto-Regular.woff) format('woff');
}
code, pre, .pre {
padding: 5px;
margin: 10px 0;
background-color: #EFEFEF;
border: 1px solid #DADADA;
border-radius: 3px;
}
code {
padding: 0 3px;
}
pre {
margin: 10px 0;
padding: 5px;
}
.page-header {
margin-top: 60px;
&:first-child {
margin-top: 20px;
}
}
h2 {
margin: 20px 0;
color: #666;
}
/**
* Navigation
*/
.navbar {
margin-top: 20px;
small {
font-size: 60%;
}
}
/**
* Footer
*/
.footer {
margin-top: 80px;
.footer-inner {
padding: 40px 0;
border-top: 1px solid #DDD;
}
.social {
float: right;
margin: 0;
list-style: none;
li {
float: left;
margin-left: 20px;
a, a:visited {
color: @gray-light;
text-decoration: none;
font-size: 40px;
.transition( 250ms ease-in-out );
&:hover {
color: @gray;
}
}
}
}
}
/**
* Now that all app-wide styles have been applied, we can load the styles for
* all the submodules and components we are using.
*
* TODO: In a later version of this boilerplate, I'd like to automate this.
*/
/* @import '../app/home/home.less'; */
/**
* These are the variables used throughout the application. This is where
* overwrites that are not specific to components should be maintained.
*/
/**
* Typography-related.
*/
@sansFontFamily: 'Roboto', sans-serif;
angular.module("placeholders",["placeholders.img","placeholders.txt"]),angular.module("placeholders.img",[]).directive("phImg",function(){return{restrict:"A",scope:{dimensions:"@phImg"},link:function(e,t,n){function s(){var t=[e.size.h,e.size.w].sort(),n=Math.round(t[1]/16);return Math.max(i.text_size,n)}function o(){r=r||document.createElement("canvas");var t=r.getContext("2d"),n,o;return r.width=e.size.w,r.height=e.size.h,t.fillStyle=i.fill_color,t.fillRect(0,0,e.size.w,e.size.h),n=s(),o=e.dimensions,t.fillStyle=i.text_color,t.textAlign="center",t.textBaseline="middle",t.font="bold "+n+"pt sans-serif",t.measureText(o).width/e.size.w>1&&(n=i.text_size/(t.measureText(o).width/e.size.w),t.font="bold "+n+"pt sans-serif"),t.fillText(e.dimensions,e.size.w/2,e.size.h/2),r.toDataURL("image/png")}var r,i={text_size:10,fill_color:"#EEEEEE",text_color:"#AAAAAA"};e.$watch("dimensions",function(){if(!angular.isDefined(e.dimensions))return;var n=e.dimensions.match(/^(\d+)x(\d+)$/),r;if(!n){console.error("Expected '000x000'. Got "+e.dimensions);return}e.size={w:n[1],h:n[2]},t.prop("title",e.dimensions),t.prop("alt",e.dimensions),r=o(),t.prop("tagName")==="IMG"?t.prop("src",r):t.css("background-image",'url("'+r+'")')})}}}),angular.module("placeholders.txt",[]).factory("TextGeneratorService",function(){function t(e,t){return Math.floor(Math.random()*(t-e+1))+e}var e=["lorem","ipsum","dolor","sit","amet,","consectetur","adipiscing","elit","ut","aliquam,","purus","sit","amet","luctus","venenatis,","lectus","magna","fringilla","urna,","porttitor","rhoncus","dolor","purus","non","enim","praesent","elementum","facilisis","leo,","vel","fringilla","est","ullamcorper","eget","nulla","facilisi","etiam","dignissim","diam","quis","enim","lobortis","scelerisque","fermentum","dui","faucibus","in","ornare","quam","viverra","orci","sagittis","eu","volutpat","odio","facilisis","mauris","sit","amet","massa","vitae","tortor","condimentum","lacinia","quis","vel","eros","donec","ac","odio","tempor","orci","dapibus","ultrices","in","iaculis","nunc","sed","augue","lacus,","viverra","vitae","congue","eu,","consequat","ac","felis","donec","et","odio","pellentesque","diam","volutpat","commodo","sed","egestas","egestas","fringilla","phasellus","faucibus","scelerisque","eleifend","donec","pretium","vulputate","sapien","nec","sagittis","aliquam","malesuada","bibendum","arcu","vitae","elementum","curabitur","vitae","nunc","sed","velit","dignissim","sodales","ut","eu","sem","integer","vitae","justo","eget","magna","fermentum","iaculis","eu","non","diam","phasellus","vestibulum","lorem","sed","risus","ultricies","tristique","nulla","aliquet","enim","tortor,","at","auctor","urna","nunc","id","cursus","metus","aliquam","eleifend","mi","in","nulla","posuere","sollicitudin","aliquam","ultrices","sagittis","orci,","a","scelerisque","purus","semper","eget","duis","at","tellus","at","urna","condimentum","mattis","pellentesque","id","nibh","tortor,","id","aliquet","lectus","proin","nibh","nisl,","condimentum","id","venenatis","a,","condimentum","vitae","sapien","pellentesque","habitant","morbi","tristique","senectus","et","netus","et","malesuada","fames","ac","turpis","egestas","sed","tempus,","urna","et","pharetra","pharetra,","massa","massa","ultricies","mi,","quis","hendrerit","dolor","magna","eget","est","lorem","ipsum","dolor","sit","amet,","consectetur","adipiscing","elit","pellentesque","habitant","morbi","tristique","senectus","et","netus","et","malesuada","fames","ac","turpis","egestas","integer","eget","aliquet","nibh","praesent","tristique","magna","sit","amet","purus","gravida","quis","blandit","turpis","cursus","in","hac","habitasse","platea","dictumst","quisque","sagittis,","purus","sit","amet","volutpat","consequat,","mauris","nunc","congue","nisi,","vitae","suscipit","tellus","mauris","a","diam","maecenas","sed","enim","ut","sem","viverra","aliquet","eget","sit","amet","tellus","cras","adipiscing","enim","eu","turpis","egestas","pretium","aenean","pharetra,","magna","ac","placerat","vestibulum,","lectus","mauris","ultrices","eros,","in","cursus","turpis","massa","tincidunt","dui","ut","ornare","lectus","sit","amet","est","placerat","in","egestas","erat","imperdiet","sed","euismod","nisi","porta","lorem","mollis","aliquam","ut","porttitor","leo","a","diam","sollicitudin","tempor","id","eu","nisl","nunc","mi","ipsum,","faucibus","vitae","aliquet","nec,","ullamcorper","sit","amet","risus","nullam","eget","felis","eget","nunc","lobortis","mattis","aliquam","faucibus","purus","in","massa","tempor","nec","feugiat","nisl","pretium","fusce","id","velit","ut","tortor","pretium","viverra","suspendisse","potenti","nullam","ac","tortor","vitae","purus","faucibus","ornare","suspendisse","sed","nisi","lacus,","sed","viverra","tellus","in","hac","habitasse","platea","dictumst","vestibulum","rhoncus","est","pellentesque","elit","ullamcorper","dignissim","cras","tincidunt","lobortis","feugiat","vivamus","at","augue","eget","arcu","dictum","varius","duis","at","consectetur","lorem","donec","massa","sapien,","faucibus","et","molestie","ac,","feugiat","sed","lectus","vestibulum","mattis","ullamcorper","velit","sed","ullamcorper","morbi","tincidunt","ornare","massa,","eget","egestas","purus","viverra","accumsan","in","nisl","nisi,","scelerisque","eu","ultrices","vitae,","auctor","eu","augue","ut","lectus","arcu,","bibendum","at","varius","vel,","pharetra","vel","turpis","nunc","eget","lorem","dolor,","sed","viverra","ipsum","nunc","aliquet","bibendum","enim,","facilisis","gravida","neque","convallis","a","cras","semper","auctor","neque,","vitae","tempus","quam","pellentesque","nec","nam","aliquam","sem","et","tortor","consequat","id","porta","nibh","venenatis","cras","sed","felis","eget","velit","aliquet","sagittis","id","consectetur","purus","ut","faucibus","pulvinar","elementum","integer","enim","neque,","volutpat","ac","tincidunt","vitae,","semper","quis","lectus","nulla","at","volutpat","diam","ut","venenatis","tellus","in","metus","vulputate","eu","scelerisque","felis","imperdiet","proin","fermentum","leo","vel","orci","porta","non","pulvinar","neque","laoreet","suspendisse","interdum","consectetur","libero,","id","faucibus","nisl","tincidunt","eget","nullam","non","nisi","est,","sit","amet","facilisis","magna","etiam","tempor,","orci","eu","lobortis","elementum,","nibh","tellus","molestie","nunc,","non","blandit","massa","enim","nec","dui","nunc","mattis","enim","ut","tellus","elementum","sagittis","vitae","et","leo","duis","ut","diam","quam","nulla","porttitor","massa","id","neque","aliquam","vestibulum","morbi","blandit","cursus","risus,","at","ultrices","mi","tempus","imperdiet","nulla","malesuada","pellentesque","elit","eget","gravida","cum","sociis","natoque","penatibus","et","magnis","dis","parturient","montes,","nascetur","ridiculus","mus","mauris","vitae","ultricies","leo","integer","malesuada","nunc","vel","risus","commodo","viverra","maecenas","accumsan,","lacus","vel","facilisis","volutpat,","est","velit","egestas","dui,","id","ornare","arcu","odio","ut","sem","nulla","pharetra","diam","sit","amet","nisl","suscipit","adipiscing","bibendum","est","ultricies","integer","quis","auctor","elit","sed","vulputate","mi","sit","amet","mauris","commodo","quis","imperdiet","massa","tincidunt","nunc","pulvinar","sapien","et","ligula","ullamcorper","malesuada","proin","libero","nunc,","consequat","interdum","varius","sit","amet,","mattis","vulputate","enim","nulla","aliquet","porttitor","lacus,","luctus","accumsan","tortor","posuere","ac","ut","consequat","semper","viverra","nam","libero","justo,","laoreet","sit","amet","cursus","sit","amet,","dictum","sit","amet","justo","donec","enim","diam,","vulputate","ut","pharetra","sit","amet,","aliquam","id","diam","maecenas","ultricies","mi","eget","mauris","pharetra","et","ultrices","neque","ornare","aenean","euismod","elementum","nisi,","quis","eleifend","quam","adipiscing","vitae","proin","sagittis,","nisl","rhoncus","mattis","rhoncus,","urna","neque","viverra","justo,","nec","ultrices","dui","sapien","eget","mi","proin","sed","libero","enim,","sed","faucibus","turpis","in","eu","mi","bibendum","neque","egestas","congue","quisque","egestas","diam","in","arcu","cursus","euismod","quis","viverra","nibh","cras","pulvinar","mattis","nunc,","sed","blandit","libero","volutpat","sed","cras","ornare","arcu","dui","vivamus","arcu","felis,","bibendum","ut","tristique","et,","egestas","quis","ipsum","suspendisse","ultrices","fusce","ut","placerat","orci","nulla","pellentesque","dignissim","enim,","sit","amet","venenatis","urna","cursus","eget","nunc","scelerisque","viverra","mauris,","in","aliquam","sem","fringilla","ut","morbi","tincidunt","augue","interdum","velit","euismod","in","pellentesque","massa","placerat","duis","ultricies","lacus","sed","turpis","tincidunt","id","aliquet","risus","feugiat","in","ante","metus,","dictum","at","tempor","commodo,","ullamcorper","a","lacus","vestibulum","sed","arcu","non","odio","euismod","lacinia","at","quis","risus","sed","vulputate","odio","ut","enim","blandit","volutpat","maecenas","volutpat","blandit","aliquam","etiam","erat","velit,","scelerisque","in","dictum","non,","consectetur","a","erat","nam","at","lectus","urna","duis","convallis","convallis","tellus,","id","interdum","velit","laoreet","id","donec","ultrices","tincidunt","arcu,","non","sodales","neque","sodales","ut","etiam","sit","amet","nisl","purus,","in","mollis","nunc","sed","id","semper","risus","in","hendrerit","gravida","rutrum","quisque","non","tellus","orci,","ac","auctor","augue","mauris","augue","neque,","gravida","in","fermentum","et,","sollicitudin","ac","orci","phasellus","egestas","tellus","rutrum","tellus","pellentesque","eu","tincidunt","tortor","aliquam","nulla","facilisi","cras","fermentum,","odio","eu","feugiat","pretium,","nibh","ipsum","consequat","nisl,","vel","pretium","lectus","quam","id","leo","in","vitae","turpis","massa","sed","elementum","tempus","egestas","sed","sed","risus","pretium","quam","vulputate","dignissim","suspendisse","in","est","ante","in","nibh","mauris,","cursus","mattis","molestie","a,","iaculis","at","erat","pellentesque","adipiscing","commodo","elit,","at","imperdiet","dui","accumsan","sit","amet","nulla","facilisi","morbi","tempus","iaculis","urna,","id","volutpat","lacus","laoreet","non","curabitur","gravida","arcu","ac","tortor","dignissim","convallis","aenean","et","tortor","at","risus","viverra","adipiscing","at","in","tellus","integer","feugiat","scelerisque","varius","morbi","enim","nunc,","faucibus","a","pellentesque","sit","amet,","porttitor","eget","dolor","morbi","non","arcu","risus,","quis","varius","quam","quisque","id","diam","vel","quam","elementum","pulvinar","etiam","non","quam","lacus","suspendisse","faucibus","interdum","posuere","lorem","ipsum","dolor","sit","amet,","consectetur","adipiscing","elit","duis","tristique","sollicitudin","nibh","sit","amet","commodo","nulla","facilisi","nullam","vehicula","ipsum","a","arcu","cursus","vitae","congue","mauris","rhoncus","aenean","vel","elit","scelerisque","mauris","pellentesque","pulvinar","pellentesque","habitant","morbi","tristique","senectus","et","netus","et","malesuada","fames","ac","turpis","egestas","maecenas","pharetra","convallis","posuere","morbi","leo","urna,","molestie","at","elementum","eu,","facilisis","sed","odio","morbi","quis","commodo","odio","aenean","sed","adipiscing","diam","donec","adipiscing","tristique","risus","nec","feugiat","in","fermentum","posuere","urna","nec","tincidunt","praesent","semper","feugiat","nibh","sed","pulvinar","proin","gravida","hendrerit","lectus","a","molestie","gravida","dictum"];return{createSentence:function(n){var r,i;return n=n||t(5,20),r=t(0,e.length-n-1),i=e.slice(r,r+n).join(" ").replace(/\,$/g,"")+".",i=i.charAt(0).toUpperCase()+i.slice(1),i},createSentences:function(e){var n=[],r=0;e=e||t(3,5);for(r=0;r<e;r++)n.push(this.createSentence());return n.join(" ")},createParagraph:function(e){var t=this.createSentences(e);return"<p>"+t+"</p>"},createParagraphs:function(e,n){var r=[],i=0;e=e||t(3,7);for(i=0;i<e;i++)r.push(this.createParagraph(n));return r.join("\n")}}}).directive("phTxt",["TextGeneratorService",function(e){return{restrict:"EA",controller:["$scope","$element","$attrs",function(t,n,r){function o(){var t;s||!i?t=e.createParagraphs(s,i):t=e.createSentences(i),n.html(t)}var i,s;r.$observe("phTxt",function(e){var t,n;t=e.match(/(\d+)p/),n=e.match(/(\d+)s/),t!==null?s=parseInt(t[1],10):s=!1,n!==null?i=parseInt(n[1],10):i=!1,o()}),r.phTxt||o()}]}}]);
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment