├── .gitignore
├── .jshintrc
├── .travis.yml
├── Gruntfile.js
├── README.md
├── book.html
├── bower.json
├── css
├── dropdown.css
└── navbar.css
├── data
└── menus.json
├── dist
├── ui-components-0.0.1.js
└── ui-components-0.0.1.min.js
├── gulpfile.js
├── img
├── BootstrapNavBar.jpg
├── BootstrapNavBarHTML.jpg
├── BootstrapNavBarMobile.jpg
├── Continuous_Delivery_process_diagram.png
├── Node_NPM.jpg
├── UI-Bootstrap.jpg
├── UI-Bootstrap2.jpg
├── UI-Bootstrap3.jpg
├── active_text_api.jpg
├── angular-seed-dir.jpg
├── bootstrap_dropdown.jpg
├── bootstrap_dropdown_html.jpg
├── css_inspection.jpg
├── custom_element1.jpg
├── custom_element2.jpg
├── dev_tools_settings.jpg
├── dropdown_html.jpg
├── dropdown_js.jpg
├── dropdown_js_include.jpg
├── dropdown_tpl_js.jpg
├── grunt_build_task.jpg
├── nav_bar_dropdown_hover.jpg
├── nav_bar_dropdown_open.jpg
├── nav_bar_full.jpg
├── nav_bar_minimal.jpg
├── nav_bar_responsive_collapsed.jpg
├── nav_bar_responsive_expanded.jpg
├── shadow_inspection.jpg
├── smart_button.jpg
├── smart_button_active.jpg
├── smart_button_complete.jpg
├── smart_button_passive.jpg
├── text_as_api.jpg
├── transclusion.jpg
└── transclusion_code.jpg
├── index.html
├── js
└── UIComponents.js
├── lib
├── angular-animate.min.js
├── angular-mocks.js
├── angular-sanitize.min.js
├── angular.js
├── angular.min.js
├── angular.min.js.map
├── bootstrap-theme.css
├── bootstrap.css
├── bootstrap.min.css
├── bootstrap.min.js
├── jquery-2.1.0.min.js
├── project
│ ├── grunt-html2js-var
│ │ └── tasks
│ │ │ └── html2js-var.js
│ └── grunt-import-js
│ │ └── tasks
│ │ └── import-js.js
└── ui-bootstrap-collapse.js
├── navbar.html
├── package.json
├── src
├── Dropdown
│ ├── Dropdown.js
│ ├── Dropdown.less
│ ├── Dropdown.tpl.html
│ └── test
│ │ └── DropdownSpec.js
├── MenuItem
│ ├── MenuItem.js
│ ├── MenuItem.tpl.html
│ └── test
│ │ └── MenuItemSpec.js
├── Navbar
│ ├── Navbar.js
│ ├── Navbar.less
│ ├── Navbar.tpl.html
│ └── test
│ │ └── NavbarSpec.js
└── SmartButton
│ ├── SmartButton.js
│ ├── SmartButton.tpl.html
│ └── test
│ └── SmartButtonSpec.js
└── test
├── karma.conf.js
└── test.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "curly": true,
3 | "immed": true,
4 | "newcap": true,
5 | "noarg": true,
6 | "sub": true,
7 | "boss": true,
8 | "eqnull": true,
9 | "quotmark": "single",
10 | "trailing": true,
11 | "strict":true,
12 | "eqeqeq":true,
13 | "camelcase":true,
14 | "laxbreak":true,
15 | "multistr":true,
16 | "globals": {
17 | "angular": true,
18 | "jquery":true,
19 | "phantom":true,
20 | "node":true,
21 | "browser":true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 |
5 | before_script:
6 | - export DISPLAY=:99.0
7 | - sh -e /etc/init.d/xvfb start
8 | - npm install --quiet -g grunt-cli karma
9 | - npm install
10 |
11 | script: grunt
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 |
3 | // load local tasks
4 |
5 | //grunt.loadNpmTasks('grunt-html2js');
6 | grunt.loadTasks('lib/project/grunt-html2js-var/tasks');
7 | //grunt.loadNpmTasks('grunt-import');
8 | grunt.loadTasks('lib/project/grunt-import-js/tasks');
9 |
10 | // load 3rd party tasks
11 |
12 | // prepare encapsulated source files
13 | grunt.loadNpmTasks('grunt-contrib-less');
14 |
15 | // check the js source code quality
16 | grunt.loadNpmTasks('grunt-contrib-jshint');
17 | grunt.loadNpmTasks('grunt-karma');
18 |
19 | // generate any ng style docs and verison info
20 | grunt.loadNpmTasks('grunt-ngdocs');
21 | grunt.loadNpmTasks('grunt-conventional-changelog');
22 |
23 | // minify and combine js and css for production dist
24 | grunt.loadNpmTasks('grunt-contrib-uglify');
25 | grunt.loadNpmTasks('grunt-contrib-concat');
26 |
27 | // set up any desired source file watches
28 | grunt.loadNpmTasks('grunt-contrib-watch');
29 |
30 | // configure the tasks
31 | grunt.initConfig({
32 |
33 | // external library versions
34 | ngversion: '1.2.16',
35 | components: [], //to be filled in by build task
36 | libs: [], //to be filled in by build task
37 | dist: 'dist',
38 | filename: 'ui-components',
39 | filenamecustom: '<%= filename %>-custom',
40 |
41 | // make the NPM configs available as vars
42 | pkg: grunt.file.readJSON('package.json'),
43 | srcDir: 'src/',
44 | buildSrcDir: 'build/src/',
45 |
46 | // keep a module:filename lookup table since there isn's
47 | // a regular naming convention to work with
48 | // all small non-component and 3rd party libs
49 | // MUST be included here for build tasks
50 | libMap: {
51 | "ui.bootstrap.custom":"ui-bootstrap-collapse.js",
52 | "ngSanitize":"angular-sanitize.min.js"
53 | },
54 |
55 | meta: {
56 | modules: 'angular.module("uiComponents", [<%= srcModules %>]);',
57 | all: '<%= meta.srcModules %>',
58 | banner: ['/*',
59 | ' * <%= pkg.name %>',
60 | ' * <%= pkg.repository.url %>\n',
61 | ' * Version: <%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>',
62 | ' * License: <%= pkg.license %>',
63 | ' */\n'].join('\n')
64 | },
65 |
66 | karma: {
67 | options: {
68 | configFile: 'test/karma.conf.js',
69 | autoWatch: false,
70 | browsers: ['PhantomJS']
71 | },
72 | unit: {
73 | singleRun: true,
74 | reporters: 'dots'
75 | },
76 | chrome: {
77 | autoWatch: true,
78 | browsers: ['Chrome']
79 | }
80 | },
81 |
82 | jshint: {
83 | options: {
84 | force: true
85 | },
86 | all: ['Gruntfile.js', 'src/**/*.js']
87 | },
88 |
89 | // provide options and subtasks for uglify
90 | uglify: {
91 | options: {
92 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n',
93 | mangle: false,
94 | sourceMap: true
95 | },
96 | build: {
97 | src: 'js/MenuItem.js',
98 | dest: 'build/MenuItem.min.js'
99 | },
100 | test: {
101 | files: [{
102 | expand: true,
103 | cwd: 'build/src',
104 | src: '*.js',
105 | dest: 'build/test',
106 | ext: '.min.js'
107 | }]
108 | },
109 | dist:{
110 | src:['<%= concat.comps.dest %>'],
111 | dest:'<%= dist %>/<%= filename %>-<%= pkg.version %>.min.js'
112 | }
113 |
114 | },
115 |
116 | html2jsVar: {
117 | options: {},
118 | dist: {
119 | options: {
120 | module: null, // no bundle module for all the html2js templates
121 | base: '.'
122 | },
123 | files: [{
124 | expand: true,
125 | src: ['template/**/*.html'],
126 | ext: '.html.js'
127 | }]
128 | },
129 | main: {
130 | options: {
131 | quoteChar: '\'',
132 | module: null
133 | },
134 | files: [{
135 | expand: true, // Enable dynamic expansion.
136 | cwd: 'src/', // Src matches are relative to this path.
137 | src: ['**/*.html'], // Actual pattern(s) to match.
138 | dest: 'build/src/', // Destination path prefix.
139 | ext: '.tpl.js' // Dest filepaths will have this extension.
140 | }]
141 | }
142 | },
143 |
144 | watch: {
145 | source: {
146 | files: ['src/**/*.js','src/**/*.html','!src/**/test/*.js'],
147 | tasks: ['dev'],
148 | options: {
149 | livereload: true
150 | }
151 | }
152 | },
153 |
154 | importJs: {
155 | options: {},
156 | dev: {
157 | expand: true,
158 | cwd: 'src/',
159 | src: ['**/*.js', '!**/test/*.js'],
160 | dest: 'build/src/',
161 | ext: '.js'
162 | }
163 | },
164 |
165 | less: {
166 | // compile seperate css for each component
167 | dev: {
168 | files: [
169 | {
170 | expand: true, // Enable dynamic expansion.
171 | cwd: 'src/', // Src matches are relative to this path.
172 | src: ['**/*.less', '!**/less/*.less'], // Actual pattern(s) to match.
173 | dest: 'build/src/', // Destination path prefix.
174 | ext: '.css' // Dest filepaths will have this extension.
175 | }
176 | ]
177 | },
178 | production: {
179 | options: {
180 | cleancss: true
181 | },
182 | files: {
183 | 'css/ui-components.css': 'src/less/ui-components.less'
184 | }
185 | }
186 | },
187 | /*uglify: {
188 | options: {
189 | banner: '<%= meta.banner %>'
190 | },
191 | dist:{
192 | src:['<%= concat.comps.dest %>'],
193 | dest:'<%= dist %>/<%= filename %>-<%= pkg.version %>.min.js'
194 | }
195 | },*/
196 | concat: {
197 | // it is assumed that libs are already minified
198 | libs: {
199 | src: [],
200 | dest: '<%= dist %>/libs.js'
201 | },
202 | // concatenate just the modules, the output will
203 | // have libs prepended after running distFull
204 | comps: {
205 | options: {
206 | banner: '<%= meta.banner %><%= meta.modules %>\n'
207 | },
208 | //src filled in by build task
209 | src: [],
210 | dest: '<%= dist %>/<%= filename %>-<%= pkg.version %>.js'
211 | },
212 | // create minified file with everything
213 | distMin: {
214 | options: {},
215 | //src filled in by build task
216 | src: ['<%= concat.libs.dest %>','<%= uglify.dist.dest %>'],
217 | dest: '<%= dist %>/<%= filename %>-<%= pkg.version %>.min.js'
218 | },
219 | // create unminified file with everything
220 | distFull: {
221 | options: {},
222 | //src filled in by build task
223 | src: ['<%= concat.libs.dest %>','<%= concat.comps.dest %>'],
224 | dest: '<%= dist %>/<%= filename %>-<%= pkg.version %>.js'
225 | }
226 | }
227 | });
228 |
229 | grunt.registerTask('preCommit', ['jshint:all', 'karma:unit']);
230 | grunt.registerTask('dev', ['html2jsVar:main', 'importJs:dev', 'less:dev']);
231 | grunt.registerTask('default', ['dev', 'preCommit']);
232 |
233 | // Credit portions of the following code to UI-Bootstrap team
234 | // functions supporting build-all and build custom tasks
235 | var foundComponents = {};
236 | //var _ = grunt.util._;
237 | var _ = require('lodash');
238 |
239 | // capitalize utility
240 | function ucwords (text) {
241 | return text.replace(/^([a-z])|\s+([a-z])/g, function ($1) {
242 | return $1.toUpperCase();
243 | });
244 | }
245 |
246 | // uncapitalize utility
247 | function lcwords (text) {
248 | return text.replace(/^([A-Z])|\s+([A-Z])/g, function ($1) {
249 | return $1.toLowerCase();
250 | });
251 | }
252 |
253 | // enclose string in quotes
254 | // for creating "angular.module(..." statements
255 | function enquote(str) {
256 | return '"' + str + '"';
257 | }
258 |
259 | function findModule(name) {
260 |
261 | // by convention, the "name" of the module for files, dirs and
262 | // other reference is Capitalized
263 | // the nme when used in AngularJS code is not
264 | name = ucwords(name);
265 |
266 | // we only need to process each component once
267 | if (foundComponents[name]) { return; }
268 | foundComponents[name] = true;
269 |
270 | // add space to display name
271 | function breakup(text, separator) {
272 | return text.replace(/[A-Z]/g, function (match) {
273 | return separator + match;
274 | });
275 | }
276 |
277 | // gather all the necessary component meta info
278 | // todo - include doc and unit test info
279 | var component = {
280 | name: name,
281 | moduleName: enquote('uiComponents.' + lcwords(name)),
282 | displayName: breakup(name, ' '),
283 | srcDir: 'src/' + name + '/',
284 | buildSrcDir: 'build/src/' + name + '/',
285 | buildSrcFile: 'build/src/' + name + '/' + name + '.js',
286 | dependencies: dependenciesForModule(name),
287 | docs: {} // get and do stuff w/ assoc docs
288 | };
289 |
290 | // recursively locate all component dependencies
291 | component.dependencies.forEach(findModule);
292 |
293 | // add this component to the official grunt config
294 | grunt.config('components', grunt.config('components')
295 | .concat(component));
296 | }
297 |
298 | // for tracking misc non-component and 3rd party dependencies
299 | // does not include main libs i.e. Angular Core, jQuery, etc
300 | var dependencyLibs = [];
301 |
302 | function dependenciesForModule(name) {
303 | var srcDir = grunt.config('buildSrcDir');
304 | var path = srcDir + name + '/';
305 | var deps = [];
306 |
307 | // read in component src file contents
308 | var source = grunt.file.read(path + name + '.js');
309 |
310 | // parse deps from "angular.module(x,[deps])" in src
311 | var getDeps = function(contents) {
312 |
313 | // Strategy: find where module is declared,
314 | // and from there get everything i
315 | // nside the [] and split them by comma
316 | var moduleDeclIndex = contents
317 | .indexOf('angular.module(');
318 | var depArrayStart = contents
319 | .indexOf('[', moduleDeclIndex);
320 | var depArrayEnd = contents
321 | .indexOf(']', depArrayStart);
322 | var dependencies = contents
323 | .substring(depArrayStart + 1, depArrayEnd);
324 | dependencies.split(',').forEach(function(dep) {
325 |
326 | // locate our components that happen to be deps
327 | // for tracking by grunt.config
328 | if (dep.indexOf('uiComponents.') > -1) {
329 | var depName = dep.trim()
330 | .replace('uiComponents.','')
331 | .replace(/['"]/g,'');
332 | if (deps.indexOf(depName) < 0) {
333 | deps.push(ucwords(depName));
334 | // recurse through deps of deps
335 | deps = deps
336 | .concat(dependenciesForModule(depName));
337 | }
338 | // attach other deps to a non-grunt var
339 | } else {
340 | var libName = dep.trim().replace(/['"]/g,'');
341 | if(libName && !_.contains(dependencyLibs ,libName) ){
342 | dependencyLibs.push(libName);
343 | }
344 | }
345 | });
346 | };
347 | getDeps(source);
348 | return deps;
349 | }
350 |
351 | grunt.registerTask('build', 'Create component build files', function() {
352 |
353 | // map of all non-component deps
354 | var libMap = grunt.config('libMap');
355 | // array of the above to include in build
356 | var libFiles = grunt.config('libs');
357 | var fileName = '';
358 | var buildSrcFiles = [];
359 |
360 | var addLibs = function(lib){
361 | fileName = 'lib/' + libMap[lib];
362 | libFiles.push(fileName);
363 | };
364 |
365 | //If arguments define what modules to build,
366 | // build those. Else, everything
367 | if (this.args.length) {
368 | this.args.forEach(findModule);
369 | _.forEach(dependencyLibs, addLibs);
370 | grunt.config('filename', grunt.config('filenamecustom'));
371 |
372 | // else build everything
373 | } else {
374 | // include all non-component deps in build
375 | var libFileNames = _.keys(grunt.config('libMap'));
376 | _.forEach(libFileNames, addLibs);
377 | }
378 | grunt.config('libs', libFiles);
379 |
380 | var components = grunt.config('components');
381 | // prepare source modules for custom build
382 | if(components.length){
383 | grunt.config('srcModules', _.pluck(components, 'moduleName'));
384 | buildSrcFiles = _.pluck(components, 'buildSrcFile');
385 | // all source files for full library
386 | }else{
387 | buildSrcFiles = grunt.file.expand([
388 | 'build/src/**/*.js',
389 | '!build/src/**/*.tpl.js',
390 | '!build/src/**/test/*.js'
391 | ]);
392 |
393 | // prepare module names for "angular.module('',[])" in build file
394 | var mods = [];
395 | _.forEach(buildSrcFiles, function(src){
396 | var filename = src.replace(/^.*[\\\/]/, '')
397 | .replace(/\.js/,'');
398 | filename = enquote('uiComponents.' + lcwords(filename));
399 | mods.push(filename);
400 | });
401 | grunt.config('srcModules', mods);
402 | }
403 |
404 | // add src files to concat sub-tasks
405 | grunt.config('concat.comps.src', grunt.config('concat.comps.src')
406 | .concat(buildSrcFiles));
407 | grunt.config('concat.libs.src', grunt.config('concat.libs.src')
408 | .concat(libFiles));
409 |
410 | // time to put it all together
411 | grunt.task.run([
412 | 'karma:unit',
413 | 'concat:libs',
414 | 'concat:comps',
415 | 'uglify',
416 | 'concat:distMin',
417 | 'concat:distFull'
418 | ]);
419 | });
420 |
421 | grunt.registerTask('default', ['dev', 'preCommit', 'build']);
422 | };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | angularjs-web-component-development
2 | ===================================
3 |
4 | Companion code to my book Web Component Architecture & Development with AngularJS
5 |
6 | ***
7 |
8 | [](http://travis-ci.org/dgs700/angularjs-web-component-development)
9 | [](https://david-dm.org/dgs700/angularjs-web-component-development#info=devDependencies)
10 |
--------------------------------------------------------------------------------
/book.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
22 |
23 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
Lorem ipsum dolor...
40 |
41 |
42 |
Class aptent taciti...
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | Click Me
51 |
52 |
53 |
54 |
55 | Click the button above to toggle.
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-components",
3 | "version": "0.0.1",
4 | "homepage": "https://github.com/dgs700/angularjs-web-component-development",
5 | "authors": {
6 | "name": "David Shapiro",
7 | "email": "dave@david-shapiro.net",
8 | "url": "https://github.com/dgs700"
9 | },
10 | "description": "AngularJS UI Components",
11 | "main": "dist/ui-components-0.0.1.min.js",
12 | "keywords": [
13 | "Angular",
14 | "Components"
15 | ],
16 | "license": "MIT",
17 | "ignore": [
18 | "**/.*",
19 | "img",
20 | "less",
21 | "src",
22 | "node_modules",
23 | "test"
24 | ],
25 | "dependencies": {
26 | "angular": "~1.2.16",
27 | "bootstrap": "~3.1.1",
28 | "ngSanitize":"~0.0.2",
29 | "jquery": "1.10.2"
30 | }
31 | }
--------------------------------------------------------------------------------
/css/dropdown.css:
--------------------------------------------------------------------------------
1 | /*
2 | an example of using a css .classname for name-spacing
3 | the styles for a component that may be applied more than once on a
4 | page
5 |
6 | .classnames provide much less specificity than #ids, so there is a
7 | higher likelihood that you may also need to use the dreaded
8 | "!important" postfix on rules
9 | */
10 | .uic-dropdown .dropdown-menu{
11 | display: none;
12 | background-color: #333;
13 | border-color: #080808;
14 | color: #AAA;
15 | }
16 | @media (max-width: 768px) {
17 | .uic-dropdown .dropdown-menu{
18 | position: static;
19 | float: none;
20 | }
21 | }
22 | .uic-dropdown .dropdown-menu li a{
23 | color: #AAA;
24 | }
25 | .uic-dropdown .dropdown-menu li a:hover{
26 | color: #EEE;
27 | background-color: #111;
28 | }
29 |
--------------------------------------------------------------------------------
/css/navbar.css:
--------------------------------------------------------------------------------
1 | /*
2 | an example of name-spacing a "singleton" component with a css #id
3 | this allows us to 1)override matching global styles with a
4 | higher specificity, and 2)prevent our component styles from
5 | bleeding to the rest if the page
6 |
7 | #ids should only be used for components meant to be used only
8 | once on a page
9 |
10 | given that this example is based off of bootstrap.css, the real component
11 | css would likely be much more extensive given that bootstrap.css may or
12 | may not be available in the host page
13 | */
14 | #uic-navbar.navbar-inverse .navbar-brand {
15 | color: #FFF;
16 | }
17 | #uic-navbar .nav a {cursor: pointer;}
18 | #uic-navbar .navbar-collapse {max-height: inherit;}
19 | #uic-navbar .navbar-collapse.in {overflow-y: inherit;}
20 | #uic-navbar .uic-dropdown .dropdown-menu {
21 | display: none;
22 | background-color: #222;
23 | border-color: #080808;
24 | color: #999;
25 | }
26 | @media (max-width: 768px) {
27 | #uic-navbar .uic-dropdown .dropdown-menu{
28 | position: static;
29 | float: none;
30 | }
31 | }
32 | #uic-navbar .uic-dropdown .dropdown-menu li a {
33 | color: #999;
34 | }
35 | #uic-navbar .uic-dropdown .dropdown-menu li a:hover {
36 | color: #FFF;
37 | background-color: #222;
38 | }
39 | #uic-navbar .navbar-inverse .navbar-nav .disabled>a,
40 | #uic-navbar .navbar-inverse .navbar-nav .disabled>a:hover,
41 | #uic-navbar .navbar-inverse .navbar-nav .disabled>a:focus,
42 | #uic-navbar .dropdown-menu>li.disabled>a,
43 | #uic-navbar .dropdown-menu>li.disabled>a:hover,
44 | #uic-navbar .dropdown-menu>li.disabled>a:focus
45 | {
46 | color: #444;
47 | background-color: transparent;
48 | }
49 |
--------------------------------------------------------------------------------
/data/menus.json:
--------------------------------------------------------------------------------
1 | {"header":[
2 | {"Products":[
3 | {"Products":[
4 | {
5 | "text":"Scrum Manager",
6 | "url":"/products/scrum-manager"
7 | },{
8 | "text":"AppBeat",
9 | "url":"/products/app-beat"
10 | },{
11 | "text":"Solidify on Request",
12 | "url":"/products/sor"
13 | },{
14 | "text":"IQ Everywhere",
15 | "url":"/products/iq-anywhere"
16 | },{
17 | "text":"Service Everywhere",
18 | "url":"/products/service-everywhere"
19 | }
20 | ]},
21 | {"Pricing":[
22 | {
23 | "text":"Scrum Manager",
24 | "url":"/pricing/scrum-manager"
25 | },{
26 | "text":"AppBeat",
27 | "url":"/pricing/app-beat"
28 | },{
29 | "text":"Solidify on Request",
30 | "url":"/pricing/sor"
31 | },{
32 | "text":"IQ Everywhere",
33 | "url":"/pricing/iq-anywhere"
34 | },{
35 | "text":"Service Everywhere",
36 | "url":"/pricing/service-everywhere"
37 | }
38 | ]},
39 | {"Support":[
40 | {
41 | "text":"White Papers",
42 | "url":"/support/papers"
43 | },{
44 | "text":"Case Studies",
45 | "url":"/support/studies"
46 | },{
47 | "text":"Videos",
48 | "url":"/support/videos"
49 | },{
50 | "text":"Webinars",
51 | "url":"/support/webinars"
52 | },{
53 | "text":"Documentation",
54 | "url":"/support/documentation"
55 | },{
56 | "text":"News & Reports",
57 | "url":"/learn/news"
58 | }
59 | ]},
60 | {"Company":[
61 | {
62 | "text":"Contact Us",
63 | "url":"/company/contact"
64 | },{
65 | "text":"Jobs",
66 | "url":"/company/jobs"
67 | },{
68 | "text":"Privacy Statement",
69 | "url":"/company/privacy"
70 | },{
71 | "text":"Terms of Use",
72 | "url":"/company/terms"
73 | }]}
74 | ]}
75 | ]}
76 |
--------------------------------------------------------------------------------
/dist/ui-components-0.0.1.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | * angular-ui-bootstrap
3 | * http://angular-ui.github.io/bootstrap/
4 |
5 | * Version: 0.10.0 - 2014-01-15
6 | * License: MIT
7 | */
8 | angular.module("ui.bootstrap.custom", ["ui.bootstrap.transition","ui.bootstrap.collapse"]);
9 | angular.module('ui.bootstrap.transition', [])
10 |
11 | /**
12 | * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
13 | * @param {DOMElement} element The DOMElement that will be animated.
14 | * @param {string|object|function} trigger The thing that will cause the transition to start:
15 | * - As a string, it represents the css class to be added to the element.
16 | * - As an object, it represents a hash of style attributes to be applied to the element.
17 | * - As a function, it represents a function to be called that will cause the transition to occur.
18 | * @return {Promise} A promise that is resolved when the transition finishes.
19 | */
20 | .factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {
21 |
22 | var $transition = function(element, trigger, options) {
23 | options = options || {};
24 | var deferred = $q.defer();
25 | var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];
26 |
27 | var transitionEndHandler = function(event) {
28 | $rootScope.$apply(function() {
29 | element.unbind(endEventName, transitionEndHandler);
30 | deferred.resolve(element);
31 | });
32 | };
33 |
34 | if (endEventName) {
35 | element.bind(endEventName, transitionEndHandler);
36 | }
37 |
38 | // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
39 | $timeout(function() {
40 | if ( angular.isString(trigger) ) {
41 | element.addClass(trigger);
42 | } else if ( angular.isFunction(trigger) ) {
43 | trigger(element);
44 | } else if ( angular.isObject(trigger) ) {
45 | element.css(trigger);
46 | }
47 | //If browser does not support transitions, instantly resolve
48 | if ( !endEventName ) {
49 | deferred.resolve(element);
50 | }
51 | });
52 |
53 | // Add our custom cancel function to the promise that is returned
54 | // We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
55 | // i.e. it will therefore never raise a transitionEnd event for that transition
56 | deferred.promise.cancel = function() {
57 | if ( endEventName ) {
58 | element.unbind(endEventName, transitionEndHandler);
59 | }
60 | deferred.reject('Transition cancelled');
61 | };
62 |
63 | return deferred.promise;
64 | };
65 |
66 | // Work out the name of the transitionEnd event
67 | var transElement = document.createElement('trans');
68 | var transitionEndEventNames = {
69 | 'WebkitTransition': 'webkitTransitionEnd',
70 | 'MozTransition': 'transitionend',
71 | 'OTransition': 'oTransitionEnd',
72 | 'transition': 'transitionend'
73 | };
74 | var animationEndEventNames = {
75 | 'WebkitTransition': 'webkitAnimationEnd',
76 | 'MozTransition': 'animationend',
77 | 'OTransition': 'oAnimationEnd',
78 | 'transition': 'animationend'
79 | };
80 | function findEndEventName(endEventNames) {
81 | for (var name in endEventNames){
82 | if (transElement.style[name] !== undefined) {
83 | return endEventNames[name];
84 | }
85 | }
86 | }
87 | $transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
88 | $transition.animationEndEventName = findEndEventName(animationEndEventNames);
89 | return $transition;
90 | }]);
91 |
92 | angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition'])
93 |
94 | .directive('collapse', ['$transition', function ($transition, $timeout) {
95 |
96 | return {
97 | link: function (scope, element, attrs) {
98 |
99 | var initialAnimSkip = true;
100 | var currentTransition;
101 |
102 | function doTransition(change) {
103 | var newTransition = $transition(element, change);
104 | if (currentTransition) {
105 | currentTransition.cancel();
106 | }
107 | currentTransition = newTransition;
108 | newTransition.then(newTransitionDone, newTransitionDone);
109 | return newTransition;
110 |
111 | function newTransitionDone() {
112 | // Make sure it's this transition, otherwise, leave it alone.
113 | if (currentTransition === newTransition) {
114 | currentTransition = undefined;
115 | }
116 | }
117 | }
118 |
119 | function expand() {
120 | if (initialAnimSkip) {
121 | initialAnimSkip = false;
122 | expandDone();
123 | } else {
124 | element.removeClass('collapse').addClass('collapsing');
125 | doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone);
126 | }
127 | }
128 |
129 | function expandDone() {
130 | element.removeClass('collapsing');
131 | element.addClass('collapse in');
132 | element.css({height: 'auto'});
133 | }
134 |
135 | function collapse() {
136 | if (initialAnimSkip) {
137 | initialAnimSkip = false;
138 | collapseDone();
139 | element.css({height: 0});
140 | } else {
141 | // CSS transitions don't work with height: auto, so we have to manually change the height to a specific value
142 | element.css({ height: element[0].scrollHeight + 'px' });
143 | //trigger reflow so a browser realizes that height was updated from auto to a specific value
144 | var x = element[0].offsetWidth;
145 |
146 | element.removeClass('collapse in').addClass('collapsing');
147 |
148 | doTransition({ height: 0 }).then(collapseDone);
149 | }
150 | }
151 |
152 | function collapseDone() {
153 | element.removeClass('collapsing');
154 | element.addClass('collapse');
155 | }
156 |
157 | scope.$watch(attrs.collapse, function (shouldCollapse) {
158 | if (shouldCollapse) {
159 | collapse();
160 | } else {
161 | expand();
162 | }
163 | });
164 | }
165 | };
166 | }]);
167 |
168 | /*
169 | AngularJS v1.2.16
170 | (c) 2010-2014 Google, Inc. http://angularjs.org
171 | License: MIT
172 | */
173 | (function(p,h,q){'use strict';function E(a){var e=[];s(e,h.noop).chars(a);return e.join("")}function k(a){var e={};a=a.split(",");var d;for(d=0;d=c;d--)e.end&&e.end(f[d]);f.length=c}}var b,g,f=[],l=a;for(f.last=function(){return f[f.length-1]};a;){g=!0;if(f.last()&&x[f.last()])a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+f.last()+"[^>]*>","i"),function(b,a){a=a.replace(H,"$1").replace(I,"$1");e.chars&&e.chars(r(a));return""}),c("",f.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",b)===b&&(e.comment&&e.comment(a.substring(4,b)),a=a.substring(b+3),g=!1);else if(y.test(a)){if(b=a.match(y))a=
175 | a.replace(b[0],""),g=!1}else if(J.test(a)){if(b=a.match(z))a=a.substring(b[0].length),b[0].replace(z,c),g=!1}else K.test(a)&&(b=a.match(A))&&(a=a.substring(b[0].length),b[0].replace(A,d),g=!1);g&&(b=a.indexOf("<"),g=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),e.chars&&e.chars(r(g)))}if(a==l)throw L("badparse",a);l=a}c()}function r(a){if(!a)return"";var e=M.exec(a);a=e[1];var d=e[3];if(e=e[2])n.innerHTML=e.replace(//g,">")}function s(a,e){var d=!1,c=h.bind(a,a.push);return{start:function(a,g,f){a=h.lowercase(a);!d&&x[a]&&(d=a);d||!0!==C[a]||(c("<"),c(a),h.forEach(g,function(d,f){var g=h.lowercase(f),k="img"===a&&"src"===g||"background"===g;!0!==O[g]||!0===D[g]&&!e(d,k)||(c(" "),c(f),c('="'),c(B(d)),c('"'))}),c(f?"/>":">"))},end:function(a){a=h.lowercase(a);d||!0!==C[a]||(c(""),c(a),c(">"));a==d&&(d=!1)},chars:function(a){d||
177 | c(B(a))}}}var L=h.$$minErr("$sanitize"),A=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,z=/^<\s*\/\s*([\w:-]+)[^>]*>/,G=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,K=/^,J=/^<\s*\//,H=/\x3c!--(.*?)--\x3e/g,y=/]*?)>/i,I=/]/,d=/^mailto:/;return function(c,b){function g(a){a&&m.push(E(a))}function f(a,c){m.push("');g(c);m.push(" ")}if(!c)return c;for(var l,k=c,m=[],n,p;l=k.match(e);)n=l[0],l[2]==l[3]&&(n="mailto:"+n),p=l.index,g(k.substr(0,p)),f(n,l[0].replace(d,"")),k=k.substring(p+l[0].length);g(k);return a(m.join(""))}}])})(window,window.angular);
181 | //# sourceMappingURL=angular-sanitize.min.js.map
182 | /*! AngularComponents 2015-02-05 */
183 |
184 | angular.module("uiComponents",["uiComponents.dropdown","uiComponents.menuItem","uiComponents.navbar","uiComponents.smartButton"]),function(){"use strict";var tpl="";tpl=' ',angular.module("uiComponents.dropdown",["uiComponents.menuItem","ui.bootstrap.custom","ngSanitize"]).service("uicDropdownService",["$document",function($document){function closeDropdown(evt){evt&&evt.isDefaultPrevented()||openScope.$apply(function(){openScope.isOpen=!1})}function escapeKeyBind(evt){27===evt.which&&(openScope.focusToggleElement(),closeDropdown())}var openScope=null,dropdowns=[];return{register:function(scope){dropdowns.push(scope)},remove:function(scope){for(var x=0;x',$scope.menuItems=menuItems,$scope.uicId=dropdownTitle)}),$attrs.text&&($scope.uicId=$attrs.text,$scope.dropdownTitle=$scope.uicId+' '),$scope.menuItems&&($scope.jsonData=!0),this.init=function(element){that.$element=element},this.toggle=function(open){return $scope.isOpen=arguments.length?!!open:!$scope.isOpen,$scope.isOpen},$scope.focusToggleElement=function(){that.toggleElement&&that.toggleElement[0].focus()},$scope.selected=function($event,scope){$scope.$emit("menu-item-selected",scope),$event.preventDefault(),$event.stopPropagation()},$scope.$watch("isOpen",function(isOpen){isOpen?($scope.focusToggleElement(),uicDropdownService.open($scope),$scope.$emit("dropdown-opened")):(uicDropdownService.close($scope),$scope.$emit("dropdown-closed"))}),$scope.$on("$locationChangeSuccess",function(){}),$scope.$on("menu-item-selected",function(){})}],link:function(scope,iElement,iAttrs,dropdownCtrl){dropdownCtrl.init(iElement),scope.iElement=iElement,uicDropdownService.register(scope)}}}]).directive("dropdownMenu",function(){return{restrict:"C",link:function(scope,element){scope.$watch("isOpen",function(isOpen,wasOpen){isOpen!==wasOpen&&element.stop().slideToggle(200)})}}}).directive("dropdownToggle",function(){return{restrict:"A",require:"?^uicDropdownMenu",link:function(scope,element,attrs,dropdownCtrl){if(dropdownCtrl){dropdownCtrl.toggleElement=element;var toggleDropdown=function(event){event.preventDefault(),event.stopPropagation(),element.hasClass("disabled")||attrs.disabled||scope.$apply(function(){dropdownCtrl.toggle()})};element.bind("click",toggleDropdown),scope.$on("$destroy",function(){element.unbind("click",toggleDropdown)})}}}})}(),function(){"use strict";var tpl="";tpl='',angular.module("uiComponents.menuItem",[]).directive("uicMenuItem",[function(){return{template:tpl,replace:!0,restrict:"E",scope:{text:"@",url:"@"},controller:["$scope",function($scope){$scope.disablable="",$scope.selected=function($event,scope){$scope.$emit("menu-item-selected",scope),$event.preventDefault(),$event.stopPropagation()}}],link:function(scope){scope.url||(scope.disablable="disabled")}}}])}(),function(){"use strict";var tpl="";tpl=' ',angular.module("uiComponents.navbar",["uiComponents.dropdown"]).service("uicNavBarService",["$window",function($window){var menus=!1;this.addMenus=function(data){angular.isArray(data)&&(menus=data)},this.getMenus=function(){return $window.UIC&&$window.UIC.header?$window.UIC.header:menus?menus:!1}}]).directive("uicInclude",function(){return{restrict:"A",link:function(scope,iElement,iAttrs,ctrl,$transclude){$transclude(scope,function(clone){iElement.append(clone)})}}}).directive("uicNavBar",["uicDropdownService","uicNavBarService","$location","$compile","$log",function(uicDropdownService,uicNavBarService,$location,$compile,$log){return{template:tpl,restrict:"E",transclude:!0,replace:!0,scope:{minimalHeader:"@minimal",homeUrl:"@"},controller:["$scope","$element","$attrs",function($scope,$element){var that=this;this.init=function(element){that.$element=element},this.addDropdown=function(menuObj){var newScope=$scope.$root.$new();newScope.menu=newScope.$parent.menu=menuObj;var $el=$compile(" ")(newScope),isolateScope=$el.isolateScope();isolateScope.$digest(),$element.find("ul").last().append($el)},this.removeDropdown=function(dropdownId){var menuArray=$scope.registeredMenus.filter(function(el){return el.uicId==dropdownId}),dropdown=menuArray[0];uicDropdownService.remove(dropdown),dropdown.iElement.remove(),dropdown.$destroy()},$scope.addOrRemove=function(dropdowns,action){action+="Dropdown",angular.isArray(dropdowns)?angular.forEach(dropdowns,function(dropdown){that[action](dropdown)}):that[action](dropdowns)},$scope.isCollapsed=!0,$scope.menus=uicNavBarService.getMenus(),$scope.registeredMenus=[],$scope.$on("header-minimize",function(){$scope.minimalHeader=!0}),$scope.$on("header-maximize",function(){$scope.minimalHeader=!1}),$scope.$on("add-nav-dropdowns",function(evt,obj){$scope.addOrRemove(obj,"add")}),$scope.$on("remove-nav-dropdowns",function(evt,ids){$scope.addOrRemove(ids,"remove")}),$scope.$on("dropdown-opened",function(evt,targetScope){$log.log("dropdown-opened",targetScope)}),$scope.$on("dropdown-closed",function(evt,targetScope){$log.log("dropdown-closed",targetScope)}),$scope.$on("menu-item-selected",function(evt,scope){var url;try{url=scope.url||scope.item.url,$log.log(url)}catch(err){$log.warn("no url")}})}],link:function(scope,iElement,iAttrs,navCtrl){scope.registeredMenus=uicDropdownService.getDropdowns(),scope.position="true"==iAttrs.sticky?"navbar-fixed-top":"navbar-static-top",scope.theme=iAttrs.theme?iAttrs.theme:null,navCtrl.init(iElement)}}}])}(),function(){"use strict";var tpl="";tpl=' {{bttnText}} ';var buttons=angular.module("uiComponents.smartButton",[]);buttons.directive("smartButton",["$timeout",function($timeout){return{template:tpl,restrict:"E",replace:!0,transclude:!0,scope:{debug:"&"},controller:function($scope){$scope.bttnClass="btn btn-default",$scope.bttnText=$scope.defaultText="Smart Button",$scope.activeText="Processing...",$scope.doSomething=function(elem){$scope.bttnText=$scope.activeText,$timeout(function(){$scope.bttnText="We're Done!"},3e3),$scope.$emit("smart-button-click",elem)},$scope.$on("smart-button-command",function(evt,targetComponentId,command){targetComponentId===$scope.$id&&command.setClass&&($scope.bttnClass="btn "+command.setClass)})},link:function(scope,iElement,iAttrs){iAttrs.defaultText&&(scope.bttnText=scope.defaultText=iAttrs.defaultText),iAttrs.activeText&&(scope.activeText=iAttrs.activeText)}}}])}();
185 | //# sourceMappingURL=ui-components-0.0.1.min.map
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var pkg = require('./package.json');
3 |
4 | var uglify = require('gulp-uglify');
5 | var concat = require('gulp-concat');
6 | var html2js = require('gulp-html2js');
7 | var include = require('gulp-include');
8 | var jshint = require('gulp-jshint');
9 | var less = require('gulp-less');
10 | var karma = require('gulp-karma');
11 |
12 | var paths = {
13 | js: [
14 | 'js/SmartButton.js'
15 | ]
16 | };
17 |
18 | gulp.task('less', function(){
19 | gulp.src('less/ui-components.less')
20 | .pipe(less({
21 | filename: 'ui-components.css'
22 | }))
23 | .pipe(gulp.dest('css'));
24 | });
25 |
26 | gulp.task('uglify', ['less'], function(){
27 | gulp.src(paths.js)
28 | //.pipe(concat(pkg.name+'.js'))
29 | .pipe(uglify())
30 | .pipe(gulp.dest('build/test'));
31 | });
32 |
33 | gulp.task('watch', function(){
34 | gulp.watch(paths.js, ['uglify']);
35 | });
36 |
37 | gulp.task('default', ['uglify']);
38 | //gulp.task('default', function() {
39 | // // place code for your default task here
40 | //});
--------------------------------------------------------------------------------
/img/BootstrapNavBar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/BootstrapNavBar.jpg
--------------------------------------------------------------------------------
/img/BootstrapNavBarHTML.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/BootstrapNavBarHTML.jpg
--------------------------------------------------------------------------------
/img/BootstrapNavBarMobile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/BootstrapNavBarMobile.jpg
--------------------------------------------------------------------------------
/img/Continuous_Delivery_process_diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/Continuous_Delivery_process_diagram.png
--------------------------------------------------------------------------------
/img/Node_NPM.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/Node_NPM.jpg
--------------------------------------------------------------------------------
/img/UI-Bootstrap.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/UI-Bootstrap.jpg
--------------------------------------------------------------------------------
/img/UI-Bootstrap2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/UI-Bootstrap2.jpg
--------------------------------------------------------------------------------
/img/UI-Bootstrap3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/UI-Bootstrap3.jpg
--------------------------------------------------------------------------------
/img/active_text_api.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/active_text_api.jpg
--------------------------------------------------------------------------------
/img/angular-seed-dir.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/angular-seed-dir.jpg
--------------------------------------------------------------------------------
/img/bootstrap_dropdown.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/bootstrap_dropdown.jpg
--------------------------------------------------------------------------------
/img/bootstrap_dropdown_html.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/bootstrap_dropdown_html.jpg
--------------------------------------------------------------------------------
/img/css_inspection.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/css_inspection.jpg
--------------------------------------------------------------------------------
/img/custom_element1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/custom_element1.jpg
--------------------------------------------------------------------------------
/img/custom_element2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/custom_element2.jpg
--------------------------------------------------------------------------------
/img/dev_tools_settings.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/dev_tools_settings.jpg
--------------------------------------------------------------------------------
/img/dropdown_html.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/dropdown_html.jpg
--------------------------------------------------------------------------------
/img/dropdown_js.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/dropdown_js.jpg
--------------------------------------------------------------------------------
/img/dropdown_js_include.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/dropdown_js_include.jpg
--------------------------------------------------------------------------------
/img/dropdown_tpl_js.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/dropdown_tpl_js.jpg
--------------------------------------------------------------------------------
/img/grunt_build_task.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/grunt_build_task.jpg
--------------------------------------------------------------------------------
/img/nav_bar_dropdown_hover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/nav_bar_dropdown_hover.jpg
--------------------------------------------------------------------------------
/img/nav_bar_dropdown_open.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/nav_bar_dropdown_open.jpg
--------------------------------------------------------------------------------
/img/nav_bar_full.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/nav_bar_full.jpg
--------------------------------------------------------------------------------
/img/nav_bar_minimal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/nav_bar_minimal.jpg
--------------------------------------------------------------------------------
/img/nav_bar_responsive_collapsed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/nav_bar_responsive_collapsed.jpg
--------------------------------------------------------------------------------
/img/nav_bar_responsive_expanded.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/nav_bar_responsive_expanded.jpg
--------------------------------------------------------------------------------
/img/shadow_inspection.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/shadow_inspection.jpg
--------------------------------------------------------------------------------
/img/smart_button.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/smart_button.jpg
--------------------------------------------------------------------------------
/img/smart_button_active.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/smart_button_active.jpg
--------------------------------------------------------------------------------
/img/smart_button_complete.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/smart_button_complete.jpg
--------------------------------------------------------------------------------
/img/smart_button_passive.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/smart_button_passive.jpg
--------------------------------------------------------------------------------
/img/text_as_api.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/text_as_api.jpg
--------------------------------------------------------------------------------
/img/transclusion.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/transclusion.jpg
--------------------------------------------------------------------------------
/img/transclusion_code.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dgs700/angularjs-web-component-development/8d5005b379d78db776f1f44ed8ec7d884c1645d1/img/transclusion_code.jpg
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | A Reusable Smart Button Component
5 |
6 |
7 |
8 |
9 |
10 |
Smart Buttons!
11 |
12 | Dumb Button
13 |
14 |
15 |
20 |
21 |
22 |
26 |
27 |
28 |
33 | / Text from transclusion.
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/js/UIComponents.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | 'use strict';
3 | angular.module('UIComponents',['uiComponents.smartButton'])
4 | .run(['$rootScope', '$window', function($rootScope, $window){
5 | // let's change the style class of a clicked smart button
6 | $rootScope.$on('smart-button-click', function(evt){
7 | // AngularJS creates unique IDs for every instantiated scope
8 | var targetComponentId = evt.targetScope.$id;
9 | var command = {setClass: 'btn-warning'};
10 | $rootScope.$broadcast('smart-button-command', targetComponentId, command);
11 | });
12 | $rootScope.showAlert = function (message) {
13 | $window.alert(message);
14 | return message;
15 | };
16 | }]);
17 | })();
18 |
--------------------------------------------------------------------------------
/lib/angular-animate.min.js:
--------------------------------------------------------------------------------
1 | /*
2 | AngularJS v1.2.16
3 | (c) 2010-2014 Google, Inc. http://angularjs.org
4 | License: MIT
5 | */
6 | (function(s,g,P){'use strict';g.module("ngAnimate",["ng"]).factory("$$animateReflow",["$$rAF","$document",function(g,s){return function(e){return g(function(){e()})}}]).config(["$provide","$animateProvider",function(ga,G){function e(e){for(var p=0;p=x&&b>=v&&f()}var l=e(b);a=b.data(n);if(-1!=l.getAttribute("class").indexOf(c)&&a){var r="";p(c.split(" "),function(a,b){r+=(0=c;d--)e.end&&e.end(f[d]);f.length=c}}var b,g,f=[],l=a;for(f.last=function(){return f[f.length-1]};a;){g=!0;if(f.last()&&x[f.last()])a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+f.last()+"[^>]*>","i"),function(b,a){a=a.replace(H,"$1").replace(I,"$1");e.chars&&e.chars(r(a));return""}),c("",f.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",b)===b&&(e.comment&&e.comment(a.substring(4,b)),a=a.substring(b+3),g=!1);else if(y.test(a)){if(b=a.match(y))a=
8 | a.replace(b[0],""),g=!1}else if(J.test(a)){if(b=a.match(z))a=a.substring(b[0].length),b[0].replace(z,c),g=!1}else K.test(a)&&(b=a.match(A))&&(a=a.substring(b[0].length),b[0].replace(A,d),g=!1);g&&(b=a.indexOf("<"),g=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),e.chars&&e.chars(r(g)))}if(a==l)throw L("badparse",a);l=a}c()}function r(a){if(!a)return"";var e=M.exec(a);a=e[1];var d=e[3];if(e=e[2])n.innerHTML=e.replace(//g,">")}function s(a,e){var d=!1,c=h.bind(a,a.push);return{start:function(a,g,f){a=h.lowercase(a);!d&&x[a]&&(d=a);d||!0!==C[a]||(c("<"),c(a),h.forEach(g,function(d,f){var g=h.lowercase(f),k="img"===a&&"src"===g||"background"===g;!0!==O[g]||!0===D[g]&&!e(d,k)||(c(" "),c(f),c('="'),c(B(d)),c('"'))}),c(f?"/>":">"))},end:function(a){a=h.lowercase(a);d||!0!==C[a]||(c(""),c(a),c(">"));a==d&&(d=!1)},chars:function(a){d||
10 | c(B(a))}}}var L=h.$$minErr("$sanitize"),A=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,z=/^<\s*\/\s*([\w:-]+)[^>]*>/,G=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,K=/^,J=/^<\s*\//,H=/\x3c!--(.*?)--\x3e/g,y=/]*?)>/i,I=/]/,d=/^mailto:/;return function(c,b){function g(a){a&&m.push(E(a))}function f(a,c){m.push("');g(c);m.push(" ")}if(!c)return c;for(var l,k=c,m=[],n,p;l=k.match(e);)n=l[0],l[2]==l[3]&&(n="mailto:"+n),p=l.index,g(k.substr(0,p)),f(n,l[0].replace(d,"")),k=k.substring(p+l[0].length);g(k);return a(m.join(""))}}])})(window,window.angular);
14 | //# sourceMappingURL=angular-sanitize.min.js.map
--------------------------------------------------------------------------------
/lib/bootstrap-theme.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.1.1 (http://getbootstrap.com)
3 | * Copyright 2011-2014 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 |
7 | .btn-default,
8 | .btn-primary,
9 | .btn-success,
10 | .btn-info,
11 | .btn-warning,
12 | .btn-danger {
13 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
14 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
15 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
16 | }
17 | .btn-default:active,
18 | .btn-primary:active,
19 | .btn-success:active,
20 | .btn-info:active,
21 | .btn-warning:active,
22 | .btn-danger:active,
23 | .btn-default.active,
24 | .btn-primary.active,
25 | .btn-success.active,
26 | .btn-info.active,
27 | .btn-warning.active,
28 | .btn-danger.active {
29 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
30 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
31 | }
32 | .btn:active,
33 | .btn.active {
34 | background-image: none;
35 | }
36 | .btn-default {
37 | background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);
38 | background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%);
39 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
40 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
41 | background-repeat: repeat-x;
42 | border-color: #dbdbdb;
43 | text-shadow: 0 1px 0 #fff;
44 | border-color: #ccc;
45 | }
46 | .btn-default:hover,
47 | .btn-default:focus {
48 | background-color: #e0e0e0;
49 | background-position: 0 -15px;
50 | }
51 | .btn-default:active,
52 | .btn-default.active {
53 | background-color: #e0e0e0;
54 | border-color: #dbdbdb;
55 | }
56 | .btn-primary {
57 | background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
58 | background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%);
59 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);
60 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
61 | background-repeat: repeat-x;
62 | border-color: #2b669a;
63 | }
64 | .btn-primary:hover,
65 | .btn-primary:focus {
66 | background-color: #2d6ca2;
67 | background-position: 0 -15px;
68 | }
69 | .btn-primary:active,
70 | .btn-primary.active {
71 | background-color: #2d6ca2;
72 | border-color: #2b669a;
73 | }
74 | .btn-success {
75 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
76 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
77 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
78 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
79 | background-repeat: repeat-x;
80 | border-color: #3e8f3e;
81 | }
82 | .btn-success:hover,
83 | .btn-success:focus {
84 | background-color: #419641;
85 | background-position: 0 -15px;
86 | }
87 | .btn-success:active,
88 | .btn-success.active {
89 | background-color: #419641;
90 | border-color: #3e8f3e;
91 | }
92 | .btn-info {
93 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
94 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
95 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
96 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
97 | background-repeat: repeat-x;
98 | border-color: #28a4c9;
99 | }
100 | .btn-info:hover,
101 | .btn-info:focus {
102 | background-color: #2aabd2;
103 | background-position: 0 -15px;
104 | }
105 | .btn-info:active,
106 | .btn-info.active {
107 | background-color: #2aabd2;
108 | border-color: #28a4c9;
109 | }
110 | .btn-warning {
111 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
112 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
113 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
114 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
115 | background-repeat: repeat-x;
116 | border-color: #e38d13;
117 | }
118 | .btn-warning:hover,
119 | .btn-warning:focus {
120 | background-color: #eb9316;
121 | background-position: 0 -15px;
122 | }
123 | .btn-warning:active,
124 | .btn-warning.active {
125 | background-color: #eb9316;
126 | border-color: #e38d13;
127 | }
128 | .btn-danger {
129 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
130 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
131 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
132 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
133 | background-repeat: repeat-x;
134 | border-color: #b92c28;
135 | }
136 | .btn-danger:hover,
137 | .btn-danger:focus {
138 | background-color: #c12e2a;
139 | background-position: 0 -15px;
140 | }
141 | .btn-danger:active,
142 | .btn-danger.active {
143 | background-color: #c12e2a;
144 | border-color: #b92c28;
145 | }
146 | .thumbnail,
147 | .img-thumbnail {
148 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
149 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
150 | }
151 | .dropdown-menu > li > a:hover,
152 | .dropdown-menu > li > a:focus {
153 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
154 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
155 | background-repeat: repeat-x;
156 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
157 | background-color: #e8e8e8;
158 | }
159 | .dropdown-menu > .active > a,
160 | .dropdown-menu > .active > a:hover,
161 | .dropdown-menu > .active > a:focus {
162 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
163 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
164 | background-repeat: repeat-x;
165 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
166 | background-color: #357ebd;
167 | }
168 | .navbar-default {
169 | background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
170 | background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);
171 | background-repeat: repeat-x;
172 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
173 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
174 | border-radius: 4px;
175 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
176 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
177 | }
178 | .navbar-default .navbar-nav > .active > a {
179 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%);
180 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%);
181 | background-repeat: repeat-x;
182 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0);
183 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
184 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
185 | }
186 | .navbar-brand,
187 | .navbar-nav > li > a {
188 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);
189 | }
190 | .navbar-inverse {
191 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%);
192 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%);
193 | background-repeat: repeat-x;
194 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
195 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
196 | }
197 | .navbar-inverse .navbar-nav > .active > a {
198 | background-image: -webkit-linear-gradient(top, #222222 0%, #282828 100%);
199 | background-image: linear-gradient(to bottom, #222222 0%, #282828 100%);
200 | background-repeat: repeat-x;
201 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0);
202 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
203 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
204 | }
205 | .navbar-inverse .navbar-brand,
206 | .navbar-inverse .navbar-nav > li > a {
207 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
208 | }
209 | .navbar-static-top,
210 | .navbar-fixed-top,
211 | .navbar-fixed-bottom {
212 | border-radius: 0;
213 | }
214 | .alert {
215 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);
216 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
217 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
218 | }
219 | .alert-success {
220 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
221 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
222 | background-repeat: repeat-x;
223 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
224 | border-color: #b2dba1;
225 | }
226 | .alert-info {
227 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
228 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
229 | background-repeat: repeat-x;
230 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
231 | border-color: #9acfea;
232 | }
233 | .alert-warning {
234 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
235 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
236 | background-repeat: repeat-x;
237 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
238 | border-color: #f5e79e;
239 | }
240 | .alert-danger {
241 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
242 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
243 | background-repeat: repeat-x;
244 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
245 | border-color: #dca7a7;
246 | }
247 | .progress {
248 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
249 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
250 | background-repeat: repeat-x;
251 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
252 | }
253 | .progress-bar {
254 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%);
255 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
256 | background-repeat: repeat-x;
257 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
258 | }
259 | .progress-bar-success {
260 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
261 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
262 | background-repeat: repeat-x;
263 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
264 | }
265 | .progress-bar-info {
266 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
267 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
268 | background-repeat: repeat-x;
269 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
270 | }
271 | .progress-bar-warning {
272 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
273 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
274 | background-repeat: repeat-x;
275 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
276 | }
277 | .progress-bar-danger {
278 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
279 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
280 | background-repeat: repeat-x;
281 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
282 | }
283 | .list-group {
284 | border-radius: 4px;
285 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
286 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
287 | }
288 | .list-group-item.active,
289 | .list-group-item.active:hover,
290 | .list-group-item.active:focus {
291 | text-shadow: 0 -1px 0 #3071a9;
292 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%);
293 | background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%);
294 | background-repeat: repeat-x;
295 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);
296 | border-color: #3278b3;
297 | }
298 | .panel {
299 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
300 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
301 | }
302 | .panel-default > .panel-heading {
303 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
304 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
305 | background-repeat: repeat-x;
306 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
307 | }
308 | .panel-primary > .panel-heading {
309 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
310 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
311 | background-repeat: repeat-x;
312 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
313 | }
314 | .panel-success > .panel-heading {
315 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
316 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
317 | background-repeat: repeat-x;
318 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
319 | }
320 | .panel-info > .panel-heading {
321 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
322 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
323 | background-repeat: repeat-x;
324 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
325 | }
326 | .panel-warning > .panel-heading {
327 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
328 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
329 | background-repeat: repeat-x;
330 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
331 | }
332 | .panel-danger > .panel-heading {
333 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
334 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
335 | background-repeat: repeat-x;
336 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
337 | }
338 | .well {
339 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
340 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
341 | background-repeat: repeat-x;
342 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
343 | border-color: #dcdcdc;
344 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
345 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
346 | }
347 |
--------------------------------------------------------------------------------
/lib/bootstrap.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.1.1 (http://getbootstrap.com)
3 | * Copyright 2011-2014 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.isLoading=!1};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",f.resetText||d.data("resetText",d[e]()),d[e](f[b]||this.options[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},b.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});return this.$element.trigger(j),j.isDefaultPrevented()?void 0:(this.sliding=!0,f&&this.pause(),this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid.bs.carousel")},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid.bs.carousel")),f&&this.cycle(),this)};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("collapse in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);!e&&f.toggle&&"show"==c&&(c=!c),e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(b){a(d).remove(),a(e).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('
').insertAfter(a(this)).on("click",b);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;f.toggleClass("open").trigger("shown.bs.dropdown",h),e.focus()}return!1}},f.prototype.keydown=function(b){if(/(38|40|27)/.test(b.keyCode)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var f=c(d),g=f.hasClass("open");if(!g||g&&27==b.keyCode)return 27==b.which&&f.find(e).focus(),d.click();var h=" li:not(.divider):visible a",i=f.find("[role=menu]"+h+", [role=listbox]"+h);if(i.length){var j=i.index(i.filter(":focus"));38==b.keyCode&&j>0&&j--,40==b.keyCode&&j ').appendTo(document.body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),d&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;d?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(a.support.transition.end,b).emulateTransitionEnd(150):b()):b&&b()};var c=a.fn.modal;a.fn.modal=function(c,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},b.DEFAULTS,e.data(),"object"==typeof c&&c);f||e.data("bs.modal",f=new b(this,g)),"string"==typeof c?f[c](d):g.show&&f.show(d)})},a.fn.modal.Constructor=b,a.fn.modal.noConflict=function(){return a.fn.modal=c,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(b){var c=a(this),d=c.attr("href"),e=a(c.attr("data-target")||d&&d.replace(/.*(?=#[^\s]+$)/,"")),f=e.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(d)&&d},e.data(),c.data());c.is("a")&&b.preventDefault(),e.modal(f,this).one("hide",function(){c.is(":visible")&&c.focus()})}),a(document).on("show.bs.modal",".modal",function(){a(document.body).addClass("modal-open")}).on("hidden.bs.modal",".modal",function(){a(document.body).removeClass("modal-open")})}(jQuery),+function(a){"use strict";var b=function(a,b){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",a,b)};b.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'',trigger:"hover focus",title:"",delay:0,html:!1,container:!1},b.prototype.init=function(b,c,d){this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d);for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},b.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},b.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);return clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show()},b.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget)[this.type](this.getDelegateOptions()).data("bs."+this.type);return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},b.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){if(this.$element.trigger(b),b.isDefaultPrevented())return;var c=this,d=this.tip();this.setContent(),this.options.animation&&d.addClass("fade");var e="function"==typeof this.options.placement?this.options.placement.call(this,d[0],this.$element[0]):this.options.placement,f=/\s?auto?\s?/i,g=f.test(e);g&&(e=e.replace(f,"")||"top"),d.detach().css({top:0,left:0,display:"block"}).addClass(e),this.options.container?d.appendTo(this.options.container):d.insertAfter(this.$element);var h=this.getPosition(),i=d[0].offsetWidth,j=d[0].offsetHeight;if(g){var k=this.$element.parent(),l=e,m=document.documentElement.scrollTop||document.body.scrollTop,n="body"==this.options.container?window.innerWidth:k.outerWidth(),o="body"==this.options.container?window.innerHeight:k.outerHeight(),p="body"==this.options.container?0:k.offset().left;e="bottom"==e&&h.top+h.height+j-m>o?"top":"top"==e&&h.top-m-j<0?"bottom":"right"==e&&h.right+i>n?"left":"left"==e&&h.left-i
'}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;(e||"destroy"!=c)&&(e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]())})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(a(c).is("body")?window:c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);{var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})}},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);if(g&&b<=e[0])return g!=(a=f[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parentsUntil(this.options.target,".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(b.RESET).addClass("affix");var a=this.$window.scrollTop(),c=this.$element.offset();return this.pinnedOffset=c.top-a},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"top"==this.affixed&&(e.top+=d),"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top(this.$element)),"function"==typeof h&&(h=f.bottom(this.$element));var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;if(this.affixed!==i){this.unpin&&this.$element.css("top","");var j="affix"+(i?"-"+i:""),k=a.Event(j+".bs.affix");this.$element.trigger(k),k.isDefaultPrevented()||(this.affixed=i,this.unpin="bottom"==i?this.getPinnedOffset():null,this.$element.removeClass(b.RESET).addClass(j).trigger(a.Event(j.replace("affix","affixed"))),"bottom"==i&&this.$element.offset({top:c-h-this.$element.height()}))}}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery);
--------------------------------------------------------------------------------
/lib/project/grunt-html2js-var/tasks/html2js-var.js:
--------------------------------------------------------------------------------
1 | /*
2 | * grunt-html2js
3 | * https://github.com/karlgoldstein/grunt-html2js
4 | *
5 | * Copyright (c) 2013 Karl Goldstein
6 | * Licensed under the MIT license.
7 | */
8 |
9 | 'use strict';
10 |
11 | module.exports = function (grunt) {
12 |
13 | var path = require('path');
14 | //var minify = require('html-minifier').minify;
15 |
16 | var escapeContent = function (content, quoteChar, indentString) {
17 | var bsRegexp = new RegExp('\\\\', 'g');
18 | var quoteRegexp = new RegExp('\\' + quoteChar, 'g');
19 | //var nlReplace = '\\n' + quoteChar + ' +\n' + indentString + indentString + quoteChar;
20 | var nlReplace = quoteChar + ' +\n' + indentString + indentString + quoteChar;
21 |
22 | //the following line was failing on escaped single quotes inside double quote in the html -
23 | //return content.replace(bsRegexp, '\\\\').replace(quoteRegexp, '\\' + quoteChar).replace(/\r?\n/g, nlReplace);
24 | return content.replace(bsRegexp, '\\').replace(/\r?\n/g, nlReplace);
25 | };
26 |
27 | // convert Windows file separator URL path separator
28 | var normalizePath = function (p) {
29 | if (path.sep !== '/') {
30 | p = p.replace(/\\/g, '/');
31 | }
32 | return p;
33 | };
34 |
35 | // Warn on and remove invalid source files (if nonull was set).
36 | var existsFilter = function (filepath) {
37 |
38 | if (!grunt.file.exists(filepath)) {
39 | grunt.log.warn('Source file "' + filepath + '" not found.');
40 | return false;
41 | } else {
42 | return true;
43 | }
44 | };
45 |
46 | // return template content
47 | var getContent = function (filepath, quoteChar, indentString, htmlmin, process) {
48 | var content = grunt.file.read(filepath);
49 |
50 | // Process files as templates if requested.
51 | if (typeof process === "function") {
52 | content = process(content, filepath);
53 | } else if (process) {
54 | if (process === true) {
55 | process = {};
56 | }
57 | content = grunt.template.process(content, process);
58 | }
59 |
60 | /*if (Object.keys(htmlmin).length) {
61 | try {
62 | content = minify(content, htmlmin);
63 | } catch (err) {
64 | grunt.warn(filepath + '\n' + err);
65 | }
66 | }*/
67 |
68 | return escapeContent(content, quoteChar, indentString);
69 | };
70 |
71 | // compile a template to an angular module
72 | var compileTemplate = function (moduleName, filepath, quoteChar, indentString, useStrict, htmlmin, process) {
73 |
74 | var content = getContent(filepath, quoteChar, indentString, htmlmin, process);
75 | var doubleIndent = indentString + indentString;
76 | var strict = (useStrict) ? indentString + quoteChar + 'use strict' + quoteChar + ';\n' : '';
77 |
78 | var module =
79 | 'tpl = ' +
80 | quoteChar +
81 | content +
82 | quoteChar +
83 | ';'
84 |
85 | return module;
86 | };
87 |
88 | grunt.registerMultiTask('html2jsVar', 'Compiles Angular-JS templates to JavaScript.', function () {
89 |
90 | var options = this.options({
91 | base: 'src',
92 | module: 'templates-' + this.target,
93 | quoteChar: '"',
94 | fileHeaderString: '',
95 | fileFooterString: '',
96 | indentString: ' ',
97 | target: 'js',
98 | htmlmin: {},
99 | process: false
100 | });
101 |
102 | var counter = 0;
103 |
104 | // generate a separate module
105 | this.files.forEach(function (f) {
106 |
107 | // f.dest must be a string or write will fail
108 |
109 | var moduleNames = [];
110 |
111 | var modules = f.src.filter(existsFilter).map(function (filepath) {
112 |
113 | var moduleName = normalizePath(path.relative(options.base, filepath));
114 | if (grunt.util.kindOf(options.rename) === 'function') {
115 | moduleName = options.rename(moduleName);
116 | }
117 | moduleNames.push("'" + moduleName + "'");
118 | if (options.target === 'js') {
119 | return compileTemplate(moduleName, filepath, options.quoteChar, options.indentString, options.useStrict, options.htmlmin, options.process);
120 | } else {
121 | grunt.fail.fatal('Unknow target "' + options.target + '" specified');
122 | }
123 |
124 | });
125 |
126 | counter += modules.length;
127 | modules = modules.join('\n');
128 |
129 | var fileHeader = options.fileHeaderString !== '' ? options.fileHeaderString + '\n' : '';
130 | var fileFooter = options.fileFooterString !== '' ? options.fileFooterString + '\n' : '';
131 | var bundle = "";
132 | var targetModule = f.module || options.module;
133 | // If options.module is a function, use that to get the targetModule
134 | if (grunt.util.kindOf(targetModule) === 'function') {
135 | targetModule = targetModule(f);
136 | }
137 | //Allow a 'no targetModule if module is null' option
138 | if (targetModule) {
139 | bundle = "angular.module('" + targetModule + "', [" + moduleNames.join(', ') + "])";
140 | if (options.target === 'js') {
141 | bundle += ';';
142 | }
143 |
144 | bundle += "\n\n";
145 | }
146 | grunt.file.write(f.dest, grunt.util.normalizelf(fileHeader + bundle + modules + fileFooter));
147 | });
148 | //Just have one output, so if we making thirty files it only does one line
149 | grunt.log.writeln("Successfully converted " + ("" + counter).green +
150 | " html templates to " + options.target + ".");
151 | });
152 | };
153 |
--------------------------------------------------------------------------------
/lib/project/grunt-import-js/tasks/import-js.js:
--------------------------------------------------------------------------------
1 | /*
2 | * grunt-import
3 | * http://gruntjs.com/
4 | *
5 | * Copyright (c) 2013 Marcin Rosinski, contributors
6 | * Licensed under the MIT license.
7 | */
8 |
9 | 'use strict';
10 |
11 | String.prototype.__fullTrim=function(){return this.replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g,'').replace(/\s+/g,' ');};
12 |
13 | module.exports = function(grunt) {
14 |
15 | grunt.registerMultiTask('importJs', '//@import - inline file import.', function() {
16 |
17 | var path = require('path');
18 |
19 | // Merge task-specific and/or target-specific options with these defaults.
20 | var options = this.options({
21 | separator: grunt.util.linefeed,
22 | banner: '',
23 | footer: ''
24 | });
25 |
26 | // Process banner and footer.
27 | var banner = grunt.template.process(options.banner);
28 | var footer = grunt.template.process(options.footer);
29 | var target = this.target;
30 |
31 | var array_unique = function(inputArr)
32 | {
33 | var key = '',
34 | tmp_arr2 = {},
35 | val = '';
36 |
37 | var __array_search = function (needle, haystack) {
38 | var fkey = '';
39 | for (fkey in haystack) {
40 | if (haystack.hasOwnProperty(fkey)) {
41 | if ((haystack[fkey] + '') === (needle + '')) {
42 | return fkey;
43 | }
44 | }
45 | }
46 | return false;
47 | };
48 |
49 | for (key in inputArr) {
50 | if (inputArr.hasOwnProperty(key)) {
51 | val = inputArr[key];
52 | if (false === __array_search(val, tmp_arr2)) {
53 | tmp_arr2[key] = val;
54 | }
55 | }
56 | }
57 |
58 | return tmp_arr2;
59 | }
60 |
61 | var importRecursive = function(filepath)
62 | {
63 | var src = grunt.file.read(filepath);
64 | // now the replacement pattern in the target file
65 | // can be a valid js comment
66 | var importReg = src.match(/\/\/@import ['"](.*)['"]/g);
67 | //var importReg = src.match(/(?:(?![/*]])[^/* ]|^ *)@import ['"](.*?)['"](?![^*]*?\*\/)/gm);
68 |
69 | if(importReg && importReg.length)
70 | {
71 | var importReg_ = new Array();
72 | for(var i in importReg)
73 | {
74 | importReg_[i] = importReg[i].__fullTrim();
75 | }
76 |
77 | importReg = importReg_;
78 | importReg = array_unique(importReg);
79 |
80 | for(var i in importReg)
81 | {
82 | var importpath = importReg[i].replace('//@import ','').replace(/"/g,'').replace(/'/g,'');
83 |
84 | if(importpath.indexOf('/')!==0)
85 | {
86 | importpath = path.resolve(path.dirname(filepath)+'/'+importpath);
87 | }
88 |
89 | if(grunt.file.exists(importpath))
90 | {
91 | var isrc = importRecursive(importpath);
92 | src = src.split(importReg[i]+';').join(isrc);
93 | src = src.split(importReg[i]).join(isrc);
94 | }
95 | else
96 | {
97 | grunt.log.warn('@import file "' + importpath + '" not found.');
98 | src = src.split(importReg[i]+';').join('');
99 | src = src.split(importReg[i]).join('');
100 | }
101 | }
102 | }
103 |
104 | return src;
105 | };
106 |
107 | // Iterate over all src-dest file pairs.
108 | this.files.forEach(function(f) {
109 |
110 | // Prepend banner + @import + specified files + footer.
111 | var src = banner + f.src.filter(function(filepath) {
112 |
113 | // Warn on and remove invalid source files (if nonull was set).
114 | if (!grunt.file.exists(filepath)) {
115 | grunt.log.warn('Source file "' + filepath + '" not found.');
116 | return false;
117 | } else {
118 | return true;
119 | }
120 |
121 | }).map(function(filepath) {
122 |
123 | return importRecursive(filepath);
124 |
125 | }).join(options.separator) + footer;
126 |
127 | // Write the destination file.
128 | grunt.file.write(f.dest, src);
129 | grunt.event.emit('import', 'imported', f.dest, target);
130 |
131 | // Print a success message.
132 | grunt.log.writeln('File "' + f.dest + '" created.');
133 | });
134 |
135 | //Run tasks
136 | if(this.data.tasks){
137 | grunt.task.run(this.data.tasks);
138 | }
139 | });
140 |
141 | };
142 |
--------------------------------------------------------------------------------
/lib/ui-bootstrap-collapse.js:
--------------------------------------------------------------------------------
1 | /*
2 | * angular-ui-bootstrap
3 | * http://angular-ui.github.io/bootstrap/
4 |
5 | * Version: 0.10.0 - 2014-01-15
6 | * License: MIT
7 | */
8 | angular.module("ui.bootstrap.custom", ["ui.bootstrap.transition","ui.bootstrap.collapse"]);
9 | angular.module('ui.bootstrap.transition', [])
10 |
11 | /**
12 | * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
13 | * @param {DOMElement} element The DOMElement that will be animated.
14 | * @param {string|object|function} trigger The thing that will cause the transition to start:
15 | * - As a string, it represents the css class to be added to the element.
16 | * - As an object, it represents a hash of style attributes to be applied to the element.
17 | * - As a function, it represents a function to be called that will cause the transition to occur.
18 | * @return {Promise} A promise that is resolved when the transition finishes.
19 | */
20 | .factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {
21 |
22 | var $transition = function(element, trigger, options) {
23 | options = options || {};
24 | var deferred = $q.defer();
25 | var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];
26 |
27 | var transitionEndHandler = function(event) {
28 | $rootScope.$apply(function() {
29 | element.unbind(endEventName, transitionEndHandler);
30 | deferred.resolve(element);
31 | });
32 | };
33 |
34 | if (endEventName) {
35 | element.bind(endEventName, transitionEndHandler);
36 | }
37 |
38 | // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
39 | $timeout(function() {
40 | if ( angular.isString(trigger) ) {
41 | element.addClass(trigger);
42 | } else if ( angular.isFunction(trigger) ) {
43 | trigger(element);
44 | } else if ( angular.isObject(trigger) ) {
45 | element.css(trigger);
46 | }
47 | //If browser does not support transitions, instantly resolve
48 | if ( !endEventName ) {
49 | deferred.resolve(element);
50 | }
51 | });
52 |
53 | // Add our custom cancel function to the promise that is returned
54 | // We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
55 | // i.e. it will therefore never raise a transitionEnd event for that transition
56 | deferred.promise.cancel = function() {
57 | if ( endEventName ) {
58 | element.unbind(endEventName, transitionEndHandler);
59 | }
60 | deferred.reject('Transition cancelled');
61 | };
62 |
63 | return deferred.promise;
64 | };
65 |
66 | // Work out the name of the transitionEnd event
67 | var transElement = document.createElement('trans');
68 | var transitionEndEventNames = {
69 | 'WebkitTransition': 'webkitTransitionEnd',
70 | 'MozTransition': 'transitionend',
71 | 'OTransition': 'oTransitionEnd',
72 | 'transition': 'transitionend'
73 | };
74 | var animationEndEventNames = {
75 | 'WebkitTransition': 'webkitAnimationEnd',
76 | 'MozTransition': 'animationend',
77 | 'OTransition': 'oAnimationEnd',
78 | 'transition': 'animationend'
79 | };
80 | function findEndEventName(endEventNames) {
81 | for (var name in endEventNames){
82 | if (transElement.style[name] !== undefined) {
83 | return endEventNames[name];
84 | }
85 | }
86 | }
87 | $transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
88 | $transition.animationEndEventName = findEndEventName(animationEndEventNames);
89 | return $transition;
90 | }]);
91 |
92 | angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition'])
93 |
94 | .directive('collapse', ['$transition', function ($transition, $timeout) {
95 |
96 | return {
97 | link: function (scope, element, attrs) {
98 |
99 | var initialAnimSkip = true;
100 | var currentTransition;
101 |
102 | function doTransition(change) {
103 | var newTransition = $transition(element, change);
104 | if (currentTransition) {
105 | currentTransition.cancel();
106 | }
107 | currentTransition = newTransition;
108 | newTransition.then(newTransitionDone, newTransitionDone);
109 | return newTransition;
110 |
111 | function newTransitionDone() {
112 | // Make sure it's this transition, otherwise, leave it alone.
113 | if (currentTransition === newTransition) {
114 | currentTransition = undefined;
115 | }
116 | }
117 | }
118 |
119 | function expand() {
120 | if (initialAnimSkip) {
121 | initialAnimSkip = false;
122 | expandDone();
123 | } else {
124 | element.removeClass('collapse').addClass('collapsing');
125 | doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone);
126 | }
127 | }
128 |
129 | function expandDone() {
130 | element.removeClass('collapsing');
131 | element.addClass('collapse in');
132 | element.css({height: 'auto'});
133 | }
134 |
135 | function collapse() {
136 | if (initialAnimSkip) {
137 | initialAnimSkip = false;
138 | collapseDone();
139 | element.css({height: 0});
140 | } else {
141 | // CSS transitions don't work with height: auto, so we have to manually change the height to a specific value
142 | element.css({ height: element[0].scrollHeight + 'px' });
143 | //trigger reflow so a browser realizes that height was updated from auto to a specific value
144 | var x = element[0].offsetWidth;
145 |
146 | element.removeClass('collapse in').addClass('collapsing');
147 |
148 | doTransition({ height: 0 }).then(collapseDone);
149 | }
150 | }
151 |
152 | function collapseDone() {
153 | element.removeClass('collapsing');
154 | element.addClass('collapse');
155 | }
156 |
157 | scope.$watch(attrs.collapse, function (shouldCollapse) {
158 | if (shouldCollapse) {
159 | collapse();
160 | } else {
161 | expand();
162 | }
163 | });
164 | }
165 | };
166 | }]);
167 |
--------------------------------------------------------------------------------
/navbar.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Container Component - NavBar
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
21 |
24 |
28 |
32 |
36 |
37 |
38 |
42 |
43 |
44 |
45 |
46 | a very tall div to give the page a body
47 |
48 |
49 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
143 |
144 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "AngularComponents",
3 | "version": "0.0.1",
4 | "description": "AngularJS Web Component Development companian examples and code",
5 | "main": "index.html",
6 | "devDependencies": {
7 | "grunt": "~0.4.4",
8 | "grunt-contrib-concat": "^0.4.0",
9 | "grunt-contrib-copy": "^0.5.0",
10 | "grunt-contrib-jshint": "^0.10.0",
11 | "grunt-contrib-less": "^0.11.0",
12 | "grunt-contrib-uglify": "^0.4.0",
13 | "grunt-contrib-watch": "^0.6.1",
14 | "grunt-conventional-changelog": "^1.1.0",
15 | "grunt-html2js": "~0.2.4",
16 | "grunt-import": "^0.1.7",
17 | "grunt-karma": "^0.8.3",
18 | "grunt-ngdocs": "^0.2.2",
19 | "gulp": "^3.6.2",
20 | "gulp-concat": "^2.2.0",
21 | "gulp-file-insert": "^1.0.2",
22 | "gulp-grunt": "^0.4.1",
23 | "gulp-html2js": "^0.1.0",
24 | "gulp-include": "^0.2.2",
25 | "gulp-jshint": "^1.6.1",
26 | "gulp-karma": "0.0.4",
27 | "gulp-less": "^1.2.3",
28 | "gulp-uglify": "^0.3.0",
29 | "karma": "^0.12.14",
30 | "karma-chrome-launcher": "^0.1.4",
31 | "karma-jasmine": "^0.1.5",
32 | "karma-phantomjs-launcher": "^0.1.4",
33 | "lodash": "^2.4.1"
34 | },
35 | "directories": {
36 | "test": "test",
37 | "src": "src",
38 | "lib": "lib",
39 | "dist": "dist",
40 | "build": "build"
41 | },
42 | "files": [],
43 | "scripts": {
44 | "test": "./test/test.sh"
45 | },
46 | "repository": {
47 | "type": "git",
48 | "url": "https://github.com/dgs700/angularjs-web-component-development.git"
49 | },
50 | "keywords": [
51 | "angular",
52 | "angularjs",
53 | "web",
54 | "components",
55 | "book",
56 | ""
57 | ],
58 | "author": {
59 | "name": "David Shapiro",
60 | "email": "dave@david-shapiro.net",
61 | "url": "https://github.com/dgs700"
62 | },
63 | "license": "MIT",
64 | "gitHead": "d356ff9cfdefeb8c72d19da6fdca47d312d50990",
65 | "readmeFilename": "README.md",
66 | "bugs": {
67 | "url": "https://github.com/dgs700/angularjs-web-component-development/issues"
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Dropdown/Dropdown.js:
--------------------------------------------------------------------------------
1 | /*Functionality for the dropdown service and dropdown toggle directive provided by the Angular-UI team*/
2 |
3 | (function(){
4 | 'use strict';
5 |
6 | var tpl = '';
7 | //@import "../../build/src/Dropdown/Dropdown.tpl.js";
8 |
9 | // Dropdown Menu Component
10 | // Credit for portions of logic to the Angular-UI Bootstrap team
11 | // https://github.com/angular-ui/bootstrap
12 | angular.module('uiComponents.dropdown', [
13 | 'uiComponents.menuItem',
14 | 'ui.bootstrap.custom',
15 | 'ngSanitize'
16 | ])
17 |
18 | // because we have a tansclusion option for the dropdowns we cannot
19 | // reliably track open menu status at the component scope level
20 | // so we prefer to dedicate a service to this task rather than pollute
21 | // the $rootScope
22 | .service('uicDropdownService', ['$document', function($document){
23 |
24 | // currently displayed dropdown
25 | var openScope = null;
26 |
27 | // array of added dropdown scopes
28 | var dropdowns = [];
29 |
30 | // event handler for click evt
31 | function closeDropdown( evt ) {
32 | if (evt && evt.isDefaultPrevented()) {
33 | return;
34 | }
35 | openScope.$apply(function() {
36 | openScope.isOpen = false;
37 | });
38 | }
39 |
40 | // event handler for escape key
41 | function escapeKeyBind( evt ) {
42 | if ( evt.which === 27 ) {
43 | openScope.focusToggleElement();
44 | closeDropdown();
45 | }
46 | }
47 |
48 | // exposed service functions
49 | return {
50 |
51 | // called by linking fn of dropdown directive
52 | register: function(scope){
53 | dropdowns.push(scope);
54 | },
55 |
56 | // remove/unregister a dropdown scope
57 | remove: function(scope){
58 | for(var x = 0; x < dropdowns.length; x++){
59 | if(dropdowns[x] === scope){
60 | dropdowns.splice(x, 1);
61 | break;
62 | }
63 | }
64 | },
65 |
66 | // access dropdown array
67 | getDropdowns: function(){
68 | return dropdowns;
69 | },
70 |
71 | // access a single dropdown scope by $id
72 | getById: function(id){
73 | var x;
74 | for(x = 0; x < dropdowns.length; x++){
75 | if(id === dropdowns[x].$id) return dropdowns[x];
76 | }
77 | return false;
78 | },
79 |
80 | // open a particular dropdown and set close evt bindings
81 | open: function( dropdownScope ) {
82 | if ( !openScope ) {
83 | $document.bind('click', closeDropdown);
84 | $document.bind('keydown', escapeKeyBind);
85 | }
86 | if ( openScope && openScope !== dropdownScope ) {
87 | openScope.isOpen = false;
88 | }
89 | openScope = dropdownScope;
90 | },
91 |
92 | // close a particular dropdown and set close evt bindings
93 | close: function( dropdownScope ) {
94 | if ( openScope === dropdownScope ) {
95 | openScope = null;
96 | // cleanup to prevent memory leaks
97 | $document.unbind('click', closeDropdown);
98 | $document.unbind('keydown', escapeKeyBind);
99 | }
100 | }
101 | };
102 | }])
103 |
104 | // Primary dropdown component direcitve
105 | // this is also technically a container component
106 | .directive('uicDropdownMenu', ['$timeout',
107 | 'uicDropdownService', function($timeout, uicDropdownService){
108 | return {
109 | template: tpl,
110 |
111 | // component directives should be elements only
112 | restrict: 'E',
113 |
114 | // replace custom tags with standard html5 markup
115 | replace: true,
116 |
117 | // allow page designer to include menu item elements
118 | transclude: true,
119 |
120 | // isolate scope
121 | scope: {
122 | url: '@'
123 | },
124 | controller: [
125 | '$scope',
126 | '$element',
127 | '$attrs', function($scope, $element, $attrs){
128 |
129 | $scope.disablable = '';
130 | $scope.isOpen = false;
131 | // persistent instance reference
132 | var that = this,
133 |
134 | // class that sets display: block
135 | closeClass = 'close',
136 | openClass = 'open';
137 |
138 | // supply the view-model with info from json if available
139 | // this only handles data from scopes generated by ng-repeat
140 | angular.forEach( $scope.$parent.menu, function(menuItems, dropdownTitle){
141 | if(angular.isArray(menuItems)){
142 |
143 | // uses ng-bind-html for template insertion
144 | $scope.dropdownTitle = dropdownTitle + ' ';
145 | $scope.menuItems = menuItems;
146 |
147 | // add a unique ID matching title string for future reference
148 | $scope.uicId = dropdownTitle;
149 | }
150 | });
151 | // supply string value for dropdown title via attribute API
152 | if($attrs.text){
153 | $scope.uicId = $attrs.text;
154 | $scope.dropdownTitle = $scope.uicId + ' ';
155 | }
156 | // indicate if this component was created via data or markup
157 | // and hide the empty if needed
158 | if($scope.menuItems) $scope.jsonData = true;
159 |
160 | // add angular element reference to controller instance
161 | // for later class toggling if desired
162 | this.init = function( element ) {
163 | that.$element = element;
164 | };
165 |
166 | // toggle the dropdown $scope.isOpen boolean
167 | this.toggle = function( open ) {
168 | $scope.isOpen = arguments.length ? !!open : !$scope.isOpen;
169 | return $scope.isOpen;
170 | };
171 |
172 | // set browser focus on active dropdown
173 | $scope.focusToggleElement = function() {
174 | if ( that.toggleElement ) {
175 | that.toggleElement[0].focus();
176 | }
177 | };
178 |
179 | $scope.selected = function($event, scope){
180 | $scope.$emit('menu-item-selected', scope);
181 | $event.preventDefault();
182 | $event.stopPropagation();
183 | // optionally perform some action before navigation
184 | };
185 |
186 | // all dropdowns need to watch the value of this expr
187 | // and set evt bindings and classes accordingly
188 | $scope.$watch('isOpen', function( isOpen, wasOpen ) {
189 | if ( isOpen ) {
190 | $scope.focusToggleElement();
191 |
192 | // tell our service we've been opened
193 | uicDropdownService.open($scope);
194 |
195 | // fire off an "opened" event (event API) for any listeners out there
196 | $scope.$emit('dropdown-opened');
197 | } else {
198 |
199 | // tell our service we've been closed
200 | uicDropdownService.close($scope);
201 |
202 | // fire a closed event (event API)
203 | $scope.$emit('dropdown-closed');
204 | }
205 | });
206 |
207 | // listen for client side route changes
208 | $scope.$on('$locationChangeSuccess', function() {
209 | // some bug in current version of angular is causing
210 | // $locationChangeSuccess to be broadcast on app.run()
211 | //$scope.isOpen = false;
212 | });
213 |
214 | // listen for menu item selected events
215 | $scope.$on('menu-item-selected', function(evt, targetScope) {
216 | // do something when a child menu item is selected
217 | });
218 | }],
219 | link: function(scope, iElement, iAttrs, dropdownCtrl){
220 | dropdownCtrl.init( iElement );
221 |
222 | // add an element ref to scope for future manipulation
223 | scope.iElement = iElement;
224 |
225 | // add to tracked array of dropdown scopes
226 | uicDropdownService.register(scope);
227 | }
228 | };
229 | }])
230 |
231 | // the angular version of $('.dropdown-menu').slideToggle(200)
232 | .directive('dropdownMenu', function(){
233 | return {
234 |
235 | // match just classnames to stay consistent with other implementations
236 | restrict: 'C',
237 | link: function(scope, element, attr) {
238 |
239 | // set watch on new/old values of isOpen boolean for component instance
240 | scope.$watch('isOpen', function( isOpen, wasOpen ){
241 |
242 | // if we detect that there has been a change for THIS instance
243 | if(isOpen !== wasOpen){
244 |
245 | // stop any existing animation and start the opposite animation
246 | element.stop().slideToggle(200);
247 | }
248 | });
249 | }
250 | };
251 | })
252 |
253 | // from Angular ui.bootstrap.dropdownToggle
254 | // helper directive for setting active/passive state on the
255 | // necessary elements
256 | .directive('dropdownToggle', function() {
257 | return {
258 |
259 | // keep to attributes since this is not a UI component
260 | restrict: 'A',
261 |
262 | // list of UI components to work for
263 | require: '?^uicDropdownMenu',
264 |
265 | link: function(scope, element, attrs, dropdownCtrl) {
266 |
267 | // render inert if no dropdown controller is injected
268 | if ( !dropdownCtrl ) {
269 | return;
270 | }
271 |
272 | // set the toggle element in the dropdown component
273 | dropdownCtrl.toggleElement = element;
274 |
275 | // click event listener for this directive
276 | var toggleDropdown = function(event) {
277 |
278 | // prevent the browser default behavior for anchor elements
279 | event.preventDefault();
280 | event.stopPropagation();
281 |
282 | // check that we are not disabed before toggling visibility
283 | if ( !element.hasClass('disabled') && !attrs.disabled ) {
284 |
285 | // call toggle() on the correct component scope
286 | scope.$apply(function() {
287 | dropdownCtrl.toggle();
288 | });
289 | }
290 | };
291 |
292 | // add click evt binding
293 | element.bind('click', toggleDropdown);
294 |
295 | // clean up click event binding
296 | scope.$on('$destroy', function() {
297 | element.unbind('click', toggleDropdown);
298 | });
299 | }
300 | };
301 | });
302 | })();
303 |
304 |
305 |
306 |
307 |
--------------------------------------------------------------------------------
/src/Dropdown/Dropdown.less:
--------------------------------------------------------------------------------
1 | /*
2 | an example of using a css .classname for name-spacing
3 | the styles for a component that may be applied more than once on a
4 | page
5 |
6 | .classnames provide much less specificity than #ids, so there is a
7 | higher likelihood that you may also need to use the dreaded
8 | "!important" postfix on rules
9 | */
10 | .uic-dropdown .dropdown-menu{
11 | display: none;
12 | background-color: #333;
13 | border-color: #080808;
14 | color: #AAA;
15 | }
16 | @media (max-width: 768px) {
17 | .uic-dropdown .dropdown-menu{
18 | position: static;
19 | float: none;
20 | }
21 | }
22 | .uic-dropdown .dropdown-menu li a{
23 | color: #AAA;
24 | }
25 | .uic-dropdown .dropdown-menu li a:hover{
26 | color: #EEE;
27 | background-color: #111;
28 | }
29 |
--------------------------------------------------------------------------------
/src/Dropdown/Dropdown.tpl.html:
--------------------------------------------------------------------------------
1 |
2 |
4 |
14 |
17 |
--------------------------------------------------------------------------------
/src/Dropdown/test/DropdownSpec.js:
--------------------------------------------------------------------------------
1 | // Unit test coverage for the Dropdown UI component
2 | describe('My Dropdown component directive', function () {
3 | var $compile, $rootScope, $scope, $element, element, $event, $_uicDropdownService;
4 |
5 | // manually initialize our component library module
6 | beforeEach(module('uiComponents.dropdown'));
7 |
8 | // make the necessary angular utility methods available
9 | // to our tests
10 | beforeEach(inject(function (_$compile_, _$rootScope_) {
11 | $compile = _$compile_;
12 | $rootScope = _$rootScope_;
13 |
14 | // note that this is actually the PARENT scope of the directive
15 | $scope = $rootScope.$new();
16 | }));
17 |
18 | // create some HTML to simulate how a developer might include
19 | // our menu item component in their page that covers all of
20 | // the API options
21 | var tpl =
22 | '' +
23 | ' ' +
24 | ' ' +
25 | ' ' +
26 | ' ';
27 |
28 | // template whose contents will be generated
29 | // with JSON data
30 | var tplJson = ' ';
31 |
32 | // some fake JSON
33 | var menu = {"Company":[
34 | {
35 | "text":"Contact Us",
36 | "url":"/company/contact"
37 | },{
38 | "text":"Jobs",
39 | "url":"/company/jobs"
40 | },{
41 | "text":"Privacy Statement",
42 | "url":"/company/privacy"
43 | },{
44 | "text":"Terms of Use",
45 | "url":"/company/terms"
46 | }]
47 | };
48 |
49 | // manually compile and link our component directive
50 | function compileDirective(directiveTpl) {
51 |
52 | // use our default template if none provided
53 | if (!directiveTpl) directiveTpl = tpl;
54 |
55 | inject(function($compile) {
56 |
57 | // manually compile the template and inject the scope in
58 | $element = $compile(directiveTpl)($scope);
59 |
60 | // manually update all of the bindings
61 | $scope.$digest();
62 |
63 | // make the html output available to our tests
64 | element = $element.html();
65 | });
66 | }
67 |
68 | // test our component APIs
69 | describe('Dropdown Component markup API coverage', function(){
70 | var scope;
71 | beforeEach(function(){
72 | compileDirective();
73 |
74 | // get access to the actual controller instance
75 | scope = $element.isolateScope();
76 |
77 | // create a fake click event
78 | $event = $.Event( "click" );
79 | });
80 |
81 | it('should use the attr value (API) of "text" as the dropdown label', function(){
82 | expect(element).toContain("Custom Dropdown");
83 | });
84 |
85 | it('should transclude the 2 menu item components (API)', function(){
86 | expect($element.find('li').length).toBe(2);
87 | });
88 |
89 | it('should toggle open state when clicked', function(){
90 | spyOn(scope, '$emit');
91 | scope.isOpen = false;
92 |
93 | // click on the dropdown to OPEN
94 | $element.find('a').trigger('click');
95 | expect(scope.isOpen).toBe(true);
96 | expect(scope.$emit).toHaveBeenCalledWith('dropdown-opened');
97 |
98 | // click on the dropdown to CLOSE
99 | $element.find('a').trigger('click');
100 | expect(scope.isOpen).toBe(false);
101 | expect(scope.$emit).toHaveBeenCalledWith('dropdown-closed');
102 | });
103 | });
104 |
105 | // test JSON constructed dropdowns
106 | describe('Dropdown Component JSON API coverage', function(){
107 | var scope;
108 | beforeEach(function(){
109 | $scope.$parent.menu = menu;
110 | compileDirective(tplJson);
111 | scope = $element.isolateScope();
112 | });
113 |
114 | it('should get its title text from the menu JSON obj key', function(){
115 | expect(scope.dropdownTitle).toContain("Company");
116 | });
117 |
118 | it('should know that it is using JSON data for rendering', function(){
119 | expect(scope.jsonData).toBe(true);
120 | });
121 |
122 | it('should render the correct number of menu items (4)', function(){
123 | expect($element.find('li').length).toBe(4);
124 | });
125 |
126 | // coverage for other service methods not covered above
127 | describe('Dropdown Service methods', function(){
128 |
129 | // create another scope so there is more than one
130 | var anotherScope;
131 |
132 | // get an explicit reference to the dropdown service
133 | beforeEach(inject(function (uicDropdownService) {
134 | $_uicDropdownService = uicDropdownService;
135 | anotherScope = $element.isolateScope();
136 | $_uicDropdownService.register(anotherScope);
137 | }));
138 |
139 | it('should now have 2 registered dropdowns', function(){
140 | // covers .register() and .getDropdowns() methods
141 | expect($_uicDropdownService.getDropdowns().length).toBe(2);
142 | });
143 |
144 | it('should retrieve a dropdown by id', function(){
145 | var testScope = $_uicDropdownService.getById(anotherScope.$id);
146 | expect(testScope).toBe(anotherScope);
147 | });
148 | });
149 | });
150 | });
--------------------------------------------------------------------------------
/src/MenuItem/MenuItem.js:
--------------------------------------------------------------------------------
1 | // menu item UI component
2 |
3 | (function () {
4 | "use strict";
5 |
6 | var tpl = '';
7 | //@import "../../build/src/MenuItem/MenuItem.tpl.js";
8 |
9 | angular.module('uiComponents.menuItem', [])
10 | // a simple menu item component directive
11 | .directive('uicMenuItem', [function(){
12 | return {
13 | // replace custom element with html5 markup
14 | //template: '',
18 | template: tpl,
19 | replace: true,
20 |
21 | // restrict usage to element only
22 | restrict: 'E',
23 |
24 | // new isolate scope
25 | scope: {
26 | // attibute API for menu item text
27 | text: '@',
28 | // attribute API for menu href URL
29 | url: '@'
30 | },
31 | controller: ['$scope', function($scope, $element, $attrs){
32 |
33 | // the default for the "disabled" API is enabled
34 | $scope.disablable = '';
35 |
36 | // called on ng-click
37 | $scope.selected = function($event, scope){
38 |
39 | // published API for selected event
40 | $scope.$emit('menu-item-selected', scope);
41 |
42 | // prevent the browser behavior for an anchor element click
43 | $event.preventDefault();
44 | $event.stopPropagation();
45 |
46 | // optionally perform some other actions before navigation
47 | };
48 | }],
49 | link: function(scope, iElement, iAttrs){
50 |
51 | // add the Bootstrap "disabled" class if there is no url
52 | if(!scope.url) scope.disablable = 'disabled';
53 | }
54 | };
55 | }]);
56 | })();
57 |
--------------------------------------------------------------------------------
/src/MenuItem/MenuItem.tpl.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/MenuItem/test/MenuItemSpec.js:
--------------------------------------------------------------------------------
1 | describe('My MenuItem component directive', function () {
2 | var $compile, $rootScope, $scope, $element, element, $event;
3 | // manually initialize our component library module
4 | beforeEach(module('uiComponents.menuItem'));
5 | // make the necessary angular utility methods available
6 | // to our tests
7 | beforeEach(inject(function (_$compile_, _$rootScope_) {
8 | $compile = _$compile_;
9 | $rootScope = _$rootScope_;
10 | // note that this is actually the PARENT scope of the directive
11 | $scope = $rootScope.$new();
12 | }));
13 |
14 | // create some HTML to simulate how a developer might include
15 | // our menu item component in their page that covers all of
16 | // the API options
17 | var tpl = ' ';
18 |
19 | // manually compile and link our component directive
20 | function compileDirective(directiveTpl) {
21 | // use our default template if none provided
22 | if (!directiveTpl) directiveTpl = tpl;
23 |
24 | inject(function($compile) {
25 | // manually compile the template and inject the scope in
26 | $element = $compile(directiveTpl)($scope);
27 | // manually update all of the bindings
28 | $scope.$digest();
29 | // make the html output available to our tests
30 | element = $element.html();
31 | });
32 | }
33 | // test our component APIs
34 | describe('Menu Item Component API coverage', function(){
35 | var scope;
36 | beforeEach(function(){
37 | compileDirective();
38 | // get access to the actual controller instance
39 | scope = $element.isolateScope();
40 | // create a fake event
41 | $event = $.Event( "click" );
42 | });
43 | it('should use the attr value (API) of "text" as the menu label', function(){
44 | expect(element).toContain("First Item");
45 | });
46 | it('should use the attr value (API) of "url" for the href url', function(){
47 | expect(element).toContain("http://david--shapiro.blogspot.com/");
48 | });
49 | it('should emit a "menu-item-selected" event (API) as when selected', function(){
50 | spyOn(scope, '$emit');
51 | scope.selected($event, scope);
52 | expect(scope.$emit).toHaveBeenCalledWith('menu-item-selected', scope);
53 | });
54 | });
55 |
56 | // template with no URL attribute
57 | var tplNoUrl = ' ';
58 | describe('Menu Item Component other functionality', function(){
59 | var scope;
60 | beforeEach(function(){
61 | compileDirective(tplNoUrl);
62 | });
63 | it('should add a "disabled" class if there is no URL provided', function(){
64 | expect($element.hasClass('disabled')).toBeTruthy();
65 | });
66 | });
67 | });
--------------------------------------------------------------------------------
/src/Navbar/Navbar.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | 'use strict';
3 |
4 | var tpl = '';
5 | //@import "../../build/src/Navbar/Navbar.tpl.js";
6 |
7 | angular.module('uiComponents.navbar', ['uiComponents.dropdown'])
8 |
9 | // utility functions for nav bar population
10 | .service('uicNavBarService', [
11 | '$window', function($window){
12 |
13 | // add menu data manually
14 | var menus = false;
15 | this.addMenus = function(data){
16 | if(angular.isArray(data)){
17 | menus = data;
18 | }
19 | };
20 |
21 | // functionality can expanded to include menu data via REST
22 | // check if a menus json object is available
23 | this.getMenus = function(){
24 | if($window.UIC && $window.UIC.header){
25 | return $window.UIC.header;
26 | }else if(menus){
27 | return menus;
28 | }else{
29 | return false;
30 | }
31 | };
32 | }])
33 |
34 | // utility directive that replaces ngTransclude to bind the content
35 | // to a child scope of the directive rather than a sibling scope
36 | // which allows the container component complete control of its
37 | // contents
38 | .directive('uicInclude', function(){
39 | return {
40 | restrict: 'A',
41 | link: function(scope, iElement, iAttrs, ctrl, $transclude) {
42 | $transclude(scope, function(clone) {
43 | iElement.append(clone);
44 | });
45 | }
46 | };
47 | })
48 |
49 | // Navigation Bar Container Component
50 | .directive('uicNavBar', [
51 | 'uicDropdownService',
52 | 'uicNavBarService',
53 | '$location',
54 | '$compile',
55 | '$log', function( uicDropdownService, uicNavBarService, $location, $compile, $log){
56 | return {
57 | template: tpl,
58 | restrict: 'E',
59 |
60 | // allow page designer to include dropdown elements
61 | transclude: true,
62 | replace: true,
63 | // isolate scope
64 | scope: {
65 | // attribute API for hiding dropdowns
66 | minimalHeader: '@minimal',
67 | homeUrl: '@'
68 | },
69 | controller: [
70 | '$scope',
71 | '$element',
72 | '$attrs', function($scope, $element, $attrs){
73 |
74 | // make sure $element is updated to the compiled/linked version
75 | var that = this;
76 | this.init = function( element ) {
77 | that.$element = element;
78 | };
79 |
80 | // add a dropdown to the nav bar during runtime
81 | // i.e. upon hash navigation
82 | this.addDropdown = function(menuObj){
83 |
84 | // create an isolate scope instance
85 | var newScope = $scope.$root.$new();
86 |
87 | // attach the json obj data at the same location
88 | // as the dropdown controller would
89 | newScope.menu = newScope.$parent.menu = menuObj;
90 |
91 | // manually compile and link a new dropdown component
92 | var $el = $compile(' ')(newScope);
93 |
94 | // retrieve access to the ISOLATE scope so we can
95 | // call digest which is necessary for unit test coverage
96 | var isolateScope = $el.isolateScope();
97 | isolateScope.$digest();
98 |
99 | // attach the new dropdown to the end of the first child
100 | // todo - add more control over DOM attach points
101 | $element.find('ul').last().append( $el );
102 | };
103 |
104 | // remove a dropdown from the nav bar during runtime
105 | // i.e. upon hash navigation
106 | this.removeDropdown = function(dropdownId){
107 |
108 | // get a reference to the target dropdown
109 | var menuArray = $scope.registeredMenus.filter(function (el){
110 | return el.uicId == dropdownId;
111 | });
112 | var dropdown = menuArray[0];
113 |
114 | // remove and destroy it and all children
115 | uicDropdownService.remove(dropdown);
116 | dropdown.iElement.remove();
117 | dropdown.$destroy();
118 | };
119 |
120 | // check for single or array of dropdowns to add
121 | // available on scope for additional invokation flexability
122 | $scope.addOrRemove = function(dropdowns, action){
123 | action = action + 'Dropdown';
124 | if(angular.isArray(dropdowns)){
125 | angular.forEach(dropdowns, function(dropdown){
126 | that[action](dropdown);
127 | });
128 | }else{
129 | that[action](dropdowns);
130 | }
131 | };
132 |
133 |
134 | // at the mobile width breakpoint
135 | // the Nav Bar items are not initially visible
136 | $scope.isCollapsed = true;
137 |
138 | // menu json data if available
139 | $scope.menus = uicNavBarService.getMenus();
140 |
141 | // keep track of added dropdowns
142 | // for container level manipulation if needed
143 | $scope.registeredMenus = [];
144 |
145 | // listen for minimize event
146 | $scope.$on('header-minimize', function(evt){
147 | $scope.minimalHeader = true;
148 | });
149 |
150 | // listen for maximize event
151 | $scope.$on('header-maximize', function(evt){
152 | $scope.minimalHeader = false;
153 | });
154 |
155 | // handle request to add dropdown(s)
156 | // obj = menu JSON obj or array of objs
157 | $scope.$on('add-nav-dropdowns', function(evt, obj){
158 | $scope.addOrRemove(obj, 'add');
159 | });
160 |
161 | // handle request to remove dropdown(s)
162 | // ids = string or array of strings matching dd titles
163 | $scope.$on('remove-nav-dropdowns', function(evt, ids){
164 | $scope.addOrRemove(ids, 'remove');
165 | });
166 |
167 | // listen for dropdown open event
168 | $scope.$on('dropdown-opened', function(evt, targetScope){
169 |
170 | // perform an action when a child dropdown is opened
171 | $log.log('dropdown-opened', targetScope);
172 | });
173 |
174 | // listen for dropdown close event
175 | $scope.$on('dropdown-closed', function(evt, targetScope){
176 |
177 | // perform an action when a child dropdown is closed
178 | $log.log('dropdown-closed', targetScope);
179 | });
180 |
181 | // listen for menu item event
182 | $scope.$on('menu-item-selected', function(evt, scope){
183 | // grab the url string from the menu iten scope
184 | var url;
185 | try{
186 | url = scope.url || scope.item.url;
187 | // handle navigation programatically
188 | //$location.path(url);
189 | $log.log(url);
190 | }catch(err){
191 | $log.warn('no url');
192 | }
193 | });
194 | }],
195 | link: function(scope, iElement, iAttrs, navCtrl, $transclude){
196 |
197 | // know who the tenants are
198 | // note that this link function executes *after*
199 | // the link functions of any inner components
200 | // at this point we could extend our NavBar component
201 | // functionality to rebuild menus based on new json or
202 | // disable individual menu items based on $location
203 | scope.registeredMenus = uicDropdownService.getDropdowns();
204 |
205 | // Attr API option for sticky vs fixed
206 | scope.position = (iAttrs.sticky == 'true') ? 'navbar-fixed-top' : 'navbar-static-top';
207 |
208 | // get theme css class from attr API if set
209 | scope.theme = (iAttrs.theme) ? iAttrs.theme : null;
210 |
211 | // send compiled/linked element back to ctrl instance
212 | navCtrl.init( iElement );
213 | }
214 | };
215 | }]);
216 | })();
217 |
218 |
219 |
220 |
221 |
--------------------------------------------------------------------------------
/src/Navbar/Navbar.less:
--------------------------------------------------------------------------------
1 | /*
2 | an example of name-spacing a "singleton" component with a css #id
3 | this allows us to 1)override matching global styles with a
4 | higher specificity, and 2)prevent our component styles from
5 | bleeding to the rest if the page
6 |
7 | #ids should only be used for components meant to be used only
8 | once on a page
9 |
10 | given that this example is based off of bootstrap.css, the real component
11 | css would likely be much more extensive given that bootstrap.css may or
12 | may not be available in the host page
13 | */
14 | #uic-navbar.navbar-inverse .navbar-brand {
15 | color: #FFF;
16 | }
17 | #uic-navbar .nav a {cursor: pointer;}
18 | #uic-navbar .navbar-collapse {max-height: inherit;}
19 | #uic-navbar .navbar-collapse.in {overflow-y: inherit;}
20 | #uic-navbar .uic-dropdown .dropdown-menu {
21 | display: none;
22 | background-color: #222;
23 | border-color: #080808;
24 | color: #999;
25 | }
26 | @media (max-width: 768px) {
27 | #uic-navbar .uic-dropdown .dropdown-menu{
28 | position: static;
29 | float: none;
30 | }
31 | }
32 | #uic-navbar .uic-dropdown .dropdown-menu li a {
33 | color: #999;
34 | }
35 | #uic-navbar .uic-dropdown .dropdown-menu li a:hover {
36 | color: #FFF;
37 | background-color: #222;
38 | }
39 | #uic-navbar .navbar-inverse .navbar-nav .disabled>a,
40 | #uic-navbar .navbar-inverse .navbar-nav .disabled>a:hover,
41 | #uic-navbar .navbar-inverse .navbar-nav .disabled>a:focus,
42 | #uic-navbar .dropdown-menu>li.disabled>a,
43 | #uic-navbar .dropdown-menu>li.disabled>a:hover,
44 | #uic-navbar .dropdown-menu>li.disabled>a:focus
45 | {
46 | color: #444;
47 | background-color: transparent;
48 | }
49 | .dropdown-menu>li>a:hover,
50 | .dropdown-menu>li>a:focus,
51 | .dropdown-menu>li>a:active
52 | {
53 | text-decoration: none;
54 | color: #fff;
55 | background-color: #000;
56 | }
--------------------------------------------------------------------------------
/src/Navbar/Navbar.tpl.html:
--------------------------------------------------------------------------------
1 |
4 |
30 |
--------------------------------------------------------------------------------
/src/Navbar/test/NavbarSpec.js:
--------------------------------------------------------------------------------
1 | // Unit test coverage for the NavBar UI container
2 | describe('My NavBar component directive', function () {
3 | var $compile, $rootScope, $scope, $element,
4 | element, $event, $_uicNavBarService, $log;
5 |
6 | // create some HTML to simulate how a developer might include
7 | // our menu item component in their page that covers all of
8 | // the API options
9 | var tpl =
10 | '' +
15 | ' ' +
18 | ' ' +
22 | ' ' +
23 | ' ' +
27 | ' ';
28 |
29 | // this tpl has some attr API values set to non-defaults
30 | var tpl2 =
31 | '' +
36 | ' ' +
39 | ' ' +
43 | ' ' +
44 | ' ' +
48 | ' ';
49 |
50 | // tpl with no HTML contents for testing JSON population
51 | var tplJson =
52 | '' +
56 | ' ';
57 |
58 | // an array of dropdowns
59 | var header = [
60 | {"Products":[
61 | {
62 | "text":"Scrum Manager",
63 | "url":"/products/scrum-manager"
64 | },{
65 | "text":"AppBeat",
66 | "url":"/products/app-beat"
67 | },{
68 | "text":"Solidify on Request",
69 | "url":"/products/sor"
70 | },{
71 | "text":"IQ Everywhere",
72 | "url":"/products/iq-anywhere"
73 | },{
74 | "text":"Service Everywhere",
75 | "url":"/products/service-everywhere"
76 | }
77 | ]},
78 | {"Company":[
79 | {
80 | "text":"Contact Us",
81 | "url":"/company/contact"
82 | },{
83 | "text":"Jobs",
84 | "url":"/company/jobs"
85 | },{
86 | "text":"Privacy Statement",
87 | "url":"/company/privacy"
88 | },{
89 | "text":"Terms of Use",
90 | "url":"/company/terms"
91 | }]
92 | }
93 | ];
94 |
95 | // a single dropdown object
96 | var dropdown = {"About Us":[
97 | {
98 | "text":"Contact Us",
99 | "url":"/company/contact"
100 | },{
101 | "text":"Jobs",
102 | "url":"/company/jobs"
103 | },{
104 | "text":"Privacy Statement",
105 | "url":"/company/privacy"
106 | },{
107 | "text":"Terms of Use",
108 | "url":"/company/terms"
109 | }]
110 | };
111 |
112 | // manually initialize our component library module
113 | beforeEach(module('uiComponents.navbar'));
114 |
115 | // make the necessary angular utility methods available
116 | // to our tests
117 | beforeEach(inject(function (_$compile_, _$rootScope_, _$log_) {
118 | $compile = _$compile_;
119 | $rootScope = _$rootScope_;
120 | $log = _$log_;
121 | // note that this is actually the PARENT scope of the directive
122 | $scope = $rootScope.$new();
123 | }));
124 |
125 | // manually compile and link our component directive
126 | function compileDirective(directiveTpl) {
127 |
128 | // use our default template if none provided
129 | if (!directiveTpl) directiveTpl = tpl;
130 |
131 | inject(function($compile) {
132 |
133 | // manually compile the template and inject the scope in
134 | $element = $compile(directiveTpl)($scope);
135 |
136 | // manually update all of the bindings
137 | $scope.$digest();
138 |
139 | // make the html output available to our tests
140 | element = $element.html();
141 | });
142 | }
143 |
144 | describe('NavBar component configuration APIs', function () {
145 |
146 | describe('configuration defauts', function () {
147 |
148 | beforeEach(function () {
149 | compileDirective();
150 | });
151 |
152 | it('should show all contents if API attribute "minimal" is not true',
153 | function () {
154 | // .ng-hide will be set on hidden menu groups
155 | expect($element.find('ul.ng-hide').length).toBe(0);
156 | });
157 |
158 | it('should set the brand logo URL to the API attribute "home-url"',
159 | function () {
160 | expect($element.find('a.navbar-brand').attr('href'))
161 | .toContain('www.david-shapiro.net');
162 | });
163 |
164 | it('should fix position the nav bar if API attribute "sticky" is true',
165 | function () {
166 |
167 | // can only determine that the "navbar-fixed-top" class is set
168 | expect($element.hasClass('navbar-fixed-top')).toBe(true);
169 | });
170 |
171 | it('should contain dropdowns and menu components as inner html',
172 | function () {
173 |
174 | //there are 2 menu items and 1 dropdown in this test template
175 | expect($element.find('li.uic-menu-item').length).toBe(2);
176 | expect($element.find('li.uic-dropdown').length).toBe(1);
177 | });
178 | });
179 |
180 | describe('configuration API alternatives', function () {
181 |
182 | beforeEach(function () {
183 | // now we are using the second test template
184 | compileDirective(tpl2);
185 | });
186 |
187 | it('should hide all contents if API attribute "minimal" is true',
188 | function () {
189 | // the 2 elements should now have .ng-hide set
190 | expect($element.find('ul.ng-hide').length).toBe(2);
191 | });
192 |
193 | it('should static position the navbar if API attr "sticky" is falsy',
194 | function () {
195 |
196 | // can only determine that the "navbar-static-top" class is set
197 | expect($element.hasClass('navbar-static-top')).toBe(true);
198 | });
199 |
200 | it('should add a style theme class if API attr "theme" equals "classname"',
201 | function () {
202 |
203 | // the "light-blue" attr val should be added as a class
204 | // on the root element
205 | expect($element.hasClass('light-blue')).toBe(true);
206 | });
207 | });
208 |
209 | describe('configuration API via JSON', function () {
210 |
211 | beforeEach(inject(function (uicNavBarService) {
212 | $_uicNavBarService = uicNavBarService;
213 |
214 | // manually add menu data to the nav bar service
215 | $_uicNavBarService.addMenus(header);
216 |
217 | compileDirective(tplJson);
218 | }));
219 |
220 | it('should contain dropdowns and menu components provided as JSON',
221 | function () {
222 |
223 | // there are 9 total menu items in the test data
224 | expect($element.find('li.uic-dropdown > ul > li').length).toBe(9);
225 | });
226 | });
227 | });
228 |
229 | describe('Runtime event APIs', function () {
230 |
231 | var scope;
232 | beforeEach(inject(function (uicNavBarService) {
233 | $_uicNavBarService = uicNavBarService;
234 |
235 | // manually add menu data to the nav bar service
236 | $_uicNavBarService.addMenus(header);
237 |
238 | compileDirective(tplJson);
239 |
240 | // get access to the actual controller instance
241 | scope = $element.isolateScope();
242 |
243 | // create a fake click event
244 | $event = $.Event( "click" );
245 | }));
246 |
247 | it('should hide contents on "header-minimize"', function () {
248 |
249 | // default state is to show all contents
250 | expect($element.find('ul.ng-hide').length).toBe(0);
251 | $rootScope.$broadcast('header-minimize');
252 | scope.$digest();
253 |
254 | // both template s should now have .ng-hide set
255 | expect($element.find('ul.ng-hide').length).toBe(2);
256 | });
257 |
258 | it('should show contents on "header-maximize"', function () {
259 |
260 | // first we need to explicitly hide contents since that
261 | // is not the default
262 | $rootScope.$broadcast('header-minimize');
263 | scope.$digest();
264 | expect($element.find('ul.ng-hide').length).toBe(2);
265 |
266 | // now broadcast the API event
267 | $rootScope.$broadcast('header-maximize');
268 | scope.$digest();
269 |
270 | // .ng-hide should now be removed
271 | expect($element.find('ul.ng-hide').length).toBe(0);
272 | });
273 |
274 | it('should add a dropdown on "add-nav-dropdowns"', function () {
275 |
276 | // upon initialization there should be 2 dropdowns with
277 | // 9 menu items total
278 | expect($element.find('li.uic-dropdown').length).toBe(2);
279 | expect($element.find('li.uic-dropdown > ul > li').length).toBe(9);
280 |
281 | // broadcast the API event with data
282 | $rootScope.$broadcast('add-nav-dropdowns', dropdown);
283 | scope.$digest();
284 |
285 | // now there should be 3 dropdowns and 13 menu items
286 | expect($element.find('li.uic-dropdown').length).toBe(3);
287 | expect($element.find('li.uic-dropdown > ul > li').length).toBe(13);
288 | });
289 |
290 | it('should remove a dropdown on "remove-nav-dropdowns"', function () {
291 |
292 | // upon initialization there should be 2 dropdowns with
293 | // 9 menu items total
294 | expect($element.find('li.uic-dropdown').length).toBe(2);
295 | expect($element.find('li.uic-dropdown > ul > li').length).toBe(9);
296 |
297 | // broadcast the API event with data
298 | $rootScope.$broadcast('remove-nav-dropdowns', 'Products');
299 | scope.$digest();
300 |
301 | // now there should be 1 dropdowns with 4 menu items
302 | expect($element.find('li.uic-dropdown').length).toBe(1);
303 | expect($element.find('li.uic-dropdown > ul > li').length).toBe(4);
304 | });
305 |
306 | it('should log dropdown-opened on "dropdown-opened"', function () {
307 | spyOn($log, 'log');
308 |
309 | // grab a reference to a dropdown and its scope
310 | var elem = $element.find('li.uic-dropdown > a');
311 | var targetScope = elem.isolateScope();
312 |
313 | // simulate opening it
314 | elem.trigger('click');
315 |
316 | // make sure the event handler gets the correct scope reference
317 | expect($log.log).toHaveBeenCalledWith('dropdown-opened', targetScope);
318 | });
319 |
320 | it('should log dropdown-closed on "dropdown-closed"', function () {
321 | spyOn($log, 'log');
322 |
323 | // grab a reference to a dropdown and its scope
324 | var elem = $element.find('li.uic-dropdown > a').last();
325 | var targetScope = elem.isolateScope();
326 |
327 | // open the dropdown
328 | elem.trigger('click');
329 | // then close it again
330 | elem.trigger('click');
331 |
332 | // make sure the event handler gets the correct scope reference
333 | expect($log.log).toHaveBeenCalledWith('dropdown-closed', targetScope);
334 | });
335 |
336 | it('should log the URL on "menu-item-selected"', function () {
337 | spyOn($log, 'log');
338 |
339 | // get a menu item reference plus scope and url if any
340 | var menuItem = $element.find('li.uic-dropdown > ul > li');
341 | var itemScope = menuItem.scope();
342 | var url = itemScope.item.url;
343 |
344 | // manually $emit a menu-item-selected event
345 | itemScope.selected($event, itemScope);
346 |
347 | // only testing menu items w/ urls since those without should
348 | // be selectable anyhow
349 | if(url) expect($log.log).toHaveBeenCalledWith(url);
350 | });
351 | });
352 | });
--------------------------------------------------------------------------------
/src/SmartButton/SmartButton.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | 'use strict';
3 |
4 | var tpl = '';
5 | //@import "../../build/src/SmartButton/SmartButton.tpl.js";
6 |
7 | var buttons = angular.module('uiComponents.smartButton', []);
8 |
9 | buttons.directive('smartButton', ['$timeout', function($timeout){
10 | return {
11 | template: tpl, // use an inline template for increaced
12 | restrict: 'E', // restrict directive matching to elements
13 | replace: true,
14 | transclude: true,
15 | // create an isolate scope
16 | scope: {
17 | debug: '&'
18 | },
19 | controller: function($scope, $element, $attrs, $injector){
20 | // declare some default values
21 | $scope.bttnClass = 'btn btn-default';
22 | $scope.bttnText = $scope.defaultText = 'Smart Button';
23 | $scope.activeText = 'Processing...';
24 | // a nice way to pull a core service into a directive
25 | //var $timeout = $injector.get('$timeout');
26 | // on click change text to activeText
27 | // after 3 seconds change text to something else
28 | $scope.doSomething = function(elem){
29 | $scope.bttnText = $scope.activeText;
30 | $timeout(function(){
31 | $scope.bttnText = "We're Done!";
32 | }, 3000);
33 | // emit a click event
34 | $scope.$emit('smart-button-click', elem);
35 | };
36 |
37 | // listen for an event from a parent container
38 | $scope.$on('smart-button-command', function(evt, targetComponentId, command){
39 | // check that our instance is the target
40 | if(targetComponentId === $scope.$id){
41 | // we can add any number of actions here
42 | if(command.setClass){
43 | // change the button style from default
44 | $scope.bttnClass = 'btn ' + command.setClass;
45 | }
46 | }
47 | });
48 | },
49 | link: function(scope, iElement, iAttrs, controller){
50 | // button text
51 | if(iAttrs.defaultText){
52 | scope.bttnText = scope.defaultText = iAttrs.defaultText;
53 | }
54 | // button text to diplay when active
55 |
56 | if(iAttrs.activeText){
57 | scope.activeText = iAttrs.activeText;
58 | }
59 | }
60 | };
61 | }]);
62 | })();
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/src/SmartButton/SmartButton.tpl.html:
--------------------------------------------------------------------------------
1 |
3 | {{bttnText}}
4 |
5 |
--------------------------------------------------------------------------------
/src/SmartButton/test/SmartButtonSpec.js:
--------------------------------------------------------------------------------
1 | describe('My SmartButton component directive', function () {
2 | var $compile, $rootScope, $scope, $element, element;
3 | // manually initialize our component library module
4 | beforeEach(module('UIComponents'));
5 | // make the necessary angular utility methods available
6 | // to our tests
7 | beforeEach(inject(function (_$compile_, _$rootScope_) {
8 | $compile = _$compile_;
9 | $rootScope = _$rootScope_;
10 | // note that this is actually the PARENT scope of the directive
11 | $scope = $rootScope.$new();
12 | }));
13 |
14 | // create some HTML to simulate how a developer might include
15 | // our smart button component in their page that covers all of
16 | // the API options
17 | var tpl = '' +
20 | '{{bttnText}} Text from transclusion. ';
21 |
22 | // manually compile and link our component directive
23 | function compileDirective(directiveTpl) {
24 | // use our default template if none provided
25 | if (!directiveTpl) directiveTpl = tpl;
26 |
27 | inject(function($compile) {
28 | // manually compile the template and inject the scope in
29 | $element = $compile(directiveTpl)($scope);
30 | // manually update all of the bindings
31 | $scope.$digest();
32 | // make the html output available to our tests
33 | element = $element.html();
34 | });
35 | }
36 | // test our component APIs
37 | describe('A smart button API', function(){
38 | var scope;
39 | beforeEach(function(){
40 | compileDirective();
41 | // get access to the actual controller instance
42 | scope = $element.isolateScope();
43 | spyOn($rootScope, '$broadcast').andCallThrough();
44 | });
45 | it('should use the attr value of "default-text" as the initially displayed bttn text', function(){
46 | expect(element).toContain('A Very Smart Button');
47 | });
48 | it('should use the attr value of "active-text" as what to display when clicked', function(){
49 | expect(scope.bttnText).toBe('A Very Smart Button');
50 | scope.doSomething();
51 | expect(scope.bttnText).toBe('Wait for 5 seconds...');
52 | });
53 | it('should transclude the content of the element', function(){
54 | expect(element).toContain('Text from transclusion.');
55 | });
56 | it('should have the injected logic available for execution', function(){
57 | expect(scope.debug()).toBe('testing $rootScope access, please ignore');
58 | });
59 | it('should emit any events as APIs', function(){
60 | spyOn(scope, '$emit');
61 | scope.$emit('smart-button-click');
62 | expect(scope.$emit).toHaveBeenCalledWith('smart-button-click');
63 | });
64 | it('should listen and handle any events as APIs', function(){
65 | $rootScope.$broadcast('smart-button-command', scope.$id, {setClass: 'btn-warning'});
66 | expect(scope.bttnClass).toContain('btn-warning');
67 | });
68 | });
69 | });
--------------------------------------------------------------------------------
/test/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Thu Apr 03 2014 16:39:13 GMT-0700 (PDT)
3 |
4 | module.exports = function(config) {
5 | config.set({
6 | // base path that will be used to resolve all patterns (eg. files, exclude)
7 | basePath: '',
8 | // frameworks to use
9 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
10 | frameworks: ['jasmine'],
11 | // list of files / patterns to load in the browser
12 | files: [
13 | '../lib/jquery-2.1.0.min.js',
14 | '../lib/bootstrap.min.js',
15 | '../lib/angular.min.js',
16 | '../lib/angular-sanitize.min.js',
17 | '../lib/angular-mocks.js',
18 | '../lib/ui-bootstrap-collapse.js',
19 | '../js/UIComponents.js',
20 | '../build/src/SmartButton/SmartButton.js',
21 | '../src/SmartButton/test/SmartButtonSpec.js',
22 | '../build/src/MenuItem/MenuItem.js',
23 | '../build/src/Dropdown/Dropdown.js',
24 | '../build/src/Navbar/Navbar.js',
25 | '../src/MenuItem/test/MenuItemSpec.js',
26 | '../src/Dropdown/test/DropdownSpec.js',
27 | '../src/Navbar/test/NavbarSpec.js'
28 | ],
29 | // list of files to exclude
30 | exclude: [
31 |
32 | ],
33 | // preprocess matching files before serving them to the browser
34 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
35 | preprocessors: {
36 |
37 | },
38 | // test results reporter to use
39 | // possible values: 'dots', 'progress'
40 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
41 | reporters: ['progress'],
42 | // web server port
43 | port: 9876,
44 | // enable / disable colors in the output (reporters and logs)
45 | colors: true,
46 | // level of logging
47 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
48 | logLevel: config.LOG_INFO,
49 |
50 | // enable / disable watching file and executing tests whenever any file changes
51 | autoWatch: true,
52 |
53 | // start these browsers
54 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
55 | //browsers: ['PhantomJS'],
56 | browsers: ['Chrome'],
57 | // Continuous Integration mode
58 | // if true, Karma captures browsers, runs the tests and exits
59 | singleRun: false
60 | });
61 | };
62 |
--------------------------------------------------------------------------------
/test/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | karma start karma.conf.js $*
4 |
--------------------------------------------------------------------------------