├── .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 | [![Build Status](https://secure.travis-ci.org/dgs700/angularjs-web-component-development.png)](http://travis-ci.org/dgs700/angularjs-web-component-development) 9 | [![devDependency Status](https://david-dm.org/dgs700/angularjs-web-component-development.png?branch=master)](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 | 33 | 34 |
35 |
36 |
37 |
38 |
39 |

Lorem ipsum dolor...

40 |

41 |
42 |

Class aptent taciti...

43 |
44 |
45 |
46 |
47 |
48 | 49 |
50 | 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(""));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=/^]*?)>/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(""));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=/^]*?)>/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(''}),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: '
    • ' + 15 | // note the use of ng-bind vs. {{}} to prevent any brief flash of the raw template 16 | // '' + 17 | // '
    • ', 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 |
    • 2 | 5 | 6 |
    • -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------