├── .gitignore ├── .npmrc ├── .travis.yml ├── Gruntfile.js ├── bower.json ├── dist ├── angular-pageslide-directive.js └── angular-pageslide-directive.min.js ├── examples ├── angular-pageslide-directive.js └── index.html ├── karma.conf.js ├── package-lock.json ├── package.json ├── readme.md ├── src └── angular-pageslide-directive.js └── test └── angular-pageslide-directive.spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | node_modules/ 3 | reports/ 4 | coverage/ 5 | *.swp 6 | .idea/** 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=true 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | before_install: npm install -g grunt-cli; 5 | install: npm install; 6 | before_script: grunt default 7 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | pkg: grunt.file.readJSON('package.json'), 5 | 6 | concat: { 7 | dist :{ 8 | src: ['src/*.js'], 9 | dest: 'dist/<%= pkg.name %>.js' 10 | } 11 | }, 12 | 13 | jshint: { 14 | files: ['src/*.js'] 15 | }, 16 | 17 | uglify: { 18 | options: { 19 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n' 20 | }, 21 | dist: { 22 | src: 'dist/<%= pkg.name %>.js', 23 | dest: 'dist/<%= pkg.name %>.min.js' 24 | } 25 | }, 26 | 27 | karma: { 28 | unit: { 29 | options: { 30 | configFile: 'karma.conf.js', 31 | runnerPort: 9999, 32 | singleRun: true, 33 | browsers: ['PhantomJS'], 34 | logLevel: 'ERROR' 35 | } 36 | } 37 | } 38 | 39 | }); 40 | 41 | // Load the plugin that provides the "uglify" task. 42 | grunt.loadNpmTasks('grunt-contrib-jshint'); 43 | grunt.loadNpmTasks('grunt-contrib-concat'); 44 | grunt.loadNpmTasks('grunt-contrib-uglify'); 45 | 46 | grunt.loadNpmTasks('grunt-karma'); 47 | 48 | // Default task(s). 49 | grunt.registerTask('default', ['jshint','concat','uglify']); 50 | 51 | // Test 52 | grunt.registerTask('test', ['karma']); 53 | 54 | }; 55 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-pageslide-directive", 3 | "main": "dist/angular-pageslide-directive.js", 4 | "authors": [ 5 | "Daniele Piccone (http://www.danielepiccone.com)", 6 | "Tim Borny " 7 | ], 8 | "license": "MIT", 9 | "repository": { 10 | "type": "git", 11 | "url": "git@github.com:dpiccone/ng-pageslide.git" 12 | }, 13 | "dependencies": { 14 | "angular": "1.x" 15 | }, 16 | "devDependencies": { 17 | "angular": "1.x", 18 | "angular-mocks": "1.x", 19 | "grunt": "^1.0.2", 20 | "grunt-cli": "^1.2.0", 21 | "grunt-contrib-concat": "^1.0.1", 22 | "grunt-contrib-jshint": "^1.1.0", 23 | "grunt-contrib-uglify": "^3.3.0", 24 | "grunt-karma": "^2.0.0", 25 | "http-server": "^0.11.1", 26 | "jasmine-core": "^3.1.0", 27 | "karma": "^2.0.0", 28 | "karma-coverage": "^1.1.1", 29 | "karma-coveralls": "^1.1.2", 30 | "karma-jasmine": "^1.1.1", 31 | "karma-phantomjs-launcher": "^1.0.4" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /dist/angular-pageslide-directive.js: -------------------------------------------------------------------------------- 1 | 2 | (function (root, factory) { 3 | if (typeof define === 'function' && define.amd) { 4 | define(['angular'], factory); 5 | } else if (typeof module === 'object' && module.exports) { 6 | module.exports = factory(require('angular')); 7 | } else { 8 | factory(root.angular); 9 | } 10 | }(this, function (angular) { 11 | 12 | angular 13 | .module('pageslide-directive', []) 14 | .directive('pageslide', ['$document', '$timeout', function ($document, $timeout) { 15 | var defaults = {}; 16 | 17 | 18 | return { 19 | restrict: 'EA', 20 | transclude: false, 21 | scope: { 22 | psOpen: '=?', 23 | psAutoClose: '@', 24 | psSide: '@', 25 | psSpeed: '@', 26 | psClass: '@', 27 | psSize: '@', 28 | psZindex: '@', 29 | psPush: '@', 30 | psContainer: '@', 31 | psKeyListener: '@', 32 | psBodyClass: '@', 33 | psClickOutside: '@', 34 | onopen: '&?', 35 | onclose: '&?' 36 | }, 37 | link: function (scope, el, attrs) { 38 | 39 | var param = {}; 40 | 41 | param.side = scope.psSide || 'right'; 42 | param.speed = scope.psSpeed || '0.5'; 43 | param.size = scope.psSize || '300px'; 44 | param.zindex = scope.psZindex || 1000; 45 | param.className = scope.psClass || 'ng-pageslide'; 46 | param.push = scope.psPush === 'true'; 47 | param.container = scope.psContainer || false; 48 | param.keyListener = scope.psKeyListener === 'true'; 49 | param.bodyClass = scope.psBodyClass || false; 50 | param.clickOutside = scope.psClickOutside !== 'false'; 51 | param.autoClose = scope.psAutoClose || false; 52 | 53 | param.push = param.push && !param.container; 54 | 55 | el.addClass(param.className); 56 | 57 | /* DOM manipulation */ 58 | 59 | var content, slider, body, isOpen = false; 60 | 61 | if (param.container) { 62 | body = document.getElementById(param.container); 63 | } else { 64 | body = document.body; 65 | } 66 | 67 | function onBodyClick(e) { 68 | var target = e.touches && e.touches[0] || e.target; 69 | if( 70 | isOpen && 71 | body.contains(target) && 72 | !slider.contains(target) 73 | ) { 74 | isOpen = false; 75 | scope.psOpen = false; 76 | scope.$apply(); 77 | } 78 | 79 | if(scope.psOpen) { 80 | isOpen = true; 81 | } 82 | } 83 | 84 | function setBodyClass(value){ 85 | if (param.bodyClass) { 86 | var bodyClass = param.className + '-body'; 87 | var bodyClassRe = new RegExp(bodyClass + '-closed|' + bodyClass + '-open'); 88 | body.className = body.className.replace(bodyClassRe, ''); 89 | var newBodyClassName = bodyClass + '-' + value; 90 | if (body.className[body.className.length -1] !== ' ') { 91 | body.className += ' ' + newBodyClassName; 92 | } else { 93 | body.className += newBodyClassName; 94 | } 95 | } 96 | } 97 | 98 | slider = el[0]; 99 | 100 | if (slider.tagName.toLowerCase() !== 'div' && 101 | slider.tagName.toLowerCase() !== 'pageslide') { 102 | throw new Error('Pageslide can only be applied to
or elements'); 103 | } 104 | 105 | if (slider.children.length === 0) { 106 | throw new Error('You need to have content inside the '); 107 | } 108 | 109 | content = angular.element(slider.children); 110 | 111 | body.appendChild(slider); 112 | 113 | slider.style.zIndex = param.zindex; 114 | slider.style.position = 'fixed'; 115 | slider.style.transitionDuration = param.speed + 's'; 116 | slider.style.webkitTransitionDuration = param.speed + 's'; 117 | slider.style.height = param.size; 118 | slider.style.transitionProperty = 'top, bottom, left, right'; 119 | 120 | if (param.push) { 121 | body.style.position = 'absolute'; 122 | body.style.transitionDuration = param.speed + 's'; 123 | body.style.webkitTransitionDuration = param.speed + 's'; 124 | body.style.transitionProperty = 'top, bottom, left, right'; 125 | } 126 | 127 | if (param.container) { 128 | slider.style.position = 'absolute'; 129 | body.style.position = 'relative'; 130 | body.style.overflow = 'hidden'; 131 | } 132 | 133 | function onTransitionEnd() { 134 | if (scope.psOpen) { 135 | if (typeof scope.onopen === 'function') { 136 | scope.onopen()(); 137 | } 138 | } else { 139 | if (typeof scope.onclose === 'function') { 140 | scope.onclose()(); 141 | } 142 | } 143 | } 144 | 145 | slider.addEventListener('transitionend', onTransitionEnd); 146 | 147 | function initSlider(slider, param) { 148 | switch (param.side) { 149 | case 'right': 150 | slider.style.width = param.size; 151 | slider.style.height = '100%'; 152 | slider.style.top = '0px'; 153 | slider.style.bottom = '0px'; 154 | break; 155 | case 'left': 156 | slider.style.width = param.size; 157 | slider.style.height = '100%'; 158 | slider.style.top = '0px'; 159 | slider.style.bottom = '0px'; 160 | break; 161 | case 'top': 162 | slider.style.height = param.size; 163 | slider.style.width = '100%'; 164 | slider.style.left = '0px'; 165 | slider.style.right = '0px'; 166 | break; 167 | case 'bottom': 168 | slider.style.height = param.size; 169 | slider.style.width = '100%'; 170 | slider.style.left = '0px'; 171 | slider.style.right = '0px'; 172 | break; 173 | } 174 | 175 | if (scope.psOpen) { 176 | psOpen(slider, param); 177 | } else { 178 | psClose(slider, param); 179 | } 180 | } 181 | 182 | function psClose(slider, param) { 183 | switch (param.side) { 184 | case 'right': 185 | slider.style.right = "-" + param.size; 186 | if (param.push) { 187 | body.style.right = '0px'; 188 | body.style.left = '0px'; 189 | } 190 | break; 191 | case 'left': 192 | slider.style.left = "-" + param.size; 193 | if (param.push) { 194 | body.style.left = '0px'; 195 | body.style.right = '0px'; 196 | } 197 | break; 198 | case 'top': 199 | slider.style.top = "-" + param.size; 200 | if (param.push) { 201 | body.style.top = '0px'; 202 | body.style.bottom = '0px'; 203 | } 204 | break; 205 | case 'bottom': 206 | slider.style.bottom = "-" + param.size; 207 | if (param.push) { 208 | body.style.bottom = '0px'; 209 | body.style.top = '0px'; 210 | } 211 | break; 212 | } 213 | 214 | if (param.keyListener) { 215 | $document.off('keydown', handleKeyDown); 216 | } 217 | 218 | if (param.clickOutside) { 219 | $document.off('touchend click', onBodyClick); 220 | } 221 | isOpen = false; 222 | setBodyClass('closed'); 223 | scope.psOpen = false; 224 | } 225 | 226 | function psOpen(slider, param) { 227 | switch (param.side) { 228 | case 'right': 229 | slider.style.right = "0px"; 230 | if (param.push) { 231 | body.style.right = param.size; 232 | body.style.left = '-' + param.size; 233 | } 234 | break; 235 | case 'left': 236 | slider.style.left = "0px"; 237 | if (param.push) { 238 | body.style.left = param.size; 239 | body.style.right = '-' + param.size; 240 | } 241 | break; 242 | case 'top': 243 | slider.style.top = "0px"; 244 | if (param.push) { 245 | body.style.top = param.size; 246 | body.style.bottom = '-' + param.size; 247 | } 248 | break; 249 | case 'bottom': 250 | slider.style.bottom = "0px"; 251 | if (param.push) { 252 | body.style.bottom = param.size; 253 | body.style.top = '-' + param.size; 254 | } 255 | break; 256 | } 257 | 258 | scope.psOpen = true; 259 | 260 | if (param.keyListener) { 261 | $document.on('keydown', handleKeyDown); 262 | } 263 | 264 | if (param.clickOutside) { 265 | $document.on('touchend click', onBodyClick); 266 | } 267 | setBodyClass('open'); 268 | } 269 | 270 | function handleKeyDown(e) { 271 | var ESC_KEY = 27; 272 | var key = e.keyCode || e.which; 273 | 274 | if (key === ESC_KEY) { 275 | psClose(slider, param); 276 | 277 | // FIXME check with tests 278 | // http://stackoverflow.com/questions/12729122/angularjs-prevent-error-digest-already-in-progress-when-calling-scope-apply 279 | 280 | $timeout(function () { 281 | scope.$apply(); 282 | }); 283 | } 284 | } 285 | 286 | // Initialize 287 | 288 | initSlider(slider, param); 289 | 290 | // Watchers 291 | 292 | scope.$watch('psOpen', function(value) { 293 | if (!!value) { 294 | psOpen(slider, param); 295 | } else { 296 | psClose(slider, param); 297 | } 298 | }); 299 | 300 | scope.$watch('psSize', function(newValue, oldValue) { 301 | if (oldValue !== newValue) { 302 | param.size = newValue; 303 | initSlider(slider, param); 304 | } 305 | }); 306 | 307 | // Events 308 | 309 | scope.$on('$destroy', function () { 310 | if (slider.parentNode === body) { 311 | if (param.clickOutside) { 312 | $document.off('touchend click', onBodyClick); 313 | } 314 | body.removeChild(slider); 315 | } 316 | 317 | slider.removeEventListener('transitionend', onTransitionEnd); 318 | }); 319 | 320 | if (param.autoClose) { 321 | scope.$on('$locationChangeStart', function() { 322 | psClose(slider, param); 323 | }); 324 | scope.$on('$stateChangeStart', function() { 325 | psClose(slider, param); 326 | }); 327 | } 328 | 329 | } 330 | }; 331 | }]); 332 | })); 333 | -------------------------------------------------------------------------------- /dist/angular-pageslide-directive.min.js: -------------------------------------------------------------------------------- 1 | /*! angular-pageslide-directive 2018-03-28 */ 2 | 3 | !function(e,t){"function"==typeof define&&define.amd?define(["angular"],t):"object"==typeof module&&module.exports?module.exports=t(require("angular")):t(e.angular)}(this,function(m){m.module("pageslide-directive",[]).directive("pageslide",["$document","$timeout",function(h,f){return{restrict:"EA",transclude:!1,scope:{psOpen:"=?",psAutoClose:"@",psSide:"@",psSpeed:"@",psClass:"@",psSize:"@",psZindex:"@",psPush:"@",psContainer:"@",psKeyListener:"@",psBodyClass:"@",psClickOutside:"@",onopen:"&?",onclose:"&?"},link:function(s,e,t){var i={};i.side=s.psSide||"right",i.speed=s.psSpeed||"0.5",i.size=s.psSize||"300px",i.zindex=s.psZindex||1e3,i.className=s.psClass||"ng-pageslide",i.push="true"===s.psPush,i.container=s.psContainer||!1,i.keyListener="true"===s.psKeyListener,i.bodyClass=s.psBodyClass||!1,i.clickOutside="false"!==s.psClickOutside,i.autoClose=s.psAutoClose||!1,i.push=i.push&&!i.container,e.addClass(i.className);var o,n,l=!1;function p(e){var t=e.touches&&e.touches[0]||e.target;l&&n.contains(t)&&!o.contains(t)&&(l=!1,s.psOpen=!1,s.$apply()),s.psOpen&&(l=!0)}function a(e){if(i.bodyClass){var t=i.className+"-body",s=new RegExp(t+"-closed|"+t+"-open");n.className=n.className.replace(s,"");var o=t+"-"+e;" "!==n.className[n.className.length-1]?n.className+=" "+o:n.className+=o}}if(n=i.container?document.getElementById(i.container):document.body,"div"!==(o=e[0]).tagName.toLowerCase()&&"pageslide"!==o.tagName.toLowerCase())throw new Error("Pageslide can only be applied to
or elements");if(0===o.children.length)throw new Error("You need to have content inside the ");function c(){s.psOpen?"function"==typeof s.onopen&&s.onopen()():"function"==typeof s.onclose&&s.onclose()()}function r(e,t){switch(t.side){case"right":case"left":e.style.width=t.size,e.style.height="100%",e.style.top="0px",e.style.bottom="0px";break;case"top":case"bottom":e.style.height=t.size,e.style.width="100%",e.style.left="0px",e.style.right="0px"}s.psOpen?u(e,t):d(e,t)}function d(e,t){switch(t.side){case"right":e.style.right="-"+t.size,t.push&&(n.style.right="0px",n.style.left="0px");break;case"left":e.style.left="-"+t.size,t.push&&(n.style.left="0px",n.style.right="0px");break;case"top":e.style.top="-"+t.size,t.push&&(n.style.top="0px",n.style.bottom="0px");break;case"bottom":e.style.bottom="-"+t.size,t.push&&(n.style.bottom="0px",n.style.top="0px")}t.keyListener&&h.off("keydown",y),t.clickOutside&&h.off("touchend click",p),l=!1,a("closed"),s.psOpen=!1}function u(e,t){switch(t.side){case"right":e.style.right="0px",t.push&&(n.style.right=t.size,n.style.left="-"+t.size);break;case"left":e.style.left="0px",t.push&&(n.style.left=t.size,n.style.right="-"+t.size);break;case"top":e.style.top="0px",t.push&&(n.style.top=t.size,n.style.bottom="-"+t.size);break;case"bottom":e.style.bottom="0px",t.push&&(n.style.bottom=t.size,n.style.top="-"+t.size)}s.psOpen=!0,t.keyListener&&h.on("keydown",y),t.clickOutside&&h.on("touchend click",p),a("open")}function y(e){27===(e.keyCode||e.which)&&(d(o,i),f(function(){s.$apply()}))}m.element(o.children),n.appendChild(o),o.style.zIndex=i.zindex,o.style.position="fixed",o.style.transitionDuration=i.speed+"s",o.style.webkitTransitionDuration=i.speed+"s",o.style.height=i.size,o.style.transitionProperty="top, bottom, left, right",i.push&&(n.style.position="absolute",n.style.transitionDuration=i.speed+"s",n.style.webkitTransitionDuration=i.speed+"s",n.style.transitionProperty="top, bottom, left, right"),i.container&&(o.style.position="absolute",n.style.position="relative",n.style.overflow="hidden"),o.addEventListener("transitionend",c),r(o,i),s.$watch("psOpen",function(e){e?u(o,i):d(o,i)}),s.$watch("psSize",function(e,t){t!==e&&(i.size=e,r(o,i))}),s.$on("$destroy",function(){o.parentNode===n&&(i.clickOutside&&h.off("touchend click",p),n.removeChild(o)),o.removeEventListener("transitionend",c)}),i.autoClose&&(s.$on("$locationChangeStart",function(){d(o,i)}),s.$on("$stateChangeStart",function(){d(o,i)}))}}}])}); -------------------------------------------------------------------------------- /examples/angular-pageslide-directive.js: -------------------------------------------------------------------------------- 1 | ../src/angular-pageslide-directive.js -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | AngularJS Pageslide directive demo 13 | 65 | 66 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |
101 |
102 |

103 | AngularJS Pageslide directive 104 |

105 |
106 | 107 |
108 | 109 |

110 | An AngularJS directive which slides another panel over your browser to reveal an additional interaction pane.
111 | It does all the css manipulation needed to position your content off canvas with html attibutes and it does not depend on jQuery 112 |

113 | 114 |
115 | Open Demo 116 |
117 |
118 |

Hello Pageslide

119 |

Put here whatever you want

120 | Close 121 |
122 |
123 |
124 | 125 |

Different speeds

126 |
127 | Slowmo 128 |
129 |
130 |

Hello Pageslidoo

131 |

Put here whatever you want

132 | Close 133 |
134 |
135 |
136 | 137 |
138 | Fastbro 139 |
140 |
141 |

Hello Pageslidoy

142 |

Put here whatever you want

143 | Close 144 |
145 |
146 |
147 | 148 |

Different directions

149 |
150 | Up up and above 151 |
152 |
153 |

Hello Pageslide

154 |

Put here whatever you want

155 | Close 156 |
157 |
158 |
159 | 160 |
161 | Go west 162 |
163 |
164 |

Hello Pageslide

165 |

Put here whatever you want

166 | Close 167 |
168 |
169 |
170 | 171 |

Push content

172 |
173 | Open Sidebar 174 |
175 |
176 |

Hello Pageslide

177 |

Put here whatever you want

178 | Close 179 |
180 |
181 |
182 | 183 |

Escape key to close

184 |
185 | Open sidebar 186 |
187 |
188 |

Hello Pageslide

189 |

hit the escape key to close the sidebar

190 | Close 191 |
192 |
193 |
194 | 195 |
196 | 197 |
198 |

As an attribute

199 |

Check to open:

200 |
201 |
202 |

Cool Pageslide

203 |

Put here whatever you want

204 |
205 |
206 |
207 | 208 |
209 |

As an element

210 |

Check to open:

211 | 212 |
213 |

Yes Pageslide

214 |

Put here whatever you want

215 |
216 |
217 |
218 | 219 |
220 |

Custom class on container

221 |

Check to open:

222 | 223 |
224 |

Yo Pageslide

225 |

Put here whatever you want

226 |

Close

227 |
228 |
229 |
230 | 231 |
232 |

Avoid click outside

233 |

Check to open:

234 | 235 |
236 |

Yo Pageslide

237 |

Put here whatever you want

238 |

Close

239 |
240 |
241 |
242 | 243 |
244 |

Dynamic psSize

245 |

Check to open:

246 |

psSize

247 | 248 |
249 |

Yo Pageslide

250 |

Put here whatever you want

251 |
252 |
253 |
254 | 255 |
256 |

Auto close

257 |

Check to open:

258 | 259 |
260 |

Yo Pageslide

261 |

Put here whatever you want

262 | 263 |
264 |
265 |
266 | 267 |
268 |

Open Close callbacks

269 |

Check to open:

270 | 271 |
272 |

Yo Pageslide

273 |

meh

274 |
275 |
276 |
277 | 278 |
279 |

280 | Pastrami pork belly strip steak meatball boudin pork. Beef doner pastrami leberkas. Frankfurter swine boudin doner. Bacon drumstick chicken landjaeger filet mignon pancetta. Corned beef leberkas frankfurter kevin. Tongue frankfurter pastrami, pork biltong alcatra leberkas meatloaf boudin strip steak spare ribs pork chop turkey. 281 |

282 |
283 | 284 |
285 |

In a container

286 |

Check to open:

287 | 288 |
289 |

Yo Pageslide

290 |

meh

291 |
292 |
293 |
294 | 295 |
296 |

297 | Pastrami pork belly strip steak meatball boudin pork. Beef doner pastrami leberkas. Frankfurter swine boudin doner. Bacon drumstick chicken landjaeger filet mignon pancetta. Corned beef leberkas frankfurter kevin. Tongue frankfurter pastrami, pork biltong alcatra leberkas meatloaf boudin strip steak spare ribs pork chop turkey. 298 |

299 |
300 | 301 |
302 |

In a container with directions

303 |

Check to open:

304 | 305 |
306 |

Yo Pageslide

307 |

meh

308 |
309 |
310 |
311 | 312 |
313 |
314 | 315 | 319 | 325 | 326 | 327 | 328 | 329 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | // Karma configuration 4 | // Generated on Mon Aug 05 2013 12:43:06 GMT+0200 (W. Europe Daylight Time) 5 | frameworks: ['jasmine'], 6 | 7 | // level of logging 8 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 9 | logLevel: config.LOG_DEBUG, 10 | 11 | preprocessors: { 12 | 'src/**/*.js': ['coverage'] 13 | }, 14 | 15 | // base path, that will be used to resolve files and exclude 16 | basePath: '', 17 | 18 | // list of files / patterns to load in the browser 19 | files: [ 20 | // Libs 21 | 'node_modules/angular/angular.js', 22 | 'node_modules/angular-mocks/angular-mocks.js', 23 | 24 | // Source 25 | 'src/*.js', 26 | 27 | // Tests 28 | 'test/*.js' 29 | ], 30 | 31 | // list of files to exclude 32 | exclude: [], 33 | 34 | // test results reporter to use 35 | // possible values: 'dots', 'progress', 'junit' 36 | reporters: ['progress', 'coverage', 'coveralls'], 37 | 38 | coverageReporter: { 39 | type : 'lcov', 40 | dir : 'coverage/' 41 | }, 42 | 43 | // web server port 44 | port: 9876, 45 | 46 | // cli runner port 47 | runnerPort: 9100, 48 | 49 | // enable / disable colors in the output (reporters and logs) 50 | colors: true, 51 | 52 | // enable / disable watching file and executing tests whenever any file changes 53 | autoWatch: true, 54 | 55 | // Start these browsers, currently available: 56 | // - Chrome 57 | // - ChromeCanary 58 | // - Firefox 59 | // - Opera 60 | // - Safari (only Mac) 61 | // - PhantomJS 62 | // - IE (only Windows) 63 | browsers: ['PhantomJS'], 64 | 65 | // If browser does not capture in given timeout [ms], kill it 66 | captureTimeout: 60000, 67 | 68 | // Continuous Integration mode 69 | // if true, it capture browsers, run tests and exit 70 | singleRun: false 71 | }); 72 | }; 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-pageslide-directive", 3 | "author": "Daniele Piccone ", 4 | "main": "dist/angular-pageslide-directive.js", 5 | "repository": { 6 | "type": "git", 7 | "url": "git@github.com:danielepiccone/ng-pageslide.git" 8 | }, 9 | "homepage": "https://github.com/danielepiccone/ng-pageslide", 10 | "keywords": [ 11 | "angular", 12 | "pageslide", 13 | "offcanvas", 14 | "modal", 15 | "pane" 16 | ], 17 | "version": "2.2.0", 18 | "license": "MIT", 19 | "devDependencies": { 20 | "angular": "1.x", 21 | "angular-mocks": "1.x", 22 | "grunt": "^1.0.2", 23 | "grunt-cli": "^1.2.0", 24 | "grunt-contrib-concat": "^1.0.1", 25 | "grunt-contrib-jshint": "^1.1.0", 26 | "grunt-contrib-uglify": "^3.3.0", 27 | "grunt-karma": "^2.0.0", 28 | "http-server": "^0.11.1", 29 | "jasmine-core": "^3.1.0", 30 | "karma": "^2.0.0", 31 | "karma-coverage": "^1.1.1", 32 | "karma-coveralls": "^1.1.2", 33 | "karma-jasmine": "^1.1.1", 34 | "karma-phantomjs-launcher": "^1.0.4" 35 | }, 36 | "peerDependencies": { 37 | "angular": "1.x" 38 | }, 39 | "scripts": { 40 | "prepublish": "grunt", 41 | "test": "grunt test", 42 | "examples": "http-server ./examples" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # AngularJS Pageslide Directive 2 | 3 | An AngularJS directive which slides another panel over your browser to reveal an additional interaction pane. 4 | 5 | It does all the css manipulation needed to position your content off canvas. 6 | 7 | See it in action [HERE](http://danielepiccone.github.io/ng-pageslide/examples/) 8 | 9 | Examples in the repository, run them with `npm run examples` 10 | 11 | [![Build Status](https://travis-ci.org/danielepiccone/ng-pageslide.svg?branch=master)](https://travis-ci.org/danielepiccone/ng-pageslide) 12 | [![Coverage Status](https://coveralls.io/repos/github/danielepiccone/ng-pageslide/badge.svg?branch=v2)](https://coveralls.io/github/danielepiccone/ng-pageslide?branch=v2) 13 | 14 | ## Usage 15 | 16 | Use within your Angular app 17 | 18 | `npm install --save angular-pageslide-directive` 19 | 20 | `var app = angular.module("app", ["pageslide-directive"]);` 21 | 22 | Just use the `````` element or attribute inside a controller scope like this: 23 | 24 | please note that you need an outer controller to define the scope of your **checked** model 25 | 26 | also you need an inner ```
``` to wrap your content in 27 | 28 | ``` 29 |
30 | ... 31 | 32 |
33 |

some random content...

34 |
35 |
36 | ... 37 |
38 | 39 | ``` 40 | 41 | ### Options: 42 | 43 | ``` 44 | pageslide (required) 45 | 46 | // Configuration 47 | ps-side (optional) = Where the panel should appear (right,left,top,bottom), if empty defaults to "right" 48 | ps-container (optional) = custom CSS ID selector to which the slider div appends (e.g:
-> ps-container="myDiv") 49 | ps-body-class (optional) = if true adds a class on the container body reflecting the state of the pageslide 50 | 51 | // Interaction 52 | ps-open (optional) = Boolean true/false used to open and close the panel (optional) 53 | ps-auto-close (optional) = true if you want the panel to close on location change 54 | ps-key-listener (optional) = close the sidebar with the ESC key (defaults to false) 55 | ps-click-outside (optional) = close the sidebar by clicking outside (defaults to true) 56 | ps-push (optional) = push the main body to show the panel (defaults to false) 57 | 58 | // Style 59 | ps-class (optional) = The class for the pageslide (default: "ng-pageslide") 60 | ps-speed (optional) = The speed of the transition (optional) 61 | ps-size (optional) = desired height/width of panel (defaults to 300px) 62 | ps-zindex (optional) = desired z-index (defaults to 1000) 63 | 64 | ``` 65 | 66 | ## Licensing 67 | 68 | Licensed under [MIT](http://opensource.org/licenses/MIT) 69 | 70 | ## Author 71 | 72 | 2013, Daniele Piccone [www.danielepiccone.com](http://www.danielepiccone.com) 73 | -------------------------------------------------------------------------------- /src/angular-pageslide-directive.js: -------------------------------------------------------------------------------- 1 | 2 | (function (root, factory) { 3 | if (typeof define === 'function' && define.amd) { 4 | define(['angular'], factory); 5 | } else if (typeof module === 'object' && module.exports) { 6 | module.exports = factory(require('angular')); 7 | } else { 8 | factory(root.angular); 9 | } 10 | }(this, function (angular) { 11 | 12 | angular 13 | .module('pageslide-directive', []) 14 | .directive('pageslide', ['$document', '$timeout', function ($document, $timeout) { 15 | var defaults = {}; 16 | 17 | 18 | return { 19 | restrict: 'EA', 20 | transclude: false, 21 | scope: { 22 | psOpen: '=?', 23 | psAutoClose: '@', 24 | psSide: '@', 25 | psSpeed: '@', 26 | psClass: '@', 27 | psSize: '@', 28 | psZindex: '@', 29 | psPush: '@', 30 | psContainer: '@', 31 | psKeyListener: '@', 32 | psBodyClass: '@', 33 | psClickOutside: '@', 34 | onopen: '&?', 35 | onclose: '&?' 36 | }, 37 | link: function (scope, el, attrs) { 38 | 39 | var param = {}; 40 | 41 | param.side = scope.psSide || 'right'; 42 | param.speed = scope.psSpeed || '0.5'; 43 | param.size = scope.psSize || '300px'; 44 | param.zindex = scope.psZindex || 1000; 45 | param.className = scope.psClass || 'ng-pageslide'; 46 | param.push = scope.psPush === 'true'; 47 | param.container = scope.psContainer || false; 48 | param.keyListener = scope.psKeyListener === 'true'; 49 | param.bodyClass = scope.psBodyClass || false; 50 | param.clickOutside = scope.psClickOutside !== 'false'; 51 | param.autoClose = scope.psAutoClose || false; 52 | 53 | param.push = param.push && !param.container; 54 | 55 | el.addClass(param.className); 56 | 57 | /* DOM manipulation */ 58 | 59 | var content, slider, body, isOpen = false; 60 | 61 | if (param.container) { 62 | body = document.getElementById(param.container); 63 | } else { 64 | body = document.body; 65 | } 66 | 67 | function onBodyClick(e) { 68 | var target = e.touches && e.touches[0] || e.target; 69 | if( 70 | isOpen && 71 | body.contains(target) && 72 | !slider.contains(target) 73 | ) { 74 | isOpen = false; 75 | scope.psOpen = false; 76 | scope.$apply(); 77 | } 78 | 79 | if(scope.psOpen) { 80 | isOpen = true; 81 | } 82 | } 83 | 84 | function setBodyClass(value){ 85 | if (param.bodyClass) { 86 | var bodyClass = param.className + '-body'; 87 | var bodyClassRe = new RegExp(bodyClass + '-closed|' + bodyClass + '-open'); 88 | body.className = body.className.replace(bodyClassRe, ''); 89 | var newBodyClassName = bodyClass + '-' + value; 90 | if (body.className[body.className.length -1] !== ' ') { 91 | body.className += ' ' + newBodyClassName; 92 | } else { 93 | body.className += newBodyClassName; 94 | } 95 | } 96 | } 97 | 98 | slider = el[0]; 99 | 100 | if (slider.tagName.toLowerCase() !== 'div' && 101 | slider.tagName.toLowerCase() !== 'pageslide') { 102 | throw new Error('Pageslide can only be applied to
or elements'); 103 | } 104 | 105 | if (slider.children.length === 0) { 106 | throw new Error('You need to have content inside the '); 107 | } 108 | 109 | content = angular.element(slider.children); 110 | 111 | body.appendChild(slider); 112 | 113 | slider.style.zIndex = param.zindex; 114 | slider.style.position = 'fixed'; 115 | slider.style.transitionDuration = param.speed + 's'; 116 | slider.style.webkitTransitionDuration = param.speed + 's'; 117 | slider.style.height = param.size; 118 | slider.style.transitionProperty = 'top, bottom, left, right'; 119 | 120 | if (param.push) { 121 | body.style.position = 'absolute'; 122 | body.style.transitionDuration = param.speed + 's'; 123 | body.style.webkitTransitionDuration = param.speed + 's'; 124 | body.style.transitionProperty = 'top, bottom, left, right'; 125 | } 126 | 127 | if (param.container) { 128 | slider.style.position = 'absolute'; 129 | body.style.position = 'relative'; 130 | body.style.overflow = 'hidden'; 131 | } 132 | 133 | function onTransitionEnd() { 134 | if (scope.psOpen) { 135 | if (typeof scope.onopen === 'function') { 136 | scope.onopen()(); 137 | } 138 | } else { 139 | if (typeof scope.onclose === 'function') { 140 | scope.onclose()(); 141 | } 142 | } 143 | } 144 | 145 | slider.addEventListener('transitionend', onTransitionEnd); 146 | 147 | function initSlider(slider, param) { 148 | switch (param.side) { 149 | case 'right': 150 | slider.style.width = param.size; 151 | slider.style.height = '100%'; 152 | slider.style.top = '0px'; 153 | slider.style.bottom = '0px'; 154 | break; 155 | case 'left': 156 | slider.style.width = param.size; 157 | slider.style.height = '100%'; 158 | slider.style.top = '0px'; 159 | slider.style.bottom = '0px'; 160 | break; 161 | case 'top': 162 | slider.style.height = param.size; 163 | slider.style.width = '100%'; 164 | slider.style.left = '0px'; 165 | slider.style.right = '0px'; 166 | break; 167 | case 'bottom': 168 | slider.style.height = param.size; 169 | slider.style.width = '100%'; 170 | slider.style.left = '0px'; 171 | slider.style.right = '0px'; 172 | break; 173 | } 174 | 175 | if (scope.psOpen) { 176 | psOpen(slider, param); 177 | } else { 178 | psClose(slider, param); 179 | } 180 | } 181 | 182 | function psClose(slider, param) { 183 | switch (param.side) { 184 | case 'right': 185 | slider.style.right = "-" + param.size; 186 | if (param.push) { 187 | body.style.right = '0px'; 188 | body.style.left = '0px'; 189 | } 190 | break; 191 | case 'left': 192 | slider.style.left = "-" + param.size; 193 | if (param.push) { 194 | body.style.left = '0px'; 195 | body.style.right = '0px'; 196 | } 197 | break; 198 | case 'top': 199 | slider.style.top = "-" + param.size; 200 | if (param.push) { 201 | body.style.top = '0px'; 202 | body.style.bottom = '0px'; 203 | } 204 | break; 205 | case 'bottom': 206 | slider.style.bottom = "-" + param.size; 207 | if (param.push) { 208 | body.style.bottom = '0px'; 209 | body.style.top = '0px'; 210 | } 211 | break; 212 | } 213 | 214 | if (param.keyListener) { 215 | $document.off('keydown', handleKeyDown); 216 | } 217 | 218 | if (param.clickOutside) { 219 | $document.off('touchend click', onBodyClick); 220 | } 221 | isOpen = false; 222 | setBodyClass('closed'); 223 | scope.psOpen = false; 224 | } 225 | 226 | function psOpen(slider, param) { 227 | switch (param.side) { 228 | case 'right': 229 | slider.style.right = "0px"; 230 | if (param.push) { 231 | body.style.right = param.size; 232 | body.style.left = '-' + param.size; 233 | } 234 | break; 235 | case 'left': 236 | slider.style.left = "0px"; 237 | if (param.push) { 238 | body.style.left = param.size; 239 | body.style.right = '-' + param.size; 240 | } 241 | break; 242 | case 'top': 243 | slider.style.top = "0px"; 244 | if (param.push) { 245 | body.style.top = param.size; 246 | body.style.bottom = '-' + param.size; 247 | } 248 | break; 249 | case 'bottom': 250 | slider.style.bottom = "0px"; 251 | if (param.push) { 252 | body.style.bottom = param.size; 253 | body.style.top = '-' + param.size; 254 | } 255 | break; 256 | } 257 | 258 | scope.psOpen = true; 259 | 260 | if (param.keyListener) { 261 | $document.on('keydown', handleKeyDown); 262 | } 263 | 264 | if (param.clickOutside) { 265 | $document.on('touchend click', onBodyClick); 266 | } 267 | setBodyClass('open'); 268 | } 269 | 270 | function handleKeyDown(e) { 271 | var ESC_KEY = 27; 272 | var key = e.keyCode || e.which; 273 | 274 | if (key === ESC_KEY) { 275 | psClose(slider, param); 276 | 277 | // FIXME check with tests 278 | // http://stackoverflow.com/questions/12729122/angularjs-prevent-error-digest-already-in-progress-when-calling-scope-apply 279 | 280 | $timeout(function () { 281 | scope.$apply(); 282 | }); 283 | } 284 | } 285 | 286 | // Initialize 287 | 288 | initSlider(slider, param); 289 | 290 | // Watchers 291 | 292 | scope.$watch('psOpen', function(value) { 293 | if (!!value) { 294 | psOpen(slider, param); 295 | } else { 296 | psClose(slider, param); 297 | } 298 | }); 299 | 300 | scope.$watch('psSize', function(newValue, oldValue) { 301 | if (oldValue !== newValue) { 302 | param.size = newValue; 303 | initSlider(slider, param); 304 | } 305 | }); 306 | 307 | // Events 308 | 309 | scope.$on('$destroy', function () { 310 | if (slider.parentNode === body) { 311 | if (param.clickOutside) { 312 | $document.off('touchend click', onBodyClick); 313 | } 314 | body.removeChild(slider); 315 | } 316 | 317 | slider.removeEventListener('transitionend', onTransitionEnd); 318 | }); 319 | 320 | if (param.autoClose) { 321 | scope.$on('$locationChangeStart', function() { 322 | psClose(slider, param); 323 | }); 324 | scope.$on('$stateChangeStart', function() { 325 | psClose(slider, param); 326 | }); 327 | } 328 | 329 | } 330 | }; 331 | }]); 332 | })); 333 | -------------------------------------------------------------------------------- /test/angular-pageslide-directive.spec.js: -------------------------------------------------------------------------------- 1 | describe('ng-pageslide: ', function() { 2 | 'use strict'; 3 | 4 | var $compile; 5 | var $timeout; 6 | var $document; 7 | var scope; 8 | var isolateScope; 9 | var element; 10 | var compilePageslide; 11 | 12 | beforeEach(function (done) { 13 | $document = document; 14 | $document.on = jasmine.createSpy('on') 15 | $document.off = jasmine.createSpy('off') 16 | 17 | module('pageslide-directive', [ 18 | '$provide', 19 | function ($provide) { 20 | $provide.value('$document', $document); 21 | } 22 | ]); 23 | 24 | compilePageslide = function (html) { 25 | inject([ 26 | '$compile', 27 | '$rootScope', 28 | '$document', 29 | '$timeout', 30 | function(_$compile_, $rootScope, $document, _$timeout_){ 31 | $compile = _$compile_; 32 | $timeout = _$timeout_; 33 | scope = $rootScope.$new(); 34 | element = angular.element(html); 35 | $compile(element)(scope); 36 | scope.$digest(); 37 | isolateScope = element.isolateScope(); 38 | } 39 | ]); 40 | }; 41 | done(); 42 | }); 43 | 44 | 45 | afterEach(function(){ 46 | // try to clean Dom 47 | var slider = document.querySelector('.ng-pageslide'); 48 | var pageslide = document.querySelector('#test-pageslide'); 49 | document.body.innerHTML = ''; 50 | }); 51 | 52 | describe('initialization', function () { 53 | describe('when the element is invalid', function () { 54 | describe('because there is no content inside of the root element', function () { 55 | it('should throw an exception for no content', function (done) { 56 | expect(function () {compilePageslide('');}).toThrow(); 57 | done(); 58 | }); 59 | }); 60 | describe('because the root element is not a div', function () { 61 | it('should throw an exception for no content', function (done) { 62 | expect(function () {compilePageslide('

');}).toThrow(); 63 | done(); 64 | }); 65 | }); 66 | }); 67 | 68 | describe('when the element is valid', function () { 69 | describe('and has defined the container', function () { 70 | beforeEach(function (done) { 71 | angular.element(document.body).append('
custom container text
'); 72 | compilePageslide([ 73 | '
', 74 | '
test
', 75 | '
' 76 | ].join('')); 77 | done(); 78 | }); 79 | 80 | afterEach(function (done) { 81 | var customContainer = document.querySelector('#customContainer'); 82 | document.body.removeChild(customContainer); 83 | done(); 84 | }); 85 | 86 | it('should contain the pageslide with the custom defined container', function (done) { 87 | expect(angular.element(document.querySelector('#customContainer')).html()) 88 | .toContain('pageslide'); 89 | done(); 90 | }); 91 | 92 | it('should set the position to relative', function (done) { 93 | var slider = document.querySelector('#customContainer'); 94 | expect(angular.element(slider).css('position')).toEqual('relative'); 95 | done(); 96 | }); 97 | }); 98 | 99 | describe('and has defined the body class', function () { 100 | beforeEach(function (done) { 101 | document.body.className = 'foobar'; 102 | compilePageslide([ 103 | '
', 104 | '
test
', 105 | '
' 106 | ].join('')); 107 | done(); 108 | }); 109 | 110 | afterEach(function (done) { 111 | document.body.className = ''; 112 | done(); 113 | }); 114 | 115 | it('should add the class to the pageslide element', function (done) { 116 | expect(angular.element(document.querySelector('.customBodyClass')).html()).toBeDefined(); 117 | done(); 118 | }); 119 | 120 | it('should add the class to the body, defaulting to closed', function (done) { 121 | expect(document.body.className).toBe('foobar customBodyClass-body-closed'); 122 | done(); 123 | }); 124 | }); 125 | 126 | describe('and has set push to true', function () { 127 | beforeEach(function (done) { 128 | compilePageslide([ 129 | '
', 130 | '
test
', 131 | '
' 132 | ].join('')); 133 | done(); 134 | }); 135 | 136 | it('should set the width ', function (done) { 137 | var body = angular.element(document.body); 138 | scope.is_open = true; 139 | scope.$digest(); 140 | scope.is_open = false; 141 | scope.$digest(); 142 | expect(body.css('right')).toEqual('0px'); 143 | expect(body.css('left')).toEqual('0px'); 144 | done(); 145 | }); 146 | }); 147 | 148 | /* 149 | describe('when the psSize is set', function () { 150 | beforeEach(function (done) { 151 | compilePageslide([ 152 | '', 153 | '
test
', 154 | '
' 155 | ].join('')); 156 | done(); 157 | }); 158 | it('should set the size accordingly without opening the pageslide', function (done) { 159 | scope.is_open = false; 160 | scope.$digest(); 161 | expect(isolateScope.psOpen).toEqual(false); 162 | scope.size = '150px'; 163 | scope.$digest(); 164 | expect(isolateScope.psOpen).toEqual(false); 165 | scope.is_open = true; 166 | scope.$digest(); 167 | var body = angular.element(document.body); 168 | expect(body.html()).toContain('width: 150px;'); 169 | done(); 170 | }); 171 | }); 172 | */ 173 | 174 | describe('when psAutoClose is set', function () { 175 | beforeEach(function (done) { 176 | compilePageslide([ 177 | '', 178 | '
test
', 179 | '
' 180 | ].join('')); 181 | done(); 182 | }); 183 | it('should open on $locationChangeStart', function (done) { 184 | isolateScope.psOpen = true; 185 | scope.$broadcast('$locationChangeStart'); 186 | expect(isolateScope.psOpen).toEqual(false); 187 | isolateScope.$digest(); 188 | isolateScope.psOpen = true; 189 | scope.$broadcast('$stateChangeStart'); 190 | expect(isolateScope.psOpen).toEqual(false); 191 | done(); 192 | }); 193 | }); 194 | 195 | describe('when onopen is set', function () { 196 | beforeEach(function (done) { 197 | compilePageslide([ 198 | '', 199 | '
test
', 200 | '
' 201 | ].join('')); 202 | done(); 203 | }); 204 | it('should call onopen callback after transition', function (done) { 205 | scope.is_open = true; 206 | scope.callback = jasmine.createSpy('callback'); 207 | scope.$digest(); 208 | element[0].dispatchEvent(new Event('transitionend')); 209 | expect(scope.callback).toHaveBeenCalled(); 210 | done(); 211 | }); 212 | }); 213 | 214 | describe('when onclose is set', function () { 215 | beforeEach(function (done) { 216 | compilePageslide([ 217 | '', 218 | '
test
', 219 | '
' 220 | ].join('')); 221 | done(); 222 | }); 223 | it('should call onOpen callback after transition', function (done) { 224 | scope.is_open = false; 225 | scope.callback = jasmine.createSpy('callback'); 226 | scope.$digest(); 227 | element[0].dispatchEvent(new Event('transitionend')); 228 | expect(scope.callback).toHaveBeenCalled(); 229 | done(); 230 | }); 231 | }); 232 | 233 | }); 234 | }); 235 | 236 | describe('functionality', function () { 237 | describe('default/right pageslide', function () { 238 | it('Should attach the pageslide panel to ', function(done) { 239 | // Create template DOM for directive 240 | compilePageslide([ 241 | '
', 242 | '
', 243 | '
', 244 | '

some random content...

', 245 | 'Click to close', 246 | '
', 247 | '
', 248 | '
' 249 | ].join('')); 250 | 251 | // Check for DOM Manipulation 252 | var el = document.querySelector('.ng-pageslide'); 253 | var attached_to = el.parentElement.tagName; 254 | expect(attached_to).toBe('BODY'); 255 | done(); 256 | }); 257 | 258 | it('Should open and close watching for ps-open', function (done) { 259 | // Create template DOM for directive 260 | compilePageslide([ 261 | '
', 262 | '
', 263 | '
', 264 | '

some random content...

', 265 | 'Click to close', 266 | '
', 267 | '
', 268 | '
' 269 | ].join('')); 270 | 271 | var right; 272 | 273 | scope.is_open = true; 274 | scope.$digest(); 275 | right = document.querySelector('.ng-pageslide').style.right; 276 | expect(right).toBe('0px'); 277 | 278 | scope.is_open = false; 279 | scope.$digest(); 280 | right = document.querySelector('.ng-pageslide').style.right; 281 | expect(right).toBe('-300px'); 282 | done(); 283 | }); 284 | 285 | it('Should attach the pageslide inner content to ', function (done) { 286 | // Create template DOM for directive 287 | compilePageslide([ 288 | '', 289 | '
', 290 | '

some random content...

', 291 | '
', 292 | '
' 293 | ].join('')); 294 | 295 | // Check for DOM Manipulation 296 | var el = document.querySelector('.ng-pageslide'); 297 | var attached_to = el.parentNode.localName; 298 | expect(attached_to).toBe('body'); 299 | done(); 300 | }); 301 | 302 | it('Should remove slider when pageslide\'s scope be destroyed', function (done) { 303 | // Create template DOM for directive 304 | compilePageslide([ 305 | '
', 306 | '
', 307 | '
', 308 | '

some random content...

', 309 | 'Click to close', 310 | '
', 311 | '
', 312 | '
' 313 | ].join('')); 314 | scope.is_open = true; 315 | scope.$digest(); 316 | scope.$destroy(); 317 | expect(isolateScope).toBeUndefined(); 318 | done(); 319 | }); 320 | 321 | describe('when binding the key listener', function () { 322 | beforeEach(function (done) { 323 | // Create template DOM for directive 324 | compilePageslide([ 325 | '
', 326 | '
', 327 | '
', 328 | '

some random content...

', 329 | 'Click to close', 330 | '
', 331 | '
', 332 | '
' 333 | ].join('')); 334 | done(); 335 | }); 336 | describe('and the user presses the escape key', function () { 337 | describe('and the keyCode is populated', function () { 338 | it('should close the slider', function (done) { 339 | $document.on.and.callFake(function (actionType, callback) { 340 | callback({ 341 | keyCode: 27 //same as ESC_KEY 342 | }); 343 | }); 344 | scope.is_open = true; 345 | scope.$digest(); 346 | expect($document.on).toHaveBeenCalled(); 347 | expect(scope.is_open).toEqual(false); 348 | done(); 349 | }); 350 | }); 351 | 352 | describe('and the "which" property is populated', function () { 353 | it('should close the slider', function (done) { 354 | $document.on.and.callFake(function (actionType, callback) { 355 | callback({ 356 | which: 27 //same as ESC_KEY 357 | }); 358 | }); 359 | scope.is_open = true; 360 | scope.$digest(); 361 | expect($document.on).toHaveBeenCalled(); 362 | expect(scope.is_open).toEqual(false); 363 | done(); 364 | }); 365 | }); 366 | }); 367 | 368 | describe('and the user presses a key thats not the escape key', function () { 369 | describe('and the keyCode is populated', function () { 370 | it('should close the slider', function (done) { 371 | $document.on.and.callFake(function (actionType, callback) { 372 | callback({ 373 | keyCode: 99 //random key 374 | }); 375 | }); 376 | scope.is_open = true; 377 | scope.$digest(); 378 | expect($document.on).toHaveBeenCalled(); 379 | expect(scope.is_open).toEqual(true); 380 | done(); 381 | }); 382 | }); 383 | 384 | describe('and the "which" property is populated', function () { 385 | it('should close the slider', function (done) { 386 | $document.on.and.callFake(function (actionType, callback) { 387 | callback({ 388 | which: 99 //random key 389 | }); 390 | }); 391 | scope.is_open = true; 392 | scope.$digest(); 393 | expect($document.on).toHaveBeenCalled(); 394 | expect(scope.is_open).toEqual(true); 395 | done(); 396 | }); 397 | }); 398 | }); 399 | }); 400 | }); 401 | 402 | describe('left pageslide', function () { 403 | describe('by default', function () { 404 | beforeEach(function (done) { 405 | compilePageslide([ 406 | '
', 407 | '
', 408 | '
', 409 | '

some random content...

', 410 | 'Click to close', 411 | '
', 412 | '
', 413 | '
' 414 | ].join('')); 415 | done(); 416 | }); 417 | 418 | it('should set the appropriate styles', function (done) { 419 | // Check for DOM Manipulation 420 | var slider = angular.element(document.body); 421 | expect(slider.html()).toContain('height: 100%;'); 422 | expect(slider.html()).toContain('top: 0px;'); 423 | expect(slider.html()).toContain('bottom: 0px;'); 424 | //when opening the slider 425 | scope.is_open = true; 426 | scope.$digest(); 427 | expect(slider.html()).toContain('left: 0px;'); 428 | //when closing the slider 429 | scope.is_open = false; 430 | scope.$digest(); 431 | expect(slider.html()).toContain('left: -300px;'); 432 | done(); 433 | }); 434 | }); 435 | 436 | describe('when push is set', function () { 437 | beforeEach(function (done) { 438 | compilePageslide([ 439 | '
', 440 | '
', 441 | '
', 442 | '

some random content...

', 443 | 'Click to close', 444 | '
', 445 | '
', 446 | '
' 447 | ].join('')); 448 | done(); 449 | }); 450 | 451 | // TODO this should check the body as well 452 | it('should set the appropriate styles', function (done) { 453 | // Check for DOM Manipulation 454 | var slider = angular.element(document.body); 455 | expect(slider.html()).toContain('height: 100%;'); 456 | expect(slider.html()).toContain('top: 0px;'); 457 | expect(slider.html()).toContain('bottom: 0px;'); 458 | //when opening the slider 459 | scope.is_open = true; 460 | scope.$digest(); 461 | expect(slider.html()).toContain('left: 0px;'); 462 | //TODO: find out why these are not being set in the test 463 | // expect(slider.html()).toContain('left: 300px;'); 464 | // expect(slider.html()).toContain('right: -300px;'); 465 | //when closing the slider 466 | scope.is_open = false; 467 | scope.$digest(); 468 | expect(slider.html()).toContain('left: -300px;'); 469 | done(); 470 | }); 471 | }); 472 | }); 473 | 474 | describe('top pageslide', function () { 475 | describe('by default', function () { 476 | beforeEach(function (done) { 477 | compilePageslide([ 478 | '
', 479 | '
', 480 | '
', 481 | '

some random content...

', 482 | 'Click to close', 483 | '
', 484 | '
', 485 | '
' 486 | ].join('')); 487 | done(); 488 | }); 489 | 490 | it('should set the appropriate styles', function (done) { 491 | // Check for DOM Manipulation 492 | var slider = angular.element(document.body); 493 | expect(slider.html()).toContain('width: 100%;'); 494 | expect(slider.html()).toContain('left: 0px;'); 495 | expect(slider.html()).toContain('right: 0px;'); 496 | //when opening the slider 497 | scope.is_open = true; 498 | scope.$digest(); 499 | expect(slider.html()).toContain('top: 0px;'); 500 | //when closing the slider 501 | scope.is_open = false; 502 | scope.$digest(); 503 | expect(slider.html()).toContain('top: -300px;'); 504 | done(); 505 | }); 506 | }); 507 | 508 | describe('when push is set', function () { 509 | beforeEach(function (done) { 510 | compilePageslide([ 511 | '
', 512 | '
', 513 | '
', 514 | '

some random content...

', 515 | 'Click to close', 516 | '
', 517 | '
', 518 | '
' 519 | ].join('')); 520 | done(); 521 | }); 522 | 523 | // TODO this should check the body as well 524 | it('should set the appropriate styles', function (done) { 525 | // Check for DOM Manipulation 526 | var slider = angular.element(document.body); 527 | expect(slider.html()).toContain('width: 100%;'); 528 | expect(slider.html()).toContain('left: 0px;'); 529 | expect(slider.html()).toContain('right: 0px;'); 530 | //when opening the slider 531 | scope.is_open = true; 532 | scope.$digest(); 533 | expect(slider.html()).toContain('top: 0px;'); 534 | //TODO: find out why these are not being set in the test 535 | // expect(slider.html()).toContain('top: 300px;'); 536 | // expect(slider.html()).toContain('bottom: -300px;'); 537 | //when closing the slider 538 | scope.is_open = false; 539 | scope.$digest(); 540 | expect(slider.html()).toContain('top: -300px;'); 541 | done(); 542 | }); 543 | }); 544 | }); 545 | 546 | describe('bottom pageslide', function () { 547 | describe('by default', function () { 548 | beforeEach(function (done) { 549 | compilePageslide([ 550 | '
', 551 | '
', 552 | '
', 553 | '

some random content...

', 554 | 'Click to close', 555 | '
', 556 | '
', 557 | '
' 558 | ].join('')); 559 | done(); 560 | }); 561 | 562 | it('should set the appropriate styles', function (done) { 563 | // Check for DOM Manipulation 564 | var slider = angular.element(document.body); 565 | expect(slider.html()).toContain('width: 100%;'); 566 | expect(slider.html()).toContain('left: 0px;'); 567 | expect(slider.html()).toContain('right: 0px;'); 568 | //when opening the slider 569 | scope.is_open = true; 570 | scope.$digest(); 571 | expect(slider.html()).toContain('bottom: 0px;'); 572 | //when closing the slider 573 | scope.is_open = false; 574 | scope.$digest(); 575 | expect(slider.html()).toContain('bottom: -300px;'); 576 | done(); 577 | }); 578 | }); 579 | 580 | // TODO this hsould check the body as well 581 | describe('when push is set', function () { 582 | beforeEach(function (done) { 583 | compilePageslide([ 584 | '
', 585 | '
', 586 | '
', 587 | '

some random content...

', 588 | 'Click to close', 589 | '
', 590 | '
', 591 | '
' 592 | ].join('')); 593 | done(); 594 | }); 595 | 596 | it('should set the appropriate styles', function (done) { 597 | // Check for DOM Manipulation 598 | var slider = angular.element(document.body); 599 | expect(slider.html()).toContain('width: 100%;'); 600 | expect(slider.html()).toContain('left: 0px;'); 601 | expect(slider.html()).toContain('right: 0px;'); 602 | //when opening the slider 603 | scope.is_open = true; 604 | scope.$digest(); 605 | expect(slider.html()).toContain('bottom: 0px;'); 606 | //TODO: find out why these are not being set in the test 607 | // expect(slider.html()).toContain('bottom: 300px;'); 608 | // expect(slider.html()).toContain('top: -300px;'); 609 | //when closing the slider 610 | scope.is_open = false; 611 | scope.$digest(); 612 | expect(slider.html()).toContain('bottom: -300px;'); 613 | done(); 614 | }); 615 | }); 616 | }); 617 | 618 | xit('Should sync ps-open state between pageslide\'s scope and parent scope', function () { 619 | //TODO: refactor code to properly assign isolateScope 620 | // Create template DOM for directive 621 | compilePageslide([ 622 | '
', 623 | '
', 624 | '
', 625 | '

some random content...

', 626 | 'Click to close', 627 | '
', 628 | '
', 629 | '
' 630 | ].join('')); 631 | 632 | scope.is_open = true; 633 | scope.$digest(); 634 | 635 | expect(isolateScope.psOpen).toBe(true); 636 | 637 | scope.is_open = false; 638 | scope.$digest(); 639 | 640 | expect(isolateScope.psOpen).toBe(false); 641 | }); 642 | }); 643 | }); 644 | --------------------------------------------------------------------------------