From c9f1b8f74eb3e29d0e7df50d522bb8a1a56109ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Zaman?= Date: Tue, 2 Jun 2015 15:10:03 +0200 Subject: [PATCH] Manual firing now invokes callback --- .jshintrc | 87 +++++++++++++++++++++++++++++++++++++++ build/ouibounce.js | 44 ++++++++++++-------- build/ouibounce.min.js | 2 +- contributing.md | 4 +- gulpfile.js | 30 +++++++++----- package.json | 4 +- source/ouibounce.js | 44 ++++++++++++-------- test/fixtures/manual.html | 19 +++++++++ test/test.js | 26 ++++++++++-- 9 files changed, 208 insertions(+), 52 deletions(-) create mode 100644 .jshintrc create mode 100644 test/fixtures/manual.html diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..9264684 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,87 @@ +{ + // JSHint Default Configuration File (as on JSHint website) + // See http://jshint.com/docs/ for more details + + "maxerr" : 50, // {int} Maximum error before stopping + + // Enforcing + "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) + "camelcase" : false, // true: Identifiers must be in camelCase + "curly" : true, // true: Require {} for every new block or scope + "eqeqeq" : true, // true: Require triple equals (===) for comparison + "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() + "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. + "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` + "indent" : 4, // {int} Number of spaces to use for indentation + "latedef" : false, // true: Require variables/functions to be defined before being used + "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` + "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` + "noempty" : true, // true: Prohibit use of empty blocks + "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters. + "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) + "plusplus" : false, // true: Prohibit use of `++` & `--` + "quotmark" : false, // Quotation mark consistency: + // false : do nothing (default) + // true : ensure whatever is used is consistent + // "single" : require single quotes + // "double" : require double quotes + "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) + "unused" : true, // true: Require all defined variables be used + "strict" : true, // true: Requires all functions run in ES5 Strict Mode + "maxparams" : false, // {int} Max number of formal params allowed per function + "maxdepth" : false, // {int} Max depth of nested blocks (within functions) + "maxstatements" : false, // {int} Max number statements per function + "maxcomplexity" : false, // {int} Max cyclomatic complexity per function + "maxlen" : false, // {int} Max number of characters per line + + // Relaxing + "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) + "boss" : false, // true: Tolerate assignments where comparisons would be expected + "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. + "eqnull" : false, // true: Tolerate use of `== null` + "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) + "esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`) + "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) + // (ex: `for each`, multiple try/catch, function expression…) + "evil" : false, // true: Tolerate use of `eval` and `new Function()` + "expr" : false, // true: Tolerate `ExpressionStatement` as Programs + "funcscope" : false, // true: Tolerate defining variables inside control statements + "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') + "iterator" : false, // true: Tolerate using the `__iterator__` property + "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block + "laxbreak" : false, // true: Tolerate possibly unsafe line breakings + "laxcomma" : false, // true: Tolerate comma-first style coding + "loopfunc" : false, // true: Tolerate functions being defined in loops + "multistr" : false, // true: Tolerate multi-line strings + "noyield" : false, // true: Tolerate generator functions with no yield statement in them. + "notypeof" : false, // true: Tolerate invalid typeof operator values + "proto" : false, // true: Tolerate using the `__proto__` property + "scripturl" : false, // true: Tolerate script-targeted URLs + "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` + "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation + "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` + "validthis" : false, // true: Tolerate using this in a non-constructor function + + // Environments + "browser" : true, // Web Browser (window, document, etc) + "browserify" : false, // Browserify (node.js code in the browser) + "couch" : false, // CouchDB + "devel" : true, // Development/debugging (alert, confirm, etc) + "dojo" : false, // Dojo Toolkit + "jasmine" : false, // Jasmine + "jquery" : true, // jQuery + "mocha" : true, // Mocha + "mootools" : false, // MooTools + "node" : false, // Node.js + "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) + "prototypejs" : false, // Prototype and Scriptaculous + "qunit" : false, // QUnit + "rhino" : false, // Rhino + "shelljs" : false, // ShellJS + "worker" : false, // Web Workers + "wsh" : false, // Windows Scripting Host + "yui" : false, // Yahoo User Interface + + // Custom Globals + "globals" : { } // additional predefined global variables +} diff --git a/build/ouibounce.js b/build/ouibounce.js index 040b169..b37b96a 100644 --- a/build/ouibounce.js +++ b/build/ouibounce.js @@ -10,8 +10,10 @@ } }(this, function(require,exports,module) { -return function ouibounce(el, config) { - var config = config || {}, +return function ouibounce(el, custom_config) { + "use strict"; + + var config = custom_config || {}, aggressive = config.aggressive || false, sensitivity = setDefault(config.sensitivity, 20), timer = setDefault(config.timer, 1000), @@ -40,18 +42,20 @@ return function ouibounce(el, config) { setTimeout(attachOuiBounce, timer); function attachOuiBounce() { + if (isDisabled()) { return; } + _html.addEventListener('mouseleave', handleMouseleave); _html.addEventListener('mouseenter', handleMouseenter); _html.addEventListener('keydown', handleKeydown); } function handleMouseleave(e) { - if (e.clientY > sensitivity || (checkCookieValue(cookieName, 'true') && !aggressive)) return; + if (e.clientY > sensitivity) { return; } - _delayTimer = setTimeout(_fireAndCallback, delay); + _delayTimer = setTimeout(fire, delay); } - function handleMouseenter(e) { + function handleMouseenter() { if (_delayTimer) { clearTimeout(_delayTimer); _delayTimer = null; @@ -60,11 +64,11 @@ return function ouibounce(el, config) { var disableKeydown = false; function handleKeydown(e) { - if (disableKeydown || checkCookieValue(cookieName, 'true') && !aggressive) return; - else if(!e.metaKey || e.keyCode !== 76) return; + if (disableKeydown) { return; } + else if(!e.metaKey || e.keyCode !== 76) { return; } disableKeydown = true; - _delayTimer = setTimeout(_fireAndCallback, delay); + _delayTimer = setTimeout(fire, delay); } function checkCookieValue(cookieName, value) { @@ -83,20 +87,23 @@ return function ouibounce(el, config) { return ret; } - function _fireAndCallback() { - fire(); - callback(); + function isDisabled() { + return checkCookieValue(cookieName, 'true') && !aggressive; } + // You can use ouibounce without passing an element + // https://github.com/carlsednaoui/ouibounce/issues/30 function fire() { - // You can use ouibounce without passing an element - // https://github.com/carlsednaoui/ouibounce/issues/30 - if (el) el.style.display = 'block'; + if (isDisabled()) { return; } + + if (el) { el.style.display = 'block'; } + + callback(); disable(); } - function disable(options) { - var options = options || {}; + function disable(custom_options) { + var options = custom_options || {}; // you can pass a specific cookie expiration when using the OuiBounce API // ex: _ouiBounce.disable({ cookieExpire: 5 }); @@ -130,9 +137,12 @@ return function ouibounce(el, config) { return { fire: fire, - disable: disable + disable: disable, + isDisabled: isDisabled }; } + +/*exported ouibounce */ ; })); diff --git a/build/ouibounce.min.js b/build/ouibounce.min.js index d61a9a4..cd43043 100644 --- a/build/ouibounce.min.js +++ b/build/ouibounce.min.js @@ -1 +1 @@ -!function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n(require,exports,module):e.ouibounce=n()}(this,function(){return function(e,n){function o(e,n){return"undefined"==typeof e?n:e}function t(e){var n=24*e*60*60*1e3,o=new Date;return o.setTime(o.getTime()+n),"; expires="+o.toUTCString()}function i(){L.addEventListener("mouseleave",u),L.addEventListener("mouseenter",r),L.addEventListener("keydown",c)}function u(e){e.clientY>v||d(T,"true")&&!l||(w=setTimeout(m,p))}function r(){w&&(clearTimeout(w),w=null)}function c(e){g||d(T,"true")&&!l||e.metaKey&&76===e.keyCode&&(g=!0,w=setTimeout(m,p))}function d(e,n){return a()[e]===n}function a(){for(var e=document.cookie.split("; "),n={},o=e.length-1;o>=0;o--){var t=e[o].split("=");n[t[0]]=t[1]}return n}function m(){f(),y()}function f(){e&&(e.style.display="block"),s()}function s(e){var e=e||{};"undefined"!=typeof e.cookieExpire&&(E=t(e.cookieExpire)),e.sitewide===!0&&(b=";path=/"),"undefined"!=typeof e.cookieDomain&&(x=";domain="+e.cookieDomain),"undefined"!=typeof e.cookieName&&(T=e.cookieName),document.cookie=T+"=true"+E+x+b,L.removeEventListener("mouseleave",u),L.removeEventListener("mouseenter",r),L.removeEventListener("keydown",c)}var n=n||{},l=n.aggressive||!1,v=o(n.sensitivity,20),k=o(n.timer,1e3),p=o(n.delay,0),y=n.callback||function(){},E=t(n.cookieExpire)||"",x=n.cookieDomain?";domain="+n.cookieDomain:"",T=n.cookieName?n.cookieName:"viewedOuibounceModal",b=n.sitewide===!0?";path=/":"",w=null,L=document.documentElement;setTimeout(i,k);var g=!1;return{fire:f,disable:s}}}); \ No newline at end of file +!function(e,n){"function"==typeof define&&define.amd?define(n):"object"==typeof exports?module.exports=n(require,exports,module):e.ouibounce=n()}(this,function(e,n,o){return function(e,n){"use strict";function o(e,n){return"undefined"==typeof e?n:e}function i(e){var n=24*e*60*60*1e3,o=new Date;return o.setTime(o.getTime()+n),"; expires="+o.toUTCString()}function t(){s()||(L.addEventListener("mouseleave",u),L.addEventListener("mouseenter",r),L.addEventListener("keydown",c))}function u(e){e.clientY>k||(D=setTimeout(m,y))}function r(){D&&(clearTimeout(D),D=null)}function c(e){g||e.metaKey&&76===e.keyCode&&(g=!0,D=setTimeout(m,y))}function d(e,n){return a()[e]===n}function a(){for(var e=document.cookie.split("; "),n={},o=e.length-1;o>=0;o--){var i=e[o].split("=");n[i[0]]=i[1]}return n}function s(){return d(T,"true")&&!v}function m(){s()||(e&&(e.style.display="block"),E(),f())}function f(e){var n=e||{};"undefined"!=typeof n.cookieExpire&&(b=i(n.cookieExpire)),n.sitewide===!0&&(w=";path=/"),"undefined"!=typeof n.cookieDomain&&(x=";domain="+n.cookieDomain),"undefined"!=typeof n.cookieName&&(T=n.cookieName),document.cookie=T+"=true"+b+x+w,L.removeEventListener("mouseleave",u),L.removeEventListener("mouseenter",r),L.removeEventListener("keydown",c)}var l=n||{},v=l.aggressive||!1,k=o(l.sensitivity,20),p=o(l.timer,1e3),y=o(l.delay,0),E=l.callback||function(){},b=i(l.cookieExpire)||"",x=l.cookieDomain?";domain="+l.cookieDomain:"",T=l.cookieName?l.cookieName:"viewedOuibounceModal",w=l.sitewide===!0?";path=/":"",D=null,L=document.documentElement;setTimeout(t,p);var g=!1;return{fire:m,disable:f,isDisabled:s}}}); \ No newline at end of file diff --git a/contributing.md b/contributing.md index 43e186f..4100b5f 100644 --- a/contributing.md +++ b/contributing.md @@ -11,10 +11,10 @@ To get OuiBounce ready locally you'll need to: 1. Run `npm install` 1. Make sure `gulp` is installed globally - You can do that by running `npm install -g gulp` - 1. Open the index file in the `/test/` folder -- + 1. Open the index file in the `/test/` folder -- - Note: Cookies won't work if you simply open the file. You'll need to have a server ready to serve the page. The easiest way is to - run: `python -m SimpleHTTPServer` - [http://127.0.0.1:8000/test/index.html](http://127.0.0.1:8000/test/index.html) ### Dev Build -To minify OuiBounce run `gulp build` from the command line. \ No newline at end of file +To minify OuiBounce run `gulp build` from the command line. diff --git a/gulpfile.js b/gulpfile.js index fa1b076..e4777f5 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -4,16 +4,26 @@ var gulp = require('gulp'), minifyCSS = require('gulp-minify-css'), umd_wrap = require('gulp-wrap-umd'), stylus = require('gulp-stylus'), - rename = require('gulp-rename'); + rename = require('gulp-rename'), + jshint = require('gulp-jshint'), + stylish = require('jshint-stylish'); -gulp.task('build', function() { +// Because it's always best to have your code checked +// If this task fails, build will fail too +gulp.task('jshint', function() { - gulp.src('source/ouibounce.js') - .pipe(umd_wrap({ namespace: 'ouibounce' })) - .pipe(gulp.dest('build')); + gulp.src('source/ouibounce.js') + .pipe( jshint( '.jshintrc' ) ) + .pipe( jshint.reporter( stylish ) ) + .pipe( jshint.reporter( 'fail' ) ); + +}); + +gulp.task('build', ['jshint'], function() { gulp.src('source/ouibounce.js') .pipe(umd_wrap({ namespace: 'ouibounce' })) + .pipe(gulp.dest('build')) .pipe(uglify()) .pipe(rename('ouibounce.min.js')) .pipe(gulp.dest('build')); @@ -22,18 +32,16 @@ gulp.task('build', function() { .pipe(stylus()) .pipe(prefix()) .pipe(rename('ouibounce.css')) - .pipe(gulp.dest('test')); - - gulp.src('test/ouibounce.styl') - .pipe(stylus()) - .pipe(prefix()) + .pipe(gulp.dest('test')) .pipe(minifyCSS()) .pipe(rename('ouibounce.min.css')) .pipe(gulp.dest('test')); }); + + // Rerun the task when a file changes -gulp.task('watch', function () { +gulp.task('watch', function() { gulp.watch('test/ouibounce.styl', ['build']); gulp.watch('source/ouibounce.js', ['build']); }); diff --git a/package.json b/package.json index c15428f..a0433c4 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,8 @@ "mocha": "~1.17.1", "should": "~3.1.2", "zombie": "~2.0.0-alpha29", - "gulp-stylus": "0.0.13" + "gulp-stylus": "0.0.13", + "jshint-stylish": "~2.0.0", + "gulp-jshint": "~1.11.0" } } diff --git a/source/ouibounce.js b/source/ouibounce.js index 02a4fb9..1bb73dd 100644 --- a/source/ouibounce.js +++ b/source/ouibounce.js @@ -1,5 +1,7 @@ -function ouibounce(el, config) { - var config = config || {}, +function ouibounce(el, custom_config) { + "use strict"; + + var config = custom_config || {}, aggressive = config.aggressive || false, sensitivity = setDefault(config.sensitivity, 20), timer = setDefault(config.timer, 1000), @@ -28,18 +30,20 @@ function ouibounce(el, config) { setTimeout(attachOuiBounce, timer); function attachOuiBounce() { + if (isDisabled()) { return; } + _html.addEventListener('mouseleave', handleMouseleave); _html.addEventListener('mouseenter', handleMouseenter); _html.addEventListener('keydown', handleKeydown); } function handleMouseleave(e) { - if (e.clientY > sensitivity || (checkCookieValue(cookieName, 'true') && !aggressive)) return; + if (e.clientY > sensitivity) { return; } - _delayTimer = setTimeout(_fireAndCallback, delay); + _delayTimer = setTimeout(fire, delay); } - function handleMouseenter(e) { + function handleMouseenter() { if (_delayTimer) { clearTimeout(_delayTimer); _delayTimer = null; @@ -48,11 +52,11 @@ function ouibounce(el, config) { var disableKeydown = false; function handleKeydown(e) { - if (disableKeydown || checkCookieValue(cookieName, 'true') && !aggressive) return; - else if(!e.metaKey || e.keyCode !== 76) return; + if (disableKeydown) { return; } + else if(!e.metaKey || e.keyCode !== 76) { return; } disableKeydown = true; - _delayTimer = setTimeout(_fireAndCallback, delay); + _delayTimer = setTimeout(fire, delay); } function checkCookieValue(cookieName, value) { @@ -71,20 +75,23 @@ function ouibounce(el, config) { return ret; } - function _fireAndCallback() { - fire(); - callback(); + function isDisabled() { + return checkCookieValue(cookieName, 'true') && !aggressive; } + // You can use ouibounce without passing an element + // https://github.com/carlsednaoui/ouibounce/issues/30 function fire() { - // You can use ouibounce without passing an element - // https://github.com/carlsednaoui/ouibounce/issues/30 - if (el) el.style.display = 'block'; + if (isDisabled()) { return; } + + if (el) { el.style.display = 'block'; } + + callback(); disable(); } - function disable(options) { - var options = options || {}; + function disable(custom_options) { + var options = custom_options || {}; // you can pass a specific cookie expiration when using the OuiBounce API // ex: _ouiBounce.disable({ cookieExpire: 5 }); @@ -118,6 +125,9 @@ function ouibounce(el, config) { return { fire: fire, - disable: disable + disable: disable, + isDisabled: isDisabled }; } + +/*exported ouibounce */ diff --git a/test/fixtures/manual.html b/test/fixtures/manual.html new file mode 100644 index 0000000..5c1125a --- /dev/null +++ b/test/fixtures/manual.html @@ -0,0 +1,19 @@ + + + + + + + +
+ + + + diff --git a/test/test.js b/test/test.js index 933a8d0..193ba17 100644 --- a/test/test.js +++ b/test/test.js @@ -5,7 +5,7 @@ var should = require('should'), var browser = new Zombie(); describe('Performs basic OuiBounce functionality', function() { - + before(function(done) { loadPage.call(this, 'basic.html', done); }); @@ -50,8 +50,8 @@ describe('Performs basic OuiBounce functionality', function() { }); -describe('Performs basic OuiBounce functionality', function() { - +describe('Performs aggressive OuiBounce functionality', function() { + before(function(done) { loadPage.call(this, 'aggressive.html', done); }); @@ -73,6 +73,26 @@ describe('Performs basic OuiBounce functionality', function() { }); }); +describe('Performs manual OuiBounce functionality', function() { + + before(function(done) { + loadPage.call(this, 'manual.html', done); + }); + + it('should invoke the callback when manually fired', function(done) { + // save window context + _this = this; + + // ensure ouiBouce already fired + should(_this.window.ouibounceFired).equal(false); + + _this.window.modal.fire(); + + should(_this.window.ouibounceFired).equal(true); + done(); + }); +}); + function loadPage(_path, done) { var _this = this,