diff --git a/.travis.yml b/.travis.yml index 6652193..9bfdddd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,6 @@ node_js: before_script: - npm install -g grunt-cli - npm install -g bower - - bower install \ No newline at end of file + - bower install +script: + - npm run-script test-on-travis diff --git a/Gruntfile.js b/Gruntfile.js index a7cb525..4e31575 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -6,11 +6,26 @@ module.exports = function( grunt ) { grunt.initConfig({ // Metadata. pkg: grunt.file.readJSON( 'package.json' ), - banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' + + title: '<%= pkg.title || pkg.name %> - v<%= pkg.version %>', + buildNumber: process.env.TRAVIS_BUILD_ID || grunt.template.today( 'isoUtcDateTime' ), + banner: '/*! <%= title %> - ' + '<%= grunt.template.today("yyyy-mm-dd") %>\n' + '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' + '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' + ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n', + + browsers: [ + { browserName: 'Internet Explorer' }, + { browserName: 'Chrome' }, + { browserName: 'iPhone' }, + { browserName: 'android' }, + { browserName: 'Firefox' }, + // old IE + { browserName: 'Internet Explorer', version: '9' }, + { browserName: 'Internet Explorer', version: '8' }, + { browserName: 'Internet Explorer', version: '6' } + ], + // Task configuration. clean: { files: [ 'dist' ] @@ -51,27 +66,69 @@ module.exports = function( grunt ) { jquery: { options: { urls: [ - 'http://localhost:8000/test/events.html?jquery=1.4.4', - 'http://localhost:8000/test/instructions.html?jquery=1.4.4', - 'http://localhost:8000/test/plugin.html?jquery=1.4.4', - 'http://localhost:8000/test/relevantWhen.html?jquery=1.4.4', - 'http://localhost:8000/test/ui.html?jquery=1.4.4', + 'http://127.0.0.1:8000/test/events.html?jquery=1.4.4', + 'http://127.0.0.1:8000/test/instructions.html?jquery=1.4.4', + 'http://127.0.0.1:8000/test/plugin.html?jquery=1.4.4', + 'http://127.0.0.1:8000/test/relevantWhen.html?jquery=1.4.4', + 'http://127.0.0.1:8000/test/ui.html?jquery=1.4.4', // 1.7.2 - 'http://localhost:8000/test/events.html?jquery=1.7.2', - 'http://localhost:8000/test/instructions.html?jquery=1.7.2', - 'http://localhost:8000/test/plugin.html?jquery=1.7.2', - 'http://localhost:8000/test/relevantWhen.html?jquery=1.7.2', - 'http://localhost:8000/test/ui.html?jquery=1.7.2', + 'http://127.0.0.1:8000/test/events.html?jquery=1.7.2', + 'http://127.0.0.1:8000/test/instructions.html?jquery=1.7.2', + 'http://127.0.0.1:8000/test/plugin.html?jquery=1.7.2', + 'http://127.0.0.1:8000/test/relevantWhen.html?jquery=1.7.2', + 'http://127.0.0.1:8000/test/ui.html?jquery=1.7.2', // 2.1.0 - 'http://localhost:8000/test/events.html?jquery=2.1.0', - 'http://localhost:8000/test/instructions.html?jquery=2.1.0', - 'http://localhost:8000/test/plugin.html?jquery=2.1.0', - 'http://localhost:8000/test/relevantWhen.html?jquery=2.1.0', - 'http://localhost:8000/test/ui.html?jquery=2.1.0', + 'http://127.0.0.1:8000/test/events.html?jquery=2.1.0', + 'http://127.0.0.1:8000/test/instructions.html?jquery=2.1.0', + 'http://127.0.0.1:8000/test/plugin.html?jquery=2.1.0', + 'http://127.0.0.1:8000/test/relevantWhen.html?jquery=2.1.0', + 'http://127.0.0.1:8000/test/ui.html?jquery=2.1.0', ] } } }, + 'saucelabs-qunit': { + events: { + options: { + testname: 'events - <%= title %>', + build: '<%= buildNumber %>', + urls: [ 'http://127.0.0.1:8000/test/events.html?jquery=1.7.2' ], + browsers: '<%= browsers %>' + } + }, + instructions: { + options: { + testname: 'instructions - <%= title %>', + build: '<%= buildNumber %>', + urls: [ 'http://127.0.0.1:8000/test/instructions.html?jquery=1.7.2' ], + browsers: '<%= browsers %>' + } + }, + plugin: { + options: { + testname: 'plugin - <%= title %>', + build: '<%= buildNumber %>', + urls: [ 'http://127.0.0.1:8000/test/plugin.html?jquery=1.7.2' ], + browsers: '<%= browsers %>' + } + }, + relevantWhen: { + options: { + testname: 'relevantWhen - <%= title %>', + build: '<%= buildNumber %>', + urls: [ 'http://127.0.0.1:8000/test/relevantWhen.html?jquery=1.7.2' ], + browsers: '<%= browsers %>' + } + }, + ui: { + options: { + testname: 'ui - <%= title %>', + build: '<%= buildNumber %>', + urls: [ 'http://127.0.0.1:8000/test/ui.html?jquery=1.7.2' ], + browsers: '<%= browsers %>' + } + }, + }, jshint: { gruntfile: { options: { @@ -114,11 +171,13 @@ module.exports = function( grunt ) { grunt.loadNpmTasks( 'grunt-contrib-uglify' ); grunt.loadNpmTasks( 'grunt-contrib-connect' ); grunt.loadNpmTasks( 'grunt-contrib-qunit' ); + grunt.loadNpmTasks( 'grunt-saucelabs' ); grunt.loadNpmTasks( 'grunt-contrib-jshint' ); grunt.loadNpmTasks( 'grunt-contrib-watch' ); // Default task. grunt.registerTask( 'test', [ 'jshint', 'connect', 'qunit' ]); + grunt.registerTask( 'test-remote', [ 'jshint', 'qunit:unit', 'connect', 'clean', 'saucelabs-qunit' ]); grunt.registerTask( 'produce', [ 'clean', 'concat', 'uglify' ]); grunt.registerTask( 'default', [ 'test', 'produce' ]); diff --git a/Readme.md b/Readme.md index 21dd7ef..7642632 100644 --- a/Readme.md +++ b/Readme.md @@ -36,7 +36,6 @@ indicates an element should be made irrelevant (hidden). No change for irrelevan 2. removes `@hidden` attribute 3. removes `aria-hidden` 4. shows the element (uses a `slideDown` transition) -5. fires event `relevant-done` ## .relevance( 'hide' ) @@ -44,18 +43,66 @@ indicates an element should be made irrelevant (hidden). No change for irrelevan 2. disables descendent form fields and self (if a form field) 3. adds `@hidden` attribute 4. adds `aria-hidden` -5. fires event `irrelevant-done` ## Events -| Event name | Fired when | If you cancel it… | -|:------------------|:------------------------------------------------------------------|:---------------------------------------------------| -| relevant | an element that is hidden has become relevant and will be shown | the element will remain irrelevant and stay hidden | -| irrelevant | an element that is shown has become irrelevant and will be hidden | the element will remain relevant and be visible | -| relevant-done | an element became relevant and is now shown | not applicable | -| irrelevant-done | an element became irrelevant and has been hidden | not applicable | +| Event name | Fired when | If you cancel it… | +|:-----------|:------------------------------------------------------------------|:---------------------------------------------------| +| relevant | an element that is hidden has become relevant and will be shown | the element will remain irrelevant and stay hidden | +| irrelevant | an element that is shown has become irrelevant and will be hidden | the element will remain relevant and be visible | You can suppress the `relevant` and `irrelevant` events and cancel them to prevent an elements relevance state from changing. You will need to cancel the event before it reaches the `document` (this is where the default handlers are bound). -The `relevant-done` and `irrelevant-done` events are fired after animations are complete and are provided as hooks in case you need to redraw the layout. +## Browser support notes + +### Hiding elements when not relevant + +Elements are hidden by toggling the HTML5 `hidden` attribute. Modern browsers support this element. You can provide support in older browsers by using: + +```css +[hidden] { + display: none; +} +``` + +However, the script will check whether 'hidden' elements remain visible. If they are, it will use the jQuery `.hide()` and `.slideDown()` methods to control relevance. This results in the style `display` being changed to `none` when elements are not relevant, and provides a fallback animation. + +You can override `hidden` in modern browsers to implement CSS transitions. For example: + +```css +body { + line-height: 1.3; +} +li { + transition: all 0.3s ease; +} +li[hidden] { + max-height: 0; + line-height: 0; + opacity: 0; + overflow: hidden; +} +``` + +### Toggling relevance with `select` + +Make sure your `option` elements have a `value` attribute to support older browsers. The relevance script relies on reading the `.value` property of the `select` element. Old browsers do not provide this property unless it is also specified on the `option` elements. + +Use this format: + +```html + +``` + +This format (missing `value`) fails in old browsers: + +```html + +``` diff --git a/bower.json b/bower.json index ac95df3..352ea1b 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "relevance", "description": "Progressive disclosure (relevance) for HTML elements", - "version": "1.2.0", + "version": "2.0.0", "homepage": "https://github.com/bboyle/relevance", "authors": [ "Ben Boyle " diff --git a/dist/relevance.js b/dist/relevance.js index 44aedb6..34d2e21 100644 --- a/dist/relevance.js +++ b/dist/relevance.js @@ -1,15 +1,19 @@ -/*! relevance - v1.6.0-beta.1 - 2015-02-24 -* https://github.com/bboyle/relevance -* Copyright (c) 2015 Ben Boyle; Licensed MIT */ +/*! relevance - v2.0.0 - 2015-02-27 +* https://github.com/bboyle/relevance +* Copyright (c) 2015 Ben Boyle; Licensed MIT */ if ( jQuery !== 'undefined' ) { (function( $ ) { 'use strict'; var relevantEvent = 'relevant', irrelevantEvent = 'irrelevant', - relevantDoneEvent = 'relevant-done', - irrelevantDoneEvent = 'irrelevant-done', elementsToDisable = 'button, input, select, textarea', + polyfillHidden = (function() { + var hidden = $( '' ); + var isHidden = hidden.appendTo( 'body' ).is( ':visible' ); + hidden.remove(); + return ! isHidden; + }()), formElementsByName = function( form, name ) { // filter out the @id matching of HTMLFormElement.elements[] @@ -94,17 +98,13 @@ if ( jQuery !== 'undefined' ) { // if the element is visible, fire an "irrelevant" event relevant: function( makeRelevant ) { var targets; - if ( ! makeRelevant ) { - targets = this.filter( ':visible' ).trigger( irrelevantEvent ); - - if ( targets.length ) { - recalculateDependents.call( targets, false ); - } + if ( makeRelevant ) { + targets = this.filter( '[hidden]' ).trigger( relevantEvent ); } else { - targets = this.filter( ':hidden' ).trigger( relevantEvent ); - if ( targets.length ) { - recalculateDependents.call( targets ); - } + targets = this.not( '[hidden]' ).trigger( irrelevantEvent ); + } + if ( targets.length ) { + recalculateDependents.call( targets, makeRelevant ); } return this; }, @@ -123,33 +123,36 @@ if ( jQuery !== 'undefined' ) { }); // stop animation, remove @hidden and @aria-hidden, start showing - return this.stop( true, true ).removeAttr( 'hidden' ).removeAttr( 'aria-hidden' ).slideDown(function() { - // done - $( this ).trigger( relevantDoneEvent ); - }); + if ( polyfillHidden ) { + this.stop( true, true ).slideDown(); + } + return this.removeAttr( 'hidden' ).removeAttr( 'aria-hidden' ); }, // $( x ).relevance( 'hide' ) // hides the element (does not check if element is already hidden) - // triggers 'irrelevant-done' after hiding is complete hide: function() { - // stop animation, start hiding - return this.stop( true, true ).hide( 0, function() { - var $this = $( this ); + this.attr({ + hidden: 'hidden', + 'aria-hidden': 'true' + }); - // disable elements (including self if appropriate) - $this.filter( elementsToDisable ).add( $this.find( elementsToDisable )).each(function() { + if ( polyfillHidden ) { + this.stop( true, true ).hide( 0, function() { + var $this = $( this ); + // disable elements (including self if appropriate) + $this.filter( elementsToDisable ).add( $this.find( elementsToDisable )).each(function() { + this.setAttribute( 'disabled', 'disabled' ); + }); + }); + } else { + this.filter( elementsToDisable ).add( this.find( elementsToDisable )).each(function() { this.setAttribute( 'disabled', 'disabled' ); }); + } - // once hidden, toggle irrelevant with @hidden and aria-hidden - this.setAttribute( 'hidden', 'hidden' ); - this.setAttribute( 'aria-hidden', 'true' ); - - // done - $this.trigger( irrelevantDoneEvent ); - }); + return this; }, // $( x ).relevance( 'relevantWhen', { name: radio/checkbox/select, value: requiredValue, negate: false | true }) diff --git a/dist/relevance.min.js b/dist/relevance.min.js index 085f088..7e2926c 100644 --- a/dist/relevance.min.js +++ b/dist/relevance.min.js @@ -1,4 +1,4 @@ -/*! relevance - v1.6.0-beta.1 - 2015-02-24 +/*! relevance - v2.0.0 - 2015-02-27 * https://github.com/bboyle/relevance * Copyright (c) 2015 Ben Boyle; Licensed MIT */ -"undefined"!==jQuery&&!function(a){"use strict";var b="relevant",c="irrelevant",d="relevant-done",e="irrelevant-done",f="button, input, select, textarea",g=function(b,c){return a(b.elements[c]).filter('[name="'+c+'"]')},h=function(a){return a.value},i=function(b,c){var d;for("object"!=typeof b&&(b=[b]),d=0;d li"},b),this.find(b.instructionSelector).each(function(){var c,d,e=a(this),f=e.text(),g=e.closest(b.questionSelector),j=g.prevAll(b.questionSelector),k=!1,l=!1;if(/If different to/.test(f))k=!0,j=j.eq(0),f=j.find(":checkbox").val(),l=!0;else for(f=f.replace(/^.*chose\s+\S([^'"’]+)\S\s+above.*$/,"$1"),c=0;c