diff --git a/.gitignore b/.gitignore index c0e499a..da35d0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # IDE files .idea -# Dependency directory -bower_components/ \ No newline at end of file +# Dependencies +bower_components/ +node_modules/ \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..5cd057c --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,67 @@ +module.exports = function(grunt) { + "use strict"; + + // Project configuration. + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + banner: + '/*! angular-adaptive v<%= pkg.version %>: <%= pkg.description %>\n' + + ' * Copyright <%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' + + ' * Licensed under <%= pkg.license %>\n' + + ' * <%= pkg.homepage %>' + + ' */\n\n', + uglify: { + nonMinMatchMediaListener: { + options: { + banner: '<%= banner %>', + mangle: false, + compress: false, + preserveComments: 'some', + beautify: { + beautify: true, + indent_level: 2 + } + }, + files: { + 'dest/adaptive.src.js': ['src/matchMedia.js', 'src/matchMedia.addListener.js', 'src/adaptive.js'] + } + }, + minMatchMediaListener: { + options: { + banner: '<%= banner %>' + }, + files: { + 'dest/adaptive.min.js': ['src/matchMedia.js', 'src/matchMedia.addListener.js', 'src/adaptive.js'] + } + } + }, + jshint: { + files: ['src/adaptive.js'], + options: { + curly: true, + eqeqeq: true, + immed: true, + latedef: false, + newcap: true, + noarg: true, + sub: true, + undef: true, + boss: true, + eqnull: true, + smarttabs: true, + node: true, + strict: false, + globals: { + angular: false + } + } + } + }); + + grunt.loadNpmTasks( 'grunt-contrib-jshint' ); + grunt.loadNpmTasks( 'grunt-contrib-uglify' ); + + // Default task. + grunt.registerTask('default', ['jshint', 'uglify']); + +}; diff --git a/README.md b/README.md index 6dbf848..4b7c48a 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,83 @@ # angular-adaptive A hybrid between adaptive and responsive templating. Provides a directive to dynamically load device specific templates in a responsive manner. +## Installation +* Install as a [bower](http://bower.io) dependency using: +``` +bower install angular-adaptive --save +``` +* Include the supplied `angular-adaptive.min.js` script to you app. +```html + +``` +* Add `'angular-adaptive'` as a module dependency in your angular app. +```javascript +angular.module('someApp', ['angular-adaptive']); +``` +### Configuration +The **angular-adaptive** can be configured using `adaptiveConfigProvider` in the **config** of your **angular** app module. +```javascript +angular + .module('someApp', [ + 'angular-adaptive' + ]) + .config(function(adaptiveConfigProvider) { + adaptiveConfigProvider.config({ + devices: [ + { + name: "large", + mediaQuery: "screen and (min-width: 1140px)" + }, + { + name: "medium", + mediaQuery: "screen and (min-width: 768px) and (max-width: 1280px)" + }, + { + name: "small", + mediaQuery: "screen and (max-width: 767px)" + } + ] + }); + }); +``` +#### Defaults +```javascript +devices: [ + { + name: "desktop", + mediaQuery: "screen and (min-width: 768px)" + }, + { + name: "mobile", + mediaQuery: "screen and (max-width: 767px)" + } +] +``` +## Usage +By default, **angular-adaptive** provides two device configurations (`desktop` and `mobile`) based on the screen widths (See [Defaults](#Defaults)). The device names can be used as the attributes of the `adaptive` directive to provide the respective template file to render when the `mediaQuery` of a certain device is matched. +```html + +``` +# Support +This script uses the [matchMedia](https://github.com/paulirish/matchMedia.js) polyfill to suppport older versions of Internet Explorer. +# License +The MIT License (MIT) + +Copyright (c) 2016 Junaid Ilyas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bower.json b/bower.json index f3fd629..c917bbe 100644 --- a/bower.json +++ b/bower.json @@ -4,7 +4,7 @@ "Junaid Ilyas " ], "description": "A hybrid between adaptive and responsive templating. Provides a directive to dynamically load device specific templates in a responsive manner.", - "main": "angular-adaptive.js", + "main": "dest/adaptive.min.js", "moduleType": [ "globals" ], @@ -15,10 +15,11 @@ "templates" ], "license": "MIT", - "homepage": "https://github.com/junaidilyas/angular-adaptive", + "homepage": "https://junaidilyas.github.io/angular-adaptive", "ignore": [ "**/.*", - "bower_components" + "bower_components", + "node_modules" ], "dependencies": { "angular": "1.1.x - *" diff --git a/package.json b/package.json new file mode 100644 index 0000000..0dd3902 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "angular-adaptive", + "version": "0.1.0", + "description": "A hybrid between adaptive and responsive templating. Provides a directive to dynamically load device specific templates in a responsive manner.", + "main": "dest/adaptive.min.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/junaidilyas/angular-adaptive.git" + }, + "keywords": [ + "angular", + "adaptive", + "responsive", + "templates" + ], + "author": "Junaid Ilyas ", + "homepage": "https://junaidilyas.github.io/angular-adaptive", + "license": "MIT", + "bugs": { + "url": "https://github.com/junaidilyas/angular-adaptive/issues" + }, + "devDependencies": { + "grunt": "^0.4.5", + "grunt-cli": "^0.1.13", + "grunt-contrib-jshint": "^0.12.0", + "grunt-contrib-uglify": "^0.11.0" + } +} diff --git a/angular-adaptive.js b/src/adaptive.js similarity index 65% rename from angular-adaptive.js rename to src/adaptive.js index 061b913..5450b2d 100644 --- a/angular-adaptive.js +++ b/src/adaptive.js @@ -1,6 +1,13 @@ (function () { 'use strict'; + /** + * angular-adaptive - A hybrid between adaptive and responsive templating. + * @version v0.1.0 + * @link https://junaidilyas.github.io/angular-adaptive + * @author Junaid Ilyas + * @license MIT License, http://www.opensource.org/licenses/MIT + */ var module = angular.module('angular-adaptive', []); module.directive('adaptive', function ($compile, $window, adaptiveConfig) { @@ -25,11 +32,20 @@ } }; - var mql = $window.matchMedia(device.mediaQuery); - mql.addListener(device.loadTemplate); - device.loadTemplate(mql); + device.mql = $window.matchMedia(device.mediaQuery); + device.mql.addListener(device.loadTemplate); + device.loadTemplate(device.mql); } }); + + // remove all listeners on element destroy. + element.on('$destroy', function() { + angular.forEach(devices, function(device) { + if (device.mql) { + device.mql.removeListener(device.loadTemplate); + } + }); + }); }; return { @@ -59,6 +75,6 @@ $get: function() { return opts; } - } + }; }); })(); diff --git a/src/matchMedia.addListener.js b/src/matchMedia.addListener.js new file mode 100644 index 0000000..b5a3a1c --- /dev/null +++ b/src/matchMedia.addListener.js @@ -0,0 +1,75 @@ +/*! matchMedia() polyfill addListener/removeListener extension. Author & copyright (c) 2012: Scott Jehl. Dual MIT/BSD license */ +(function(){ + // Bail out for browsers that have addListener support + if (window.matchMedia && window.matchMedia('all').addListener) { + return false; + } + + var localMatchMedia = window.matchMedia, + hasMediaQueries = localMatchMedia('only all').matches, + isListening = false, + timeoutID = 0, // setTimeout for debouncing 'handleChange' + queries = [], // Contains each 'mql' and associated 'listeners' if 'addListener' is used + handleChange = function(evt) { + // Debounce + clearTimeout(timeoutID); + + timeoutID = setTimeout(function() { + for (var i = 0, il = queries.length; i < il; i++) { + var mql = queries[i].mql, + listeners = queries[i].listeners || [], + matches = localMatchMedia(mql.media).matches; + + // Update mql.matches value and call listeners + // Fire listeners only if transitioning to or from matched state + if (matches !== mql.matches) { + mql.matches = matches; + + for (var j = 0, jl = listeners.length; j < jl; j++) { + listeners[j].call(window, mql); + } + } + } + }, 30); + }; + + window.matchMedia = function(media) { + var mql = localMatchMedia(media), + listeners = [], + index = 0; + + mql.addListener = function(listener) { + // Changes would not occur to css media type so return now (Affects IE <= 8) + if (!hasMediaQueries) { + return; + } + + // Set up 'resize' listener for browsers that support CSS3 media queries (Not for IE <= 8) + // There should only ever be 1 resize listener running for performance + if (!isListening) { + isListening = true; + window.addEventListener('resize', handleChange, true); + } + + // Push object only if it has not been pushed already + if (index === 0) { + index = queries.push({ + mql : mql, + listeners : listeners + }); + } + + listeners.push(listener); + }; + + mql.removeListener = function(listener) { + for (var i = 0, il = listeners.length; i < il; i++){ + if (listeners[i] === listener){ + listeners.splice(i, 1); + } + } + }; + + return mql; + }; +}()); diff --git a/src/matchMedia.js b/src/matchMedia.js new file mode 100644 index 0000000..803f626 --- /dev/null +++ b/src/matchMedia.js @@ -0,0 +1,46 @@ +/*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas, David Knight. Dual MIT/BSD license */ + +window.matchMedia || (window.matchMedia = function() { + "use strict"; + + // For browsers that support matchMedium api such as IE 9 and webkit + var styleMedia = (window.styleMedia || window.media); + + // For those that don't support matchMedium + if (!styleMedia) { + var style = document.createElement('style'), + script = document.getElementsByTagName('script')[0], + info = null; + + style.type = 'text/css'; + style.id = 'matchmediajs-test'; + + script.parentNode.insertBefore(style, script); + + // 'style.currentStyle' is used by IE <= 8 and 'window.getComputedStyle' for all other browsers + info = ('getComputedStyle' in window) && window.getComputedStyle(style, null) || style.currentStyle; + + styleMedia = { + matchMedium: function(media) { + var text = '@media ' + media + '{ #matchmediajs-test { width: 1px; } }'; + + // 'style.styleSheet' is used by IE <= 8 and 'style.textContent' for all other browsers + if (style.styleSheet) { + style.styleSheet.cssText = text; + } else { + style.textContent = text; + } + + // Test if media query is true or false + return info.width === '1px'; + } + }; + } + + return function(media) { + return { + matches: styleMedia.matchMedium(media || 'all'), + media: media || 'all' + }; + }; +}());