diff --git a/Gruntfile.js b/Gruntfile.js index 8f13608..f1cf03e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,9 +1,14 @@ 'use strict'; module.exports = function(grunt) { + var npmTasks = [ + 'grunt-contrib-uglify', + 'grunt-karma', + 'grunt-ng-annotate' + ]; + grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), - karma: { unit: { options: { @@ -27,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 */' @@ -40,12 +61,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', [ + 'ngAnnotate', 'test', 'uglify' ]); diff --git a/angular-css.js b/angular-css.js index 26f4f61..57692ee 100644 --- a/angular-css.js +++ b/angular-css.js @@ -10,33 +10,19 @@ (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 = /** @ngInject */ ['$rootScope', '$injector', '$q', '$window', '$timeout', '$compile', '$http', '$filter', '$log', function $get($rootScope, $injector, $q, $window, $timeout, $compile, $http, $filter, $log) { var $css = {}; @@ -105,7 +91,7 @@ stylesheet.media = options.breakpoints[stylesheet.breakpoint]; } delete stylesheet.breakpoints; - } + } } /** @@ -199,7 +185,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 +232,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 +335,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 +399,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 +418,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 +437,7 @@ $css.remove(result); $log.debug('$css.bind(): Removed', result); }); - }; + }; /** * Add: adds stylesheets to scope @@ -463,10 +449,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 +483,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 +519,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..613bd88 100644 --- a/package.json +++ b/package.json @@ -51,10 +51,18 @@ "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-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", - "grunt-cli": "~0.1.11" + "karma-phantomjs-launcher": "0.1.4", + "ng-annotate": "^0.15.4" } } diff --git a/src/$css-provider.js b/src/$css-provider.js new file mode 100644 index 0000000..0faf59d --- /dev/null +++ b/src/$css-provider.js @@ -0,0 +1,510 @@ +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 = /** @ngInject */ 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 += '