diff --git a/README.md b/README.md index 3edf08b..913e1d5 100644 --- a/README.md +++ b/README.md @@ -222,20 +222,58 @@ AngularCSS supports "smart media queries". This means that stylesheets with medi This will significantly optimize the load time of your apps. ```js -css: [ - { - href: 'my-page/my-page.mobile.css', - media: 'screen and (max-width: 480px)' - }, { - href: 'my-page/my-page.tablet.css', - media: 'screen and (min-width: 768px) and (max-width: 1024px)' - }, { - href: 'my-page/my-page.desktop.css', - media: 'screen and (min-width: 1224px)' - } -] +$routeProvider + .when('/my-page', { + templateUrl: 'my-page/my-page.html', + css: [ + { + href: 'my-page/my-page.mobile.css', + media: '(max-width: 480px)' + }, { + href: 'my-page/my-page.tablet.css', + media: '(min-width: 768px) and (max-width: 1024px)' + }, { + href: 'my-page/my-page.desktop.css', + media: '(min-width: 1224px)' + } + ] + }); ``` +Even though you can use the `media` property to specify media queries, the best way to manage your breakpoins is by settings them in the provider's defaults. For example: + +```js +myApp.config(function($routeProvider, $cssProvider) { + + angular.extend($cssProvider.defaults, { + breakpoints: { + mobile: '(max-width: 480px)', + tablet: '(min-width: 768px) and (max-width: 1024px)', + desktop: '(min-width: 1224px)' + } + }); + + $routeProvider + .when('/my-page', { + templateUrl: 'my-page/my-page.html', + css: [ + { + href: 'my-page/my-page.mobile.css', + breakpoint: 'mobile' + }, { + href: 'my-page/my-page.tablet.css', + breakpoint: 'tablet' + }, { + href: 'my-page/my-page.desktop.css', + breakpoint: 'desktop' + } + ] + }); + +}); +``` + + ### Config You can configure AngularCSS at the global level or at the stylesheet level. diff --git a/angular-css.js b/angular-css.js index 4969756..26f4f61 100644 --- a/angular-css.js +++ b/angular-css.js @@ -1,6 +1,6 @@ /** * AngularCSS - CSS on-demand for AngularJS - * @version v1.0.6 + * @version v1.0.7 * @author DOOR3, Alex Castillo * @link http://door3.github.io/angular-css * @license MIT License, http://www.opensource.org/licenses/MIT @@ -35,8 +35,8 @@ weight: 0 }; - this.$get = ['$rootScope','$injector','$window','$timeout','$compile','$http','$filter','$log', - function $get($rootScope, $injector, $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 = {}; @@ -44,7 +44,8 @@ // 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]); + container = angular.element(document.querySelector ? document.querySelector(options.container) : document.getElementsByTagName(options.container)[0]), + dynamicPaths = []; // Parse all directives angular.forEach($directives, function (directive, key) { @@ -69,7 +70,9 @@ function $routeEventListener(event, current, prev) { // Removes previously added css rules if (prev) { - $css.remove($css.getFromRoute(prev)); + $css.remove($css.getFromRoute(prev).concat(dynamicPaths)); + // Reset dynamic paths array + dynamicPaths.length = 0; } // Adds current css rules if (current) { @@ -83,7 +86,9 @@ function $stateEventListener(event, current, params, prev) { // Removes previously added css rules if (prev) { - $css.remove($css.getFromState(prev)); + $css.remove($css.getFromState(prev).concat(dynamicPaths)); + // Reset dynamic paths array + dynamicPaths.length = 0; } // Adds current css rules if (current) { @@ -91,6 +96,18 @@ } } + /** + * 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 **/ @@ -98,6 +115,10 @@ if (!obj) { return; } + // Function syntax + if (angular.isFunction(obj)) { + obj = angular.copy($injector.invoke(obj)); + } // String syntax if (angular.isString(obj)) { obj = angular.extend({ @@ -122,6 +143,8 @@ obj = angular.extend(item, options); }); } + // Map breakpoint to media attribute + mapBreakpointToMedia(obj); return obj; } @@ -135,10 +158,10 @@ $rootScope.$on('$directiveAdd', $directiveAddEventListener); // Routes event listener ($route required) - $rootScope.$on('$routeChangeStart', $routeEventListener); + $rootScope.$on('$routeChangeSuccess', $routeEventListener); // States event listener ($state required) - $rootScope.$on('$stateChangeStart', $stateEventListener); + $rootScope.$on('$stateChangeSuccess', $stateEventListener); /** * Bust Cache @@ -249,10 +272,16 @@ if (css) { if (angular.isArray(css)) { angular.forEach(css, function (cssItem) { + if (angular.isFunction(cssItem)) { + dynamicPaths.push(parse(cssItem)); + } result.push(parse(cssItem)); }); } else { - result.push(parse(css)); + if (angular.isFunction(css)) { + dynamicPaths.push(parse(css)); + } + result.push(parse(css)); } } return result; @@ -288,6 +317,9 @@ 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)); } }); @@ -296,11 +328,17 @@ 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)); } }); @@ -312,10 +350,16 @@ // 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)); } } @@ -363,21 +407,26 @@ if ($injector.has('$state')) { Array.prototype.push.apply(stylesheets, $css.getFromStates($injector.get('$state').get())); } + stylesheets = filterBy(stylesheets, 'preload'); } - stylesheets = filterBy(stylesheets, 'preload'); - angular.forEach(stylesheets, function(stylesheet, index) { - // Preload via ajax request - $http.get(stylesheet.href) - .success(function (response) { - $log.debug('preload response: ' + response); - if (stylesheets.length === (index + 1) && angular.isFunction(callback)) { - callback(stylesheets); - } + 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); }) - .error(function (response) { - $log.error('Incorrect path for ' + stylesheet.href); - }); + ); }); + if (angular.isFunction(callback)) { + $q.all(stylesheetLoadPromises).then(function () { + callback(stylesheets); + }); + } }; /** diff --git a/angular-css.min.js b/angular-css.min.js index 9d4bb90..07f2f84 100644 --- a/angular-css.min.js +++ b/angular-css.min.js @@ -1 +1 @@ -"use strict";!function(e){var r=e.module("door3.css",[]);r.config(["$logProvider",function(e){e.debugEnabled(!1)}]),r.provider("$css",[function(){var r=this.defaults={element:"link",rel:"stylesheet",type:"text/css",container:"head",method:"append",weight:0};this.$get=["$rootScope","$injector","$window","$timeout","$compile","$http","$filter","$log",function(s,n,o,i,c,u,a,f){function h(e,r,t){t&&r.hasOwnProperty("css")&&b.bind([p(r.css)],t)}function d(e,r,t){t&&b.remove(b.getFromRoute(t)),r&&b.add(b.getFromRoute(r))}function l(e,r,t,s){s&&b.remove(b.getFromState(s)),r&&b.add(b.getFromState(r))}function p(r){return r?(e.isString(r)&&(r=e.extend({href:r},N)),e.isArray(r)&&e.isString(r[0])&&e.forEach(r,function(t){r=e.extend({href:t},N)}),e.isObject(r)&&!e.isArray(r)&&(r=e.extend(r,N)),e.isArray(r)&&e.isObject(r[0])&&e.forEach(r,function(t){r=e.extend(t,N)}),r):void 0}function y(e){if(!e)return f.error("No stylesheets provided");var r="?cache=";-1===e.href.indexOf(r)&&(e.href=e.href+(e.bustCache?r+(new Date).getTime():""))}function m(e,r){return e&&r?a("filter")(e,function(e){return e[r]}):f.error("filterBy: missing array or property")}function v(e){return e?(F[e.href]=o.matchMedia(e.media),A[e.href]=function(r){i(function(){if(r.matches)s.stylesheets.push(e);else{var t=s.stylesheets.indexOf(a("filter")(s.stylesheets,{href:e.href})[0]);-1!==t&&s.stylesheets.splice(t,1)}})},F[e.href].addListener(A[e.href]),void A[e.href](F[e.href])):f.error("No stylesheet provided")}function g(r){return r?void(s&&e.isDefined(F)&&F[r.href]&&e.isDefined(A)&&F[r.href].removeListener(A[r.href])):f.error("No stylesheet provided")}function $(e){return e?!(!e.media||-1!==S.indexOf(e.media)||!o.matchMedia):f.error("No stylesheet provided")}var b={},E='',F={},A={},S=["print"],N=e.extend({},r),w=e.element(document.querySelector?document.querySelector(N.container):document.getElementsByTagName(N.container)[0]);return e.forEach(t,function(e,r){e.hasOwnProperty("css")&&(t[r]=p(e.css))}),s.stylesheets=[],w[N.method](c(E)(s)),s.$on("$directiveAdd",h),s.$on("$routeChangeStart",d),s.$on("$stateChangeStart",l),b.getFromRoute=function(r){if(!r)return f.error("Get From Route: No route provided");var t=null,s=[];return r.$$route&&r.$$route.css?t=r.$$route.css:r.css&&(t=r.css),t&&(e.isArray(t)?e.forEach(t,function(e){s.push(p(e))}):s.push(p(t))),s},b.getFromRoutes=function(r){if(!r)return f.error("Get From Routes: No routes provided");var t=[];return e.forEach(r,function(e){var r=b.getFromRoute(e);r.length&&t.push(r[0])}),t},b.getFromState=function(r){if(!r)return f.error("Get From State: No state provided");var t=[];return e.isDefined(r.views)&&e.forEach(r.views,function(e){e.css&&t.push(p(e.css))}),e.isDefined(r.children)&&e.forEach(r.children,function(r){r.css&&t.push(p(r.css)),e.isDefined(r.children)&&e.forEach(r.children,function(e){e.css&&t.push(p(e.css))})}),e.isDefined(r.css)&&(e.isArray(r.css)?e.forEach(r.css,function(e){t.push(p(e))}):t.push(p(r.css))),t},b.getFromStates=function(r){if(!r)return f.error("Get From States: No states provided");var t=[];return e.forEach(r,function(r){var s=b.getFromState(r);e.isArray(s)?e.forEach(s,function(e){t.push(e)}):t.push(s)}),t},b.preload=function(r,s){r||(r=[],t.length&&Array.prototype.push.apply(r,t),n.has("$route")&&Array.prototype.push.apply(r,b.getFromRoutes(n.get("$route").routes)),n.has("$state")&&Array.prototype.push.apply(r,b.getFromStates(n.get("$state").get()))),r=m(r,"preload"),e.forEach(r,function(t,n){u.get(t.href).success(function(t){f.debug("preload response: "+t),r.length===n+1&&e.isFunction(s)&&s(r)}).error(function(){f.error("Incorrect path for "+t.href)})})},b.bind=function(r,t){if(!r||!t)return f.error("No scope or stylesheets provided");var s=[];e.isArray(r)?e.forEach(r,function(e){s.push(p(e))}):s.push(p(r)),b.add(s),f.debug("$css.bind(): Added",s),t.$on("$destroy",function(){b.remove(s),f.debug("$css.bind(): Removed",s)})},b.add=function(r){return r?(e.isArray(r)||(r=[r]),e.forEach(r,function(e){e=p(e),e.href&&!a("filter")(s.stylesheets,{href:e.href}).length&&(y(e),$(e)?v(e):s.stylesheets.push(e),f.debug("$css.add(): "+e.href))}),void s.$broadcast("$cssAdd",r,s.stylesheets)):f.error("No stylesheets provided")},b.remove=function(r){return r?(r=a("filter")(r,function(e){return!e.persist}),e.forEach(r,function(e){var r=s.stylesheets.indexOf(a("filter")(s.stylesheets,{href:e.href})[0]);-1!==r&&s.stylesheets.splice(r,1),g(e),f.debug("$css.remove(): "+e.href)}),void s.$broadcast("$cssRemove",r,s.stylesheets)):f.error("No stylesheets provided")},b.removeAll=function(){s&&s.hasOwnProperty("stylesheets")&&(s.stylesheets.length=0),f.debug("all stylesheets removed")},b.preload(),b}]}]),r.filter("$cssLinks",function(){return function(r){if(!r||!e.isArray(r))return r;var t="";return e.forEach(r,function(e){t+='',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+='" diff --git a/package.json b/package.json index 62bb600..d91c523 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "angular-css", "filename": "angular-css.js", "description": "CSS on-demand for AngularJS", - "version": "1.0.6", + "version": "1.0.7", "homepage": "http://door3.github.io/angular-css", "repository": { "type": "git",