From e7271c1b507d774e7b1e72e317795722f9b10fde Mon Sep 17 00:00:00 2001 From: Ben Tesser Date: Fri, 20 Feb 2015 06:22:36 -0500 Subject: [PATCH 1/2] Separated components into files Separated each separate component of the angular-css into its own js file which gets concatted at build time. Bug Info: Separate Separate services, etc into different files #21 https://github.com/door3/angular-css/issues/21 --- Gruntfile.js | 26 +- angular-css.js | 215 ++++++++-------- angular-css.min.js | 2 +- bower.json | 6 +- package.json | 11 +- src/$css-provider.js | 511 +++++++++++++++++++++++++++++++++++++++ src/$cssLinks-filter.js | 17 ++ src/angularCSS-module.js | 12 + src/angularHack.js | 51 ++++ src/prefix.js | 11 + src/suffix.js | 1 + 11 files changed, 744 insertions(+), 119 deletions(-) create mode 100644 src/$css-provider.js create mode 100644 src/$cssLinks-filter.js create mode 100644 src/angularCSS-module.js create mode 100644 src/angularHack.js create mode 100644 src/prefix.js create mode 100644 src/suffix.js diff --git a/Gruntfile.js b/Gruntfile.js index 8f13608..31d7b01 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,9 +1,27 @@ 'use strict'; module.exports = function(grunt) { + var npmTasks = [ + 'grunt-contrib-concat', + 'grunt-contrib-uglify', + 'grunt-karma' + ]; + grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), - + concat: { + dist: { + src: [ + 'src/prefix.js', + 'src/$css-provider.js', + 'src/$cssLinks-filter.js', + 'src/angularCSS-module.js', + 'src/angularHack.js', + 'src/suffix.js' + ], + dest: 'angular-css.js' + } + }, karma: { unit: { options: { @@ -40,12 +58,14 @@ module.exports = function(grunt) { } }); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-karma'); + npmTasks.forEach(function (task) { + grunt.loadNpmTasks(task); + }); grunt.registerTask('test', ['karma']); grunt.registerTask('default', [ + 'concat', 'test', 'uglify' ]); diff --git a/angular-css.js b/angular-css.js index 26f4f61..4fa3435 100644 --- a/angular-css.js +++ b/angular-css.js @@ -10,33 +10,20 @@ (function (angular) { - /** - * AngularCSS Module - * Contains: config, constant, provider and run - **/ - var angularCSS = angular.module('door3.css', []); - - // Config - angularCSS.config(['$logProvider', function ($logProvider) { - // Turn off/on in order to see console logs during dev mode - $logProvider.debugEnabled(false); - }]); - - // Provider - angularCSS.provider('$css', [function $cssProvider() { - - // Defaults - default options that can be overridden from application config - var defaults = this.defaults = { - element: 'link', - rel: 'stylesheet', - type: 'text/css', - container: 'head', - method: 'append', - weight: 0 - }; +function $cssProvider() { + + // Defaults - default options that can be overridden from application config + var defaults = this.defaults = { + element: 'link', + rel: 'stylesheet', + type: 'text/css', + container: 'head', + method: 'append', + weight: 0 + }; - this.$get = ['$rootScope','$injector','$q','$window','$timeout','$compile','$http','$filter','$log', - function $get($rootScope, $injector, $q, $window, $timeout, $compile, $http, $filter, $log) { + this.$get = ['$rootScope', '$injector', '$q', '$window', '$timeout', '$compile', '$http', '$filter', '$log', + function $get($rootScope, $injector, $q, $window, $timeout, $compile, $http, $filter, $log) { var $css = {}; @@ -105,7 +92,7 @@ stylesheet.media = options.breakpoints[stylesheet.breakpoint]; } delete stylesheet.breakpoints; - } + } } /** @@ -199,7 +186,7 @@ // Media query object mediaQuery[stylesheet.href] = $window.matchMedia(stylesheet.media); // Media Query Listener function - mediaQueryListener[stylesheet.href] = function(mediaQuery) { + mediaQueryListener[stylesheet.href] = function (mediaQuery) { // Trigger digest $timeout(function () { if (mediaQuery.matches) { @@ -246,11 +233,11 @@ } return !!( // Check for media query setting - stylesheet.media + stylesheet.media // Check for media queries to be ignored - && (mediaQueriesToIgnore.indexOf(stylesheet.media) === -1) + && (mediaQueriesToIgnore.indexOf(stylesheet.media) === -1) // Check for matchMedia support - && $window.matchMedia + && $window.matchMedia ); } @@ -349,12 +336,12 @@ if (angular.isDefined(state.css)) { // For multiple stylesheets if (angular.isArray(state.css)) { - angular.forEach(state.css, function (itemCss) { - if (angular.isFunction(itemCss)) { - dynamicPaths.push(parse(itemCss)); - } - result.push(parse(itemCss)); - }); + angular.forEach(state.css, function (itemCss) { + if (angular.isFunction(itemCss)) { + dynamicPaths.push(parse(itemCss)); + } + result.push(parse(itemCss)); + }); // For single stylesheets } else { if (angular.isFunction(state.css)) { @@ -413,7 +400,7 @@ stylesheets = [stylesheets]; } var stylesheetLoadPromises = []; - angular.forEach(stylesheets, function(stylesheet, key) { + angular.forEach(stylesheets, function (stylesheet, key) { stylesheet = stylesheets[key] = parse(stylesheet); stylesheetLoadPromises.push( // Preload via ajax request @@ -432,7 +419,7 @@ /** * Bind: binds css in scope with own scope create/destroy events **/ - $css.bind = function (css, $scope) { + $css.bind = function (css, $scope) { if (!css || !$scope) { return $log.error('No scope or stylesheets provided'); } @@ -451,7 +438,7 @@ $css.remove(result); $log.debug('$css.bind(): Removed', result); }); - }; + }; /** * Add: adds stylesheets to scope @@ -463,10 +450,10 @@ if (!angular.isArray(stylesheets)) { stylesheets = [stylesheets]; } - angular.forEach(stylesheets, function(stylesheet) { + angular.forEach(stylesheets, function (stylesheet) { stylesheet = parse(stylesheet); // Avoid adding duplicate stylesheets - if (stylesheet.href && !$filter('filter')($rootScope.stylesheets, { href: stylesheet.href }).length) { + if (stylesheet.href && !$filter('filter')($rootScope.stylesheets, {href: stylesheet.href}).length) { // Bust Cache feature bustCache(stylesheet) // Media Query add support check @@ -497,7 +484,7 @@ stylesheets = $filter('filter')(stylesheets, function (stylesheet) { return !stylesheet.persist; }); - angular.forEach(stylesheets, function(stylesheet) { + angular.forEach(stylesheets, function (stylesheet) { stylesheet = parse(stylesheet); // Get index of current item to be removed based on href var index = $rootScope.stylesheets.indexOf($filter('filter')($rootScope.stylesheets, { @@ -533,81 +520,89 @@ }]; - }]); +} - /** - * Links filter - renders the stylesheets array in html format - **/ - angularCSS.filter('$cssLinks', function () { - return function (stylesheets) { - if (!stylesheets || !angular.isArray(stylesheets)) { - return stylesheets; - } - var result = ''; - angular.forEach(stylesheets, function (stylesheet) { - result += '',y={},z={},A=["print"],B=a.extend({},b),C=a.element(document.querySelector?document.querySelector(B.container):document.getElementsByTagName(B.container)[0]),D=[];return a.forEach(c,function(a,b){a.hasOwnProperty("css")&&(c[b]=q(a.css))}),d.stylesheets=[],C[B.method](i(x)(d)),d.$on("$directiveAdd",m),d.$on("$routeChangeSuccess",n),d.$on("$stateChangeSuccess",o),w.getFromRoute=function(b){if(!b)return l.error("Get From Route: No route provided");var c=null,d=[];return b.$$route&&b.$$route.css?c=b.$$route.css:b.css&&(c=b.css),c&&(a.isArray(c)?a.forEach(c,function(b){a.isFunction(b)&&D.push(q(b)),d.push(q(b))}):(a.isFunction(c)&&D.push(q(c)),d.push(q(c)))),d},w.getFromRoutes=function(b){if(!b)return l.error("Get From Routes: No routes provided");var c=[];return a.forEach(b,function(a){var b=w.getFromRoute(a);b.length&&c.push(b[0])}),c},w.getFromState=function(b){if(!b)return l.error("Get From State: No state provided");var c=[];return a.isDefined(b.views)&&a.forEach(b.views,function(b){b.css&&(a.isFunction(b.css)&&D.push(q(b.css)),c.push(q(b.css)))}),a.isDefined(b.children)&&a.forEach(b.children,function(b){b.css&&(a.isFunction(b.css)&&D.push(q(b.css)),c.push(q(b.css))),a.isDefined(b.children)&&a.forEach(b.children,function(b){b.css&&(a.isFunction(b.css)&&D.push(q(b.css)),c.push(q(b.css)))})}),a.isDefined(b.css)&&(a.isArray(b.css)?a.forEach(b.css,function(b){a.isFunction(b)&&D.push(q(b)),c.push(q(b))}):(a.isFunction(b.css)&&D.push(q(b.css)),c.push(q(b.css)))),c},w.getFromStates=function(b){if(!b)return l.error("Get From States: No states provided");var c=[];return a.forEach(b,function(b){var d=w.getFromState(b);a.isArray(d)?a.forEach(d,function(a){c.push(a)}):c.push(d)}),c},w.preload=function(b,d){b||(b=[],c.length&&Array.prototype.push.apply(b,c),e.has("$route")&&Array.prototype.push.apply(b,w.getFromRoutes(e.get("$route").routes)),e.has("$state")&&Array.prototype.push.apply(b,w.getFromStates(e.get("$state").get())),b=s(b,"preload")),a.isArray(b)||(b=[b]);var g=[];a.forEach(b,function(a,c){a=b[c]=q(a),g.push(j.get(a.href).error(function(){l.error("AngularCSS: Incorrect path for "+a.href)}))}),a.isFunction(d)&&f.all(g).then(function(){d(b)})},w.bind=function(b,c){if(!b||!c)return l.error("No scope or stylesheets provided");var d=[];a.isArray(b)?a.forEach(b,function(a){d.push(q(a))}):d.push(q(b)),w.add(d),l.debug("$css.bind(): Added",d),c.$on("$destroy",function(){w.remove(d),l.debug("$css.bind(): Removed",d)})},w.add=function(b){return b?(a.isArray(b)||(b=[b]),a.forEach(b,function(a){a=q(a),a.href&&!k("filter")(d.stylesheets,{href:a.href}).length&&(r(a),v(a)?t(a):d.stylesheets.push(a),l.debug("$css.add(): "+a.href))}),void d.$broadcast("$cssAdd",b,d.stylesheets)):l.error("No stylesheets provided")},w.remove=function(b){return b?(a.isArray(b)||(b=[b]),b=k("filter")(b,function(a){return!a.persist}),a.forEach(b,function(a){a=q(a);var b=d.stylesheets.indexOf(k("filter")(d.stylesheets,{href:a.href})[0]);-1!==b&&d.stylesheets.splice(b,1),u(a),l.debug("$css.remove(): "+a.href)}),void d.$broadcast("$cssRemove",b,d.stylesheets)):l.error("No stylesheets provided")},w.removeAll=function(){d&&d.hasOwnProperty("stylesheets")&&(d.stylesheets.length=0),l.debug("all stylesheets removed")},w.preload(),w}]}]),b.filter("$cssLinks",function(){return function(b){if(!b||!a.isArray(b))return b;var c="";return a.forEach(b,function(a){c+='',y={},z={},A=["print"],B=a.extend({},b),C=a.element(document.querySelector?document.querySelector(B.container):document.getElementsByTagName(B.container)[0]),D=[];return a.forEach(d,function(a,b){a.hasOwnProperty("css")&&(d[b]=q(a.css))}),c.stylesheets=[],C[B.method](i(x)(c)),c.$on("$directiveAdd",m),c.$on("$routeChangeSuccess",n),c.$on("$stateChangeSuccess",o),w.getFromRoute=function(b){if(!b)return l.error("Get From Route: No route provided");var c=null,d=[];return b.$$route&&b.$$route.css?c=b.$$route.css:b.css&&(c=b.css),c&&(a.isArray(c)?a.forEach(c,function(b){a.isFunction(b)&&D.push(q(b)),d.push(q(b))}):(a.isFunction(c)&&D.push(q(c)),d.push(q(c)))),d},w.getFromRoutes=function(b){if(!b)return l.error("Get From Routes: No routes provided");var c=[];return a.forEach(b,function(a){var b=w.getFromRoute(a);b.length&&c.push(b[0])}),c},w.getFromState=function(b){if(!b)return l.error("Get From State: No state provided");var c=[];return a.isDefined(b.views)&&a.forEach(b.views,function(b){b.css&&(a.isFunction(b.css)&&D.push(q(b.css)),c.push(q(b.css)))}),a.isDefined(b.children)&&a.forEach(b.children,function(b){b.css&&(a.isFunction(b.css)&&D.push(q(b.css)),c.push(q(b.css))),a.isDefined(b.children)&&a.forEach(b.children,function(b){b.css&&(a.isFunction(b.css)&&D.push(q(b.css)),c.push(q(b.css)))})}),a.isDefined(b.css)&&(a.isArray(b.css)?a.forEach(b.css,function(b){a.isFunction(b)&&D.push(q(b)),c.push(q(b))}):(a.isFunction(b.css)&&D.push(q(b.css)),c.push(q(b.css)))),c},w.getFromStates=function(b){if(!b)return l.error("Get From States: No states provided");var c=[];return a.forEach(b,function(b){var d=w.getFromState(b);a.isArray(d)?a.forEach(d,function(a){c.push(a)}):c.push(d)}),c},w.preload=function(b,c){b||(b=[],d.length&&Array.prototype.push.apply(b,d),e.has("$route")&&Array.prototype.push.apply(b,w.getFromRoutes(e.get("$route").routes)),e.has("$state")&&Array.prototype.push.apply(b,w.getFromStates(e.get("$state").get())),b=s(b,"preload")),a.isArray(b)||(b=[b]);var g=[];a.forEach(b,function(a,c){a=b[c]=q(a),g.push(j.get(a.href).error(function(){l.error("AngularCSS: Incorrect path for "+a.href)}))}),a.isFunction(c)&&f.all(g).then(function(){c(b)})},w.bind=function(b,c){if(!b||!c)return l.error("No scope or stylesheets provided");var d=[];a.isArray(b)?a.forEach(b,function(a){d.push(q(a))}):d.push(q(b)),w.add(d),l.debug("$css.bind(): Added",d),c.$on("$destroy",function(){w.remove(d),l.debug("$css.bind(): Removed",d)})},w.add=function(b){return b?(a.isArray(b)||(b=[b]),a.forEach(b,function(a){a=q(a),a.href&&!k("filter")(c.stylesheets,{href:a.href}).length&&(r(a),v(a)?t(a):c.stylesheets.push(a),l.debug("$css.add(): "+a.href))}),void c.$broadcast("$cssAdd",b,c.stylesheets)):l.error("No stylesheets provided")},w.remove=function(b){return b?(a.isArray(b)||(b=[b]),b=k("filter")(b,function(a){return!a.persist}),a.forEach(b,function(a){a=q(a);var b=c.stylesheets.indexOf(k("filter")(c.stylesheets,{href:a.href})[0]);-1!==b&&c.stylesheets.splice(b,1),u(a),l.debug("$css.remove(): "+a.href)}),void c.$broadcast("$cssRemove",b,c.stylesheets)):l.error("No stylesheets provided")},w.removeAll=function(){c&&c.hasOwnProperty("stylesheets")&&(c.stylesheets.length=0),l.debug("all stylesheets removed")},w.preload(),w}]}function c(){return function(b){if(!b||!a.isArray(b))return b;var c="";return a.forEach(b,function(a){c+='= 1.3.0" @@ -33,4 +33,4 @@ "test", "tests" ] -} \ No newline at end of file +} diff --git a/package.json b/package.json index d91c523..66f3eca 100644 --- a/package.json +++ b/package.json @@ -51,10 +51,17 @@ "test": "grunt test" }, "devDependencies": { + "angular": "^1.3.13", + "angular-mocks": "^1.3.13", + "chai": "^2.0.0", "grunt": "~0.4.1", + "grunt-cli": "~0.1.11", + "grunt-contrib-concat": "~0.5.1", "grunt-contrib-uglify": "~0.2.4", "grunt-karma": "~0.7.1", - "karma-mocha": "~0.1.0", - "grunt-cli": "~0.1.11" + "karma-chrome-launcher": "0.1.7", + "karma-firefox-launcher": "0.1.4", + "karma-phantomjs-launcher": "0.1.4", + "karma-mocha": "~0.1.0" } } diff --git a/src/$css-provider.js b/src/$css-provider.js new file mode 100644 index 0000000..e208ce7 --- /dev/null +++ b/src/$css-provider.js @@ -0,0 +1,511 @@ +function $cssProvider() { + + // Defaults - default options that can be overridden from application config + var defaults = this.defaults = { + element: 'link', + rel: 'stylesheet', + type: 'text/css', + container: 'head', + method: 'append', + weight: 0 + }; + + this.$get = ['$rootScope', '$injector', '$q', '$window', '$timeout', '$compile', '$http', '$filter', '$log', + function $get($rootScope, $injector, $q, $window, $timeout, $compile, $http, $filter, $log) { + + var $css = {}; + + var template = ''; + + // Variables - default options that can be overridden from application config + var mediaQuery = {}, mediaQueryListener = {}, mediaQueriesToIgnore = ['print'], options = angular.extend({}, defaults), + container = angular.element(document.querySelector ? document.querySelector(options.container) : document.getElementsByTagName(options.container)[0]), + dynamicPaths = []; + + // Parse all directives + angular.forEach($directives, function (directive, key) { + if (directive.hasOwnProperty('css')) { + $directives[key] = parse(directive.css); + } + }); + + /** + * Listen for directive add event in order to add stylesheet(s) + **/ + function $directiveAddEventListener(event, directive, scope) { + // Binds directive's css + if (scope && directive.hasOwnProperty('css')) { + $css.bind([parse(directive.css)], scope); + } + } + + /** + * Listen for route change event and add/remove stylesheet(s) + **/ + function $routeEventListener(event, current, prev) { + // Removes previously added css rules + if (prev) { + $css.remove($css.getFromRoute(prev).concat(dynamicPaths)); + // Reset dynamic paths array + dynamicPaths.length = 0; + } + // Adds current css rules + if (current) { + $css.add($css.getFromRoute(current)); + } + } + + /** + * Listen for state change event and add/remove stylesheet(s) + **/ + function $stateEventListener(event, current, params, prev) { + // Removes previously added css rules + if (prev) { + $css.remove($css.getFromState(prev).concat(dynamicPaths)); + // Reset dynamic paths array + dynamicPaths.length = 0; + } + // Adds current css rules + if (current) { + $css.add($css.getFromState(current)); + } + } + + /** + * Map breakpoitns defined in defaults to stylesheet media attribute + **/ + function mapBreakpointToMedia(stylesheet) { + if (angular.isDefined(options.breakpoints)) { + if (stylesheet.breakpoint in options.breakpoints) { + stylesheet.media = options.breakpoints[stylesheet.breakpoint]; + } + delete stylesheet.breakpoints; + } + } + + /** + * Parse: returns array with full all object based on defaults + **/ + function parse(obj) { + if (!obj) { + return; + } + // Function syntax + if (angular.isFunction(obj)) { + obj = angular.copy($injector.invoke(obj)); + } + // String syntax + if (angular.isString(obj)) { + obj = angular.extend({ + href: obj + }, options); + } + // Array of strings syntax + if (angular.isArray(obj) && angular.isString(obj[0])) { + angular.forEach(obj, function (item) { + obj = angular.extend({ + href: item + }, options); + }); + } + // Object syntax + if (angular.isObject(obj) && !angular.isArray(obj)) { + obj = angular.extend(obj, options); + } + // Array of objects syntax + if (angular.isArray(obj) && angular.isObject(obj[0])) { + angular.forEach(obj, function (item) { + obj = angular.extend(item, options); + }); + } + // Map breakpoint to media attribute + mapBreakpointToMedia(obj); + return obj; + } + + // Add stylesheets to scope + $rootScope.stylesheets = []; + + // Adds compiled link tags to container element + container[options.method]($compile(template)($rootScope)); + + // Directive event listener (emulated internally) + $rootScope.$on('$directiveAdd', $directiveAddEventListener); + + // Routes event listener ($route required) + $rootScope.$on('$routeChangeSuccess', $routeEventListener); + + // States event listener ($state required) + $rootScope.$on('$stateChangeSuccess', $stateEventListener); + + /** + * Bust Cache + **/ + function bustCache(stylesheet) { + if (!stylesheet) { + return $log.error('No stylesheets provided'); + } + var queryString = '?cache='; + // Append query string for bust cache only once + if (stylesheet.href.indexOf(queryString) === -1) { + stylesheet.href = stylesheet.href + (stylesheet.bustCache ? queryString + (new Date().getTime()) : ''); + } + } + + /** + * Filter By: returns an array of routes based on a property option + **/ + function filterBy(array, prop) { + if (!array || !prop) { + return $log.error('filterBy: missing array or property'); + } + return $filter('filter')(array, function (item) { + return item[prop]; + }); + } + + /** + * Add Media Query + **/ + function addViaMediaQuery(stylesheet) { + if (!stylesheet) { + return $log.error('No stylesheet provided'); + } + // Media query object + mediaQuery[stylesheet.href] = $window.matchMedia(stylesheet.media); + // Media Query Listener function + mediaQueryListener[stylesheet.href] = function (mediaQuery) { + // Trigger digest + $timeout(function () { + if (mediaQuery.matches) { + // Add stylesheet + $rootScope.stylesheets.push(stylesheet); + } else { + var index = $rootScope.stylesheets.indexOf($filter('filter')($rootScope.stylesheets, { + href: stylesheet.href + })[0]); + // Remove stylesheet + if (index !== -1) { + $rootScope.stylesheets.splice(index, 1); + } + } + }); + } + // Listen for media query changes + mediaQuery[stylesheet.href].addListener(mediaQueryListener[stylesheet.href]); + // Invoke first media query check + mediaQueryListener[stylesheet.href](mediaQuery[stylesheet.href]); + } + + /** + * Remove Media Query + **/ + function removeViaMediaQuery(stylesheet) { + if (!stylesheet) { + return $log.error('No stylesheet provided'); + } + // Remove media query listener + if ($rootScope && angular.isDefined(mediaQuery) + && mediaQuery[stylesheet.href] + && angular.isDefined(mediaQueryListener)) { + mediaQuery[stylesheet.href].removeListener(mediaQueryListener[stylesheet.href]); + } + } + + /** + * Is Media Query: checks for media settings, media queries to be ignore and match media support + **/ + function isMediaQuery(stylesheet) { + if (!stylesheet) { + return $log.error('No stylesheet provided'); + } + return !!( + // Check for media query setting + stylesheet.media + // Check for media queries to be ignored + && (mediaQueriesToIgnore.indexOf(stylesheet.media) === -1) + // Check for matchMedia support + && $window.matchMedia + ); + } + + /** + * Get From Route: returns array of css objects from single route + **/ + $css.getFromRoute = function (route) { + if (!route) { + return $log.error('Get From Route: No route provided'); + } + var css = null, result = []; + if (route.$$route && route.$$route.css) { + css = route.$$route.css; + } + else if (route.css) { + css = route.css; + } + // Adds route css rules to array + if (css) { + if (angular.isArray(css)) { + angular.forEach(css, function (cssItem) { + if (angular.isFunction(cssItem)) { + dynamicPaths.push(parse(cssItem)); + } + result.push(parse(cssItem)); + }); + } else { + if (angular.isFunction(css)) { + dynamicPaths.push(parse(css)); + } + result.push(parse(css)); + } + } + return result; + }; + + /** + * Get From Routes: returns array of css objects from ng routes + **/ + $css.getFromRoutes = function (routes) { + if (!routes) { + return $log.error('Get From Routes: No routes provided'); + } + var result = []; + // Make array of all routes + angular.forEach(routes, function (route) { + var css = $css.getFromRoute(route); + if (css.length) { + result.push(css[0]); + } + }); + return result; + }; + + /** + * Get From State: returns array of css objects from single state + **/ + $css.getFromState = function (state) { + if (!state) { + return $log.error('Get From State: No state provided'); + } + var result = []; + // State "views" notation + if (angular.isDefined(state.views)) { + angular.forEach(state.views, function (item) { + if (item.css) { + if (angular.isFunction(item.css)) { + dynamicPaths.push(parse(item.css)); + } + result.push(parse(item.css)); + } + }); + } + // State "children" notation + if (angular.isDefined(state.children)) { + angular.forEach(state.children, function (child) { + if (child.css) { + if (angular.isFunction(child.css)) { + dynamicPaths.push(parse(child.css)); + } + result.push(parse(child.css)); + } + if (angular.isDefined(child.children)) { + angular.forEach(child.children, function (childChild) { + if (childChild.css) { + if (angular.isFunction(childChild.css)) { + dynamicPaths.push(parse(childChild.css)); + } + result.push(parse(childChild.css)); + } + }); + } + }); + } + // State default notation + if (angular.isDefined(state.css)) { + // For multiple stylesheets + if (angular.isArray(state.css)) { + angular.forEach(state.css, function (itemCss) { + if (angular.isFunction(itemCss)) { + dynamicPaths.push(parse(itemCss)); + } + result.push(parse(itemCss)); + }); + // For single stylesheets + } else { + if (angular.isFunction(state.css)) { + dynamicPaths.push(parse(state.css)); + } + result.push(parse(state.css)); + } + } + return result; + }; + + /** + * Get From States: returns array of css objects from states + **/ + $css.getFromStates = function (states) { + if (!states) { + return $log.error('Get From States: No states provided'); + } + var result = []; + // Make array of all routes + angular.forEach(states, function (state) { + var css = $css.getFromState(state); + if (angular.isArray(css)) { + angular.forEach(css, function (cssItem) { + result.push(cssItem); + }); + } else { + result.push(css); + } + }); + return result; + }; + + /** + * Preload: preloads css via http request + **/ + $css.preload = function (stylesheets, callback) { + // If no stylesheets provided, then preload all + if (!stylesheets) { + stylesheets = []; + // Add all stylesheets from custom directives to array + if ($directives.length) { + Array.prototype.push.apply(stylesheets, $directives); + } + // Add all stylesheets from ngRoute to array + if ($injector.has('$route')) { + Array.prototype.push.apply(stylesheets, $css.getFromRoutes($injector.get('$route').routes)); + } + // Add all stylesheets from UI Router to array + if ($injector.has('$state')) { + Array.prototype.push.apply(stylesheets, $css.getFromStates($injector.get('$state').get())); + } + stylesheets = filterBy(stylesheets, 'preload'); + } + if (!angular.isArray(stylesheets)) { + stylesheets = [stylesheets]; + } + var stylesheetLoadPromises = []; + angular.forEach(stylesheets, function (stylesheet, key) { + stylesheet = stylesheets[key] = parse(stylesheet); + stylesheetLoadPromises.push( + // Preload via ajax request + $http.get(stylesheet.href).error(function (response) { + $log.error('AngularCSS: Incorrect path for ' + stylesheet.href); + }) + ); + }); + if (angular.isFunction(callback)) { + $q.all(stylesheetLoadPromises).then(function () { + callback(stylesheets); + }); + } + }; + + /** + * Bind: binds css in scope with own scope create/destroy events + **/ + $css.bind = function (css, $scope) { + if (!css || !$scope) { + return $log.error('No scope or stylesheets provided'); + } + var result = []; + // Adds route css rules to array + if (angular.isArray(css)) { + angular.forEach(css, function (cssItem) { + result.push(parse(cssItem)); + }); + } else { + result.push(parse(css)); + } + $css.add(result); + $log.debug('$css.bind(): Added', result); + $scope.$on('$destroy', function () { + $css.remove(result); + $log.debug('$css.bind(): Removed', result); + }); + }; + + /** + * Add: adds stylesheets to scope + **/ + $css.add = function (stylesheets, callback) { + if (!stylesheets) { + return $log.error('No stylesheets provided'); + } + if (!angular.isArray(stylesheets)) { + stylesheets = [stylesheets]; + } + angular.forEach(stylesheets, function (stylesheet) { + stylesheet = parse(stylesheet); + // Avoid adding duplicate stylesheets + if (stylesheet.href && !$filter('filter')($rootScope.stylesheets, {href: stylesheet.href}).length) { + // Bust Cache feature + bustCache(stylesheet) + // Media Query add support check + if (isMediaQuery(stylesheet)) { + addViaMediaQuery(stylesheet); + } + else { + $rootScope.stylesheets.push(stylesheet); + } + $log.debug('$css.add(): ' + stylesheet.href); + } + }); + // Broadcasts custom event for css add + $rootScope.$broadcast('$cssAdd', stylesheets, $rootScope.stylesheets); + }; + + /** + * Remove: removes stylesheets from scope + **/ + $css.remove = function (stylesheets, callback) { + if (!stylesheets) { + return $log.error('No stylesheets provided'); + } + if (!angular.isArray(stylesheets)) { + stylesheets = [stylesheets]; + } + // Only proceed based on persist setting + stylesheets = $filter('filter')(stylesheets, function (stylesheet) { + return !stylesheet.persist; + }); + angular.forEach(stylesheets, function (stylesheet) { + stylesheet = parse(stylesheet); + // Get index of current item to be removed based on href + var index = $rootScope.stylesheets.indexOf($filter('filter')($rootScope.stylesheets, { + href: stylesheet.href + })[0]); + // Remove stylesheet from scope (if found) + if (index !== -1) { + $rootScope.stylesheets.splice(index, 1); + } + // Remove stylesheet via media query + removeViaMediaQuery(stylesheet); + $log.debug('$css.remove(): ' + stylesheet.href); + }); + // Broadcasts custom event for css remove + $rootScope.$broadcast('$cssRemove', stylesheets, $rootScope.stylesheets); + }; + + /** + * Remove All: removes all style tags from the DOM + **/ + $css.removeAll = function () { + // Remove all stylesheets from scope + if ($rootScope && $rootScope.hasOwnProperty('stylesheets')) { + $rootScope.stylesheets.length = 0; + } + $log.debug('all stylesheets removed'); + }; + + // Preload all stylesheets + $css.preload(); + + return $css; + + }]; + +} diff --git a/src/$cssLinks-filter.js b/src/$cssLinks-filter.js new file mode 100644 index 0000000..9c2344a --- /dev/null +++ b/src/$cssLinks-filter.js @@ -0,0 +1,17 @@ +/** + * Links filter - renders the stylesheets array in html format + **/ +function $cssLinksFilter() { + return function $cssLinks(stylesheets) { + if (!stylesheets || !angular.isArray(stylesheets)) { + return stylesheets; + } + var result = ''; + angular.forEach(stylesheets, function (stylesheet) { + result += ' Date: Fri, 20 Feb 2015 06:54:49 -0500 Subject: [PATCH 2/2] Added auto-annotation for min-safe deps Use ngAnnotate During Build for Min Safe #20 Automatically min-safe definitions using grunt-ng-annotate This removes necessity for grunt-contrib-concat so it is also removed --- Gruntfile.js | 37 ++++++++++++++++++++----------------- angular-css.js | 3 +-- package.json | 5 +++-- src/$css-provider.js | 5 ++--- src/angularCSS-module.js | 6 +++--- 5 files changed, 29 insertions(+), 27 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 31d7b01..f1cf03e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -2,26 +2,13 @@ module.exports = function(grunt) { var npmTasks = [ - 'grunt-contrib-concat', 'grunt-contrib-uglify', - 'grunt-karma' + 'grunt-karma', + 'grunt-ng-annotate' ]; grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), - concat: { - dist: { - src: [ - 'src/prefix.js', - 'src/$css-provider.js', - 'src/$cssLinks-filter.js', - 'src/angularCSS-module.js', - 'src/angularHack.js', - 'src/suffix.js' - ], - dest: 'angular-css.js' - } - }, karma: { unit: { options: { @@ -45,7 +32,23 @@ module.exports = function(grunt) { singleRun: true } }, - + ngAnnotate: { + options: { + singleQuotes: true + }, + angularCss: { + files: { + 'angular-css.js': [ + 'src/prefix.js', + 'src/$css-provider.js', + 'src/$cssLinks-filter.js', + 'src/angularCSS-module.js', + 'src/angularHack.js', + 'src/suffix.js' + ] + } + } + }, uglify: { options: { banner: '/*! <%= pkg.name %> <%= pkg.version %> | Copyright (c) <%= grunt.template.today("yyyy") %> DOOR3, Alex Castillo | MIT License */' @@ -65,7 +68,7 @@ module.exports = function(grunt) { grunt.registerTask('test', ['karma']); grunt.registerTask('default', [ - 'concat', + 'ngAnnotate', 'test', 'uglify' ]); diff --git a/angular-css.js b/angular-css.js index 4fa3435..57692ee 100644 --- a/angular-css.js +++ b/angular-css.js @@ -22,8 +22,7 @@ function $cssProvider() { weight: 0 }; - this.$get = ['$rootScope', '$injector', '$q', '$window', '$timeout', '$compile', '$http', '$filter', '$log', - function $get($rootScope, $injector, $q, $window, $timeout, $compile, $http, $filter, $log) { + this.$get = /** @ngInject */ ['$rootScope', '$injector', '$q', '$window', '$timeout', '$compile', '$http', '$filter', '$log', function $get($rootScope, $injector, $q, $window, $timeout, $compile, $http, $filter, $log) { var $css = {}; diff --git a/package.json b/package.json index 66f3eca..613bd88 100644 --- a/package.json +++ b/package.json @@ -56,12 +56,13 @@ "chai": "^2.0.0", "grunt": "~0.4.1", "grunt-cli": "~0.1.11", - "grunt-contrib-concat": "~0.5.1", "grunt-contrib-uglify": "~0.2.4", "grunt-karma": "~0.7.1", + "grunt-ng-annotate": "^0.10.0", "karma-chrome-launcher": "0.1.7", "karma-firefox-launcher": "0.1.4", + "karma-mocha": "~0.1.0", "karma-phantomjs-launcher": "0.1.4", - "karma-mocha": "~0.1.0" + "ng-annotate": "^0.15.4" } } diff --git a/src/$css-provider.js b/src/$css-provider.js index e208ce7..0faf59d 100644 --- a/src/$css-provider.js +++ b/src/$css-provider.js @@ -10,8 +10,7 @@ function $cssProvider() { weight: 0 }; - this.$get = ['$rootScope', '$injector', '$q', '$window', '$timeout', '$compile', '$http', '$filter', '$log', - function $get($rootScope, $injector, $q, $window, $timeout, $compile, $http, $filter, $log) { + this.$get = /** @ngInject */ function $get($rootScope, $injector, $q, $window, $timeout, $compile, $http, $filter, $log) { var $css = {}; @@ -506,6 +505,6 @@ function $cssProvider() { return $css; - }]; + }; } diff --git a/src/angularCSS-module.js b/src/angularCSS-module.js index f8c5774..9664317 100644 --- a/src/angularCSS-module.js +++ b/src/angularCSS-module.js @@ -1,12 +1,12 @@ angular .module('door3.css', []) - .config(['$logProvider', function ($logProvider) { + .config(function ($logProvider) { // Turn off/on in order to see console logs during dev mode $logProvider.debugEnabled(false); - }]) + }) /** * Run - auto instantiate the $css provider by injecting it in the run phase of this module **/ - .run(['$css', function ($css) { }]) + .run(function ($css) { }) .provider('$css', [$cssProvider]) .filter('$cssLinks', $cssLinksFilter);