diff --git a/.jshintrc b/.jshintrc index 2e88ababdd..4ff0d97920 100644 --- a/.jshintrc +++ b/.jshintrc @@ -109,13 +109,18 @@ // The parameter "predef" should remain empty for this configuration file // to remain as general as possible. "predef": [ - // jQuery library. + // jQuery globals "jQuery", "$", - // Underscore.js library. + // Underscore.js globals "_", - // Jasmine library. + // RequireJS globals + "define", + "require", + "RequireJS", + + // Jasmine globals "jasmine", "describe", "xdescribe", "it", "xit", @@ -126,12 +131,15 @@ "waitsFor", "runs", - // jQuery-Jasmine library. + // jQuery-Jasmine globals "loadFixtures", "appendLoadFixtures", "readFixtures", "setFixtures", "appendSetFixtures", - "spyOnEvent" + "spyOnEvent", + + // Miscellaneous globals + "JSON" ] } diff --git a/cms/static/cms/js/require-config.js b/cms/static/cms/js/require-config.js index 8139da6206..3167c3136f 100644 --- a/cms/static/cms/js/require-config.js +++ b/cms/static/cms/js/require-config.js @@ -17,6 +17,7 @@ require.config({ paths: { "domReady": "js/vendor/domReady", "gettext": "/i18n", + "json2": "js/vendor/json2", "mustache": "js/vendor/mustache", "codemirror": "js/vendor/codemirror-compressed", "codemirror/stex": "js/vendor/CodeMirror/stex", @@ -95,6 +96,9 @@ require.config({ ] }, shim: { + "json2": { + exports: "JSON" + }, "gettext": { exports: "gettext" }, diff --git a/cms/static/coffee/spec/main.coffee b/cms/static/coffee/spec/main.coffee index a2a5cb3083..f567289d27 100644 --- a/cms/static/coffee/spec/main.coffee +++ b/cms/static/coffee/spec/main.coffee @@ -23,6 +23,7 @@ requirejs.config({ "jquery.simulate": "xmodule_js/common_static/js/vendor/jquery.simulate", "datepair": "xmodule_js/common_static/js/vendor/timepicker/datepair", "date": "xmodule_js/common_static/js/vendor/date", + "json2": "xmodule_js/common_static/js/vendor/json2", "moment": "xmodule_js/common_static/js/vendor/moment.min", "moment-with-locales": "xmodule_js/common_static/js/vendor/moment-with-locales.min", "text": "xmodule_js/common_static/js/vendor/requirejs/text", @@ -58,6 +59,9 @@ requirejs.config({ "js/spec/test_utils": "js/spec/test_utils", } shim: { + "json2": { + exports: "JSON" + }, "gettext": { exports: "gettext" }, diff --git a/cms/static/js/utils/drag_and_drop.js b/cms/static/js/utils/drag_and_drop.js index 66fe01a255..436ca7d4ff 100644 --- a/cms/static/js/utils/drag_and_drop.js +++ b/cms/static/js/utils/drag_and_drop.js @@ -1,8 +1,9 @@ -define(["jquery", "jquery.ui", "underscore", "gettext", "common/js/components/views/feedback_notification", "draggabilly", - "js/utils/module"], - function ($, ui, _, gettext, NotificationView, Draggabilly, ModuleUtils) { +define(["jquery", "jquery.ui", "underscore", "json2", "gettext", "draggabilly", + "js/utils/module", "common/js/components/views/feedback_notification"], + function ($, ui, _, JSON, gettext, Draggabilly, ModuleUtils, NotificationView) { + 'use strict'; - var contentDragger = { + var contentDragger = { droppableClasses: 'drop-target drop-target-prepend drop-target-before drop-target-after', validDropClass: "valid-drop", expandOnDropClass: "expand-on-drop", @@ -17,14 +18,15 @@ define(["jquery", "jquery.ui", "underscore", "gettext", "common/js/components/vi var eleY = ele.offset().top; var eleYEnd = eleY + ele.outerHeight(); var containers = $(ele.data('droppable-class')); + var isSibling = function () { + return $(this).data('locator') !== undefined && !$(this).is(ele); + }; for (var i = 0; i < containers.length; i++) { var container = $(containers[i]); // Exclude the 'new unit' buttons, and make sure we don't // prepend an element to itself - var siblings = container.children().filter(function () { - return $(this).data('locator') !== undefined && !$(this).is(ele); - }); + var siblings = container.children().filter(isSibling); // If the container is collapsed, check to see if the // element is on top of its parent list -- don't check the // position of the container @@ -37,8 +39,8 @@ define(["jquery", "jquery.ui", "underscore", "gettext", "common/js/components/vi var collapseFudge = 10; if (Math.abs(eleY - parentListTop) < collapseFudge || (eleY > parentListTop && - eleYEnd - collapseFudge <= parentListTop + parentList.outerHeight()) - ) { + eleYEnd - collapseFudge <= parentListTop + parentList.outerHeight()) + ) { return { ele: container, attachMethod: 'prepend', @@ -101,11 +103,12 @@ define(["jquery", "jquery.ui", "underscore", "gettext", "common/js/components/vi } else { // Dragging up into end of list. - if (j === siblings.length - 1 && yChange < 0 && Math.abs(eleY - siblingYEnd) <= fudge) { + if (j === siblings.length - 1 && yChange < 0 && + Math.abs(eleY - siblingYEnd) <= fudge) { return { - ele: $sibling, - attachMethod: 'after' - }; + ele: $sibling, + attachMethod: 'after' + }; } // Dragging up or down into beginning of list. else if (j === 0 && Math.abs(eleY - siblingY) <= fudge) { @@ -145,8 +148,8 @@ define(["jquery", "jquery.ui", "underscore", "gettext", "common/js/components/vi // Information about the current drag. dragState: {}, - onDragStart: function (draggie, event, pointer) { - var ele = $(draggie.element); + onDragStart: function (draggable) { + var ele = $(draggable.element); this.dragState = { // Which element will be dropped into/onto on success dropDestination: null, @@ -162,7 +165,9 @@ define(["jquery", "jquery.ui", "underscore", "gettext", "common/js/components/vi if (!ele.hasClass(this.collapsedClass)) { ele.addClass(this.collapsedClass); ele.find('.expand-collapse').first().addClass('expand').removeClass('collapse'); - // onDragStart gets called again after the collapse, so we can't just store a variable in the dragState. + + // onDragStart gets called again after the collapse, so we can't + // just store a variable in the dragState. ele.addClass(this.expandOnDropClass); } @@ -171,7 +176,7 @@ define(["jquery", "jquery.ui", "underscore", "gettext", "common/js/components/vi ele.removeClass('was-dragging'); }, - onDragMove: function (draggie, event, pointer) { + onDragMove: function (draggable, event, pointer) { // Handle scrolling of the browser. var scrollAmount = 0; var dragBuffer = 10; @@ -186,13 +191,13 @@ define(["jquery", "jquery.ui", "underscore", "gettext", "common/js/components/vi return; } - var yChange = draggie.dragPoint.y - this.dragState.lastY; + var yChange = draggable.dragPoint.y - this.dragState.lastY; if (yChange !== 0) { this.dragState.direction = yChange; } - this.dragState.lastY = draggie.dragPoint.y; + this.dragState.lastY = draggable.dragPoint.y; - var ele = $(draggie.element); + var ele = $(draggable.element); var destinationInfo = this.findDestination(ele, this.dragState.direction); var destinationEle = destinationInfo.ele; this.dragState.parentList = destinationInfo.parentList; @@ -215,8 +220,8 @@ define(["jquery", "jquery.ui", "underscore", "gettext", "common/js/components/vi } }, - onDragEnd: function (draggie, event, pointer) { - var ele = $(draggie.element); + onDragEnd: function (draggable, event, pointer) { + var ele = $(draggable.element); var destination = this.dragState.dropDestination; // Clear dragging state in preparation for the next event. @@ -284,7 +289,7 @@ define(["jquery", "jquery.ui", "underscore", "gettext", "common/js/components/vi // If drop was into a collapsed parent, the parent will have been // expanded. Views using this class may need to track the // collapse/expand state, so send it with the refresh callback. - var collapsed = element.hasClass(this.collapsedClass); + var collapsed = element.hasClass(contentDragger.collapsedClass); if (_.isFunction(refresh)) { refresh(collapsed); } }; @@ -364,20 +369,20 @@ define(["jquery", "jquery.ui", "underscore", "gettext", "common/js/components/vi if ($(element).data('droppable-class') !== options.droppableClass) { $(element).data({ - 'droppable-class': options.droppableClass, - 'parent-location-selector': options.parentLocationSelector, - 'child-selector': options.type, - 'refresh': options.refresh, - 'ensureChildrenRendered': options.ensureChildrenRendered + 'droppable-class': options.droppableClass, + 'parent-location-selector': options.parentLocationSelector, + 'child-selector': options.type, + 'refresh': options.refresh, + 'ensureChildrenRendered': options.ensureChildrenRendered }); draggable = new Draggabilly(element, { handle: options.handleClass, containment: '.wrapper-dnd' }); - draggable.on('dragStart', _.bind(contentDragger.onDragStart, contentDragger)); - draggable.on('dragMove', _.bind(contentDragger.onDragMove, contentDragger)); - draggable.on('dragEnd', _.bind(contentDragger.onDragEnd, contentDragger)); + draggable.on('dragStart', _.bind(contentDragger.onDragStart, contentDragger, draggable)); + draggable.on('dragMove', _.bind(contentDragger.onDragMove, contentDragger, draggable)); + draggable.on('dragEnd', _.bind(contentDragger.onDragEnd, contentDragger, draggable)); } } }; diff --git a/cms/static/js_test.yml b/cms/static/js_test.yml index 37e229f4eb..b28c63aaf2 100644 --- a/cms/static/js_test.yml +++ b/cms/static/js_test.yml @@ -59,6 +59,7 @@ lib_paths: - xmodule_js/common_static/js/vendor/draggabilly.pkgd.js - xmodule_js/common_static/js/vendor/date.js - xmodule_js/common_static/js/vendor/domReady.js + - xmodule_js/common_static/js/vendor/json2.js - xmodule_js/common_static/js/vendor/URI.min.js - xmodule_js/common_static/js/vendor/jquery.smooth-scroll.min.js - xmodule_js/common_static/coffee/src/jquery.immediateDescendents.js diff --git a/common/static/js/vendor/draggabilly.pkgd.js b/common/static/js/vendor/draggabilly.pkgd.js index 75337fd1a3..9289408151 100644 --- a/common/static/js/vendor/draggabilly.pkgd.js +++ b/common/static/js/vendor/draggabilly.pkgd.js @@ -1,12 +1,155 @@ /*! - * Draggabilly PACKAGED v1.0.5 + * Draggabilly PACKAGED v1.2.4 * Make that shiz draggable * http://draggabilly.desandro.com + * MIT license */ +/** + * Bridget makes jQuery widgets + * v1.1.0 + * MIT license + */ + +( function( window ) { + + + +// -------------------------- utils -------------------------- // + +var slice = Array.prototype.slice; + +function noop() {} + +// -------------------------- definition -------------------------- // + +function defineBridget( $ ) { + +// bail if no jQuery +if ( !$ ) { + return; +} + +// -------------------------- addOptionMethod -------------------------- // + +/** + * adds option method -> $().plugin('option', {...}) + * @param {Function} PluginClass - constructor class + */ +function addOptionMethod( PluginClass ) { + // don't overwrite original option method + if ( PluginClass.prototype.option ) { + return; + } + + // option setter + PluginClass.prototype.option = function( opts ) { + // bail out if not an object + if ( !$.isPlainObject( opts ) ){ + return; + } + this.options = $.extend( true, this.options, opts ); + }; +} + +// -------------------------- plugin bridge -------------------------- // + +// helper function for logging errors +// $.error breaks jQuery chaining +var logError = typeof console === 'undefined' ? noop : + function( message ) { + console.error( message ); + }; + +/** + * jQuery plugin bridge, access methods like $elem.plugin('method') + * @param {String} namespace - plugin name + * @param {Function} PluginClass - constructor class + */ +function bridge( namespace, PluginClass ) { + // add to jQuery fn namespace + $.fn[ namespace ] = function( options ) { + if ( typeof options === 'string' ) { + // call plugin method when first argument is a string + // get arguments for method + var args = slice.call( arguments, 1 ); + + for ( var i=0, len = this.length; i < len; i++ ) { + var elem = this[i]; + var instance = $.data( elem, namespace ); + if ( !instance ) { + logError( "cannot call methods on " + namespace + " prior to initialization; " + + "attempted to call '" + options + "'" ); + continue; + } + if ( !$.isFunction( instance[options] ) || options.charAt(0) === '_' ) { + logError( "no such method '" + options + "' for " + namespace + " instance" ); + continue; + } + + // trigger method with arguments + var returnValue = instance[ options ].apply( instance, args ); + + // break look and return first value if provided + if ( returnValue !== undefined ) { + return returnValue; + } + } + // return this if no return value + return this; + } else { + return this.each( function() { + var instance = $.data( this, namespace ); + if ( instance ) { + // apply options & init + instance.option( options ); + instance._init(); + } else { + // initialize new instance + instance = new PluginClass( this, options ); + $.data( this, namespace, instance ); + } + }); + } + }; + +} + +// -------------------------- bridget -------------------------- // + +/** + * converts a Prototypical class into a proper jQuery plugin + * the class must have a ._init method + * @param {String} namespace - plugin name, used in $().pluginName + * @param {Function} PluginClass - constructor class + */ +$.bridget = function( namespace, PluginClass ) { + addOptionMethod( PluginClass ); + bridge( namespace, PluginClass ); +}; + +return $.bridget; + +} + +// transport +if ( typeof define === 'function' && define.amd ) { + // AMD + define( 'jquery-bridget/jquery.bridget',[ 'jquery' ], defineBridget ); +} else if ( typeof exports === 'object' ) { + defineBridget( require('jquery') ); +} else { + // get jquery from browser global + defineBridget( window.jQuery ); +} + +})( window ); + /*! - * classie - class helper functions + * classie v1.0.1 + * class helper functions * from bonzo https://github.com/ded/bonzo + * MIT license * * classie.has( elem, 'my-class' ) -> true/false * classie.add( elem, 'my-new-class' ) @@ -14,161 +157,479 @@ * classie.toggle( elem, 'my-class' ) */ -/*jshint browser: true, strict: true, undef: true */ -/*global define: false */ +/*jshint browser: true, strict: true, undef: true, unused: true */ +/*global define: false, module: false */ ( function( window ) { - 'use strict'; + // class helper functions from bonzo https://github.com/ded/bonzo - function classReg( className ) { - return new RegExp("(^|\\s+)" + className + "(\\s+|$)"); - } +function classReg( className ) { + return new RegExp("(^|\\s+)" + className + "(\\s+|$)"); +} // classList support for class management // altho to be fair, the api sucks because it won't accept multiple classes at once - var hasClass, addClass, removeClass; +var hasClass, addClass, removeClass; - if ( 'classList' in document.documentElement ) { - hasClass = function( elem, c ) { - return elem.classList.contains( c ); - }; - addClass = function( elem, c ) { - elem.classList.add( c ); - }; - removeClass = function( elem, c ) { - elem.classList.remove( c ); - }; - } - else { - hasClass = function( elem, c ) { - return classReg( c ).test( elem.className ); - }; - addClass = function( elem, c ) { - if ( !hasClass( elem, c ) ) { - elem.className = elem.className + ' ' + c; - } - }; - removeClass = function( elem, c ) { - elem.className = elem.className.replace( classReg( c ), ' ' ); - }; +if ( 'classList' in document.documentElement ) { + hasClass = function( elem, c ) { + return elem.classList.contains( c ); + }; + addClass = function( elem, c ) { + elem.classList.add( c ); + }; + removeClass = function( elem, c ) { + elem.classList.remove( c ); + }; +} +else { + hasClass = function( elem, c ) { + return classReg( c ).test( elem.className ); + }; + addClass = function( elem, c ) { + if ( !hasClass( elem, c ) ) { + elem.className = elem.className + ' ' + c; } + }; + removeClass = function( elem, c ) { + elem.className = elem.className.replace( classReg( c ), ' ' ); + }; +} - function toggleClass( elem, c ) { - var fn = hasClass( elem, c ) ? removeClass : addClass; - fn( elem, c ); - } +function toggleClass( elem, c ) { + var fn = hasClass( elem, c ) ? removeClass : addClass; + fn( elem, c ); +} - var classie = { - // full names - hasClass: hasClass, - addClass: addClass, - removeClass: removeClass, - toggleClass: toggleClass, - // short names - has: hasClass, - add: addClass, - remove: removeClass, - toggle: toggleClass - }; +var classie = { + // full names + hasClass: hasClass, + addClass: addClass, + removeClass: removeClass, + toggleClass: toggleClass, + // short names + has: hasClass, + add: addClass, + remove: removeClass, + toggle: toggleClass +}; // transport - if ( typeof define === 'function' && define.amd ) { - // AMD - define("classie", classie); - } else { - // browser global - window.classie = classie; - } +if ( typeof define === 'function' && define.amd ) { + // AMD + define( 'classie/classie',classie ); +} else if ( typeof exports === 'object' ) { + // CommonJS + module.exports = classie; +} else { + // browser global + window.classie = classie; +} })( window ); /*! - * eventie v1.0.3 - * event binding helper - * eventie.bind( elem, 'click', myFn ) - * eventie.unbind( elem, 'click', myFn ) + * getStyleProperty v1.0.4 + * original by kangax + * http://perfectionkills.com/feature-testing-css-properties/ + * MIT license */ -/*jshint browser: true, undef: true, unused: true */ -/*global define: false */ +/*jshint browser: true, strict: true, undef: true */ +/*global define: false, exports: false, module: false */ ( function( window ) { - 'use strict'; - var docElem = document.documentElement; - var bind = function() {}; +var prefixes = 'Webkit Moz ms Ms O'.split(' '); +var docElemStyle = document.documentElement.style; - if ( docElem.addEventListener ) { - bind = function( obj, type, fn ) { - obj.addEventListener( type, fn, false ); - }; - } else if ( docElem.attachEvent ) { - bind = function( obj, type, fn ) { - obj[ type + fn ] = fn.handleEvent ? - function() { - var event = window.event; - // add event.target - event.target = event.target || event.srcElement; - fn.handleEvent.call( fn, event ); - } : - function() { - var event = window.event; - // add event.target - event.target = event.target || event.srcElement; - fn.call( obj, event ); - }; - obj.attachEvent( "on" + type, obj[ type + fn ] ); - }; +function getStyleProperty( propName ) { + if ( !propName ) { + return; + } + + // test standard property first + if ( typeof docElemStyle[ propName ] === 'string' ) { + return propName; + } + + // capitalize + propName = propName.charAt(0).toUpperCase() + propName.slice(1); + + // test vendor specific properties + var prefixed; + for ( var i=0, len = prefixes.length; i < len; i++ ) { + prefixed = prefixes[i] + propName; + if ( typeof docElemStyle[ prefixed ] === 'string' ) { + return prefixed; } - - var unbind = function() {}; - - if ( docElem.removeEventListener ) { - unbind = function( obj, type, fn ) { - obj.removeEventListener( type, fn, false ); - }; - } else if ( docElem.detachEvent ) { - unbind = function( obj, type, fn ) { - obj.detachEvent( "on" + type, obj[ type + fn ] ); - try { - delete obj[ type + fn ]; - } catch ( err ) { - // can't delete window object properties - obj[ type + fn ] = undefined; - } - }; - } - - var eventie = { - bind: bind, - unbind: unbind - }; + } +} // transport - if ( typeof define === 'function' && define.amd ) { - // AMD - define("eventie", eventie); - } else { - // browser global - window.eventie = eventie; - } +if ( typeof define === 'function' && define.amd ) { + // AMD + define( 'get-style-property/get-style-property',[],function() { + return getStyleProperty; + }); +} else if ( typeof exports === 'object' ) { + // CommonJS for Component + module.exports = getStyleProperty; +} else { + // browser global + window.getStyleProperty = getStyleProperty; +} -})( this ); +})( window ); /*! - * EventEmitter v4.2.4 - git.io/ee - * Oliver Caldwell + * getSize v1.2.2 + * measure size of elements * MIT license + */ + +/*jshint browser: true, strict: true, undef: true, unused: true */ +/*global define: false, exports: false, require: false, module: false, console: false */ + +( function( window, undefined ) { + + + +// -------------------------- helpers -------------------------- // + +// get a number from a string, not a percentage +function getStyleSize( value ) { + var num = parseFloat( value ); + // not a percent like '100%', and a number + var isValid = value.indexOf('%') === -1 && !isNaN( num ); + return isValid && num; +} + +function noop() {} + +var logError = typeof console === 'undefined' ? noop : + function( message ) { + console.error( message ); + }; + +// -------------------------- measurements -------------------------- // + +var measurements = [ + 'paddingLeft', + 'paddingRight', + 'paddingTop', + 'paddingBottom', + 'marginLeft', + 'marginRight', + 'marginTop', + 'marginBottom', + 'borderLeftWidth', + 'borderRightWidth', + 'borderTopWidth', + 'borderBottomWidth' +]; + +function getZeroSize() { + var size = { + width: 0, + height: 0, + innerWidth: 0, + innerHeight: 0, + outerWidth: 0, + outerHeight: 0 + }; + for ( var i=0, len = measurements.length; i < len; i++ ) { + var measurement = measurements[i]; + size[ measurement ] = 0; + } + return size; +} + + + +function defineGetSize( getStyleProperty ) { + +// -------------------------- setup -------------------------- // + +var isSetup = false; + +var getStyle, boxSizingProp, isBoxSizeOuter; + +/** + * setup vars and functions + * do it on initial getSize(), rather than on script load + * For Firefox bug https://bugzilla.mozilla.org/show_bug.cgi?id=548397 + */ +function setup() { + // setup once + if ( isSetup ) { + return; + } + isSetup = true; + + var getComputedStyle = window.getComputedStyle; + getStyle = ( function() { + var getStyleFn = getComputedStyle ? + function( elem ) { + return getComputedStyle( elem, null ); + } : + function( elem ) { + return elem.currentStyle; + }; + + return function getStyle( elem ) { + var style = getStyleFn( elem ); + if ( !style ) { + logError( 'Style returned ' + style + + '. Are you running this code in a hidden iframe on Firefox? ' + + 'See http://bit.ly/getsizebug1' ); + } + return style; + }; + })(); + + // -------------------------- box sizing -------------------------- // + + boxSizingProp = getStyleProperty('boxSizing'); + + /** + * WebKit measures the outer-width on style.width on border-box elems + * IE & Firefox measures the inner-width + */ + if ( boxSizingProp ) { + var div = document.createElement('div'); + div.style.width = '200px'; + div.style.padding = '1px 2px 3px 4px'; + div.style.borderStyle = 'solid'; + div.style.borderWidth = '1px 2px 3px 4px'; + div.style[ boxSizingProp ] = 'border-box'; + + var body = document.body || document.documentElement; + body.appendChild( div ); + var style = getStyle( div ); + + isBoxSizeOuter = getStyleSize( style.width ) === 200; + body.removeChild( div ); + } + +} + +// -------------------------- getSize -------------------------- // + +function getSize( elem ) { + setup(); + + // use querySeletor if elem is string + if ( typeof elem === 'string' ) { + elem = document.querySelector( elem ); + } + + // do not proceed on non-objects + if ( !elem || typeof elem !== 'object' || !elem.nodeType ) { + return; + } + + var style = getStyle( elem ); + + // if hidden, everything is 0 + if ( style.display === 'none' ) { + return getZeroSize(); + } + + var size = {}; + size.width = elem.offsetWidth; + size.height = elem.offsetHeight; + + var isBorderBox = size.isBorderBox = !!( boxSizingProp && + style[ boxSizingProp ] && style[ boxSizingProp ] === 'border-box' ); + + // get all measurements + for ( var i=0, len = measurements.length; i < len; i++ ) { + var measurement = measurements[i]; + var value = style[ measurement ]; + value = mungeNonPixel( elem, value ); + var num = parseFloat( value ); + // any 'auto', 'medium' value will be 0 + size[ measurement ] = !isNaN( num ) ? num : 0; + } + + var paddingWidth = size.paddingLeft + size.paddingRight; + var paddingHeight = size.paddingTop + size.paddingBottom; + var marginWidth = size.marginLeft + size.marginRight; + var marginHeight = size.marginTop + size.marginBottom; + var borderWidth = size.borderLeftWidth + size.borderRightWidth; + var borderHeight = size.borderTopWidth + size.borderBottomWidth; + + var isBorderBoxSizeOuter = isBorderBox && isBoxSizeOuter; + + // overwrite width and height if we can get it from style + var styleWidth = getStyleSize( style.width ); + if ( styleWidth !== false ) { + size.width = styleWidth + + // add padding and border unless it's already including it + ( isBorderBoxSizeOuter ? 0 : paddingWidth + borderWidth ); + } + + var styleHeight = getStyleSize( style.height ); + if ( styleHeight !== false ) { + size.height = styleHeight + + // add padding and border unless it's already including it + ( isBorderBoxSizeOuter ? 0 : paddingHeight + borderHeight ); + } + + size.innerWidth = size.width - ( paddingWidth + borderWidth ); + size.innerHeight = size.height - ( paddingHeight + borderHeight ); + + size.outerWidth = size.width + marginWidth; + size.outerHeight = size.height + marginHeight; + + return size; +} + +// IE8 returns percent values, not pixels +// taken from jQuery's curCSS +function mungeNonPixel( elem, value ) { + // IE8 and has percent value + if ( window.getComputedStyle || value.indexOf('%') === -1 ) { + return value; + } + var style = elem.style; + // Remember the original values + var left = style.left; + var rs = elem.runtimeStyle; + var rsLeft = rs && rs.left; + + // Put in the new values to get a computed value out + if ( rsLeft ) { + rs.left = elem.currentStyle.left; + } + style.left = value; + value = style.pixelLeft; + + // Revert the changed values + style.left = left; + if ( rsLeft ) { + rs.left = rsLeft; + } + + return value; +} + +return getSize; + +} + +// transport +if ( typeof define === 'function' && define.amd ) { + // AMD for RequireJS + define( 'get-size/get-size',[ 'get-style-property/get-style-property' ], defineGetSize ); +} else if ( typeof exports === 'object' ) { + // CommonJS for Component + module.exports = defineGetSize( require('desandro-get-style-property') ); +} else { + // browser global + window.getSize = defineGetSize( window.getStyleProperty ); +} + +})( window ); + +/*! + * eventie v1.0.6 + * event binding helper + * eventie.bind( elem, 'click', myFn ) + * eventie.unbind( elem, 'click', myFn ) + * MIT license + */ + +/*jshint browser: true, undef: true, unused: true */ +/*global define: false, module: false */ + +( function( window ) { + + + +var docElem = document.documentElement; + +var bind = function() {}; + +function getIEEvent( obj ) { + var event = window.event; + // add event.target + event.target = event.target || event.srcElement || obj; + return event; +} + +if ( docElem.addEventListener ) { + bind = function( obj, type, fn ) { + obj.addEventListener( type, fn, false ); + }; +} else if ( docElem.attachEvent ) { + bind = function( obj, type, fn ) { + obj[ type + fn ] = fn.handleEvent ? + function() { + var event = getIEEvent( obj ); + fn.handleEvent.call( fn, event ); + } : + function() { + var event = getIEEvent( obj ); + fn.call( obj, event ); + }; + obj.attachEvent( "on" + type, obj[ type + fn ] ); + }; +} + +var unbind = function() {}; + +if ( docElem.removeEventListener ) { + unbind = function( obj, type, fn ) { + obj.removeEventListener( type, fn, false ); + }; +} else if ( docElem.detachEvent ) { + unbind = function( obj, type, fn ) { + obj.detachEvent( "on" + type, obj[ type + fn ] ); + try { + delete obj[ type + fn ]; + } catch ( err ) { + // can't delete window object properties + obj[ type + fn ] = undefined; + } + }; +} + +var eventie = { + bind: bind, + unbind: unbind +}; + +// ----- module definition ----- // + +if ( typeof define === 'function' && define.amd ) { + // AMD + define( 'eventie/eventie',eventie ); +} else if ( typeof exports === 'object' ) { + // CommonJS + module.exports = eventie; +} else { + // browser global + window.eventie = eventie; +} + +})( window ); + +/*! + * EventEmitter v4.2.11 - git.io/ee + * Unlicense - http://unlicense.org/ + * Oliver Caldwell - http://oli.me.uk/ * @preserve */ -(function () { - 'use strict'; +;(function () { + /** * Class for managing events. @@ -179,12 +640,12 @@ function EventEmitter() {} // Shortcuts to improve speed and size - - // Easy access to the prototype var proto = EventEmitter.prototype; + var exports = this; + var originalGlobalValue = exports.EventEmitter; /** - * Finds the index of the listener for the event in it's storage array. + * Finds the index of the listener for the event in its storage array. * * @param {Function[]} listeners Array of listeners to search through. * @param {Function} listener Method to look for. @@ -231,7 +692,7 @@ // Return a concatenated array of all matching events if // the selector is a regular expression. - if (typeof evt === 'object') { + if (evt instanceof RegExp) { response = {}; for (key in events) { if (events.hasOwnProperty(key) && evt.test(key)) { @@ -315,7 +776,7 @@ /** * Semi-alias of addListener. It will add a listener that will be - * automatically removed after it's first execution. + * automatically removed after its first execution. * * @param {String|RegExp} evt Name of the event to attach the listener to. * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling. @@ -437,7 +898,7 @@ var single = remove ? this.removeListener : this.addListener; var multiple = remove ? this.removeListeners : this.addListeners; - // If evt is an object then pass each of it's properties to this method + // If evt is an object then pass each of its properties to this method if (typeof evt === 'object' && !(evt instanceof RegExp)) { for (i in evt) { if (evt.hasOwnProperty(i) && (value = evt[i])) { @@ -484,7 +945,7 @@ // Remove all listeners for the specified event delete events[evt]; } - else if (type === 'object') { + else if (evt instanceof RegExp) { // Remove all events matching the regex. for (key in events) { if (events.hasOwnProperty(key) && evt.test(key)) { @@ -609,9 +1070,19 @@ return this._events || (this._events = {}); }; + /** + * Reverts the global {@link EventEmitter} to its previous value and returns a reference to this version. + * + * @return {Function} Non conflicting EventEmitter class. + */ + EventEmitter.noConflict = function noConflict() { + exports.EventEmitter = originalGlobalValue; + return EventEmitter; + }; + // Expose the class either via AMD, CommonJS or the global object if (typeof define === 'function' && define.amd) { - define("EventEmitter", function () { + define('eventEmitter/EventEmitter',[],function () { return EventEmitter; }); } @@ -619,758 +1090,1164 @@ module.exports = EventEmitter; } else { - this.EventEmitter = EventEmitter; + exports.EventEmitter = EventEmitter; } }.call(this)); /*! - * getStyleProperty by kangax - * http://perfectionkills.com/feature-testing-css-properties/ + * Unipointer v1.1.0 + * base class for doing one thing with pointer event + * MIT license */ -/*jshint browser: true, strict: true, undef: true */ -/*globals define: false */ +/*jshint browser: true, undef: true, unused: true, strict: true */ +/*global define: false, module: false, require: false */ -( function( window ) { +( function( window, factory ) { + + // universal module definition - 'use strict'; + if ( typeof define == 'function' && define.amd ) { + // AMD + define( 'unipointer/unipointer',[ + 'eventEmitter/EventEmitter', + 'eventie/eventie' + ], function( EventEmitter, eventie ) { + return factory( window, EventEmitter, eventie ); + }); + } else if ( typeof exports == 'object' ) { + // CommonJS + module.exports = factory( + window, + require('wolfy87-eventemitter'), + require('eventie') + ); + } else { + // browser global + window.Unipointer = factory( + window, + window.EventEmitter, + window.eventie + ); + } - var prefixes = 'Webkit Moz ms Ms O'.split(' '); - var docElemStyle = document.documentElement.style; +}( window, function factory( window, EventEmitter, eventie ) { - function getStyleProperty( propName ) { - if ( !propName ) { - return; - } - // test standard property first - if ( typeof docElemStyle[ propName ] === 'string' ) { - return propName; - } - // capitalize - propName = propName.charAt(0).toUpperCase() + propName.slice(1); +function noop() {} - // test vendor specific properties - var prefixed; - for ( var i=0, len = prefixes.length; i < len; i++ ) { - prefixed = prefixes[i] + propName; - if ( typeof docElemStyle[ prefixed ] === 'string' ) { - return prefixed; - } - } - } +function Unipointer() {} -// transport - if ( typeof define === 'function' && define.amd ) { - // AMD - define("getStyleProperty", function() { - return getStyleProperty; - }); - } else { - // browser global - window.getStyleProperty = getStyleProperty; - } +// inherit EventEmitter +Unipointer.prototype = new EventEmitter(); -})( window ); +Unipointer.prototype.bindStartEvent = function( elem ) { + this._bindStartEvent( elem, true ); +}; + +Unipointer.prototype.unbindStartEvent = function( elem ) { + this._bindStartEvent( elem, false ); +}; /** - * getSize v1.1.4 - * measure size of elements + * works as unbinder, as you can ._bindStart( false ) to unbind + * @param {Boolean} isBind - will unbind if falsey */ +Unipointer.prototype._bindStartEvent = function( elem, isBind ) { + // munge isBind, default to true + isBind = isBind === undefined ? true : !!isBind; + var bindMethod = isBind ? 'bind' : 'unbind'; -/*jshint browser: true, strict: true, undef: true, unused: true */ -/*global define: false */ + if ( window.navigator.pointerEnabled ) { + // W3C Pointer Events, IE11. See https://coderwall.com/p/mfreca + eventie[ bindMethod ]( elem, 'pointerdown', this ); + } else if ( window.navigator.msPointerEnabled ) { + // IE10 Pointer Events + eventie[ bindMethod ]( elem, 'MSPointerDown', this ); + } else { + // listen for both, for devices like Chrome Pixel + eventie[ bindMethod ]( elem, 'mousedown', this ); + eventie[ bindMethod ]( elem, 'touchstart', this ); + } +}; -( function( window, undefined ) { +// trigger handler methods for events +Unipointer.prototype.handleEvent = function( event ) { + var method = 'on' + event.type; + if ( this[ method ] ) { + this[ method ]( event ); + } +}; - 'use strict'; - -// -------------------------- helpers -------------------------- // - - var defView = document.defaultView; - - var getStyle = defView && defView.getComputedStyle ? - function( elem ) { - return defView.getComputedStyle( elem, null ); - } : - function( elem ) { - return elem.currentStyle; - }; - -// get a number from a string, not a percentage - function getStyleSize( value ) { - var num = parseFloat( value ); - // not a percent like '100%', and a number - var isValid = value.indexOf('%') === -1 && !isNaN( num ); - return isValid && num; +// returns the touch that we're keeping track of +Unipointer.prototype.getTouch = function( touches ) { + for ( var i=0, len = touches.length; i < len; i++ ) { + var touch = touches[i]; + if ( touch.identifier == this.pointerIdentifier ) { + return touch; } + } +}; -// -------------------------- measurements -------------------------- // +// ----- start event ----- // - var measurements = [ - 'paddingLeft', - 'paddingRight', - 'paddingTop', - 'paddingBottom', - 'marginLeft', - 'marginRight', - 'marginTop', - 'marginBottom', - 'borderLeftWidth', - 'borderRightWidth', - 'borderTopWidth', - 'borderBottomWidth' - ]; +Unipointer.prototype.onmousedown = function( event ) { + // dismiss clicks from right or middle buttons + var button = event.button; + if ( button && ( button !== 0 && button !== 1 ) ) { + return; + } + this._pointerDown( event, event ); +}; - function getZeroSize() { - var size = { - width: 0, - height: 0, - innerWidth: 0, - innerHeight: 0, - outerWidth: 0, - outerHeight: 0 - }; - for ( var i=0, len = measurements.length; i < len; i++ ) { - var measurement = measurements[i]; - size[ measurement ] = 0; - } - return size; - } +Unipointer.prototype.ontouchstart = function( event ) { + this._pointerDown( event, event.changedTouches[0] ); +}; + +Unipointer.prototype.onMSPointerDown = +Unipointer.prototype.onpointerdown = function( event ) { + this._pointerDown( event, event ); +}; + +/** + * pointer start + * @param {Event} event + * @param {Event or Touch} pointer + */ +Unipointer.prototype._pointerDown = function( event, pointer ) { + // dismiss other pointers + if ( this.isPointerDown ) { + return; + } + + this.isPointerDown = true; + // save pointer identifier to match up touch events + this.pointerIdentifier = pointer.pointerId !== undefined ? + // pointerId for pointer events, touch.indentifier for touch events + pointer.pointerId : pointer.identifier; + + this.pointerDown( event, pointer ); +}; + +Unipointer.prototype.pointerDown = function( event, pointer ) { + this._bindPostStartEvents( event ); + this.emitEvent( 'pointerDown', [ event, pointer ] ); +}; + +// hash of events to be bound after start event +var postStartEvents = { + mousedown: [ 'mousemove', 'mouseup' ], + touchstart: [ 'touchmove', 'touchend', 'touchcancel' ], + pointerdown: [ 'pointermove', 'pointerup', 'pointercancel' ], + MSPointerDown: [ 'MSPointerMove', 'MSPointerUp', 'MSPointerCancel' ] +}; + +Unipointer.prototype._bindPostStartEvents = function( event ) { + if ( !event ) { + return; + } + // get proper events to match start event + var events = postStartEvents[ event.type ]; + // IE8 needs to be bound to document + var node = event.preventDefault ? window : document; + // bind events to node + for ( var i=0, len = events.length; i < len; i++ ) { + var evnt = events[i]; + eventie.bind( node, evnt, this ); + } + // save these arguments + this._boundPointerEvents = { + events: events, + node: node + }; +}; + +Unipointer.prototype._unbindPostStartEvents = function() { + var args = this._boundPointerEvents; + // IE8 can trigger dragEnd twice, check for _boundEvents + if ( !args || !args.events ) { + return; + } + + for ( var i=0, len = args.events.length; i < len; i++ ) { + var event = args.events[i]; + eventie.unbind( args.node, event, this ); + } + delete this._boundPointerEvents; +}; + +// ----- move event ----- // + +Unipointer.prototype.onmousemove = function( event ) { + this._pointerMove( event, event ); +}; + +Unipointer.prototype.onMSPointerMove = +Unipointer.prototype.onpointermove = function( event ) { + if ( event.pointerId == this.pointerIdentifier ) { + this._pointerMove( event, event ); + } +}; + +Unipointer.prototype.ontouchmove = function( event ) { + var touch = this.getTouch( event.changedTouches ); + if ( touch ) { + this._pointerMove( event, touch ); + } +}; + +/** + * pointer move + * @param {Event} event + * @param {Event or Touch} pointer + * @private + */ +Unipointer.prototype._pointerMove = function( event, pointer ) { + this.pointerMove( event, pointer ); +}; + +// public +Unipointer.prototype.pointerMove = function( event, pointer ) { + this.emitEvent( 'pointerMove', [ event, pointer ] ); +}; + +// ----- end event ----- // +Unipointer.prototype.onmouseup = function( event ) { + this._pointerUp( event, event ); +}; - function defineGetSize( getStyleProperty ) { +Unipointer.prototype.onMSPointerUp = +Unipointer.prototype.onpointerup = function( event ) { + if ( event.pointerId == this.pointerIdentifier ) { + this._pointerUp( event, event ); + } +}; -// -------------------------- box sizing -------------------------- // +Unipointer.prototype.ontouchend = function( event ) { + var touch = this.getTouch( event.changedTouches ); + if ( touch ) { + this._pointerUp( event, touch ); + } +}; - var boxSizingProp = getStyleProperty('boxSizing'); - var isBoxSizeOuter; +/** + * pointer up + * @param {Event} event + * @param {Event or Touch} pointer + * @private + */ +Unipointer.prototype._pointerUp = function( event, pointer ) { + this._pointerDone(); + this.pointerUp( event, pointer ); +}; - /** - * WebKit measures the outer-width on style.width on border-box elems - * IE & Firefox measures the inner-width - */ - ( function() { - if ( !boxSizingProp ) { - return; - } +// public +Unipointer.prototype.pointerUp = function( event, pointer ) { + this.emitEvent( 'pointerUp', [ event, pointer ] ); +}; - var div = document.createElement('div'); - div.style.width = '200px'; - div.style.padding = '1px 2px 3px 4px'; - div.style.borderStyle = 'solid'; - div.style.borderWidth = '1px 2px 3px 4px'; - div.style[ boxSizingProp ] = 'border-box'; +// ----- pointer done ----- // - var body = document.body || document.documentElement; - body.appendChild( div ); - var style = getStyle( div ); +// triggered on pointer up & pointer cancel +Unipointer.prototype._pointerDone = function() { + // reset properties + this.isPointerDown = false; + delete this.pointerIdentifier; + // remove events + this._unbindPostStartEvents(); + this.pointerDone(); +}; - isBoxSizeOuter = getStyleSize( style.width ) === 200; - body.removeChild( div ); - })(); +Unipointer.prototype.pointerDone = noop; +// ----- pointer cancel ----- // -// -------------------------- getSize -------------------------- // +Unipointer.prototype.onMSPointerCancel = +Unipointer.prototype.onpointercancel = function( event ) { + if ( event.pointerId == this.pointerIdentifier ) { + this._pointerCancel( event, event ); + } +}; - function getSize( elem ) { - // use querySeletor if elem is string - if ( typeof elem === 'string' ) { - elem = document.querySelector( elem ); - } +Unipointer.prototype.ontouchcancel = function( event ) { + var touch = this.getTouch( event.changedTouches ); + if ( touch ) { + this._pointerCancel( event, touch ); + } +}; - // do not proceed on non-objects - if ( !elem || typeof elem !== 'object' || !elem.nodeType ) { - return; - } +/** + * pointer cancel + * @param {Event} event + * @param {Event or Touch} pointer + * @private + */ +Unipointer.prototype._pointerCancel = function( event, pointer ) { + this._pointerDone(); + this.pointerCancel( event, pointer ); +}; - var style = getStyle( elem ); +// public +Unipointer.prototype.pointerCancel = function( event, pointer ) { + this.emitEvent( 'pointerCancel', [ event, pointer ] ); +}; - // if hidden, everything is 0 - if ( style.display === 'none' ) { - return getZeroSize(); - } +// ----- ----- // - var size = {}; - size.width = elem.offsetWidth; - size.height = elem.offsetHeight; +// utility function for getting x/y cooridinates from event, because IE8 +Unipointer.getPointerPoint = function( pointer ) { + return { + x: pointer.pageX !== undefined ? pointer.pageX : pointer.clientX, + y: pointer.pageY !== undefined ? pointer.pageY : pointer.clientY + }; +}; - var isBorderBox = size.isBorderBox = !!( boxSizingProp && - style[ boxSizingProp ] && style[ boxSizingProp ] === 'border-box' ); +// ----- ----- // - // get all measurements - for ( var i=0, len = measurements.length; i < len; i++ ) { - var measurement = measurements[i]; - var value = style[ measurement ]; - var num = parseFloat( value ); - // any 'auto', 'medium' value will be 0 - size[ measurement ] = !isNaN( num ) ? num : 0; - } +return Unipointer; - var paddingWidth = size.paddingLeft + size.paddingRight; - var paddingHeight = size.paddingTop + size.paddingBottom; - var marginWidth = size.marginLeft + size.marginRight; - var marginHeight = size.marginTop + size.marginBottom; - var borderWidth = size.borderLeftWidth + size.borderRightWidth; - var borderHeight = size.borderTopWidth + size.borderBottomWidth; - - var isBorderBoxSizeOuter = isBorderBox && isBoxSizeOuter; - - // overwrite width and height if we can get it from style - var styleWidth = getStyleSize( style.width ); - if ( styleWidth !== false ) { - size.width = styleWidth + - // add padding and border unless it's already including it - ( isBorderBoxSizeOuter ? 0 : paddingWidth + borderWidth ); - } - - var styleHeight = getStyleSize( style.height ); - if ( styleHeight !== false ) { - size.height = styleHeight + - // add padding and border unless it's already including it - ( isBorderBoxSizeOuter ? 0 : paddingHeight + borderHeight ); - } - - size.innerWidth = size.width - ( paddingWidth + borderWidth ); - size.innerHeight = size.height - ( paddingHeight + borderHeight ); - - size.outerWidth = size.width + marginWidth; - size.outerHeight = size.height + marginHeight; - - return size; - } - - return getSize; - - } - -// transport - if ( typeof define === 'function' && define.amd ) { - // AMD - define("getSize", [ 'getStyleProperty' ], defineGetSize ); - } else { - // browser global - window.getSize = defineGetSize( window.getStyleProperty ); - } - -})( window ); +})); /*! - * Draggabilly v1.0.5 - * Make that shiz draggable - * http://draggabilly.desandro.com + * Unidragger v1.1.0 + * Draggable base class + * MIT license */ -( function( window ) { +/*jshint browser: true, unused: true, undef: true, strict: true */ + +( function( window, factory ) { + /*global define: false, module: false, require: false */ + + // universal module definition + + if ( typeof define == 'function' && define.amd ) { + // AMD + define( 'unidragger/unidragger',[ + 'eventie/eventie', + 'unipointer/unipointer' + ], function( eventie, Unipointer ) { + return factory( window, eventie, Unipointer ); + }); + } else if ( typeof exports == 'object' ) { + // CommonJS + module.exports = factory( + window, + require('eventie'), + require('unipointer') + ); + } else { + // browser global + window.Unidragger = factory( + window, + window.eventie, + window.Unipointer + ); + } + +}( window, function factory( window, eventie, Unipointer ) { + + + +// ----- ----- // + +function noop() {} + +// handle IE8 prevent default +function preventDefaultEvent( event ) { + if ( event.preventDefault ) { + event.preventDefault(); + } else { + event.returnValue = false; + } +} + +function getParentLink( elem ) { + while ( elem != document.body ) { + elem = elem.parentNode; + if ( elem.nodeName == 'A' ) { + return elem; + } + } +} + +// -------------------------- Unidragger -------------------------- // + +function Unidragger() {} + +// inherit Unipointer & EventEmitter +Unidragger.prototype = new Unipointer(); + +// ----- bind start ----- // + +Unidragger.prototype.bindHandles = function() { + this._bindHandles( true ); +}; + +Unidragger.prototype.unbindHandles = function() { + this._bindHandles( false ); +}; + +var navigator = window.navigator; +/** + * works as unbinder, as you can .bindHandles( false ) to unbind + * @param {Boolean} isBind - will unbind if falsey + */ +Unidragger.prototype._bindHandles = function( isBind ) { + // munge isBind, default to true + isBind = isBind === undefined ? true : !!isBind; + // extra bind logic + var binderExtra; + if ( navigator.pointerEnabled ) { + binderExtra = function( handle ) { + // disable scrolling on the element + handle.style.touchAction = isBind ? 'none' : ''; + }; + } else if ( navigator.msPointerEnabled ) { + binderExtra = function( handle ) { + // disable scrolling on the element + handle.style.msTouchAction = isBind ? 'none' : ''; + }; + } else { + binderExtra = function() { + // TODO re-enable img.ondragstart when unbinding + if ( isBind ) { + disableImgOndragstart( handle ); + } + }; + } + // bind each handle + var bindMethod = isBind ? 'bind' : 'unbind'; + for ( var i=0, len = this.handles.length; i < len; i++ ) { + var handle = this.handles[i]; + this._bindStartEvent( handle, isBind ); + binderExtra( handle ); + eventie[ bindMethod ]( handle, 'click', this ); + } +}; + +// remove default dragging interaction on all images in IE8 +// IE8 does its own drag thing on images, which messes stuff up + +function noDragStart() { + return false; +} + +// TODO replace this with a IE8 test +var isIE8 = 'attachEvent' in document.documentElement; + +// IE8 only +var disableImgOndragstart = !isIE8 ? noop : function( handle ) { + + if ( handle.nodeName == 'IMG' ) { + handle.ondragstart = noDragStart; + } + + var images = handle.querySelectorAll('img'); + for ( var i=0, len = images.length; i < len; i++ ) { + var img = images[i]; + img.ondragstart = noDragStart; + } +}; + +// ----- start event ----- // + +var allowTouchstartNodes = Unidragger.allowTouchstartNodes = { + INPUT: true, + A: true, + BUTTON: true, + SELECT: true +}; + +/** + * pointer start + * @param {Event} event + * @param {Event or Touch} pointer + */ +Unidragger.prototype.pointerDown = function( event, pointer ) { + this._dragPointerDown( event, pointer ); + // kludge to blur focused inputs in dragger + var focused = document.activeElement; + if ( focused && focused.blur ) { + focused.blur(); + } + // bind move and end events + this._bindPostStartEvents( event ); + this.emitEvent( 'pointerDown', [ event, pointer ] ); +}; + +// base pointer down logic +Unidragger.prototype._dragPointerDown = function( event, pointer ) { + // track to see when dragging starts + this.pointerDownPoint = Unipointer.getPointerPoint( pointer ); + + var targetNodeName = event.target.nodeName; + // HACK iOS, allow clicks on buttons, inputs, and links, or children of links + var isTouchstartNode = event.type == 'touchstart' && + ( allowTouchstartNodes[ targetNodeName ] || getParentLink( event.target ) ); + // do not prevent default on touchstart nodes or