Commit 0785aeb9 by Andy Armstrong

Merge pull request #10426 from edx/andya/update-draggabilly

Upgrade draggabilly.js to fix Firefox iframe bug
parents 183fce37 18dabc33
......@@ -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"
]
}
......@@ -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"
},
......
......@@ -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"
},
......
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 = {
droppableClasses: 'drop-target drop-target-prepend drop-target-before drop-target-after',
......@@ -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
......@@ -101,7 +103,8 @@ 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'
......@@ -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); }
};
......@@ -375,9 +380,9 @@ define(["jquery", "jquery.ui", "underscore", "gettext", "common/js/components/vi
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));
}
}
};
......
......@@ -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
......
/*!
* 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,24 +157,24 @@
* 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 ) {
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 ) {
if ( 'classList' in document.documentElement ) {
hasClass = function( elem, c ) {
return elem.classList.contains( c );
};
......@@ -41,8 +184,8 @@
removeClass = function( elem, c ) {
elem.classList.remove( c );
};
}
else {
}
else {
hasClass = function( elem, c ) {
return classReg( c ).test( elem.className );
};
......@@ -54,14 +197,14 @@
removeClass = function( elem, c ) {
elem.className = elem.className.replace( classReg( c ), ' ' );
};
}
}
function toggleClass( elem, c ) {
function toggleClass( elem, c ) {
var fn = hasClass( elem, c ) ? removeClass : addClass;
fn( elem, c );
}
}
var classie = {
var classie = {
// full names
hasClass: hasClass,
addClass: addClass,
......@@ -72,199 +215,517 @@
add: addClass,
remove: removeClass,
toggle: toggleClass
};
};
// transport
if ( typeof define === 'function' && define.amd ) {
if ( typeof define === 'function' && define.amd ) {
// AMD
define("classie", classie);
} else {
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;
}
var unbind = function() {};
// test standard property first
if ( typeof docElemStyle[ propName ] === 'string' ) {
return propName;
}
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;
// 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 eventie = {
bind: bind,
unbind: unbind
};
}
// transport
if ( typeof define === 'function' && define.amd ) {
if ( typeof define === 'function' && define.amd ) {
// AMD
define("eventie", eventie);
} else {
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.eventie = eventie;
}
window.getStyleProperty = getStyleProperty;
}
})( this );
})( window );
/*!
* EventEmitter v4.2.4 - git.io/ee
* Oliver Caldwell
* getSize v1.2.2
* measure size of elements
* MIT license
* @preserve
*/
(function () {
'use strict';
/*jshint browser: true, strict: true, undef: true, unused: true */
/*global define: false, exports: false, require: false, module: false, console: false */
/**
* Class for managing events.
* Can be extended to provide event functionality in other classes.
*
* @class EventEmitter Manages event registering and emitting.
*/
function EventEmitter() {}
( function( window, undefined ) {
// Shortcuts to improve speed and size
// Easy access to the prototype
var proto = EventEmitter.prototype;
/**
* Finds the index of the listener for the event in it's storage array.
*
* @param {Function[]} listeners Array of listeners to search through.
* @param {Function} listener Method to look for.
* @return {Number} Index of the specified listener, -1 if not found
* @api private
*/
function indexOfListener(listeners, listener) {
var i = listeners.length;
while (i--) {
if (listeners[i].listener === listener) {
return i;
}
}
// -------------------------- helpers -------------------------- //
return -1;
}
// 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;
}
/**
* Alias a method while keeping the context correct, to allow for overwriting of target method.
*
* @param {String} name The name of the target method.
* @return {Function} The aliased method
* @api private
*/
function alias(name) {
return function aliasClosure() {
return this[name].apply(this, arguments);
function noop() {}
var logError = typeof console === 'undefined' ? noop :
function( message ) {
console.error( message );
};
}
/**
* Returns the listener array for the specified event.
* Will initialise the event object and listener arrays if required.
* Will return an object if you use a regex search. The object contains keys for each matched event. So /ba[rz]/ might return an object containing bar and baz. But only if you have either defined them with defineEvent or added some listeners to them.
* Each property in the object response is an array of listener functions.
*
* @param {String|RegExp} evt Name of the event to return the listeners from.
* @return {Function[]|Object} All listener functions for the event.
*/
proto.getListeners = function getListeners(evt) {
var events = this._getEvents();
var response;
var key;
// -------------------------- measurements -------------------------- //
// Return a concatenated array of all matching events if
// the selector is a regular expression.
if (typeof evt === 'object') {
response = {};
for (key in events) {
if (events.hasOwnProperty(key) && evt.test(key)) {
response[key] = events[key];
}
}
}
else {
response = events[evt] || (events[evt] = []);
}
var measurements = [
'paddingLeft',
'paddingRight',
'paddingTop',
'paddingBottom',
'marginLeft',
'marginRight',
'marginTop',
'marginBottom',
'borderLeftWidth',
'borderRightWidth',
'borderTopWidth',
'borderBottomWidth'
];
return response;
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;
}
/**
* Takes a list of listener objects and flattens it into a list of listener functions.
*
* @param {Object[]} listeners Raw listener objects.
* @return {Function[]} Just the listener functions.
*/
proto.flattenListeners = function flattenListeners(listeners) {
var flatListeners = [];
var i;
for (i = 0; i < listeners.length; i += 1) {
flatListeners.push(listeners[i].listener);
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;
return flatListeners;
var getComputedStyle = window.getComputedStyle;
getStyle = ( function() {
var getStyleFn = getComputedStyle ?
function( elem ) {
return getComputedStyle( elem, null );
} :
function( elem ) {
return elem.currentStyle;
};
/**
* Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful.
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 () {
/**
* Class for managing events.
* Can be extended to provide event functionality in other classes.
*
* @class EventEmitter Manages event registering and emitting.
*/
function EventEmitter() {}
// Shortcuts to improve speed and size
var proto = EventEmitter.prototype;
var exports = this;
var originalGlobalValue = exports.EventEmitter;
/**
* 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.
* @return {Number} Index of the specified listener, -1 if not found
* @api private
*/
function indexOfListener(listeners, listener) {
var i = listeners.length;
while (i--) {
if (listeners[i].listener === listener) {
return i;
}
}
return -1;
}
/**
* Alias a method while keeping the context correct, to allow for overwriting of target method.
*
* @param {String} name The name of the target method.
* @return {Function} The aliased method
* @api private
*/
function alias(name) {
return function aliasClosure() {
return this[name].apply(this, arguments);
};
}
/**
* Returns the listener array for the specified event.
* Will initialise the event object and listener arrays if required.
* Will return an object if you use a regex search. The object contains keys for each matched event. So /ba[rz]/ might return an object containing bar and baz. But only if you have either defined them with defineEvent or added some listeners to them.
* Each property in the object response is an array of listener functions.
*
* @param {String|RegExp} evt Name of the event to return the listeners from.
* @return {Function[]|Object} All listener functions for the event.
*/
proto.getListeners = function getListeners(evt) {
var events = this._getEvents();
var response;
var key;
// Return a concatenated array of all matching events if
// the selector is a regular expression.
if (evt instanceof RegExp) {
response = {};
for (key in events) {
if (events.hasOwnProperty(key) && evt.test(key)) {
response[key] = events[key];
}
}
}
else {
response = events[evt] || (events[evt] = []);
}
return response;
};
/**
* Takes a list of listener objects and flattens it into a list of listener functions.
*
* @param {Object[]} listeners Raw listener objects.
* @return {Function[]} Just the listener functions.
*/
proto.flattenListeners = function flattenListeners(listeners) {
var flatListeners = [];
var i;
for (i = 0; i < listeners.length; i += 1) {
flatListeners.push(listeners[i].listener);
}
return flatListeners;
};
/**
* Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful.
*
* @param {String|RegExp} evt Name of the event to return the listeners from.
* @return {Object} All listener functions for an event in an object.
......@@ -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)) {
......@@ -569,324 +1030,777 @@
return this.emitEvent(evt, args);
};
/**
* Sets the current value to check against when executing listeners. If a
* listeners return value matches the one set here then it will be removed
* after execution. This value defaults to true.
*
* @param {*} value The new value to check for when executing listeners.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.setOnceReturnValue = function setOnceReturnValue(value) {
this._onceReturnValue = value;
return this;
};
/**
* Sets the current value to check against when executing listeners. If a
* listeners return value matches the one set here then it will be removed
* after execution. This value defaults to true.
*
* @param {*} value The new value to check for when executing listeners.
* @return {Object} Current instance of EventEmitter for chaining.
*/
proto.setOnceReturnValue = function setOnceReturnValue(value) {
this._onceReturnValue = value;
return this;
};
/**
* Fetches the current value to check against when executing listeners. If
* the listeners return value matches this one then it should be removed
* automatically. It will return true by default.
*
* @return {*|Boolean} The current value to check for or the default, true.
* @api private
*/
proto._getOnceReturnValue = function _getOnceReturnValue() {
if (this.hasOwnProperty('_onceReturnValue')) {
return this._onceReturnValue;
}
else {
return true;
}
};
/**
* Fetches the events object and creates one if required.
*
* @return {Object} The events storage object.
* @api private
*/
proto._getEvents = function _getEvents() {
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/EventEmitter',[],function () {
return EventEmitter;
});
}
else if (typeof module === 'object' && module.exports){
module.exports = EventEmitter;
}
else {
exports.EventEmitter = EventEmitter;
}
}.call(this));
/*!
* Unipointer v1.1.0
* base class for doing one thing with pointer event
* MIT license
*/
/*jshint browser: true, undef: true, unused: true, strict: true */
/*global define: false, module: false, require: false */
( function( window, factory ) {
// universal module definition
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
);
}
}( window, function factory( window, EventEmitter, eventie ) {
function noop() {}
function Unipointer() {}
// inherit EventEmitter
Unipointer.prototype = new EventEmitter();
Unipointer.prototype.bindStartEvent = function( elem ) {
this._bindStartEvent( elem, true );
};
Unipointer.prototype.unbindStartEvent = function( elem ) {
this._bindStartEvent( elem, false );
};
/**
* 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';
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 );
}
};
// trigger handler methods for events
Unipointer.prototype.handleEvent = function( event ) {
var method = 'on' + event.type;
if ( this[ method ] ) {
this[ method ]( event );
}
};
// 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;
}
}
};
// ----- start event ----- //
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 );
};
Unipointer.prototype.ontouchstart = function( event ) {
this._pointerDown( event, event.changedTouches[0] );
};
/**
* Fetches the current value to check against when executing listeners. If
* the listeners return value matches this one then it should be removed
* automatically. It will return true by default.
*
* @return {*|Boolean} The current value to check for or the default, true.
* @api private
Unipointer.prototype.onMSPointerDown =
Unipointer.prototype.onpointerdown = function( event ) {
this._pointerDown( event, event );
};
/**
* pointer start
* @param {Event} event
* @param {Event or Touch} pointer
*/
proto._getOnceReturnValue = function _getOnceReturnValue() {
if (this.hasOwnProperty('_onceReturnValue')) {
return this._onceReturnValue;
Unipointer.prototype._pointerDown = function( event, pointer ) {
// dismiss other pointers
if ( this.isPointerDown ) {
return;
}
else {
return true;
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
};
};
/**
* Fetches the events object and creates one if required.
*
* @return {Object} The events storage object.
* @api private
*/
proto._getEvents = function _getEvents() {
return this._events || (this._events = {});
};
Unipointer.prototype._unbindPostStartEvents = function() {
var args = this._boundPointerEvents;
// IE8 can trigger dragEnd twice, check for _boundEvents
if ( !args || !args.events ) {
return;
}
// Expose the class either via AMD, CommonJS or the global object
if (typeof define === 'function' && define.amd) {
define("EventEmitter", function () {
return EventEmitter;
});
for ( var i=0, len = args.events.length; i < len; i++ ) {
var event = args.events[i];
eventie.unbind( args.node, event, this );
}
else if (typeof module === 'object' && module.exports){
module.exports = EventEmitter;
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 );
}
else {
this.EventEmitter = EventEmitter;
};
Unipointer.prototype.ontouchmove = function( event ) {
var touch = this.getTouch( event.changedTouches );
if ( touch ) {
this._pointerMove( event, touch );
}
}.call(this));
};
/*!
* getStyleProperty by kangax
* http://perfectionkills.com/feature-testing-css-properties/
/**
* pointer move
* @param {Event} event
* @param {Event or Touch} pointer
* @private
*/
Unipointer.prototype._pointerMove = function( event, pointer ) {
this.pointerMove( event, pointer );
};
/*jshint browser: true, strict: true, undef: true */
/*globals define: false */
// public
Unipointer.prototype.pointerMove = function( event, pointer ) {
this.emitEvent( 'pointerMove', [ event, pointer ] );
};
( function( window ) {
// ----- end event ----- //
'use strict';
var prefixes = 'Webkit Moz ms Ms O'.split(' ');
var docElemStyle = document.documentElement.style;
Unipointer.prototype.onmouseup = function( event ) {
this._pointerUp( event, event );
};
function getStyleProperty( propName ) {
if ( !propName ) {
return;
Unipointer.prototype.onMSPointerUp =
Unipointer.prototype.onpointerup = function( event ) {
if ( event.pointerId == this.pointerIdentifier ) {
this._pointerUp( event, event );
}
};
// test standard property first
if ( typeof docElemStyle[ propName ] === 'string' ) {
return propName;
Unipointer.prototype.ontouchend = function( event ) {
var touch = this.getTouch( event.changedTouches );
if ( touch ) {
this._pointerUp( event, touch );
}
};
// capitalize
propName = propName.charAt(0).toUpperCase() + propName.slice(1);
/**
* pointer up
* @param {Event} event
* @param {Event or Touch} pointer
* @private
*/
Unipointer.prototype._pointerUp = function( event, pointer ) {
this._pointerDone();
this.pointerUp( event, pointer );
};
// public
Unipointer.prototype.pointerUp = function( event, pointer ) {
this.emitEvent( 'pointerUp', [ event, pointer ] );
};
// ----- pointer done ----- //
// triggered on pointer up & pointer cancel
Unipointer.prototype._pointerDone = function() {
// reset properties
this.isPointerDown = false;
delete this.pointerIdentifier;
// remove events
this._unbindPostStartEvents();
this.pointerDone();
};
// 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;
}
Unipointer.prototype.pointerDone = noop;
// ----- pointer cancel ----- //
Unipointer.prototype.onMSPointerCancel =
Unipointer.prototype.onpointercancel = function( event ) {
if ( event.pointerId == this.pointerIdentifier ) {
this._pointerCancel( event, event );
}
};
Unipointer.prototype.ontouchcancel = function( event ) {
var touch = this.getTouch( event.changedTouches );
if ( touch ) {
this._pointerCancel( event, touch );
}
};
// transport
if ( typeof define === 'function' && define.amd ) {
/**
* pointer cancel
* @param {Event} event
* @param {Event or Touch} pointer
* @private
*/
Unipointer.prototype._pointerCancel = function( event, pointer ) {
this._pointerDone();
this.pointerCancel( event, pointer );
};
// public
Unipointer.prototype.pointerCancel = function( event, pointer ) {
this.emitEvent( 'pointerCancel', [ event, pointer ] );
};
// ----- ----- //
// 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
};
};
// ----- ----- //
return Unipointer;
}));
/*!
* Unidragger v1.1.0
* Draggable base class
* MIT license
*/
/*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("getStyleProperty", function() {
return getStyleProperty;
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.getStyleProperty = getStyleProperty;
window.Unidragger = factory(
window,
window.eventie,
window.Unipointer
);
}
})( window );
}( window, function factory( window, eventie, Unipointer ) {
/**
* getSize v1.1.4
* measure size of elements
*/
/*jshint browser: true, strict: true, undef: true, unused: true */
/*global define: false */
( function( window, undefined ) {
// ----- ----- //
'use strict';
function noop() {}
// -------------------------- helpers -------------------------- //
// handle IE8 prevent default
function preventDefaultEvent( event ) {
if ( event.preventDefault ) {
event.preventDefault();
} else {
event.returnValue = false;
}
}
var defView = document.defaultView;
function getParentLink( elem ) {
while ( elem != document.body ) {
elem = elem.parentNode;
if ( elem.nodeName == 'A' ) {
return elem;
}
}
}
var getStyle = defView && defView.getComputedStyle ?
function( elem ) {
return defView.getComputedStyle( elem, null );
} :
function( elem ) {
return elem.currentStyle;
};
// -------------------------- Unidragger -------------------------- //
// 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 Unidragger() {}
// -------------------------- measurements -------------------------- //
// inherit Unipointer & EventEmitter
Unidragger.prototype = new Unipointer();
var measurements = [
'paddingLeft',
'paddingRight',
'paddingTop',
'paddingBottom',
'marginLeft',
'marginRight',
'marginTop',
'marginBottom',
'borderLeftWidth',
'borderRightWidth',
'borderTopWidth',
'borderBottomWidth'
];
// ----- bind start ----- //
function getZeroSize() {
var size = {
width: 0,
height: 0,
innerWidth: 0,
innerHeight: 0,
outerWidth: 0,
outerHeight: 0
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' : '';
};
for ( var i=0, len = measurements.length; i < len; i++ ) {
var measurement = measurements[i];
size[ measurement ] = 0;
} else {
binderExtra = function() {
// TODO re-enable img.ondragstart when unbinding
if ( isBind ) {
disableImgOndragstart( handle );
}
return size;
};
}
// 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;
}
function defineGetSize( getStyleProperty ) {
// TODO replace this with a IE8 test
var isIE8 = 'attachEvent' in document.documentElement;
// -------------------------- box sizing -------------------------- //
// IE8 only
var disableImgOndragstart = !isIE8 ? noop : function( handle ) {
var boxSizingProp = getStyleProperty('boxSizing');
var isBoxSizeOuter;
if ( handle.nodeName == 'IMG' ) {
handle.ondragstart = noDragStart;
}
/**
* WebKit measures the outer-width on style.width on border-box elems
* IE & Firefox measures the inner-width
*/
( function() {
if ( !boxSizingProp ) {
return;
var images = handle.querySelectorAll('img');
for ( var i=0, len = images.length; i < len; i++ ) {
var img = images[i];
img.ondragstart = noDragStart;
}
};
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';
// ----- start event ----- //
var body = document.body || document.documentElement;
body.appendChild( div );
var style = getStyle( div );
var allowTouchstartNodes = Unidragger.allowTouchstartNodes = {
INPUT: true,
A: true,
BUTTON: true,
SELECT: true
};
isBoxSizeOuter = getStyleSize( style.width ) === 200;
body.removeChild( div );
})();
/**
* 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 );
// -------------------------- getSize -------------------------- //
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 <select>
if ( !isTouchstartNode && targetNodeName != 'SELECT' ) {
preventDefaultEvent( event );
}
};
function getSize( elem ) {
// use querySeletor if elem is string
if ( typeof elem === 'string' ) {
elem = document.querySelector( elem );
// ----- move event ----- //
/**
* drag move
* @param {Event} event
* @param {Event or Touch} pointer
*/
Unidragger.prototype.pointerMove = function( event, pointer ) {
var moveVector = this._dragPointerMove( event, pointer );
this.emitEvent( 'pointerMove', [ event, pointer, moveVector ] );
this._dragMove( event, pointer, moveVector );
};
// base pointer move logic
Unidragger.prototype._dragPointerMove = function( event, pointer ) {
var movePoint = Unipointer.getPointerPoint( pointer );
var moveVector = {
x: movePoint.x - this.pointerDownPoint.x,
y: movePoint.y - this.pointerDownPoint.y
};
// start drag if pointer has moved far enough to start drag
if ( !this.isDragging && this.hasDragStarted( moveVector ) ) {
this._dragStart( event, pointer );
}
return moveVector;
};
// do not proceed on non-objects
if ( !elem || typeof elem !== 'object' || !elem.nodeType ) {
return;
// condition if pointer has moved far enough to start drag
Unidragger.prototype.hasDragStarted = function( moveVector ) {
return Math.abs( moveVector.x ) > 3 || Math.abs( moveVector.y ) > 3;
};
// ----- end event ----- //
/**
* pointer up
* @param {Event} event
* @param {Event or Touch} pointer
*/
Unidragger.prototype.pointerUp = function( event, pointer ) {
this.emitEvent( 'pointerUp', [ event, pointer ] );
this._dragPointerUp( event, pointer );
};
Unidragger.prototype._dragPointerUp = function( event, pointer ) {
if ( this.isDragging ) {
this._dragEnd( event, pointer );
} else {
// pointer didn't move enough for drag to start
this._staticClick( event, pointer );
}
};
var style = getStyle( elem );
// -------------------------- drag -------------------------- //
// if hidden, everything is 0
if ( style.display === 'none' ) {
return getZeroSize();
// dragStart
Unidragger.prototype._dragStart = function( event, pointer ) {
this.isDragging = true;
this.dragStartPoint = Unidragger.getPointerPoint( pointer );
// prevent clicks
this.isPreventingClicks = true;
this.dragStart( event, pointer );
};
Unidragger.prototype.dragStart = function( event, pointer ) {
this.emitEvent( 'dragStart', [ event, pointer ] );
};
// dragMove
Unidragger.prototype._dragMove = function( event, pointer, moveVector ) {
// do not drag if not dragging yet
if ( !this.isDragging ) {
return;
}
var size = {};
size.width = elem.offsetWidth;
size.height = elem.offsetHeight;
this.dragMove( event, pointer, moveVector );
};
var isBorderBox = size.isBorderBox = !!( boxSizingProp &&
style[ boxSizingProp ] && style[ boxSizingProp ] === 'border-box' );
Unidragger.prototype.dragMove = function( event, pointer, moveVector ) {
this.emitEvent( 'dragMove', [ event, pointer, moveVector ] );
};
// 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;
}
// dragEnd
Unidragger.prototype._dragEnd = function( event, pointer ) {
// set flags
this.isDragging = false;
// re-enable clicking async
var _this = this;
setTimeout( function() {
delete _this.isPreventingClicks;
});
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;
this.dragEnd( event, pointer );
};
var isBorderBoxSizeOuter = isBorderBox && isBoxSizeOuter;
Unidragger.prototype.dragEnd = function( event, pointer ) {
this.emitEvent( 'dragEnd', [ event, pointer ] );
};
// 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 );
// ----- onclick ----- //
// handle all clicks and prevent clicks when dragging
Unidragger.prototype.onclick = function( event ) {
if ( this.isPreventingClicks ) {
preventDefaultEvent( event );
}
};
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 );
// ----- staticClick ----- //
// triggered after pointer down & up with no/tiny movement
Unidragger.prototype._staticClick = function( event, pointer ) {
// allow click in text input
if ( event.target.nodeName == 'INPUT' && event.target.type == 'text' ) {
event.target.focus();
}
this.staticClick( event, pointer );
};
size.innerWidth = size.width - ( paddingWidth + borderWidth );
size.innerHeight = size.height - ( paddingHeight + borderHeight );
Unidragger.prototype.staticClick = function( event, pointer ) {
this.emitEvent( 'staticClick', [ event, pointer ] );
};
size.outerWidth = size.width + marginWidth;
size.outerHeight = size.height + marginHeight;
// ----- ----- //
return size;
}
Unidragger.getPointerPoint = function( pointer ) {
return {
x: pointer.pageX !== undefined ? pointer.pageX : pointer.clientX,
y: pointer.pageY !== undefined ? pointer.pageY : pointer.clientY
};
};
return getSize;
// ----- ----- //
}
Unidragger.getPointerPoint = Unipointer.getPointerPoint;
// transport
if ( typeof define === 'function' && define.amd ) {
// AMD
define("getSize", [ 'getStyleProperty' ], defineGetSize );
} else {
// browser global
window.getSize = defineGetSize( window.getStyleProperty );
}
return Unidragger;
})( window );
}));
/*!
* Draggabilly v1.0.5
* Draggabilly v1.2.4
* Make that shiz draggable
* http://draggabilly.desandro.com
* MIT license
*/
( function( window ) {
( function( window, factory ) {
if ( typeof define == 'function' && define.amd ) {
// AMD
define( [
'classie/classie',
'get-style-property/get-style-property',
'get-size/get-size',
'unidragger/unidragger'
],
function( classie, getStyleProperty, getSize, Unidragger ) {
return factory( window, classie, getStyleProperty, getSize, Unidragger );
});
} else if ( typeof exports == 'object' ) {
// CommonJS
module.exports = factory(
window,
require('desandro-classie'),
require('desandro-get-style-property'),
require('get-size'),
require('unidragger')
);
} else {
// browser global
window.Draggabilly = factory(
window,
window.classie,
window.getStyleProperty,
window.getSize,
window.Unidragger
);
}
}( window, function factory( window, classie, getStyleProperty, getSize, Unidragger ) {
'use strict';
// vars
var document = window.document;
var document = window.document;
function noop() {}
// -------------------------- helpers -------------------------- //
// extend objects
function extend( a, b ) {
function extend( a, b ) {
for ( var prop in b ) {
a[ prop ] = b[ prop ];
}
return a;
}
function noop() {}
}
// ----- get style ----- //
var defView = document.defaultView;
var defView = document.defaultView;
var getStyle = defView && defView.getComputedStyle ?
var getStyle = defView && defView.getComputedStyle ?
function( elem ) {
return defView.getComputedStyle( elem, null );
} :
......@@ -896,27 +1810,27 @@
// http://stackoverflow.com/a/384380/182183
var isElement = ( typeof HTMLElement === 'object' ) ?
var isElement = ( typeof HTMLElement == 'object' ) ?
function isElementDOM2( obj ) {
return obj instanceof HTMLElement;
} :
function isElementQuirky( obj ) {
return obj && typeof obj === 'object' &&
obj.nodeType === 1 && typeof obj.nodeName === 'string';
return obj && typeof obj == 'object' &&
obj.nodeType == 1 && typeof obj.nodeName == 'string';
};
// -------------------------- requestAnimationFrame -------------------------- //
// https://gist.github.com/1866474
var lastTime = 0;
var prefixes = 'webkit moz ms o'.split(' ');
var lastTime = 0;
var prefixes = 'webkit moz ms o'.split(' ');
// get unprefixed rAF and cAF, if present
var requestAnimationFrame = window.requestAnimationFrame;
var cancelAnimationFrame = window.cancelAnimationFrame;
var requestAnimationFrame = window.requestAnimationFrame;
var cancelAnimationFrame = window.cancelAnimationFrame;
// loop through vendor prefixes and get prefixed rAF and cAF
var prefix;
for( var i = 0; i < prefixes.length; i++ ) {
var prefix;
for( var i = 0; i < prefixes.length; i++ ) {
if ( requestAnimationFrame && cancelAnimationFrame ) {
break;
}
......@@ -924,10 +1838,10 @@
requestAnimationFrame = requestAnimationFrame || window[ prefix + 'RequestAnimationFrame' ];
cancelAnimationFrame = cancelAnimationFrame || window[ prefix + 'CancelAnimationFrame' ] ||
window[ prefix + 'CancelRequestAnimationFrame' ];
}
}
// fallback to setTimeout and clearTimeout if either request/cancel is not supported
if ( !requestAnimationFrame || !cancelAnimationFrame ) {
if ( !requestAnimationFrame || !cancelAnimationFrame ) {
requestAnimationFrame = function( callback ) {
var currTime = new Date().getTime();
var timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) );
......@@ -941,37 +1855,49 @@
cancelAnimationFrame = function( id ) {
window.clearTimeout( id );
};
}
// -------------------------- definition -------------------------- //
function draggabillyDefinition( classie, EventEmitter, eventie, getStyleProperty, getSize ) {
}
// -------------------------- support -------------------------- //
var transformProperty = getStyleProperty('transform');
var transformProperty = getStyleProperty('transform');
// TODO fix quick & dirty check for 3D support
var is3d = !!getStyleProperty('perspective');
var is3d = !!getStyleProperty('perspective');
var jQuery = window.jQuery;
// -------------------------- -------------------------- //
function Draggabilly( element, options ) {
this.element = element;
function Draggabilly( element, options ) {
// querySelector if string
this.element = typeof element == 'string' ?
document.querySelector( element ) : element;
if ( jQuery ) {
this.$element = jQuery( this.element );
}
this.options = extend( {}, this.options );
extend( this.options, options );
// options
this.options = extend( {}, this.constructor.defaults );
this.option( options );
this._create();
}
}
// inherit Unidragger methods
extend( Draggabilly.prototype, Unidragger.prototype );
// inherit EventEmitter methods
extend( Draggabilly.prototype, EventEmitter.prototype );
Draggabilly.defaults = {
};
Draggabilly.prototype.options = {
};
/**
* set options
* @param {Object} opts
*/
Draggabilly.prototype.option = function( opts ) {
extend( this.options, opts );
};
Draggabilly.prototype._create = function() {
Draggabilly.prototype._create = function() {
// properties
this.position = {};
......@@ -984,60 +1910,53 @@
// set relative positioning
var style = getStyle( this.element );
if ( style.position !== 'relative' && style.position !== 'absolute' ) {
if ( style.position != 'relative' && style.position != 'absolute' ) {
this.element.style.position = 'relative';
}
this.enable();
this.setHandles();
};
};
/**
/**
* set this.handles and bind start events to 'em
*/
Draggabilly.prototype.setHandles = function() {
Draggabilly.prototype.setHandles = function() {
this.handles = this.options.handle ?
this.element.querySelectorAll( this.options.handle ) : [ this.element ];
for ( var i=0, len = this.handles.length; i < len; i++ ) {
var handle = this.handles[i];
// bind pointer start event
// listen for both, for devices like Chrome Pixel
// which has touch and mouse events
eventie.bind( handle, 'mousedown', this );
eventie.bind( handle, 'touchstart', this );
disableImgOndragstart( handle );
}
};
// 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;
this.bindHandles();
};
// IE8 only
var disableImgOndragstart = !isIE8 ? noop : function( handle ) {
if ( handle.nodeName === 'IMG' ) {
handle.ondragstart = noDragStart;
/**
* emits events via eventEmitter and jQuery events
* @param {String} type - name of event
* @param {Event} event - original event
* @param {Array} args - extra arguments
*/
Draggabilly.prototype.dispatchEvent = function( type, event, args ) {
var emitArgs = [ event ].concat( args );
this.emitEvent( type, emitArgs );
var jQuery = window.jQuery;
// trigger jQuery event
if ( jQuery && this.$element ) {
if ( event ) {
// create jQuery event
var $event = jQuery.Event( event );
$event.type = type;
this.$element.trigger( $event, args );
} else {
// just trigger with type if no event available
this.$element.trigger( type, args );
}
var images = handle.querySelectorAll('img');
for ( var i=0, len = images.length; i < len; i++ ) {
var img = images[i];
img.ondragstart = noDragStart;
}
};
};
// -------------------------- position -------------------------- //
// get left/top position from style
Draggabilly.prototype._getPosition = function() {
Draggabilly.prototype._getPosition = function() {
// properties
var style = getStyle( this.element );
......@@ -1049,10 +1968,10 @@
this.position.y = isNaN( y ) ? 0 : y;
this._addTransformPosition( style );
};
};
// add transform: translate( x, y ) to position
Draggabilly.prototype._addTransformPosition = function( style ) {
Draggabilly.prototype._addTransformPosition = function( style ) {
if ( !transformProperty ) {
return;
}
......@@ -1070,123 +1989,68 @@
var translateY = parseInt( matrixValues[ xIndex + 1 ], 10 );
this.position.x += translateX;
this.position.y += translateY;
};
};
// -------------------------- events -------------------------- //
// trigger handler methods for events
Draggabilly.prototype.handleEvent = function( event ) {
var method = 'on' + event.type;
if ( this[ method ] ) {
this[ method ]( event );
}
};
// returns the touch that we're keeping track of
Draggabilly.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;
}
}
};
// ----- start event ----- //
Draggabilly.prototype.onmousedown = function( event ) {
this.dragStart( event, event );
};
Draggabilly.prototype.ontouchstart = function( event ) {
// disregard additional touches
if ( this.isDragging ) {
return;
/**
* pointer start
* @param {Event} event
* @param {Event or Touch} pointer
*/
Draggabilly.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 );
classie.add( this.element, 'is-pointer-down' );
this.dispatchEvent( 'pointerDown', event, [ pointer ] );
};
this.dragStart( event, event.changedTouches[0] );
};
function setPointerPoint( point, pointer ) {
point.x = pointer.pageX !== undefined ? pointer.pageX : pointer.clientX;
point.y = pointer.pageY !== undefined ? pointer.pageY : pointer.clientY;
}
/**
* drag move
* @param {Event} event
* @param {Event or Touch} pointer
*/
Draggabilly.prototype.pointerMove = function( event, pointer ) {
var moveVector = this._dragPointerMove( event, pointer );
this.dispatchEvent( 'pointerMove', event, [ pointer, moveVector ] );
this._dragMove( event, pointer, moveVector );
};
/**
/**
* drag start
* @param {Event} event
* @param {Event or Touch} pointer
*/
Draggabilly.prototype.dragStart = function( event, pointer ) {
Draggabilly.prototype.dragStart = function( event, pointer ) {
if ( !this.isEnabled ) {
return;
}
if ( event.preventDefault ) {
event.preventDefault();
} else {
event.returnValue = false;
}
var isTouch = event.type === 'touchstart';
// save pointer identifier to match up touch events
this.pointerIdentifier = pointer.identifier;
this._getPosition();
this.measureContainment();
// point where drag began
setPointerPoint( this.startPoint, pointer );
// position _when_ drag began
this.startPosition.x = this.position.x;
this.startPosition.y = this.position.y;
// reset left/top style
this.setLeftTop();
this.dragPoint.x = 0;
this.dragPoint.y = 0;
// bind move and end events
this._bindEvents({
events: isTouch ? [ 'touchmove', 'touchend', 'touchcancel' ] :
[ 'mousemove', 'mouseup' ],
// IE8 needs to be bound to document
node: event.preventDefault ? window : document
});
classie.add( this.element, 'is-dragging' );
// reset isDragging flag
this.isDragging = true;
this.emitEvent( 'dragStart', [ this, event, pointer ] );
classie.add( this.element, 'is-dragging' );
this.dispatchEvent( 'dragStart', event, [ pointer ] );
// start animation
this.animate();
};
Draggabilly.prototype._bindEvents = function( args ) {
for ( var i=0, len = args.events.length; i < len; i++ ) {
var event = args.events[i];
eventie.bind( args.node, event, this );
}
// save these arguments
this._boundEvents = args;
};
Draggabilly.prototype._unbindEvents = function() {
var args = this._boundEvents;
for ( var i=0, len = args.events.length; i < len; i++ ) {
var event = args.events[i];
eventie.unbind( args.node, event, this );
}
delete this._boundEvents;
};
};
Draggabilly.prototype.measureContainment = function() {
Draggabilly.prototype.measureContainment = function() {
var containment = this.options.containment;
if ( !containment ) {
return;
......@@ -1198,7 +2062,7 @@
// use element if element
var container = isElement( containment ) ? containment :
// fallback to querySelector if string
typeof containment === 'string' ? document.querySelector( containment ) :
typeof containment == 'string' ? document.querySelector( containment ) :
// otherwise just `true`, use the parent
this.element.parentNode;
......@@ -1209,97 +2073,98 @@
x: elemRect.left - containerRect.left,
y: elemRect.top - containerRect.top
};
};
};
// ----- move event ----- //
Draggabilly.prototype.onmousemove = function( event ) {
this.dragMove( event, event );
};
Draggabilly.prototype.ontouchmove = function( event ) {
var touch = this.getTouch( event.changedTouches );
if ( touch ) {
this.dragMove( event, touch );
}
};
/**
/**
* drag move
* @param {Event} event
* @param {Event or Touch} pointer
*/
Draggabilly.prototype.dragMove = function( event, pointer ) {
Draggabilly.prototype.dragMove = function( event, pointer, moveVector ) {
if ( !this.isEnabled ) {
return;
}
var dragX = moveVector.x;
var dragY = moveVector.y;
setPointerPoint( this.dragPoint, pointer );
this.dragPoint.x -= this.startPoint.x;
this.dragPoint.y -= this.startPoint.y;
var grid = this.options.grid;
var gridX = grid && grid[0];
var gridY = grid && grid[1];
if ( this.options.containment ) {
var relX = this.relativeStartPosition.x;
var relY = this.relativeStartPosition.y;
this.dragPoint.x = Math.max( this.dragPoint.x, -relX );
this.dragPoint.y = Math.max( this.dragPoint.y, -relY );
this.dragPoint.x = Math.min( this.dragPoint.x, this.containerSize.width - relX - this.size.width );
this.dragPoint.y = Math.min( this.dragPoint.y, this.containerSize.height - relY - this.size.height );
}
dragX = applyGrid( dragX, gridX );
dragY = applyGrid( dragY, gridY );
this.position.x = this.startPosition.x + this.dragPoint.x;
this.position.y = this.startPosition.y + this.dragPoint.y;
dragX = this.containDrag( 'x', dragX, gridX );
dragY = this.containDrag( 'y', dragY, gridY );
this.emitEvent( 'dragMove', [ this, event, pointer ] );
};
// constrain to axis
dragX = this.options.axis == 'y' ? 0 : dragX;
dragY = this.options.axis == 'x' ? 0 : dragY;
this.position.x = this.startPosition.x + dragX;
this.position.y = this.startPosition.y + dragY;
// set dragPoint properties
this.dragPoint.x = dragX;
this.dragPoint.y = dragY;
// ----- end event ----- //
this.dispatchEvent( 'dragMove', event, [ pointer, moveVector ] );
};
Draggabilly.prototype.onmouseup = function( event ) {
this.dragEnd( event, event );
};
function applyGrid( value, grid, method ) {
method = method || 'round';
return grid ? Math[ method ]( value / grid ) * grid : value;
}
Draggabilly.prototype.ontouchend = function( event ) {
var touch = this.getTouch( event.changedTouches );
if ( touch ) {
this.dragEnd( event, touch );
Draggabilly.prototype.containDrag = function( axis, drag, grid ) {
if ( !this.options.containment ) {
return drag;
}
};
var measure = axis == 'x' ? 'width' : 'height';
/**
var rel = this.relativeStartPosition[ axis ];
var min = applyGrid( -rel, grid, 'ceil' );
var max = this.containerSize[ measure ] - rel - this.size[ measure ];
max = applyGrid( max, grid, 'floor' );
return Math.min( max, Math.max( min, drag ) );
};
// ----- end event ----- //
/**
* pointer up
* @param {Event} event
* @param {Event or Touch} pointer
*/
Draggabilly.prototype.pointerUp = function( event, pointer ) {
classie.remove( this.element, 'is-pointer-down' );
this.dispatchEvent( 'pointerUp', event, [ pointer ] );
this._dragPointerUp( event, pointer );
};
/**
* drag end
* @param {Event} event
* @param {Event or Touch} pointer
*/
Draggabilly.prototype.dragEnd = function( event, pointer ) {
Draggabilly.prototype.dragEnd = function( event, pointer ) {
if ( !this.isEnabled ) {
return;
}
this.isDragging = false;
delete this.pointerIdentifier;
// use top left position when complete
if ( transformProperty ) {
this.element.style[ transformProperty ] = '';
this.setLeftTop();
}
// remove events
this._unbindEvents();
classie.remove( this.element, 'is-dragging' );
this.emitEvent( 'dragEnd', [ this, event, pointer ] );
};
// ----- cancel event ----- //
// coerce to end event
Draggabilly.prototype.ontouchcancel = function( event ) {
var touch = this.getTouch( event.changedTouches );
this.dragEnd( event, touch );
};
this.dispatchEvent( 'dragEnd', event, [ pointer ] );
};
// -------------------------- animation -------------------------- //
Draggabilly.prototype.animate = function() {
Draggabilly.prototype.animate = function() {
// only render and animate if dragging
if ( !this.isDragging ) {
return;
......@@ -1312,10 +2177,10 @@
_this.animate();
});
};
};
// transform translate function
var translate = is3d ?
var translate = is3d ?
function( x, y ) {
return 'translate3d( ' + x + 'px, ' + y + 'px, 0)';
} :
......@@ -1324,53 +2189,65 @@
};
// left/top positioning
Draggabilly.prototype.setLeftTop = function() {
Draggabilly.prototype.setLeftTop = function() {
this.element.style.left = this.position.x + 'px';
this.element.style.top = this.position.y + 'px';
};
};
Draggabilly.prototype.positionDrag = transformProperty ?
Draggabilly.prototype.positionDrag = transformProperty ?
function() {
// position with transform
this.element.style[ transformProperty ] = translate( this.dragPoint.x, this.dragPoint.y );
} : Draggabilly.prototype.setLeftTop;
Draggabilly.prototype.enable = function() {
// ----- staticClick ----- //
Draggabilly.prototype.staticClick = function( event, pointer ) {
this.dispatchEvent( 'staticClick', event, [ pointer ] );
};
// ----- methods ----- //
Draggabilly.prototype.enable = function() {
this.isEnabled = true;
};
};
Draggabilly.prototype.disable = function() {
Draggabilly.prototype.disable = function() {
this.isEnabled = false;
if ( this.isDragging ) {
this.dragEnd();
}
};
};
Draggabilly.prototype.destroy = function() {
this.disable();
// reset styles
if ( transformProperty ) {
this.element.style[ transformProperty ] = '';
}
this.element.style.left = '';
this.element.style.top = '';
this.element.style.position = '';
// unbind handles
this.unbindHandles();
// remove jQuery data
if ( this.$element ) {
this.$element.removeData('draggabilly');
}
};
return Draggabilly;
// ----- jQuery bridget ----- //
} // end definition
// required for jQuery bridget
Draggabilly.prototype._init = noop;
// -------------------------- transport -------------------------- //
if ( jQuery && jQuery.bridget ) {
jQuery.bridget( 'draggabilly', Draggabilly );
}
if ( typeof define === 'function' && define.amd ) {
// AMD
define('draggabilly', [
'classie',
'EventEmitter',
'eventie',
'getStyleProperty',
'getSize'
],
draggabillyDefinition );
} else {
// browser global
window.Draggabilly = draggabillyDefinition(
window.classie,
window.EventEmitter,
window.eventie,
window.getStyleProperty,
window.getSize
);
}
// ----- ----- //
return Draggabilly;
}));
})( window );
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment