diff --git a/composer/swipe-composer.info/sample/index.html b/composer/swipe-composer.info/sample/index.html new file mode 100644 index 0000000000..a46f50ad2f --- /dev/null +++ b/composer/swipe-composer.info/sample/index.html @@ -0,0 +1,19 @@ + + + + + Swipe Composer Sample + + + + + + + + diff --git a/composer/swipe-composer.info/sample/package.json b/composer/swipe-composer.info/sample/package.json new file mode 100644 index 0000000000..c2602bb824 --- /dev/null +++ b/composer/swipe-composer.info/sample/package.json @@ -0,0 +1,10 @@ +{ + "name": "swipe-composer-sample", + "version": "0.0.1", + "dependencies": { + "montage": "*" + }, + "mappings": { + "montage": "../../../" + } +} diff --git a/composer/swipe-composer.info/sample/ui/main.reel/main.css b/composer/swipe-composer.info/sample/ui/main.reel/main.css new file mode 100644 index 0000000000..0ad5005214 --- /dev/null +++ b/composer/swipe-composer.info/sample/ui/main.reel/main.css @@ -0,0 +1,29 @@ +html, body { + padding: 0; + margin: 0; + font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.Main { + padding: 20px; +} + +header { + margin: 40px 0; + font-size: 2rem; + text-align: center; + color: #33495d; + height: 40px; +} + +.HitBox { + margin: auto; + width: 400px; + height: 400px; + border: 1px solid black; +} diff --git a/composer/swipe-composer.info/sample/ui/main.reel/main.html b/composer/swipe-composer.info/sample/ui/main.reel/main.html new file mode 100644 index 0000000000..e06dcea786 --- /dev/null +++ b/composer/swipe-composer.info/sample/ui/main.reel/main.html @@ -0,0 +1,79 @@ + + + + + + + + + +
+
Swipe Composer Sample
+
+ + + +
+
+

+ Direction: + +

+

+ Distance: + +

+

+ Velocity: + +

+

+ Angle: + +

+
+
+ + diff --git a/composer/swipe-composer.info/sample/ui/main.reel/main.js b/composer/swipe-composer.info/sample/ui/main.reel/main.js new file mode 100644 index 0000000000..42034bf458 --- /dev/null +++ b/composer/swipe-composer.info/sample/ui/main.reel/main.js @@ -0,0 +1,133 @@ +var Component = require("montage/ui/component").Component, + SwipeComposer = require("montage/composer/swipe-composer").SwipeComposer; + +exports.Main = Component.specialize({ + + _x1: { + value: 0 + }, + + _y1: { + value: 0 + }, + + _x2: { + value: 0 + }, + + _y2: { + value: 0 + }, + + __swipeComposer: { + value: null + }, + + _swipeComposer: { + get: function () { + if (!this.__swipeComposer) { + this.__swipeComposer = new SwipeComposer(); + this.addComposerForElement(this.__swipeComposer, this._hitBox); + } + + return this.__swipeComposer; + } + }, + + _translateComposer: { + get: function () { + return this._swipeComposer._translateComposer; + } + }, + + enterDocument: { + value: function (firstTime) { + if (firstTime) { + this._translateComposer.addEventListener("translateStart", this); + this._swipeComposer.addEventListener("swipe", this); + } + } + }, + + handleTranslateStart: { + value: function () { + this._x1 = ( + this._translateComposer.pointerStartEventPosition.pageX - + this._hitBoxRect.left + ); + + this._y1 = ( + this._translateComposer.pointerStartEventPosition.pageY - + this._hitBoxRect.top + ); + + this._translateComposer.addEventListener("translate", this); + this._translateComposer.addEventListener("translateEnd", this); + this._translateComposer.addEventListener("translateCancel", this); + } + }, + + handleTranslate: { + value: function (event) { + this._x2 = this._x1 + event.translateX; + this._y2 = this._y1 + event.translateY; + this.needsDraw = true; + } + }, + + handleTranslateEnd: { + value: function () { + this._reset(); + } + }, + + handleTranslateCancel: { + value: function () { + this._reset(); + } + }, + + handleSwipe: { + value: function (event) { + this.swipeDirection = event.direction; + this.swipeVelocity = event.velocity.toFixed(2); + this.swipeAngle = event.angle.toFixed(2); + this.swipeDistance = event.distance.toFixed(2); + } + }, + + _reset: { + value: function () { + this._translateComposer.removeEventListener("translate", this); + this._translateComposer.removeEventListener("translateEnd", this); + this._translateComposer.removeEventListener("translateCancel", this); + + this._x1 = 0; + this._x2 = 0; + this._y1 = 0; + this._y2 = 0; + + this.needsDraw = true; + } + }, + + willDraw: { + value: function () { + if (!this._hitBoxRect && this._hitBox) { + this._hitBoxRect = this._hitBox.getBoundingClientRect(); + } + } + }, + + draw: { + value: function () { + if (this._hitBoxRect && this._hitBox) { + this._line.setAttribute("x1", this._x1); + this._line.setAttribute("y1", this._y1); + this._line.setAttribute("x2", this._x2); + this._line.setAttribute("y2", this._y2); + } + } + } + +}); diff --git a/composer/swipe-composer.js b/composer/swipe-composer.js index 099e023be8..d43410fd76 100644 --- a/composer/swipe-composer.js +++ b/composer/swipe-composer.js @@ -4,235 +4,444 @@ * @requires montage/composer/composer */ var Montage = require("../core/core").Montage, - Composer = require("./composer").Composer; + Composer = require("./composer").Composer, + MutableEvent = require("../core/event/mutable-event").MutableEvent, + TranslateComposer = require("./translate-composer").TranslateComposer; -/** - * @class SwipeComposer - * @classdesc `Composer` for detecting swipe gestures. - * @extends Composer - */ -exports.SwipeComposer = Composer.specialize( /** @lends SwipeComposer# */ { +var DIRECTION_LEFT = 'left', + DIRECTION_RIGHT = 'right', + DIRECTION_UP = 'up', + DIRECTION_DOWN = 'down', + SWIPE = 'swipe'; - load: { - value: function () { - document.addEventListener("touchstart", this, true); - } +var SwipeEvent = exports.SwipeEvent = MutableEvent.specialize({ + + /** + * @public + * @type {String} + * @default "swipe" + * @description Name of the swipe event. + * + * Possible values: + * - "swipe" + * - "swipeUp" + * - "swipeRight" + * - "swipeDown" + * - "swipeLeft" + */ + type: { + value: SWIPE }, - unload: { - value: function () { - document.removeEventListener("touchstart", this, true); + _event: { + enumerable: false, + value: null + }, + + event: { + get: function () { + return this._event; + }, + set: function (value) { + this._event = value; } }, - _startX: { - enumerable: false, - value: 0 + bubbles: { + value: true }, - _startY: { - enumerable: false, - value: 0 + /** + * @public + * @type {String} + * @default null + * @description The direction of a swipe gesture. + * Possible values: + * - "up" + * - "right" + * - "down" + * - "left" + */ + direction: { + value: null }, - _deltaX: { - enumerable: false, - value: 0 + /** + * @public + * @type {Number} + * @default null + * @description The angle of a swipe gesture. + */ + angle: { + value: null }, - _deltaY: { - enumerable: false, - value: 0 + /** + * @public + * @type {Number} + * @default null + * @description The distance traveled of a swipe gesture. + */ + distance: { + value: null + }, + + /** + * @public + * @type {Number} + * @default null + * @description The velocity of a swipe gesture. + */ + velocity: { + value: null + }, + + /** + * @public + * @type {Number} + * @default null + * @description Start x coordinate of a swipe gesture. + */ + startPositionX: { + value: null + }, + + /** + * @public + * @type {Number} + * @default null + * @description Start y coordinate of a swipe gesture. + */ + startPositionY: { + value: null + }, + + /** + * @public + * @type {Number} + * @default null + * @description End x coordinate of a swipe gesture. + */ + endPositionX: { + value: null }, /** - * The number of pixels a gesture must continue to be recognized as a - * swipe. - * @type {number} + * @public + * @type {Number} + * @default null + * @description End y coordinate of a swipe gesture. */ - _threshold: { - enumerable: false, - value: 50 + endPositionY: { + value: null }, + constructor: { + value: function (type, eventInit) { + this._event = new CustomEvent(type, eventInit); + this.type = type; + } + } + +}); + +/** + * @class SwipeComposer + * @classdesc `Composer` for detecting swipe gestures. + * @extends Composer + */ +exports.SwipeComposer = Composer.specialize( /** @lends SwipeComposer# */ { + /** - * The maximum angle (in degrees) away from horizontal or vertical - * for a gesture to be recognized as a swipe. - * @type {number} + * @private + * @type {Number} + * @default null + * @description Start x coordinate of a swipe gesture. */ - _thresholdSwipeAngle: { - enumerable: false, - value: 20 + _startPositionX: { + value: null }, + /** + * @private + * @type {Number} + * @default null + * @description Start y coordinate of a swipe gesture. + */ + _startPositionY: { + value: null + }, + + /** + * @private + * @type {Number} + * @default null + * @description Start Timestamp of a swipe gesture. + */ _startTimestamp: { - enumerable: false, - value: 0 + value: null }, - captureTouchstart: { - value: function (event) { - this._reset(); - var touches = event.touches, - touch = touches[0]; - this._startX = touch.clientX; - this._startY = touch.clientY; - this._startTimestamp = event.timeStamp; - document.addEventListener("touchmove", this, true); - document.addEventListener("touchend", this, true); - document.addEventListener("touchcancel", this, true); + __translateComposer: { + value: null + }, + + /** + * @private + * @typedef {Object} TranslateComposer + * @readOnly + * @default null + * @description SwipeComposer's translate composer. + */ + _translateComposer: { + get: function () { + if (!this.__translateComposer) { + this.__translateComposer = new TranslateComposer(); + this.__translateComposer.hasMomentum = false; + } + + return this.__translateComposer; } }, - _reset: { - enumerable: false, + /** + * @public + * @type {Number} + * @default 10 + * @description Minimal distance required before recognizing. (px) + */ + minDistance: { + value: 10 + }, + + /** + * @public + * @type {Number} + * @default 0.3 + * @description Minimal velocity required before recognizing.(px per ms) + */ + minVelocity: { + value: 0.3 + }, + + /** + * @public + * @function load + * @description load the swipe composer. + */ + load: { value: function () { - this._startX = 0; - this._startY = 0; - this._deltaX = 0; - this._deltaY = 0; - this._startSwipeAngle = null; + this.component.addComposerForElement( + this._translateComposer, this.element + ); + this._translateComposer.load(); + this._translateComposer.addEventListener( + "translateStart", this, false + ); } }, - _startSwipeAngle: { - enumerable: false, - value: null + /** + * @public + * @function unload + * @description unload the swipe composer. + */ + unload: { + value: function () { + this.component.unloadComposer(this._translateComposer); + this._translateComposer.unload(); + this._translateComposer.removeEventListener( + "translateStart", this, false + ); + } }, - captureTouchcancel: { + handleTranslateStart: { value: function (event) { - document.removeEventListener("touchmove", this, true); - document.removeEventListener("touchend", this, true); - document.removeEventListener("touchcancel", this, true); + this._startPositionX = this._translateComposer.pointerStartEventPosition.pageX; + this._startPositionY = this._translateComposer.pointerStartEventPosition.pageY; + this._startTimestamp = event.timeStamp; + this._addTranslateEventListeners(); } }, - captureTouchmove: { + handleTranslateEnd: { value: function (event) { - event.preventDefault(); - var touches = event.changedTouches[0], swipeEvent, direction; - - this._deltaX = touches.clientX - this._startX; - this._deltaY = touches.clientY - this._startY; - - var dX = this._deltaX, - dY = this._deltaY, - threshold = this._threshold, - swipeAngle = this._findSwipeAngle(dX, dY); - - if ( - this._startSwipeAngle !== null && - Math.abs(this._startSwipeAngle - swipeAngle) > this._thresholdSwipeAngle - ) { - //Direction changed; Abort touch - //this.captureTouchcancel(); - this._startSwipeAngle = null; - } - - if (this._startSwipeAngle === null) { - this._startSwipeAngle = swipeAngle; - this._startX = touches.clientX; - this._startY = touches.clientY; - this._deltaX = 0; - this._deltaY = 0; - } - - if (dX > threshold && dY > threshold) { - direction = "DIAGONAL"; - } else if (dX > threshold && dY < threshold) { - if (this._deltaX > 0) { - direction = "RIGHT"; - } else { - direction = "LEFT"; - } - } else if (dX < threshold && dY > threshold) { - if (this._deltaY > 0) { - direction = "DOWN"; - } else { - direction = "UP"; + var distance = this._findDistance( + event.translateX, event.translateY + ); + + if (distance >= this.minDistance) { + var velocity = this._findVelocity( + distance, + event.timeStamp - this._startTimestamp + ); + + if (velocity > this.minVelocity) { + var angle = this._findAngle( + 0, 0, event.translateX, event.translateY + ), direction; + + if ( + angle >= 0 && angle <= 45 || + angle >= 315 && angle <= 360 + ) { + direction = DIRECTION_RIGHT; + } else if (angle > 45 && angle < 165) { + direction = DIRECTION_UP; + } else if (angle > 165 && angle < 225) { + direction = DIRECTION_LEFT; + } else { + direction = DIRECTION_DOWN; + } + + this._dispatchSwipeEvent( + distance, velocity, angle, direction + ); } } - if (dX !== 0 || dY !== 0) { - swipeEvent = document.createEvent("CustomEvent"); - swipeEvent.initCustomEvent("swipemove", true, false, null); - swipeEvent.direction = direction; - swipeEvent.angle = this._startSwipeAngle; - swipeEvent.velocity = this._findVelocity((event.timeStamp - this._startTimestamp)); - swipeEvent.startX = this._startX; - swipeEvent.startY = this._startY; - swipeEvent.dX = this._deltaX; - swipeEvent.dY = this._deltaY; - - this.dispatchEvent(swipeEvent); - } + this._resetComposerState(); } }, - _findSwipeAngle: { - enumerable: false, - value: function (dX, dY) { - var swipeAngle = -1 * (Math.atan2(dY, dX) * 180 / 3.14); - return swipeAngle.toFixed(2); + handleTranslateCancel: { + value: function () { + this._resetComposerState(); } }, - captureTouchend: { - value: function (event) { - - if (!event) { - return; + /** + * @private + * @function _findAngle + * @param p1x - start x coordinate + * @param p1y - start y coordinate + * @param p2x - end x coordinate + * @param p2y - end y coordinate + * @description Find the angle of a swipe gesture + * @returns Number + */ + _findAngle: { + value: function (p1x, p1y, p2x, p2y) { + var arctangent = ( + (Math.atan2(p2y - p1y, p2x - p1x) * -180) / Math.PI + ); + + if (arctangent < 0) { + arctangent = 360 + arctangent; } - var deltaX = Math.abs(this._deltaX), - deltaY = Math.abs(this._deltaY), - threshold = this._threshold, - direction, - swipeEvent; + return arctangent; + } + }, - if (deltaX < threshold && deltaY < threshold) { - this.captureTouchcancel(); - return; + /** + * @private + * @function _findVelocity + * @param distance - distance traveled by the swipe gesture. + * @param deltaTime - the time difference between the begining and + * the end of the translate gesture. + * @description Find the velocity of a swipe gesture. + * @returns Number + */ + _findVelocity: { + value: function (distance, deltaTime) { + if (deltaTime > 300) { + return 0; } - document.removeEventListener("touchmove", this, true); + return distance / deltaTime; + } + }, - if (deltaX > threshold && deltaY > threshold) { - direction = "DIAGONAL"; - } else if (deltaX > threshold && deltaY < threshold) { - if (this._deltaX > 0) { - direction = "RIGHT"; - } else { - direction = "LEFT"; - } - } else if (deltaX < threshold && deltaY > threshold) { - if (this._deltaY > 0) { - direction = "DOWN"; - } else { - direction = "UP"; - } - } + /** + * @private + * @function _findDistance + * @param deltaX - the difference between the two x coordinates. + * @param deltaY - the difference between the two y coordinates. + * @description Find the distance of a swipe gesture. + * @returns Number + */ + _findDistance: { + value: function (deltaX, deltaY) { + return Math.sqrt( + (deltaX * deltaX) + + (deltaY * deltaY) + ); + } + }, - swipeEvent = document.createEvent("CustomEvent"); - swipeEvent.initCustomEvent("swipe", true, false, null); + /** + * @private + * @function _dispatchSwipeEvent + * @param distance - the distance of a swipe gesture. + * @param velocity - the velocity of a swipe gesture. + * @param angle - the angle of a swipe gesture. + * @param direction - the direction of a swipe gesture. + * @description Dispatch two swipe events. + */ + _dispatchSwipeEvent: { + value: function (distance, velocity, angle, direction, endPositionX, endPositionY) { + var swipeEvent = new SwipeEvent(SWIPE); + + swipeEvent.distance = distance; + swipeEvent.velocity = velocity; + swipeEvent.angle = angle; swipeEvent.direction = direction; - swipeEvent.angle = this._startSwipeAngle; - swipeEvent.velocity = this._findVelocity((event.timeStamp - this._startTimestamp)); - swipeEvent.startX = this._startX; - swipeEvent.startY = this._startY; - swipeEvent.dX = this._deltaX; - swipeEvent.dY = this._deltaY; + swipeEvent.startPositionX = this._startPositionX; + swipeEvent.startPositionY = this._startPositionY; + swipeEvent.endPositionX = endPositionX; + swipeEvent.endPositionY = endPositionY; this.dispatchEvent(swipeEvent); } }, - _findVelocity: { - enumerable: false, - value: function (deltaTime) { - return (Math.sqrt(/*xSquare*/(this._deltaX * this._deltaX) + /*ySquare*/(this._deltaY * this._deltaY)) / /*deltaTime*/(deltaTime)); + /** + * @private + * @function _addTranslateEventListeners + * @description add the translate event listeners + */ + _addTranslateEventListeners: { + value: function () { + this._translateComposer.addEventListener( + "translateCancel", this + ); + this._translateComposer.addEventListener( + "translateEnd", this + ); + } + }, + /** + * @private + * @function _removeTranslateEventListeners + * @description remove the translate event listeners + */ + _removeTranslateEventListeners: { + value: function () { + this._translateComposer.removeEventListener( + "translateCancel", this + ); + this._translateComposer.removeEventListener( + "translateEnd", this + ); + } + }, + + /** + * @private + * @function _resetComposerState + * @description Reset the swipe composer state. + */ + _resetComposerState: { + value: function () { + this._startPositionX = 0; + this._startPositionY = 0; + this._direction = null; + this.__translateComposer.translateX = 0; + this.__translateComposer.translateY = 0; + this._removeTranslateEventListeners(); } } diff --git a/composer/translate-composer.js b/composer/translate-composer.js index 351c973d81..4edba8276f 100644 --- a/composer/translate-composer.js +++ b/composer/translate-composer.js @@ -958,7 +958,7 @@ var TranslateComposer = exports.TranslateComposer = Composer.specialize(/** @len isNegativeDeltaX, deltaX, deltaY; - + if (event.type === "wheel" || event.type === "mousewheel") { if (this._axis !== "vertical") { deltaX = ((event.wheelDeltaX || -event.deltaX || 0) * 20) / 120; @@ -1069,9 +1069,12 @@ var TranslateComposer = exports.TranslateComposer = Composer.specialize(/** @len this.endX = this.posX = this.startX=this._translateX; this.endY=this.posY=this.startY=this._translateY; - var velocity = event.velocity; + var velocity; - if ((this._hasMomentum) && ((velocity.speed>40) || this.translateStrideX || this.translateStrideY)) { + if ( + this._hasMomentum && (velocity = event.velocity) && + ((velocity.speed > 40) || this.translateStrideX || this.translateStrideY) + ) { if (this._axis !== "vertical") { this.momentumX = velocity.x * this._pointerSpeedMultiplier * (this._invertXAxis ? 1 : -1); } else { diff --git a/core/core.js b/core/core.js index 944ab6e4a9..fcd2f05df5 100644 --- a/core/core.js +++ b/core/core.js @@ -9,8 +9,11 @@ require("./extras/object"); require("./extras/date"); require("./extras/element"); require("./extras/function"); +require("./extras/map"); require("./extras/regexp"); require("./extras/string"); +require("./extras/set"); +require("./extras/weak-map"); require("proxy-polyfill/proxy.min"); diff --git a/core/drag/drag-event.js b/core/drag/drag-event.js new file mode 100644 index 0000000000..8a8ca4c365 --- /dev/null +++ b/core/drag/drag-event.js @@ -0,0 +1,375 @@ +var MutableEvent = require("../event/mutable-event").MutableEvent, + Montage = require("../core").Montage; + +// XXX Does not presently function server-side +if (typeof window !== "undefined") { + + var OBJECT_MIME_TYPE = 'application/object'; + + var DataTransfer = exports.DataTransfer = Montage.specialize({ + + __data: { + enumerable: false, + value: null + }, + + _data: { + enumerable: false, + get: function () { + return this.__data || (this.__data = new Map()); + } + }, + + _dragImage: { + value: null, + enumerable: false, + }, + + _dragEffect: { + value: null + }, + + dragEffect: { + set: function (effect) { + if (this.isEffectAllowed(effect)) { + this._dragEffect = effect; + } + }, + get: function () { + return this._dragEffect || DataTransfer.Default; + } + }, + + _dropEffect: { + value: null + }, + + dropEffect: { + set: function (effect) { + if ( + effect && + this.isEffectAllowed(effect) && + DataTransfer.isDropEffectAllowed( + effect, this.effectAllowed + ) + ) { + this._dropEffect = effect; + } else { + this._dropEffect = null; + } + }, + get: function () { + if (!this._dropEffect) { + if ( + this.effectAllowed === DataTransfer.All || + this.effectAllowed.startsWith('c') + ) { + this._dropEffect = DataTransfer.Copy; + } else if (this.isEffectAllowed(this.effectAllowed)) { + this._dropEffect = this.effectAllowed; + } else { + this._dropEffect = DataTransfer.Link; + } + } + + return this._dropEffect; + } + }, + + _effectAllowed: { + value: null + }, + + effectAllowed: { + set: function (effect) { + if (!!DataTransfer.allowedDropEffectsMap[effect]) { + this._effectAllowed = effect; + } + }, + get: function () { + return this._effectAllowed || DataTransfer.All; + } + }, + + files: { + value: null + }, + + items: { + value: null + }, + + types: { + value: null + }, + + dragTarget: { + value: null + }, + + _dropTargetCandidates: { + value: null, + enumerable: false + }, + + dropTargetCandidates: { + get: function () { + return this._dropTargetCandidates || + (this._dropTargetCandidates = new Set()); + } + }, + + showPlaceholder: { + value: false + }, + + draggedObject: { + set: function (object) { + this._data.set(OBJECT_MIME_TYPE, object); + }, + get: function () { + return this._data.get(OBJECT_MIME_TYPE); + } + }, + + clearData: { + value: function () { + return this._data.clear(); + } + }, + + getData: { + value: function (key) { + return this._data.get(key); + } + }, + + hasData: { + value: function (key) { + return this._data.has(key); + } + }, + + setData: { + value: function (key, value) { + return this._data.set(key, value); + } + }, + + dragImageXOffset: { + value: null + }, + + dragImageYOffset: { + value: null + }, + + setDragImage: { + value: function (img, xOffset, yOffset) { + if (!this._dragImage) { + this._dragImage = img; + + if (xOffset >= 0) { + this.dragImageXOffset = xOffset; + } + + if (yOffset >= 0) { + this.dragImageYOffset = yOffset; + } + } + } + }, + + getDragImage: { + value: function () { + return this._dragImage; + } + }, + + isEffectAllowed: { + value: function (effect) { + return !!DataTransfer.allowedEffectsMap[effect]; + } + } + + }, { + Default: { + value: "default" + }, + + Copy: { + value: "copy" + }, + + Move: { + value: "move" + }, + + Link: { + value: "alias" + }, + + CopyLink: { + value: "copyLink" + }, + + CopyMove: { + value: "copyMove" + }, + + LinkMove: { + value: "linkMove" + }, + + All: { + value: "all" + }, + + _allowedEffectsMap: { + value: null + }, + + allowedEffectsMap: { + get: function () { + if (!this._allowedEffectsMap) { + this._allowedEffectsMap = {}; + this._allowedEffectsMap[this.Default] = true; + this._allowedEffectsMap[this.Copy] = true; + this._allowedEffectsMap[this.Link] = true; + this._allowedEffectsMap[this.Move] = true; + } + + return this._allowedEffectsMap; + } + }, + + _allowedDropEffectsMap: { + value: null + }, + + allowedDropEffectsMap: { + get: function () { + if (!this._allowedDropEffectsMap) { + var tmp = {}; + tmp[this.All] = true; + tmp[this.CopyMove] = true; + tmp[this.CopyLink] = true; + tmp[this.LinkMove] = true; + + this._allowedDropEffectsMap = Object.assign( + tmp, this._allowedEffectsMap + ); + } + + return this._allowedDropEffectsMap; + } + }, + + isDropEffectAllowed: { + value: function (effect, effectAllowed) { + return effectAllowed === this.All || + effect === effectAllowed || + (effect === this.Copy && ( + effectAllowed === this.CopyMove || + effectAllowed === this.CopyLink + )) || + (effect === this.Move && ( + effectAllowed === this.CopyMove || + effectAllowed === this.LinkMove + )) || + (effect === this.Link && ( + effectAllowed === this.LinkMove || + effectAllowed === this.CopyLink + )); + } + }, + + /** + * @function + * @param {window.DataTransfer} dataTransfer The original DataTransfer. + * @returns DataTransfer + */ + fromDataTransfer: { + value: function (dataTransfer) { + // can't be re used it for security purposes. + var montageDataTransfer = new DataTransfer(); + + montageDataTransfer.items = dataTransfer.items; + montageDataTransfer.files = dataTransfer.files; + montageDataTransfer.types = dataTransfer.types; + montageDataTransfer.dropEffect = dataTransfer.dropEffect === 'none' ? + this.Default : dataTransfer.dropEffect; + montageDataTransfer.effectAllowed = dataTransfer.effectAllowed; + + return montageDataTransfer; + } + }, + }); + + exports.DragEvent = MutableEvent.specialize({ + + type: { + value: "drag" + }, + + _event: { + enumerable: false, + value: null + }, + + event: { + get: function () { + return this._event; + }, + set: function (value) { + this._event = value; + } + }, + + bubbles: { + value: true + }, + + dataTransfer: { + value: null + }, + + constructor: { + value: function (type, eventInit) { + this.dataTransfer = new DataTransfer(); + this._event = new CustomEvent(type, eventInit); + this.type = type; + } + } + + }, { + DRAGSTART: { + value: "dragstart" + }, + + DRAG: { + value: "drag" + }, + + DRAGENTER: { + value: "dragenter" + }, + + DRAGEXIT: { + value: "dragexit" + }, + + DRAGLEAVE: { + value: "dragleave" + }, + + DROP: { + value: "drop" + }, + + DRAGEND: { + value: "dragend" + } + }); + +} diff --git a/core/drag/drag-manager.js b/core/drag/drag-manager.js new file mode 100644 index 0000000000..af30800db4 --- /dev/null +++ b/core/drag/drag-manager.js @@ -0,0 +1,1317 @@ +var Montage = require("../core").Montage, + DragEvent = require("./drag-event").DragEvent, + DataTransfer = require("./drag-event").DataTransfer, + TranslateComposer = require("../../composer/translate-composer").TranslateComposer, + DraggingOperationContext = require("./dragging-operation-context").DraggingOperationContext; + +var DRAGGABLE = 0; +var DROPABBLE = 1; +var TOUCH_POINTER = "touch"; +var PX = "px"; + +var DragManager = exports.DragManager = Montage.specialize({ + + __draggables: { + value: null + }, + + _draggables: { + get: function () { + return this.__draggables || (this.__draggables = []); + } + }, + + __droppables: { + value: null + }, + + _droppables: { + get: function () { + return this.__droppables || (this.__droppables = []); + } + }, + + __rootComponent: { + value: null + }, + + _rootComponent: { + set: function (component) { + if (this.__rootComponent !== component) { + if (this.__rootComponent) { + this.__rootComponent.removeComposer( + this._translateComposer + ); + } + + if (component) { + component.addComposerForElement( + this._translateComposer, component.element + ); + } + + this.__rootComponent = component; + } + }, + get: function () { + return this.__rootComponent; + } + }, + + __translateComposer: { + value: null + }, + + _translateComposer: { + get: function () { + if (!this.__translateComposer) { + this.__translateComposer = new TranslateComposer(); + this.__translateComposer.hasMomentum = false; + this.__translateComposer.shouldCancelOnSroll = true; + this.__translateComposer.translateX = 0; + this.__translateComposer.translateY = 0; + this.__translateComposer.lazyLoad = false; + } + + return this.__translateComposer; + } + }, + + _draggingOperationContext: { + value: null + }, + + _willTerminateDraggingOperation: { + value: false + }, + + _needsToWaitforDraggedImageBoundaries: { + value: false + }, + + _draggableContainerBoundingRect: { + value: null + }, + + _draggedImageBoundingRect: { + value: null + }, + + _oldDraggableDisplayStyle: { + value: null + }, + + _dragEnterCounter: { + value: 0 + }, + + _cursorStyle: { + value: null + }, + + _scrollThreshold: { + value: 25 //px + }, + + _shouldRemovePlaceholder: { + value: false + }, + + initWithComponent: { + value: function (component) { + this._rootComponent = component; + var element = this._rootComponent.element; + + if ("webkitTransform" in element.style) { + DragManager.cssTransform = "webkitTransform"; + } else if ("MozTransform" in element.style) { + DragManager.cssTransform = "MozTransform"; + } else if ("oTransform" in element.style) { + DragManager.cssTransform = "oTransform"; + } else { + DragManager.cssTransform = "transform"; + } + + if (window.PointerEvent) { + element.addEventListener("pointerdown", this, true); + } else if (window.MSPointerEvent && window.navigator.msPointerEnabled) { + element.addEventListener("MSPointerDown", this, true); + } else { + element.addEventListener("touchstart", this, true); + } + + this._translateComposer.addEventListener("translateStart", this); + element.addEventListener("dragenter", this, true); + + return this; + } + }, + + /** + * @public + * @function + * @param {Component} component - a component + * @description Register a component to be draggable + */ + registerDraggable: { + value: function (component) { + this._register(component, DRAGGABLE); + } + }, + + /** + * @public + * @function + * @param {Component} component - a component + * @description Register a component to be droppable + */ + registerDroppable: { + value: function (component) { + this._register(component, DROPABBLE); + } + }, + + /** + * @public + * @function + * @param {Component} component - a component + * @description Unregister a component to be draggable + */ + unregisterDraggable: { + value: function (component) { + this._unregister(component, DRAGGABLE); + } + }, + + /** + * @public + * @function + * @param {Component} component - a component + * @description Unregister a component to be droppable + */ + unregisterDroppable: { + value: function (component) { + this._unregister(component, DROPABBLE); + } + }, + + /** + * Private APIs + */ + + /** + * @private + * @function + * @param {Component} component + * @param {number} role - component's role -> draggable or droppable + * @description register an component to be draggable or droppable + */ + _register: { + value: function (component, role) { + if (component) { + var components = role === DRAGGABLE ? + this._draggables : this._droppables; + + if (components.indexOf(component) === -1) { + components.push(component); + } + } + } + }, + + /** + * @private + * @function + * @param {Component} component + * @param {number} role - component's role -> draggable or droppable + * @description unregister an component from beeing draggable or droppable + */ + _unregister: { + value: function (component, role) { + if (component) { + var components = role === DRAGGABLE ? + this._draggables : this._droppables, + index; + + if ((index = components.indexOf(component)) > -1) { + components.splice(index, 1); + } + } + } + }, + + /** + * @private + * @function + * @param {Element} draggedImage - node element that will be used + * as a dragged image + * @param {Object} startPosition - coordinates of the start position + * @description set some default css style on the dragged image. + */ + _createDraggingOperationContextWithDraggableAndPosition: { + value: function (draggable, startPosition) { + var draggingOperationContext = new DraggingOperationContext(); + draggingOperationContext.draggable = draggable; + draggingOperationContext.startPositionX = startPosition.pageX; + draggingOperationContext.startPositionY = startPosition.pageY; + draggingOperationContext.positionX = startPosition.pageX; + draggingOperationContext.positionY = startPosition.pageY; + + return draggingOperationContext; + } + }, + + /** + * @private + * @function + * @param {Element} draggedImage - node element that will be used + * as a dragged image + * @description set some default css style on the dragged image. + */ + _sanitizeDraggedImage: { + value: function (draggedImage) { + draggedImage.classList.add("montage-dragged-image"); + draggedImage.style.visibility = "hidden"; + draggedImage.style.position = "absolute"; + draggedImage.style.pointerEvents = "none"; + draggedImage.style.boxSizing = "border-box"; + draggedImage.style.zIndex = 999999; + draggedImage.style.opacity = 0.95; + + return draggedImage; + } + }, + + /** + * @private + * @function + * @param {string} type drag event type + * @param {DraggingOperationContext} draggingOperationContext dragging operation context + * @description Create a Custon Drag Event + */ + _createDragEvent: { + value: function (type, draggingOperationContext) { + var dragEvent = new DragEvent(type); + + if (!draggingOperationContext.dataTransfer) { + draggingOperationContext.dataTransfer = dragEvent.dataTransfer; + } else { + dragEvent.dataTransfer = draggingOperationContext.dataTransfer; + } + + return dragEvent; + } + }, + + /** + * @private + * @function + * @description Dispatch Drag Start Event + */ + _dispatchDragStart: { + value: function () { + var draggable = this._draggingOperationContext.draggable, + dragStartEvent = this._createDragEvent( + "dragstart", this._draggingOperationContext + ); + + if (draggable) { + draggable.dispatchEvent(dragStartEvent); + } else { + this._rootComponent.application.dispatchEvent(dragStartEvent); + } + + this._draggingOperationContext.dropTargetCandidates = ( + dragStartEvent.dataTransfer.dropTargetCandidates + ); + + this._draggingOperationContext.dropTargetCandidates.forEach( + function (droppable) { + droppable.classList.add('valid-drop-target'); + } + ); + + return dragStartEvent; + } + }, + + /** + * @private + * @function + * @param {DraggingOperationContext} draggingOperationContext - current dragging + * operation info object + * @description Dispatch Drag End Event + */ + _dispatchDragEnd: { + value: function (draggingOperationContext) { + draggingOperationContext.dropTargetCandidates.forEach(function (droppable) { + droppable.classList.remove('valid-drop-target'); + }); + + if (draggingOperationContext.draggable) { + draggingOperationContext.draggable.dispatchEvent( + this._createDragEvent( + "dragend", draggingOperationContext + ) + ); + } + } + }, + + /** + * @private + * @function + * @param {DraggingOperationContext} draggingOperationContext - current dragging + * operation info object + * @description Dispatch Drop Event + */ + _dispatchDrop: { + value: function (draggingOperationContext) { + if (draggingOperationContext.currentDropTarget) { + draggingOperationContext.currentDropTarget.classList.remove("drag-enter"); + draggingOperationContext.currentDropTarget.classList.remove("drag-over"); + + draggingOperationContext.currentDropTarget.dispatchEvent(this._createDragEvent( + "drop", draggingOperationContext + )); + } + } + }, + + /** + * @private + * @function + * @description Dispatch Drag Enter Event + */ + _dispatchDragEnter: { + value: function (draggingOperationContext) { + if (draggingOperationContext.currentDropTarget) { + draggingOperationContext.currentDropTarget.classList.add("drag-enter"); + var dragEnterEvent = this._createDragEvent( + "dragenter", draggingOperationContext + ); + + draggingOperationContext.currentDropTarget.dispatchEvent(dragEnterEvent); + draggingOperationContext.dropEffect = ( + dragEnterEvent.dataTransfer.dropEffect || "default" + ); + } + } + }, + + /** + * @private + * @function + * @description Dispatch Drag Over Event + */ + _dispatchDragOver: { + value: function (draggingOperationContext) { + if (draggingOperationContext.currentDropTarget) { + draggingOperationContext.currentDropTarget.classList.add("drag-over"); + var dragOverEvent = this._createDragEvent( + "dragover", draggingOperationContext + ); + + draggingOperationContext.currentDropTarget.dispatchEvent(dragOverEvent); + draggingOperationContext.dropEffect = ( + dragOverEvent.dataTransfer.dropEffect || "default" + ); + } + } + }, + + /** + * @private + * @function + * @param {DraggingOperationContext} draggingOperationContext - current dragging + * operation info object + * @description Dispatch Drag Leave Event + */ + _dispatchDragLeave: { + value: function (draggingOperationContext) { + if (draggingOperationContext.currentDropTarget) { + draggingOperationContext.currentDropTarget.classList.remove( + "drag-over", "drag-enter" + ); + + draggingOperationContext.currentDropTarget.dispatchEvent(this._createDragEvent( + "dragleave", draggingOperationContext + )); + } + } + }, + + /** + * @private + * @function + * @param {string} positionX - x coordinate + * @param {string} positionY - y coordinate + * @description try to find a draggable component at the given position. + * @returns {Component|null} + */ + _findDraggableAtPosition: { + value: function (positionX, positionY) { + return this._findRegisteredComponentAtPosistion( + positionX, + positionY, + DRAGGABLE + ); + } + }, + + /** + * @private + * @function + * @param {string} positionX - x coordinate + * @param {string} positionY - y coordinate + * @description try to find a droppable component at the given position. + * @returns {Component|null} + */ + _findDroppableAtPosition: { + value: function (positionX, positionY) { + return this._findRegisteredComponentAtPosistion( + positionX, + positionY, + DROPABBLE + ); + } + }, + + /** + * @private + * @function + * @param {string} positionX - x coordinate + * @param {string} positionY - y coordinate + * @description try to find a droppable or draggable component + * at the given position. + * @returns {Component|null} + */ + _findRegisteredComponentAtPosistion: { + value: function (positionX, positionY, role) { + var targetComponent = this._findComponentAtPosition( + positionX, positionY + ), + registeredComponent = null; + + if (targetComponent) { + var components = role === DRAGGABLE ? + this._draggables : this._droppables, + index; + + while (targetComponent) { + if ((index = components.indexOf(targetComponent)) > -1) { + registeredComponent = components[index]; + targetComponent = null; + } else { + targetComponent = targetComponent.parentComponent; + } + } + } + + return registeredComponent; + } + }, + + /** + * @private + * @function + * @param {string} positionX - x coordinate + * @param {string} positionY - y coordinate + * @description try to find a component at the given position. + * @returns {Component|null} + */ + _findComponentAtPosition: { + value: function (positionX, positionY) { + var element = document.elementFromPoint(positionX, positionY), + component = null; + + // that is done at several place in the framework + // we should re-organize that + while (element && !(component = element.component)) { + element = element.parentElement; + } + + return component; + } + }, + + /** + * @private + * @function + * @description add translate listeners. + */ + _addTranslateListeners: { + value: function () { + this._translateComposer.addEventListener('translate', this); + this._translateComposer.addEventListener('translateEnd', this); + this._translateComposer.addEventListener('translateCancel', this); + } + }, + + /** + * @private + * @function + * @description remove translate listeners. + */ + _removeTranslateListeners: { + value: function () { + this._translateComposer.removeEventListener('translate', this); + this._translateComposer.removeEventListener('translateEnd', this); + this._translateComposer.removeEventListener('translateCancel', this); + } + }, + + /** + * @private + * @function + * @description add drag event listeners. + */ + _addDragListeners: { + value: function () { + var element = this._rootComponent.element; + element.addEventListener("dragover", this, true); + element.addEventListener("drop", this, true); + element.addEventListener("dragleave", this, true); + } + }, + + /** + * @private + * @function + * @description remove drag event listeners. + */ + _removeDragListeners: { + value: function () { + var element = this._rootComponent.element; + element.removeEventListener("dragover", this, true); + element.removeEventListener("drop", this, true); + element.removeEventListener("dragleave", this, true); + } + }, + + /** + * @private + * @function + * @description reset the dragging operation context. + */ + _resetTranslateContext: { + value: function () { + this._removeTranslateListeners(); + this._removeDragListeners(); + this._dragEnterCounter = 0; + this._draggingOperationContext.isDragging = false; + this.__translateComposer.translateX = 0; + this.__translateComposer.translateY = 0; + this._oldDraggableDisplayStyle = null; + this._draggedImageBoundingRect = null; + this._draggableContainerBoundingRect = null; + this._willTerminateDraggingOperation = false; + this._needsToWaitforDraggedImageBoundaries = false; + } + }, + + /** + * Events Handlers + */ + + capturePointerdown: { + value: function (event) { + if (event.pointerType === TOUCH_POINTER || + (window.MSPointerEvent && + event.pointerType === window.MSPointerEvent.MSPOINTER_TYPE_TOUCH) + ) { + this.captureTouchstart(event); + } + } + }, + + captureTouchstart: { + value: function (event) { + var draggable = this._findDraggableAtPosition( + event.pageX, + event.pageY + ); + + if (draggable) { + if (window.PointerEvent) { + this._rootComponent.element.addEventListener( + "pointermove", this, true + ); + } else if ( + window.MSPointerEvent && + window.navigator.msPointerEnabled + ) { + this._rootComponent.element.addEventListener( + "MSPointerMove", this, true + ); + } else { + this._rootComponent.element.addEventListener( + "touchmove", this, true + ); + } + } + } + }, + + capturePointermove: { + value: function (event) { + this.captureTouchmove(event); + } + }, + + captureTouchmove: { + value: function (event) { + // Prevent Scroll on touch devices + event.preventDefault(); + + if (window.PointerEvent) { + this._rootComponent.element.removeEventListener( + "pointermove", this, true + ); + } else if (window.MSPointerEvent && window.navigator.msPointerEnabled) { + this._rootComponent.element.removeEventListener( + "MSPointerMove", this, true + ); + } else { + this._rootComponent.element.removeEventListener( + "touchmove", this, true + ); + } + } + }, + + captureDragenter: { + value: function (event) { + if (!this._draggingOperationContext && !(event instanceof DragEvent)) { + var types = event.dataTransfer.types, + draggingOperationContext; + + this._draggingOperationContext = (draggingOperationContext = ( + this._createDraggingOperationContextWithDraggableAndPosition( + null, + event + ) + )); + + draggingOperationContext.dataTransfer = ( + DataTransfer.fromDataTransfer(event.dataTransfer) + ); + + var dragStartEvent = this._dispatchDragStart(); + + draggingOperationContext.dragEffect = ( + dragStartEvent.dataTransfer.dragEffect + ); + + this._addDragListeners(); + draggingOperationContext.isDragging = true; + this._rootComponent.needsDraw = true; + } + + this._dragEnterCounter++; + } + }, + + captureDragover: { + value: function (event) { + if (this._draggingOperationContext.currentDropTarget) { + event.preventDefault(); + } + + if ( + this._draggingOperationContext.positionX !== event.pageX || + this._draggingOperationContext.positionY !== event.pageY + ) { + this._draggingOperationContext.deltaX = ( + event.pageX - this._draggingOperationContext.startPositionX + ); + this._draggingOperationContext.deltaY = ( + event.pageY - this._draggingOperationContext.startPositionY + ); + this._draggingOperationContext.positionX = event.pageX; + this._draggingOperationContext.positionY = event.pageY; + this._draggingOperationContext.dataTransfer = event.dataTransfer; + + this._draggingOperationContext.dataTransfer = ( + DataTransfer.fromDataTransfer(event.dataTransfer) + ); + + this._rootComponent.needsDraw = true; + } + } + }, + + captureDrop: { + value: function (event) { + event.preventDefault(); + event.stopPropagation(); + + this._draggingOperationContext.deltaX = ( + event.pageX - this._draggingOperationContext.startPositionX + ); + this._draggingOperationContext.deltaY = ( + event.pageY - this._draggingOperationContext.startPositionY + ); + this._draggingOperationContext.positionX = event.pageX; + this._draggingOperationContext.positionY = event.pageY; + this._draggingOperationContext.dataTransfer = ( + DataTransfer.fromDataTransfer(event.dataTransfer) + ); + + this._removeDragListeners(); + this.handleTranslateEnd(); + } + }, + + captureDragleave: { + value: function (event) { + this._dragEnterCounter--; + + if (!this._dragEnterCounter) { + this.handleTranslateCancel(); + } + } + }, + + handleTranslateStart: { + value: function (event) { + var startPosition = this._translateComposer.pointerStartEventPosition, + draggable = this._findDraggableAtPosition( + startPosition.pageX, + startPosition.pageY + ); + + if (draggable) { + var draggingOperationContext, draggedImage; + + this._draggingOperationContext = (draggingOperationContext = ( + this._createDraggingOperationContextWithDraggableAndPosition( + draggable, + startPosition + ) + )); + + var dragStartEvent = this._dispatchDragStart(); + + this._draggingOperationContext.dataTransfer = dragStartEvent.dataTransfer; + this._draggingOperationContext.dragEffect = dragStartEvent.dataTransfer.dragEffect; + this._draggingOperationContext.showPlaceholder = ( + dragStartEvent.dataTransfer.showPlaceholder + ); + + if (!(draggedImage = dragStartEvent.dataTransfer.getDragImage())) { + draggedImage = draggable.element.cloneNode(true); + } + + draggingOperationContext.draggedImage = this._sanitizeDraggedImage( + draggedImage + ); + + this._addTranslateListeners(); + draggingOperationContext.isDragging = true; + this._rootComponent.needsDraw = true; + } else { + this._translateComposer._cancel(); + } + } + }, + + handleTranslate: { + value: function (event) { + this._draggingOperationContext.deltaX = event.translateX; + this._draggingOperationContext.deltaY = event.translateY; + this._draggingOperationContext.positionX = ( + this._draggingOperationContext.startPositionX + event.translateX + ); + this._draggingOperationContext.positionY = ( + this._draggingOperationContext.startPositionY + event.translateY + ); + this._rootComponent.needsDraw = true; + } + }, + + handleTranslateEnd: { + value: function () { + this._willTerminateDraggingOperation = true; + this._rootComponent.needsDraw = true; + } + }, + + handleTranslateCancel: { + value: function () { + this.draggingOperationContext.currentDropTarget = null; + this._willTerminateDraggingOperation = true; + this._rootComponent.needsDraw = true; + } + }, + + /** + * Draw Cycles Management + */ + + willDraw: { + value: function () { + var draggingOperationContext; + + if ( + (draggingOperationContext = this._draggingOperationContext) && + draggingOperationContext.isDragging + ) { + var draggable = draggingOperationContext.draggable; + + if ( + !this._draggedImageBoundingRect && + draggingOperationContext.draggable + ) { + this._draggedImageBoundingRect = ( + draggingOperationContext.draggable.element.getBoundingClientRect() + ); + + if (draggable.draggableContainer) { + this._draggableContainerBoundingRect = ( + draggable.draggableContainer.getBoundingClientRect() + ); + } + } else { + var droppable = this._findDroppableAtPosition( + draggingOperationContext.positionX, + draggingOperationContext.positionY + ); + + if (droppable && + !draggingOperationContext.dropTargetCandidates.has( + droppable + ) + ) { + droppable.classList.add('invalid-drop-target'); + this._invalidDroppable = droppable; + droppable = null; + } else if (this._invalidDroppable) { + this._invalidDroppable.classList.remove('invalid-drop-target'); + this._invalidDroppable = null; + } + + if (draggable) { + draggable.dispatchEvent(this._createDragEvent( + "drag", draggingOperationContext + )); + } + + if (droppable !== draggingOperationContext.currentDropTarget) { + if (draggingOperationContext.currentDropTarget) { + this._dispatchDragLeave( + draggingOperationContext + ); + } + + draggingOperationContext.currentDropTarget = droppable; + + if (droppable) { + this._dispatchDragEnter( + draggingOperationContext + ); + } + } else if (droppable) { + this._dispatchDragOver( + draggingOperationContext + ); + } else { + draggingOperationContext.currentDropTarget = null; + } + + if (droppable) { + this._cursorStyle = draggingOperationContext.dropEffect; + } else { + this._cursorStyle = draggingOperationContext.dragEffect; + } + } + } + } + }, + + draw: { + value: function () { + var draggingOperationContext = this._draggingOperationContext; + + if ( + draggingOperationContext && + draggingOperationContext.isDragging && + !this._willTerminateDraggingOperation + ) { + var draggedImage = draggingOperationContext.draggedImage; + + if (draggedImage) { + var translateX = draggingOperationContext.deltaX, + translateY = draggingOperationContext.deltaY; + + this._drawDraggedImageIfNeeded(draggedImage); + + if (!this._needsToWaitforDraggedImageBoundaries) { + draggedImage.style.visibility = "visible"; + } else { + this._needsToWaitforDraggedImageBoundaries = false; + } + + if (this._draggableContainerBoundingRect) { + var rect = this._draggableContainerBoundingRect, + deltaPointerLeft, deltaPointerRight, + deltaPointerTop, deltaPointerBottom; + + if (draggingOperationContext.positionX - ( + deltaPointerLeft = ( + draggingOperationContext.startPositionX - + this._draggedImageBoundingRect.left + ) + ) < rect.left) { + translateX = ( + rect.left - + draggingOperationContext.startPositionX + + deltaPointerLeft + ); + } else if (draggingOperationContext.positionX + ( + deltaPointerRight = ( + this._draggedImageBoundingRect.right - + draggingOperationContext.startPositionX + ) + ) > rect.right) { + translateX = ( + rect.right - + draggingOperationContext.startPositionX - + deltaPointerRight + ); + } + + if (draggingOperationContext.positionY - ( + deltaPointerTop = ( + draggingOperationContext.startPositionY - + this._draggedImageBoundingRect.top + ) + ) < rect.top) { + translateY = ( + rect.top - + draggingOperationContext.startPositionY + + deltaPointerTop + ); + } else if (draggingOperationContext.positionY + ( + deltaPointerBottom = ( + this._draggedImageBoundingRect.bottom - + draggingOperationContext.startPositionY + ) + ) > rect.bottom) { + translateY = ( + rect.bottom - + draggingOperationContext.startPositionY - + deltaPointerBottom + ); + } + } + + var translate = "translate3d("; + translate += translateX; + translate += "px,"; + translate += translateY; + translate += "px,0)"; + + draggedImage.style[DragManager.cssTransform] = translate; + + this._scrollIfNeeded( + draggingOperationContext.positionX, + draggingOperationContext.positionY + ); + } + + this._rootComponent.element.style.cursor = this._cursorStyle; + + } else if (this._willTerminateDraggingOperation) { + this._rootComponent.element.style.cursor = "default"; + + if (draggingOperationContext.draggedImage) { + document.body.removeChild(draggingOperationContext.draggedImage); + } + + if (draggingOperationContext.currentDropTarget) { + draggingOperationContext.hasBeenDrop = true; + draggingOperationContext.droppable = draggingOperationContext.currentDropTarget; + } + + this._dispatchDrop( + draggingOperationContext + ); + + this._resetTranslateContext(); + draggingOperationContext.currentDropTarget = null; + this._dispatchDragEnd(draggingOperationContext); + + if ( + draggingOperationContext.draggable && + draggingOperationContext.dragEffect === "move" + ) { + this._shouldRemovePlaceholder = true; + this._rootComponent.needsDraw = true; + // Wait for the next draw cycle to remove the placeholder, + // or in order to be synchronised with the draw cyle when + // the draggable component will become visible again. + // Plus it allows the receiver to perform any necessary clean-up. + return void 0; + } else { + this._draggingOperationContext = null; + } + } + + this._removeDraggablePlaceholderIfNeeded(); + } + }, + + _drawDraggedImageIfNeeded: { + value: function (draggedImage) { + var draggingOperationContext = this._draggingOperationContext; + + if (!draggedImage.parentElement) { + var draggedImageBoundingRect = this._draggedImageBoundingRect, + top = 0, left = 0; + draggedImage.style.width = draggedImageBoundingRect.width + PX; + draggedImage.style.height = draggedImageBoundingRect.height + PX; + + + if (draggingOperationContext.dataTransfer.dragImageXOffset !== null) { + left = draggingOperationContext.startPositionX; + + if (draggingOperationContext.dataTransfer.dragImageXOffset > draggedImageBoundingRect.width) { + left -= draggedImageBoundingRect.width; + } else { + left -= draggingOperationContext.dataTransfer.dragImageXOffset; + } + + } else { + left = draggedImageBoundingRect.left; + } + + if (draggingOperationContext.dataTransfer.dragImageXOffset !== null) { + top = draggingOperationContext.startPositionY; + + if (draggingOperationContext.dataTransfer.dragImageYOffset > draggedImageBoundingRect.height) { + top -= draggedImageBoundingRect.height; + } else { + top -= draggingOperationContext.dataTransfer.dragImageYOffset; + } + } else { + top = draggedImageBoundingRect.top; + } + + top += PX; + left += PX; + + draggedImage.style.top = top; + draggedImage.style.left = left; + + if (draggingOperationContext.dragEffect === "move") { + var draggableElement = draggingOperationContext.draggable.element; + this._oldDraggableDisplayStyle = draggableElement.style.display; + draggableElement.style.display = 'none'; + + if (draggingOperationContext.showPlaceholder) { + var placeholderElement = document.createElement('div'), + width = draggedImageBoundingRect.width + PX, + height = draggedImageBoundingRect.height + PX; + + placeholderElement.style.width = width; + placeholderElement.style.height = height; + placeholderElement.style.boxSizing = "border-box"; + placeholderElement.classList.add( + 'montage-drag-placeholder' + ); + + draggableElement.parentNode.insertBefore( + placeholderElement, + draggableElement + ); + + this._placeholderElement = placeholderElement; + } + } + + document.body.appendChild(draggedImage); + this._needsToWaitforDraggedImageBoundaries = true; + } + } + }, + + _removeDraggablePlaceholderIfNeeded: { + value: function () { + if (this._shouldRemovePlaceholder) { + var draggingOperationContext = this._draggingOperationContext; + this._draggingOperationContext = null; + this._shouldRemovePlaceholder = false; + + if (draggingOperationContext && draggingOperationContext.draggable) { + var draggableElement = draggingOperationContext.draggable.element; + draggableElement.style.display = this._oldDraggableDisplayStyle; + + if (draggingOperationContext.showPlaceholder) { + draggableElement.parentNode.removeChild( + this._placeholderElement + ); + } + } + } + } + }, + + _scrollIfNeeded: { + value: function (positionX, positionY) { + var element = document.elementFromPoint(positionX, positionY), + containerBoundingRect = this._draggableContainerBoundingRect, + scrollThreshold = this._scrollThreshold, + stopSearchingX = false, + stopSearchingY = false, + rect, height, width, top, bottom, right, left, scrollWidth, + scrollLeft, scrollHeight, scrollTop, outsideBoundariesCounter, + notScrollable; + + while (element) { + rect = element.getBoundingClientRect(); + height = rect.height; + width = rect.width; + + if ( + (!height || !width) || + ((notScrollable = (scrollHeight = element.scrollHeight) <= height)) || + (notScrollable && (scrollWidth = element.scrollWidth) <= width) + ) { + // if no height or width + // or not scrollable pass to to the next parent. + element = element.parentElement; + continue; + } + + top = rect.top; + bottom = rect.bottom; + left = rect.left; + right = rect.right; + outsideBoundariesCounter = 0; + stopSearchingY = false; + + // Check for horizontal scroll up + if ( + positionY >= top && + (!containerBoundingRect || positionY >= containerBoundingRect.top) + ) { + if (positionY <= top + scrollThreshold) { + scrollTop = element.scrollTop; + + // if not already reached the bottom edge + if (scrollTop) { + element.scrollTop = ( + scrollTop - + this._getScrollMultiplier(positionY - top) + ); + + this._rootComponent.needsDraw = true; + } + + stopSearchingY = true; + } else { + outsideBoundariesCounter++; + } + } else { + outsideBoundariesCounter++; + } + + // Check for horizontal scroll down + if ( + !stopSearchingY && positionY <= bottom && + (!containerBoundingRect || positionY <= containerBoundingRect.bottom) + ) { + if (positionY >= bottom - scrollThreshold) { + scrollTop = element.scrollTop; + + // if not already reached the bottom edge + if (scrollTop < scrollHeight) { + element.scrollTop = ( + scrollTop + + this._getScrollMultiplier(bottom - positionY) + ); + this._rootComponent.needsDraw = true; + } + + stopSearchingY = true; + } else { + outsideBoundariesCounter++; + } + } else { + outsideBoundariesCounter++; + } + + // Check for vertical scroll left + if ( + positionX >= left && + (!containerBoundingRect || positionX >= containerBoundingRect.left) + ) { + if (positionX <= left + scrollThreshold) { + scrollLeft = element.scrollLeft; + + // if not already reached the left edge + if (scrollLeft) { + element.scrollLeft = ( + scrollLeft - + this._getScrollMultiplier(positionX - left) + ); + + this._rootComponent.needsDraw = true; + } + + stopSearchingX = true; + } else { + outsideBoundariesCounter++; + } + } else { + outsideBoundariesCounter++; + } + + // Check for horizontal scroll right + if ( + !stopSearchingX && positionX <= right && + (!containerBoundingRect || positionX <= containerBoundingRect.right) + ) { + if (positionX >= right - scrollThreshold) { + scrollLeft = element.scrollLeft; + scrollWidth = scrollWidth || element.scrollWidth; + + // if not already reached the right edge + if (scrollLeft < scrollWidth) { + element.scrollLeft = ( + scrollLeft + + this._getScrollMultiplier(right - positionX) + ); + this._rootComponent.needsDraw = true; + } + + stopSearchingX = true; + } else { + outsideBoundariesCounter++; + } + } else { + outsideBoundariesCounter++; + } + + if (stopSearchingY || stopSearchingX || + outsideBoundariesCounter === 4 + ) { + element = null; + } else { + element = element.parentElement; + } + } + } + }, + + _getScrollMultiplier: { + value: function (delta) { + return (this._scrollThreshold / (delta >= 1 ? delta : 1)) * 4; + } + } + +}); + +DragManager.prototype.captureMSPointerDown = DragManager.prototype.capturePointerdown; +DragManager.prototype.captureMSPointerMove = DragManager.prototype.capturePointermove; diff --git a/core/drag/drag.info/sample/index.html b/core/drag/drag.info/sample/index.html new file mode 100644 index 0000000000..57427bfd06 --- /dev/null +++ b/core/drag/drag.info/sample/index.html @@ -0,0 +1,20 @@ + + + + + + Drag & Drop Samples + + + + + + + + diff --git a/core/drag/drag.info/sample/package.json b/core/drag/drag.info/sample/package.json new file mode 100644 index 0000000000..386a944e5e --- /dev/null +++ b/core/drag/drag.info/sample/package.json @@ -0,0 +1,11 @@ +{ + "name": "placeholder-samples", + "version": "0.1.0", + "private": true, + "dependencies": { + "montage": "*" + }, + "mappings": { + "montage": "../../../../" + } +} diff --git a/core/drag/drag.info/sample/ui/drop-file.reel/drop-file.css b/core/drag/drag.info/sample/ui/drop-file.reel/drop-file.css new file mode 100644 index 0000000000..fc7703c5fd --- /dev/null +++ b/core/drag/drag.info/sample/ui/drop-file.reel/drop-file.css @@ -0,0 +1,3 @@ +.DropFile { + +} \ No newline at end of file diff --git a/core/drag/drag.info/sample/ui/drop-file.reel/drop-file.html b/core/drag/drag.info/sample/ui/drop-file.reel/drop-file.html new file mode 100644 index 0000000000..cde3da97a4 --- /dev/null +++ b/core/drag/drag.info/sample/ui/drop-file.reel/drop-file.html @@ -0,0 +1,30 @@ + + + + + + + +
+
+
+
+ + diff --git a/core/drag/drag.info/sample/ui/drop-file.reel/drop-file.js b/core/drag/drag.info/sample/ui/drop-file.reel/drop-file.js new file mode 100644 index 0000000000..0539c505d4 --- /dev/null +++ b/core/drag/drag.info/sample/ui/drop-file.reel/drop-file.js @@ -0,0 +1,71 @@ +/** + * @module ui/drop-file.reel + */ +var Component = require("montage/ui/component").Component; + +/** + * @class DropFile + * @extends Component + */ +exports.DropFile = Component.specialize(/** @lends DropFile# */ { + + fileName: { + value: null + }, + + droppable: { + value: true + }, + + enterDocument: { + value: function () { + this.application.addEventListener("dragstart", this); + } + }, + + exitDocument: { + value: function () { + this.application.removeEventListener("dragstart", this); + } + }, + + handleDragstart: { + value: function (event) { + var shouldAccept = !!(event.dataTransfer.types && + event.dataTransfer.types.indexOf('Files') > -1); + + if (shouldAccept) { + event.dataTransfer.dropTargetCandidates.add(this); + this._addEventListeners(); + } + } + }, + + handleDrop: { + value: function (event) { + console.log(event.dataTransfer.files); + this.fileName = event.dataTransfer.files[0].name; + } + }, + + handleDragended: { + value: function (event) { + this._removeEventListeners(); + } + }, + + _addEventListeners: { + value: function () { + this.application.addEventListener("dragend", this); + this.addEventListener("drop", this); + } + }, + + _removeEventListeners: { + value: function () { + this.application.removeEventListener("dragend", this); + this.removeEventListener("drop", this); + } + } + +}); diff --git a/core/drag/drag.info/sample/ui/drop-inner.reel/drop-inner.css b/core/drag/drag.info/sample/ui/drop-inner.reel/drop-inner.css new file mode 100644 index 0000000000..fca84730e7 --- /dev/null +++ b/core/drag/drag.info/sample/ui/drop-inner.reel/drop-inner.css @@ -0,0 +1,25 @@ +.DropInner::before { + position: absolute; + top: 5px; + left: 5px; + content: "inner"; +} +.Drop.DropInner.valid-drop-target { + border: 4px solid #34495e; + animation: none; +} + +.Drop.DropInner.valid-drop-target.is-empty .drop-placeholder { + color:#34495e; + animation: none; +} + +.Drop.DropInner.drag-over.is-empty .drop-placeholder { + color:#3498db; + animation: none; +} + +.Drop.DropInner.drag-over { + border: 4px solid #3498db; + animation: none; +} diff --git a/core/drag/drag.info/sample/ui/drop-inner.reel/drop-inner.html b/core/drag/drag.info/sample/ui/drop-inner.reel/drop-inner.html new file mode 100644 index 0000000000..d47c8c6f1b --- /dev/null +++ b/core/drag/drag.info/sample/ui/drop-inner.reel/drop-inner.html @@ -0,0 +1,19 @@ + + + + + + + +
+
+ + diff --git a/core/drag/drag.info/sample/ui/drop-inner.reel/drop-inner.js b/core/drag/drag.info/sample/ui/drop-inner.reel/drop-inner.js new file mode 100644 index 0000000000..ef1972ba74 --- /dev/null +++ b/core/drag/drag.info/sample/ui/drop-inner.reel/drop-inner.js @@ -0,0 +1,71 @@ +/** + * @module ui/drop-inner.reel + */ +var Component = require("montage/ui/component").Component; + +/** + * @class DropInner + * @extends Component + */ +exports.DropInner = Component.specialize(/** @lends DropInner# */ { + + droppable: { + value: true + }, + + enterDocument: { + value: function () { + this.application.addEventListener("dragstart", this, false); + } + }, + + exitDocument: { + value: function () { + this.application.removeEventListener("dragstart", this, false); + } + }, + + handleDragstart: { + value: function (event) { + event.dataTransfer.dropTargetCandidates.add(this); + this._addEventListeners(); + } + }, + + handleDragenter: { + value: function (event) { + event.stopPropagation(); + event.dataTransfer.dropEffect = "move"; + } + }, + + handleDrop: { + value: function (event) { + event.stopPropagation(); + console.log('dropzone inner received drop'); + } + }, + + handleDragended: { + value: function (event) { + this._removeEventListeners(); + } + }, + + _addEventListeners: { + value: function () { + this.addEventListener("dragenter", this); + this.addEventListener("dragended", this); + this.addEventListener("drop", this); + } + }, + + _removeEventListeners: { + value: function () { + this.removeEventListener("dragenter", this); + this.removeEventListener("dragended", this); + this.removeEventListener("drop", this); + } + } + +}); diff --git a/core/drag/drag.info/sample/ui/drop-outer.reel/drop-outer.css b/core/drag/drag.info/sample/ui/drop-outer.reel/drop-outer.css new file mode 100644 index 0000000000..8ad4a58c12 --- /dev/null +++ b/core/drag/drag.info/sample/ui/drop-outer.reel/drop-outer.css @@ -0,0 +1,32 @@ +.DropOuter { + height: 400px !important; + width: 400px !important; + margin-top: 20px; +} + +.DropOuter::before { + position: absolute; + top: 5px; + left: 5px; + content: "outer"; +} + +.Drop.DropOuter.valid-drop-target { + border: 4px solid #34495e; + animation: none; +} + +.Drop.DropOuter.valid-drop-target.is-empty .drop-placeholder { + color:#34495e; + animation: none; +} + +.Drop.DropOuter.drag-over.is-empty .drop-placeholder { + color:#3498db; + animation: none; +} + +.Drop.DropOuter.drag-over { + border: 4px solid #3498db; + animation: none; +} diff --git a/core/drag/drag.info/sample/ui/drop-outer.reel/drop-outer.html b/core/drag/drag.info/sample/ui/drop-outer.reel/drop-outer.html new file mode 100644 index 0000000000..eeaab973be --- /dev/null +++ b/core/drag/drag.info/sample/ui/drop-outer.reel/drop-outer.html @@ -0,0 +1,20 @@ + + + + + + + +
+
+
+ + diff --git a/core/drag/drag.info/sample/ui/drop-outer.reel/drop-outer.js b/core/drag/drag.info/sample/ui/drop-outer.reel/drop-outer.js new file mode 100644 index 0000000000..9c440d0c2e --- /dev/null +++ b/core/drag/drag.info/sample/ui/drop-outer.reel/drop-outer.js @@ -0,0 +1,71 @@ +/** + * @module ui/drop-outer.reel + */ +var Component = require("montage/ui/component").Component; + +/** + * @class DropOuter + * @extends Component + */ +exports.DropOuter = Component.specialize(/** @lends DropOuter# */ { + + droppable: { + value: true + }, + + enterDocument: { + value: function () { + this.application.addEventListener("dragstart", this, false); + } + }, + + exitDocument: { + value: function () { + this.application.removeEventListener("dragstart", this, false); + } + }, + + handleDragstart: { + value: function (event) { + event.dataTransfer.dropTargetCandidates.add(this); + this._addEventListeners(); + } + }, + + handleDragenter: { + value: function (event) { + event.stopPropagation(); + event.dataTransfer.dropEffect = "alias"; + } + }, + + handleDrop: { + value: function (event) { + event.stopPropagation(); + console.log('dropzone outer received drop'); + } + }, + + handleDragended: { + value: function (event) { + this._removeEventListeners(); + } + }, + + _addEventListeners: { + value: function () { + this.addEventListener("dragenter", this); + this.addEventListener("dragended", this); + this.addEventListener("drop", this); + } + }, + + _removeEventListeners: { + value: function () { + this.removeEventListener("dragenter", this); + this.removeEventListener("dragended", this); + this.removeEventListener("drop", this); + } + } + +}); diff --git a/core/drag/drag.info/sample/ui/drop.reel/drop.css b/core/drag/drag.info/sample/ui/drop.reel/drop.css new file mode 100644 index 0000000000..1cfd2b10bf --- /dev/null +++ b/core/drag/drag.info/sample/ui/drop.reel/drop.css @@ -0,0 +1,54 @@ +.Drop { + height: 200px; + width: 200px; + border: 4px solid #34495e; + display: -webkit-flex; + display: flex; + -webkit-align-items: center; + -ms-align-items: center; + align-items: center; + -webkit-justify-content: center; + -ms-justify-content: center; + justify-content: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-tap-highlight-color: rgba(0,0,0,0); + touch-action: none; + box-sizing: border-box; + position: relative; + color: #34495e; +} + +.Drop .drop-placeholder { + display: none; +} + +.Drop.is-empty .drop-placeholder { + display: block; +} + +.Drop.valid-drop-target { + border: 4px dashed #3498db25; + animation: blink 1.5s linear infinite alternate; +} + +.Drop.valid-drop-target.is-empty .drop-placeholder { + color:#3498db25; + animation: blink 1.5s linear infinite alternate; +} + +.Drop.drag-over.is-empty .drop-placeholder { + color:#3498db; + animation: none; +} + +.Drop.drag-over { + border: 4px solid #3498db; + animation: none; +} + +@keyframes blink { + 50% { border-color: #3498db; color: #3498db } + } diff --git a/core/drag/drag.info/sample/ui/drop.reel/drop.html b/core/drag/drag.info/sample/ui/drop.reel/drop.html new file mode 100644 index 0000000000..1b67324cb3 --- /dev/null +++ b/core/drag/drag.info/sample/ui/drop.reel/drop.html @@ -0,0 +1,42 @@ + + + + + + + +
+
+
+
+
+
+ + diff --git a/core/drag/drag.info/sample/ui/drop.reel/drop.js b/core/drag/drag.info/sample/ui/drop.reel/drop.js new file mode 100644 index 0000000000..9e45c4c3e8 --- /dev/null +++ b/core/drag/drag.info/sample/ui/drop.reel/drop.js @@ -0,0 +1,97 @@ +/** + * @module ui/drop.reel + */ +var Component = require("montage/ui/component").Component; + +/** + * @class Drop + * @extends Component + */ +exports.Drop = Component.specialize(/** @lends Drop# */ { + + dataSource: { + value: null + }, + + data: { + value: null + }, + + droppable: { + value: true + }, + + enterDocument: { + value: function () { + this.data = []; + this.application.addEventListener("dragstart", this, false); + } + }, + + exitDocument: { + value: function () { + this.application.removeEventListener("dragstart", this, false); + } + }, + + handleDragstart: { + value: function (event) { + var shouldAddToCandidate = false; + + if (this.dataSource) { + var value = event.dataTransfer.getData("text/plain"); + shouldAddToCandidate = value && this.data.indexOf(value) === -1 && + this.dataSource.indexOf(+value) > -1; + } + + if (shouldAddToCandidate) { + event.dataTransfer.dropTargetCandidates.add(this); + this._addEventListeners(); + } + } + }, + + handleDrop: { + value: function (event) { + var value = event.dataTransfer.getData("text/plain"); + + if (value && this.data.indexOf(value) === -1) { + this.data.push(value); + + if (event.dataTransfer.dropEffect === "move") { + var index; + + if ( + this.dataSource && + (index = this.dataSource.indexOf(value)) > -1 + ) { + this.dataSource.splice(index, 1); + } + } + } + + console.log(event.dataTransfer.draggedObject); + } + }, + + handleDragended: { + value: function (event) { + this._removeEventListeners(); + } + }, + + _addEventListeners: { + value: function () { + this.addEventListener("dragended", this); + this.addEventListener("drop", this); + } + }, + + _removeEventListeners: { + value: function () { + this.removeEventListener("dragended", this); + this.removeEventListener("drop", this); + } + } + +}); diff --git a/core/drag/drag.info/sample/ui/main.reel/main.css b/core/drag/drag.info/sample/ui/main.reel/main.css new file mode 100644 index 0000000000..7155d2d735 --- /dev/null +++ b/core/drag/drag.info/sample/ui/main.reel/main.css @@ -0,0 +1,106 @@ +html, body { + padding: 0; + margin: 0; + font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + position: fixed; +} + +.Main { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +header { + margin: 20px 0; + font-size: 2rem; + text-align: center; + color: #33495d; + min-height: 40px; +} + +h4 { + font-size: 1rem; + color: #33495d; + margin: 40px 0; +} + +.options { + border-bottom: 1px solid #33495d; + position: absolute; + top: 80px; + left: 0px; + height: 236px; + box-sizing: border-box; +} + +.options, +.samples { + padding: 15px; +} + +.samples { + position: absolute; + top: 316px; + bottom: 0; + left: 0; + right: 0; + overflow: auto; + -webkit-overflow-scrolling: touch; +} + +.flex { + display: flex; + flex-wrap: wrap; +} + +.flex-row { + flex-direction: row; +} + +.list { + padding: 8px; +} + +.list > * { + margin-top: 8px; + margin-left: 8px; +} + +.samples .montage-drag-placeholder { + border: 2px dashed #9b59b6; +} + +.scrollable { + margin-top: 20px; + overflow: auto; + width: 200px; + height: 200px; + border: 1px solid #33495d; + box-sizing: border-box; +} + +.scrollable-inner { + width: 2000px; + height: 2000px; + background: linear-gradient(to bottom, #f8c291 0%,#b71540 100%); +} + + +.CustomDrag { + height: 50px; + width: 50px; + border-radius: 25px; + background: #9b59b6; +} + +.invalid-drop-target { + cursor: not-allowed; +} diff --git a/core/drag/drag.info/sample/ui/main.reel/main.html b/core/drag/drag.info/sample/ui/main.reel/main.html new file mode 100644 index 0000000000..eec80ec021 --- /dev/null +++ b/core/drag/drag.info/sample/ui/main.reel/main.html @@ -0,0 +1,200 @@ + + + + + + + +
+
Drag & Drop Samples
+ +
+

Options:

+

+ Enable Visible Placeholder + +

+

+ Enable Move operation + +

+ +

+ Switch Dragged Image + +

+
+ +
+

Simple drag

+
+
+
+
+ +

Drag within a container

+
+
+
+ +

Drag&Drop

+
+
+
+
+
+
Drop Here
+
+
+
Drop not allowed
+
+
+ +

inner Dropzone

+
+
+
+
+ +

Drop file

+
+
Drop File Here
+
+ +

Custom Drag

+
+ +
+
+ + diff --git a/core/drag/drag.info/sample/ui/main.reel/main.js b/core/drag/drag.info/sample/ui/main.reel/main.js new file mode 100644 index 0000000000..59b3db1163 --- /dev/null +++ b/core/drag/drag.info/sample/ui/main.reel/main.js @@ -0,0 +1,9 @@ +var Component = require("montage/ui/component").Component; + +exports.Main = Component.specialize(/** @lends Main# */{ + + data: { + value: [1, 2, 3, 4, 5] + } + +}); diff --git a/core/drag/drag.info/sample/ui/square.reel/square.css b/core/drag/drag.info/sample/ui/square.reel/square.css new file mode 100644 index 0000000000..c58afbb3f4 --- /dev/null +++ b/core/drag/drag.info/sample/ui/square.reel/square.css @@ -0,0 +1,29 @@ +.Square { + height: 50px; + width: 50px; + background-color: #34495e; + display: -webkit-flex; + display: flex; + -webkit-align-items: center; + -ms-align-items: center; + align-items: center; + -webkit-justify-content: center; + -ms-justify-content: center; + justify-content: center; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + color: white; + -webkit-tap-highlight-color: rgba(0,0,0,0); + touch-action: none; + text-align: center; +} + +.Square.hide { + display: none; +} + +.Square.montage-dragged-image { + background-color: #9b59b6; +} diff --git a/core/drag/drag.info/sample/ui/square.reel/square.html b/core/drag/drag.info/sample/ui/square.reel/square.html new file mode 100644 index 0000000000..479482a866 --- /dev/null +++ b/core/drag/drag.info/sample/ui/square.reel/square.html @@ -0,0 +1,28 @@ + + + + + + + +
+
+
+ + diff --git a/core/drag/drag.info/sample/ui/square.reel/square.js b/core/drag/drag.info/sample/ui/square.reel/square.js new file mode 100644 index 0000000000..96fdc9c617 --- /dev/null +++ b/core/drag/drag.info/sample/ui/square.reel/square.js @@ -0,0 +1,80 @@ +/** + * @module ui/square.reel + */ +var Component = require("montage/ui/component").Component; + +/** + * @class Square + * @extends Component + */ +exports.Square = Component.specialize(/** @lends Square# */ { + + enableMoveOperation: { + value: false + }, + + enableVisiblePlaceholder: { + value: false + }, + + draggable: { + value: true + }, + + enterDocument: { + value: function () { + this.addEventListener("dragstart", this, false); + } + }, + + exitDocument: { + value: function () { + this.removeEventListener("dragstart", this, false); + } + }, + + handleDragstart: { + value: function (event) { + if (this.enableMoveOperation) { + event.dataTransfer.effectAllowed = "move"; + event.dataTransfer.dragEffect = "move"; + } else { + event.dataTransfer.effectAllowed = "all"; + } + + + if (this.container) { + this.draggableContainer = this.container; + } else { + this.draggableContainer = null; + } + + event.dataTransfer.draggedObject = { secret: "montage" }; + + if (this.enableVisiblePlaceholder) { + event.dataTransfer.showPlaceholder = true; + } + + event.dataTransfer.setData("text/plain", this.value); + + if (this.switchDraggedImage) { + var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), + rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + + svg.style.height = "50px"; + svg.style.width = "50px"; + + rect.setAttributeNS(null, 'x', 0); + rect.setAttributeNS(null, 'y', 0); + rect.setAttributeNS(null, 'width', 50); + rect.setAttributeNS(null, 'height', 50); + rect.setAttributeNS(null, 'style', 'fill: #e74c3c;'); + + svg.appendChild(rect); + + event.dataTransfer.setDragImage(svg, 25, 25); // center + } + } + } + +}); diff --git a/core/drag/dragging-operation-context.js b/core/drag/dragging-operation-context.js new file mode 100644 index 0000000000..e90368dc90 --- /dev/null +++ b/core/drag/dragging-operation-context.js @@ -0,0 +1,73 @@ +var Montage = require("../core").Montage; + +exports.DraggingOperationContext = Montage.specialize({ + + draggable: { + value: null + }, + + draggedImage: { + value: null + }, + + startPositionX: { + value: 0 + }, + + startPositionY: { + value: 0 + }, + + positionX: { + value: 0 + }, + + positionY: { + value: 0 + }, + + deltaX: { + value: 0 + }, + + deltaY: { + value: 0 + }, + + showPlaceholder: { + value: false + }, + + dragEffect: { + value: null + }, + + dropEffect: { + value: null + }, + + effectAllowed: { + value: null + }, + + dataTransfer: { + value: null + }, + + hasBeenDrop: { + value: false + }, + + dragTarget: { + value: null + }, + + currentDropTarget: { + value: null + }, + + _isDragging: { + value: false + } + +}); diff --git a/core/event/event-manager.js b/core/event/event-manager.js index 65f6d89d7f..f284e03b50 100644 --- a/core/event/event-manager.js +++ b/core/event/event-manager.js @@ -15,7 +15,7 @@ */ var Montage = require("../core").Montage, - MutableEvent = require("./mutable-event").MutableEvent, + MutableEvent = require("./mutable-event").MutableEvent, Serializer = require("../serialization/serializer/montage-serializer").MontageSerializer, Deserializer = require("../serialization/deserializer/montage-deserializer").MontageDeserializer, Map = require("collections/map"), @@ -24,7 +24,6 @@ var Montage = require("../core").Montage, var defaultEventManager; - //This is a quick polyfill for IE10 that is not exposing CustomEvent as a function. //From https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill if ( diff --git a/core/extras/map.js b/core/extras/map.js new file mode 100644 index 0000000000..5c8c1f3d14 --- /dev/null +++ b/core/extras/map.js @@ -0,0 +1,61 @@ + /** + Defines extensions to intrinsic `Map`. + @see {external:Map} + @module montage/core/extras/map + */ + + /** + * Deserialize a map object included in a serialization + * @param {Deserializer} deserializer The Deserializer containing the serialization information + * @function external:Map.deserializeSelf + */ + Object.defineProperty(Map.prototype, "deserializeSelf", { + value: function (deserializer) { + var entries, keys, values, + i, n; + + entries = deserializer.getProperty("entries"); + if (entries) { + for (i = 0, n = entries.length; i < n; i++) { + this.set(entries[i].key, entries[i].value); + } + } + if (!entries) { + keys = deserializer.getProperty("keys"); + values = deserializer.getProperty("values"); + if (keys && values) { + for (i = 0, n = keys.length; i < n; i++) { + this.set(keys[i], values[i]); + } + } + } + }, + writable: true, + configurable: true + }); + + + /** + * Sserialize a Map object + * @param {Deserializer} serializer The Serializer building out the serialization + * @function external:Map.serializeSelf + */ + Object.defineProperty(Map.prototype, "serializeSelf", { + value: function (serializer) { + var entries = this.entries(), + serializedKeys = [], + serializedValues = [], + entry; + + while ((entry = entries.next().value)) { + serializedKeys.push(entry[0]); + serializedValues.push(entry[1]); + } + + serializer.setProperty("keys", serializedKeys); + serializer.setProperty("values", serializedValues); + + }, + writable: true, + configurable: true + }); \ No newline at end of file diff --git a/core/extras/set.js b/core/extras/set.js new file mode 100644 index 0000000000..e64023e86c --- /dev/null +++ b/core/extras/set.js @@ -0,0 +1,48 @@ +/** + Defines extensions to intrinsic `Set`. + @see {external:Set} + @module montage/core/extras/set +*/ + +/** + * Deserialize a set object included in a serialization + * @param {Deserializer} deserializer The Deserializer containing the serialization information + * @function external:Set.deserializeSelf +*/ +Object.defineProperty(Set.prototype, "deserializeSelf", { + value: function (deserializer) { + var values, + i, n; + + values = deserializer.getProperty("values"); + if (values) { + for (i = 0, n = values.length; i < n; i++) { + this.add(values[i]); + } + } + }, + writable: true, + configurable: true +}); + + +/** + * Serialize a Set object + * @param {Deserializer} serializer The Serializer building out the serialization + * @function external:Set.serializeSelf +*/ +Object.defineProperty(Set.prototype, "serializeSelf", { + value: function (serializer) { + var serializedValues = [], + items = this.keys(), + item; + + while ((item = items.next().value)) { + serializedValues.push(item); + } + + serializer.setProperty("values", serializedValues); + }, + writable: true, + configurable: true +}); \ No newline at end of file diff --git a/core/extras/weak-map.js b/core/extras/weak-map.js new file mode 100644 index 0000000000..6e5eb2fd1f --- /dev/null +++ b/core/extras/weak-map.js @@ -0,0 +1,59 @@ +/** + Defines extensions to intrinsic `WeakMap`. + @see {external:WeakMap} + @module montage/core/extras/weak-map +*/ + +/** + * Deserialize a weak-map object included in a serialization + * @param {Deserializer} deserializer The Deserializer containing the serialization information + * @function external:WeakMap.deserializeSelf +*/ +Object.defineProperty(WeakMap.prototype, "deserializeSelf", { + value: function (deserializer) { + var entries, keys, values, + i, n; + + entries = deserializer.getProperty("entries"); + if (entries) { + for (i = 0, n = entries.length; i < n; i++) { + this.set(entries[i].key, entries[i].value); + } + } + if (!entries) { + keys = deserializer.getProperty("keys"); + values = deserializer.getProperty("values"); + if (keys && values) { + for (i = 0, n = keys.length; i < n; i++) { + this.set(keys[i], values[i]); + } + } + } + }, + writable: true, + configurable: true +}); + + +/** + * Serialize a WeakMap object + * @param {Deserializer} serializer The Serializer building out the serialization + * @function external:WeakMap.serializeSelf +*/ +Object.defineProperty(WeakMap.prototype, "serializeSelf", { + value: function (serializer) { + var entries = this.entries(), + serializedKeys = [], + serializedValues = [], + entry; + + while ((entry = entries.next().value)) { + serializedKeys.push(entry[0]); + serializedValues.push(entry[1]); + } + serializer.setProperty("keys", serializedKeys); + serializer.setProperty("values", serializedValues); + }, + writable: true, + configurable: true +}); \ No newline at end of file diff --git a/core/meta/model.js b/core/meta/model.js index a14fc53557..9b4539312a 100644 --- a/core/meta/model.js +++ b/core/meta/model.js @@ -236,7 +236,7 @@ var Model = exports.Model = Montage.specialize( /** @lends Model.prototype # */ objectDescriptorForPrototype: { value: deprecate.deprecateMethod(void 0, function (prototypeName) { return this.objectDescriptorForName(prototypeName); - }, "objectDescriptorForPrototype", "objectDescriptorForName") + }, "objectDescriptorForPrototype", "objectDescriptorForName", true) }, /** @@ -297,7 +297,7 @@ var Model = exports.Model = Montage.specialize( /** @lends Model.prototype # */ blueprints: { get: deprecate.deprecateMethod(void 0, function () { return this.objectDescriptors; - }, "blueprints", "objectDescriptors") + }, "blueprints", "objectDescriptors", true) }, /** @@ -309,7 +309,7 @@ var Model = exports.Model = Montage.specialize( /** @lends Model.prototype # */ addBlueprint: { value: deprecate.deprecateMethod(void 0, function (blueprint) { return this.addObjectDescriptor(blueprint); - }, "addBlueprint", "addObjectDescriptor") + }, "addBlueprint", "addObjectDescriptor", true) }, /** @@ -321,7 +321,7 @@ var Model = exports.Model = Montage.specialize( /** @lends Model.prototype # */ removeBlueprint: { value: deprecate.deprecateMethod(void 0, function (blueprint) { return this.removeObjectDescriptor(blueprint); - }, "removeBlueprint", "removeObjectDescriptor") + }, "removeBlueprint", "removeObjectDescriptor", true) }, /** @@ -334,7 +334,7 @@ var Model = exports.Model = Montage.specialize( /** @lends Model.prototype # */ addBlueprintNamed: { value: deprecate.deprecateMethod(void 0, function (name) { return this.addObjectDescriptorNamed(name); - }, "addBlueprintNamed", "addObjectDescriptorNamed") + }, "addBlueprintNamed", "addObjectDescriptorNamed", true) }, /** @@ -348,7 +348,7 @@ var Model = exports.Model = Montage.specialize( /** @lends Model.prototype # */ blueprintForPrototype: { value: deprecate.deprecateMethod(void 0, function (prototypeName) { return this.blueprintForName(prototypeName); - }, "blueprintForPrototype", "blueprintForName") + }, "blueprintForPrototype", "blueprintForName", true) }, /** @@ -359,7 +359,7 @@ var Model = exports.Model = Montage.specialize( /** @lends Model.prototype # */ blueprintForName: { value: deprecate.deprecateMethod(void 0, function (name) { return this.objectDescriptorForName(name); - }, "blueprintForName", "objectDescriptorForName") + }, "blueprintForName", "objectDescriptorForName", true) }, blueprintModuleId: require("../core")._objectDescriptorModuleIdDescriptor, diff --git a/core/meta/object-descriptor.js b/core/meta/object-descriptor.js index c323836bda..5b6a927b61 100644 --- a/core/meta/object-descriptor.js +++ b/core/meta/object-descriptor.js @@ -410,7 +410,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends isReady = true, descriptor, i, n; if (!this._propertyDescriptorsAreCached) { - + for (i = 0, n = ownDescriptors.length; i < n && isReady; ++i) { descriptor = ownDescriptors[i]; isReady = !!(descriptor && descriptor.name); @@ -1004,7 +1004,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends if (!this._userInterfaceDescriptor) { if (this.userInterfaceDescriptorModules && this.userInterfaceDescriptorModules["*"]) { - + Montage.defineProperty(this, "_userInterfaceDescriptor", { enumerable: false, value: this.userInterfaceDescriptorModules["*"].require.async( @@ -1094,7 +1094,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends addEventBlueprintNamed: { value: deprecate.deprecateMethod(void 0, function (name) { return this.addEventDescriptorNamed(name); - }, "addEventBlueprintNamed", "addEventDescriptorNamed") + }, "addEventBlueprintNamed", "addEventDescriptorNamed", true) }, /** @@ -1111,7 +1111,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends addPropertyBlueprint: { value: deprecate.deprecateMethod(void 0, function (propertyBlueprint) { this.addPropertyDescriptor(propertyBlueprint); - }, "addPropertyBlueprint", "addPropertyDescriptor") + }, "addPropertyBlueprint", "addPropertyDescriptor", true) }, /** @@ -1123,7 +1123,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends addPropertyBlueprintGroupNamed: { value: deprecate.deprecateMethod(void 0, function (groupName) { this.addPropertyDescriptorGroupNamed(groupName); - }, "addPropertyBlueprintGroupNamed", "addPropertyDescriptorGroupNamed") + }, "addPropertyBlueprintGroupNamed", "addPropertyDescriptorGroupNamed", true) }, /** @@ -1137,7 +1137,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends addPropertyBlueprintToGroupNamed: { value: deprecate.deprecateMethod(void 0, function (propertyBlueprint, groupName) { this.addPropertyDescriptorToGroupNamed(propertyBlueprint, groupName); - }, "addPropertyBlueprintToGroupNamed", "addPropertyDescriptorToGroupNamed") + }, "addPropertyBlueprintToGroupNamed", "addPropertyDescriptorToGroupNamed", true) }, /** @@ -1146,7 +1146,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends addToOnePropertyBlueprintNamed: { value: deprecate.deprecateMethod(void 0, function (name) { return this.addToOnePropertyDescriptorNamed(name); - }, "addToOnePropertyBlueprintNamed", "addToOnePropertyDescriptorNamed") + }, "addToOnePropertyBlueprintNamed", "addToOnePropertyDescriptorNamed", true) }, /** @@ -1155,7 +1155,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends addToManyPropertyBlueprintNamed: { value: deprecate.deprecateMethod(void 0, function (name) { return this.addToManyPropertyDescriptorNamed(name); - }, "addToManyPropertyBlueprintNamed", "addToManyPropertyDescriptorNamed") + }, "addToManyPropertyBlueprintNamed", "addToManyPropertyDescriptorNamed", true) }, /** @@ -1167,10 +1167,10 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends serializable: false, get: deprecate.deprecateMethod(void 0, function () { return this.objectDescriptorInstanceModule; - }, "blueprintInstanceModule.get", "objectDescriptorInstanceModule.get"), + }, "blueprintInstanceModule.get", "objectDescriptorInstanceModule.get", true), set: deprecate.deprecateMethod(void 0, function (value) { this.objectDescriptorInstanceModule = value; - }, "blueprintInstanceModule.set", "objectDescriptorInstanceModule.set") + }, "blueprintInstanceModule.set", "objectDescriptorInstanceModule.set", true) }, /** @@ -1182,10 +1182,10 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends serializable: false, get: deprecate.deprecateMethod(void 0, function () { return this.model; - }, "binder.get", "model.get"), + }, "binder.get", "model.get", true), set: deprecate.deprecateMethod(void 0, function (value) { this.model = value; - }, "binder.set", "model.set") + }, "binder.set", "model.set", true) }, /** @@ -1197,7 +1197,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends eventBlueprintForName: { value: deprecate.deprecateMethod(void 0, function (name) { return this.eventDescriptorForName(name); - }, "eventBlueprintForName", "eventDescriptorForName") + }, "eventBlueprintForName", "eventDescriptorForName", true) }, /** @@ -1207,10 +1207,10 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends // value: null get: deprecate.deprecateMethod(void 0, function () { return this.eventDescriptors; - }, "eventBlueprints.get", "eventDescriptors.get"), + }, "eventBlueprints.get", "eventDescriptors.get", true), set: deprecate.deprecateMethod(void 0, function (value) { this.eventDescriptors = value; - }, "eventBlueprints.set", "eventDescriptors.set") + }, "eventBlueprints.set", "eventDescriptors.set", true) }, /** @@ -1243,7 +1243,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends newDerivedPropertyBlueprint: { value: deprecate.deprecateMethod(void 0, function (name, cardinality) { return this.newDerivedDescriptor(name, cardinality); - }, "newDerivedPropertyBlueprint", "newDerivedDescriptor") + }, "newDerivedPropertyBlueprint", "newDerivedDescriptor", true) }, /** @@ -1271,7 +1271,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends newEventBlueprint: { value: deprecate.deprecateMethod(void 0, function (name) { return this.newEventDescriptor(name); - }, "newEventBlueprint", "newEventDescriptor") + }, "newEventBlueprint", "newEventDescriptor", true) }, /** @@ -1287,7 +1287,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends newPropertyBlueprint: { value: deprecate.deprecateMethod(void 0, function (name, cardinality) { return this.newPropertyDescriptor(name, cardinality); - }, "newPropertyBlueprint", "newPropertyDescriptor") + }, "newPropertyBlueprint", "newPropertyDescriptor", true) }, /** @@ -1299,7 +1299,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends propertyBlueprintForName: { value: deprecate.deprecateMethod(void 0, function (name) { return this.propertyDescriptorForName(name); - }, "propertyBlueprintForName", "propertyDescriptorForName") + }, "propertyBlueprintForName", "propertyDescriptorForName", true) }, /** @@ -1310,7 +1310,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends propertyBlueprintGroups: { get: deprecate.deprecateMethod(void 0, function () { return this.propertyDescriptorGroups; - }, "propertyBlueprintGroups", "propertyDescriptorGroups") + }, "propertyBlueprintGroups", "propertyDescriptorGroups", true) }, /** @@ -1322,7 +1322,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends propertyBlueprintGroupForName: { value: deprecate.deprecateMethod(void 0, function (groupName) { return this.propertyDescriptorGroupForName(groupName); - }, "propertyBlueprintGroupForName", "propertyDescriptorForName") + }, "propertyBlueprintGroupForName", "propertyDescriptorForName", true) }, /** @@ -1332,7 +1332,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends propertyBlueprints: { get: deprecate.deprecateMethod(void 0, function () { return this.propertyDescriptors; - }, "propertyBlueprints", "propertyDescriptors") + }, "propertyBlueprints", "propertyDescriptors", true) }, /** @@ -1346,7 +1346,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends removeEventBlueprint: { value: deprecate.deprecateMethod(void 0, function (eventBlueprint) { this.removeEventDescriptor(eventBlueprint); - }, "removeEventBlueprint", "removeEventDescriptor") + }, "removeEventBlueprint", "removeEventDescriptor", true) }, /** @@ -1362,7 +1362,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends removePropertyBlueprint: { value: deprecate.deprecateMethod(void 0, function (propertyBlueprint) { this.removePropertyDescriptor(propertyBlueprint); - }, "removePropertyBlueprint", "removePropertyDescriptor") + }, "removePropertyBlueprint", "removePropertyDescriptor", true) }, /** @@ -1376,7 +1376,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends removePropertyBlueprintFromGroupNamed: { value: deprecate.deprecateMethod(void 0, function (propertyBlueprint, groupName) { this.removePropertyDescriptorFromGroupNamed(propertyBlueprint, groupName); - }, "removePropertyBlueprintFromGroupNamed", "removePropertyDescriptorGroupNamed") + }, "removePropertyBlueprintFromGroupNamed", "removePropertyDescriptorGroupNamed", true) }, /** @@ -1389,7 +1389,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends removePropertyBlueprintGroupNamed: { value: deprecate.deprecateMethod(void 0, function (groupName) { this.removePropertyDescriptorGroupNamed(groupName); - }, "removePropertyBlueprintGroupNamed", "removePropertyDescriptorGroupNamed") + }, "removePropertyBlueprintGroupNamed", "removePropertyDescriptorGroupNamed", true) }, /** @@ -1419,7 +1419,7 @@ var ObjectDescriptor = exports.ObjectDescriptor = Montage.specialize( /** @lends createDefaultBlueprintForObject: { value: deprecate.deprecateMethod(void 0, function (object) { return ObjectDescriptor.createDefaultObjectDescriptorForObject(object); - }, "Blueprint.createDefaultBlueprintForObject", "ObjectDescriptor.createDefaultObjectDescriptorForObject") + }, "Blueprint.createDefaultBlueprintForObject", "ObjectDescriptor.createDefaultObjectDescriptorForObject", true) }, /** diff --git a/core/meta/property-descriptor.js b/core/meta/property-descriptor.js index 7ae0ccc1bc..c68640be27 100644 --- a/core/meta/property-descriptor.js +++ b/core/meta/property-descriptor.js @@ -54,7 +54,7 @@ var Defaults = { * @class PropertyDescriptor */ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# */ { - + /** * Initialize a newly allocated property descriptor. * @function @@ -71,7 +71,7 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# return this; } }, - + /** * Initialize a newly allocated property descriptor. * @deprecated @@ -84,9 +84,9 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# initWithNameBlueprintAndCardinality: { value: deprecate.deprecateMethod(void 0, function (name, blueprint, cardinality) { return this.initWithNameObjectDescriptorAndCardinality(name, blueprint, cardinality); - }, "new PropertyBlueprint().initWithNameBlueprintAndCardinality", "new PropertyDescriptor().initWithNameObjectDescriptorAndCardinality") + }, "new PropertyBlueprint().initWithNameBlueprintAndCardinality", "new PropertyDescriptor().initWithNameObjectDescriptorAndCardinality", true) }, - + serializeSelf: { value:function (serializer) { serializer.setProperty("name", this.name); @@ -110,10 +110,10 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# this._setPropertyWithDefaults(serializer, "defaultValue", this.defaultValue); this._setPropertyWithDefaults(serializer, "helpKey", this.helpKey); this._setPropertyWithDefaults(serializer, "definition", this.definition); - + } }, - + deserializeSelf: { value:function (deserializer) { var value; @@ -125,13 +125,13 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# if (value !== void 0) { this._owner = value; } - + this._overridePropertyWithDefaults(deserializer, "cardinality"); - + if (this.cardinality === -1) { this.cardinality = Infinity; } - + this._overridePropertyWithDefaults(deserializer, "mandatory"); this._overridePropertyWithDefaults(deserializer, "readOnly"); this._overridePropertyWithDefaults(deserializer, "denyDelete"); @@ -146,7 +146,7 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# this._overridePropertyWithDefaults(deserializer, "definition"); } }, - + _setPropertyWithDefaults: { value: function (serializer, propertyName, value) { if (value !== null && value !== Defaults[propertyName]) { @@ -154,7 +154,7 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# } } }, - + _getPropertyWithDefaults: { value:function (deserializer) { var propertyNames = Array.prototype.slice.call(arguments).slice(1, Infinity), @@ -165,7 +165,7 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# return value || Defaults[propertyNames[0]]; } }, - + /** * Applies a property from the deserializer to the object. If no such * property is defined on the deserializer, then the current value @@ -185,25 +185,25 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# _overridePropertyWithDefaults: { value: function (deserializer, objectKey /*, deserializerKeys... */) { var propertyNames, value, i, n; - + if (arguments.length > 2) { propertyNames = Array.prototype.slice.call(arguments, 2, Infinity); } else { propertyNames = [objectKey]; } - + for (i = 0, n = propertyNames.length; i < n && !value; i++) { value = deserializer.getProperty(propertyNames[i]); } - + this[objectKey] = value === undefined ? Defaults[propertyNames[0]] : value; } }, - + _owner: { value: null }, - + /** * Component description attached to this property descriptor. */ @@ -212,11 +212,11 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# return this._owner; } }, - + _name: { value: null }, - + /** * Name of the object. The name is used to define the property on the * object. @@ -229,7 +229,7 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# return this._name; } }, - + /** * The identifier is the name of the descriptor, dot, the name of the * property descriptor, and is used to make the serialization of property @@ -245,7 +245,7 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# ].join("_"); } }, - + /** * Cardinality of the property descriptor. * @@ -260,7 +260,7 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# cardinality: { value: Defaults["cardinality"] }, - + /** * @type {boolean} * @default false @@ -268,7 +268,7 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# mandatory: { value: Defaults["mandatory"] }, - + /** * @type {boolean} * @default false @@ -276,7 +276,7 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# denyDelete: { value: Defaults["denyDelete"] }, - + /** * @type {boolean} * @default false @@ -284,7 +284,7 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# readOnly: { value: Defaults["readOnly"] }, - + /** * Returns true if the cardinality is more than one. * @readonly @@ -296,7 +296,7 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# return this.cardinality === Infinity || this.cardinality > 1; } }, - + /** * @type {boolean} * @default false @@ -306,7 +306,7 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# return false; } }, - + /** * @type {string} * Definition can be used to express a property as the result of evaluating an expression @@ -322,7 +322,7 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# definition: { value: null }, - + /** * @type {string} * TODO: This is semantically similar to valueDescriptor @@ -332,28 +332,28 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# valueType: { value: Defaults["valueType"] }, - + /** * @type {string} */ collectionValueType: { value: Defaults["collectionValueType"] }, - + /** * @type {string} */ valueObjectPrototypeName: { value: Defaults["valueObjectPrototypeName"] }, - + /** * @type {string} */ valueObjectModuleId: { value: Defaults["valueObjectModuleId"] }, - + /** * Promise for the descriptor targeted by this association. * @@ -377,15 +377,15 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# this._valueDescriptorReference = descriptor; } }, - + _targetObjectDescriptorReference: { value: null }, - + _enumValues: { value:null }, - + /** * List of values for enumerated value types * @type {Array} @@ -403,18 +403,18 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# } } }, - + defaultValue: { value: Defaults["defaultValue"] }, - + helpKey:{ value: Defaults["helpKey"] }, - + objectDescriptorModuleId:require("../core")._objectDescriptorModuleIdDescriptor, objectDescriptor:require("../core")._objectDescriptorDescriptor, - + /** * @type {boolean} * possible values are: "reference" | "value" | "auto" | true | false, @@ -423,11 +423,11 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# serializable: { value: true }, - + /******************************************************** * Deprecated functions */ - + /** * @deprecated * @readonly @@ -438,19 +438,19 @@ exports.PropertyDescriptor = Montage.specialize( /** @lends PropertyDescriptor# isAssociationBlueprint: { get: deprecate.deprecateMethod(void 0, function () { return !!this._valueDescriptorReference; - }, "isAssociationBlueprint", "No analog") + }, "isAssociationBlueprint", "No analog", true) }, - + targetBlueprint: { get: deprecate.deprecateMethod(void 0, function () { return this.valueDescriptor; - }, "targetBlueprint.get", "valueDescriptor.get"), + }, "targetBlueprint.get", "valueDescriptor.get", true), set: deprecate.deprecateMethod(void 0, function (value) { this.valueDescriptor = value; - }, "targetBlueprint.get", "valueDescriptor.set") + }, "targetBlueprint.get", "valueDescriptor.set", true) }, - + blueprintDescriptorModuleId: require("../core")._objectDescriptorModuleIdDescriptor, blueprint: require("../core")._objectDescriptorDescriptor - + }); diff --git a/core/range-controller.js b/core/range-controller.js index d1eca2c6fc..e59482dc84 100644 --- a/core/range-controller.js +++ b/core/range-controller.js @@ -4,7 +4,6 @@ var Montage = require("./core").Montage; var GenericCollection = require("collections/generic-collection"); var observableArrayProperties = require("collections/listen/array-changes").observableArrayProperties; -var deprecate = require("core/deprecate"); // The content controller is responsible for determining which content from a // source collection are visible, their order of appearance, and whether they @@ -364,21 +363,6 @@ var RangeController = exports.RangeController = Montage.specialize( /** @lends R */ allowsMultipleSelection: {value: false}, - /** - * @deprecated - */ - multiSelect: { - set: function (multiSelect) { - deprecate.deprecationWarning("multiSelect", "allowsMultipleSelection"); - this.allowsMultipleSelection = !!multiSelect; - }, - get: function () { - deprecate.deprecationWarning("multiSelect", "allowsMultipleSelection"); - return this.allowsMultipleSelection; - } - }, - - // Properties managed by the controller // ------------------------------------ diff --git a/core/range-controller.meta b/core/range-controller.meta index 2e7fda17d7..eff2058a8b 100644 --- a/core/range-controller.meta +++ b/core/range-controller.meta @@ -100,17 +100,6 @@ "helpKey": "" } }, - "rangeController_multiSelect": { - "prototype": "core/meta/property-descriptor", - "values": { - "name": "multiSelect", - "objectDescriptor": { - "@": "root" - }, - "valueType": "boolean", - "helpKey": "" - } - }, "root": { "prototype": "core/meta/module-object-descriptor", "values": { @@ -143,9 +132,6 @@ }, { "@": "rangeController_avoidsEmptySelection" - }, - { - "@": "rangeController_multiSelect" } ], "propertyDescriptorGroups": { @@ -176,9 +162,6 @@ }, { "@": "rangeController_avoidsEmptySelection" - }, - { - "@": "rangeController_multiSelect" } ] }, diff --git a/core/serialization/deserializer/montage-deserializer.js b/core/serialization/deserializer/montage-deserializer.js index c893b69400..6b9825218e 100644 --- a/core/serialization/deserializer/montage-deserializer.js +++ b/core/serialization/deserializer/montage-deserializer.js @@ -32,7 +32,8 @@ var MontageDeserializer = exports.MontageDeserializer = Montage.specialize({ this._serializationString = JSON.stringify(serialization); } this._require = _require; - this._locationId = locationId; + this._locationId = locationId ? locationId.indexOf(_require.location) === 0 ? locationId : _require.location + locationId : locationId; + this._reviver = new MontageReviver().init( _require, objectRequires, this.constructor ); diff --git a/core/serialization/deserializer/montage-reviver.js b/core/serialization/deserializer/montage-reviver.js index 1753ed9ae7..d7224c8451 100644 --- a/core/serialization/deserializer/montage-reviver.js +++ b/core/serialization/deserializer/montage-reviver.js @@ -454,7 +454,10 @@ var MontageReviver = exports.MontageReviver = Montage.specialize(/** @lends Mont (locationId.endsWith(".mjson") || locationId.endsWith(".meta"))), module, locationDesc, location, objectName; - if (locationId) { + if (global[locationId] && typeof global[locationId] === "function") { + module = global; + objectName = locationId; + } else if (locationId) { locationDesc = MontageReviver.parseObjectLocationId(locationId); module = this.moduleLoader.getModule(locationDesc.moduleId, label); objectName = locationDesc.objectName; diff --git a/core/serialization/serializer/montage-visitor.js b/core/serialization/serializer/montage-visitor.js index 293eaef142..0bafee6774 100644 --- a/core/serialization/serializer/montage-visitor.js +++ b/core/serialization/serializer/montage-visitor.js @@ -43,10 +43,23 @@ var MontageVisitor = Montage.specialize({ return "MontageReference"; } else if (typeof Element !== "undefined" && Element.isElement(object)) { return "Element"; + } else if (this._isSerializableNativeObject(object)) { + return "NativeObject"; } } }, + _isSerializableNativeObject: { + value: function (object) { + var typeName = object.constructor.name, + nativeType = global[typeName], + isNative = typeof nativeType === "function", + isSerializeable = isNative && typeof object.serializeSelf === "function"; + + return isNative && isSerializeable; + } + }, + visitMontageReference: { value: function (malker, object, name) { this.builder.top.setProperty(name, object.reference); @@ -128,6 +141,34 @@ var MontageVisitor = Montage.specialize({ } }, + visitNativeObject: { + value: function (malker, object, name) { + if (this.isObjectSerialized(object)) { + this.serializeReferenceToMontageObject(malker, object, name); + } else { + this.handleNativeObject(malker, object, name); + } + } + }, + + handleNativeObject: { + value: function (malker, object, name) { + var builderObject = this.builder.createCustomObject(), + substituteObject; + + this.setObjectSerialization(object, builderObject); + + substituteObject = this.serializeNativeObject(malker, object, builderObject); + + if (substituteObject) { + this.serializeSubstituteObject(malker, object, name, builderObject, substituteObject); + } else { + builderObject.setLabel(this.labeler.getObjectLabel(object)); + this.builder.top.setProperty(name, builderObject); + } + } + }, + serializeReferenceToMontageObject: { value: function (malker, object, name) { var label = this.labeler.getObjectLabel(object), @@ -234,6 +275,35 @@ var MontageVisitor = Montage.specialize({ } }, + serializeNativeObject: { + value: function (malker, object, builderObject) { + var selfSerializer, + substituteObject, + valuesBuilderObject = this.builder.createObjectLiteral(); + + builderObject.setProperty("prototype", object.constructor.name); + builderObject.setProperty("values", valuesBuilderObject); + + this.builder.push(builderObject); + selfSerializer = new SelfSerializer(). + initWithMalkerAndVisitorAndObject( + malker, this, object, builderObject); + substituteObject = object.serializeSelf(selfSerializer); + + this.builder.pop(); + + // Remove the values unit in case none was serialized, + // we need to add it before any other units to make sure that + // it's the first unit to show up in the serialization, since we + // don't have a way to order the property names in a serialization. + if (valuesBuilderObject.getPropertyNames().length === 0) { + builderObject.clearProperty("values"); + } + + return substituteObject; + } + }, + setObjectType: { value: function (object, builderObject) { var isInstance = Montage.getInfoForObject(object).isInstance, diff --git a/data/service/data-service.js b/data/service/data-service.js index 61a2137b57..413f4a61a7 100644 --- a/data/service/data-service.js +++ b/data/service/data-service.js @@ -56,7 +56,7 @@ exports.DataService = Montage.specialize(/** @lends DataService.prototype */ { deserializeSelf: { value:function (deserializer) { var self = this, - result = null, + result = null, value; value = deserializer.getProperty("childServices"); @@ -86,7 +86,7 @@ exports.DataService = Montage.specialize(/** @lends DataService.prototype */ { if (value) { this.delegate = value; } - + return result; } }, @@ -485,7 +485,7 @@ exports.DataService = Montage.specialize(/** @lends DataService.prototype */ { value: function (type) { var descriptor = this._constructorToObjectDescriptorMap.get(type) || typeof type === "string" && this._moduleIdToObjectDescriptorMap[type]; - + return descriptor || type; } }, @@ -834,7 +834,7 @@ exports.DataService = Montage.specialize(/** @lends DataService.prototype */ { }, /** - * Performs whatever tasks are necessary to authorize + * Performs whatever tasks are necessary to authorize * this service and returns a Promise that resolves with * an Authorization object. * @@ -968,6 +968,11 @@ exports.DataService = Montage.specialize(/** @lends DataService.prototype */ { prototype = this._dataObjectPrototypes.get(type); if (type && !prototype) { prototype = Object.create(type.objectPrototype || Montage.prototype); + prototype.constuctor = type.objectPrototype.constructor; + if(prototype.constuctor.name === "constructor" ) { + Object.defineProperty(prototype.constuctor, "name", { value: type.typeName }); + } + this._dataObjectPrototypes.set(type, prototype); if (type instanceof ObjectDescriptor || type instanceof DataObjectDescriptor) { triggers = DataTrigger.addTriggers(this, type, prototype); @@ -1178,7 +1183,7 @@ exports.DataService = Montage.specialize(/** @lends DataService.prototype */ { var self = this, propertyName = propertiesToRequest.shift(), promise = this.getObjectProperties(object, propertyName); - + if (promise) { return promise.then(function () { var result = null; @@ -1309,7 +1314,7 @@ exports.DataService = Montage.specialize(/** @lends DataService.prototype */ { mapping = objectDescriptor && this.mappingWithType(objectDescriptor), data = {}, result; - + if (mapping) { Object.assign(data, this.snapshotForObject(object)); diff --git a/index.html b/index.html index fcad4fb9e1..1733edacda 100644 --- a/index.html +++ b/index.html @@ -23,6 +23,7 @@

Composers:

Press Composer Translate Composer + Swipe Composer

Components:

Anchor @@ -45,6 +46,9 @@

Components:

TreeList VirtualList +

Managers:

+ Drag & Drop +

Tests:

Run tests diff --git a/package.json b/package.json index 6242de0238..7fa2866dda 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "production": true, "dependencies": { "bluebird": "~3.5.0", - "collections": "~5.0.x", + "collections": "~5.1.x", "frb": "~4.0.x", "htmlparser2": "~3.0.5", "xhr2": "^0.1.4", @@ -107,4 +107,4 @@ "test", "tools" ] -} +} \ No newline at end of file diff --git a/test/all.js b/test/all.js index 37607e0bd9..9da863fdeb 100644 --- a/test/all.js +++ b/test/all.js @@ -40,6 +40,7 @@ module.exports = require("montage-testing").run(require, [ "spec/core/range-controller-spec", {name: "spec/core/media-controller-spec", node: false}, {name: "spec/core/radio-button-controller-spec", node: false}, + {name: "spec/core/drag/drag-spec", node: false, karma: false}, // Base {name: "spec/base/abstract-control-spec", node: false}, {name: "spec/base/abstract-alert-spec", node: false}, diff --git a/test/spec/base/abstract-select-spec.js b/test/spec/base/abstract-select-spec.js index ce5d160db2..af9c6dad03 100644 --- a/test/spec/base/abstract-select-spec.js +++ b/test/spec/base/abstract-select-spec.js @@ -152,11 +152,11 @@ describe("test/base/abstract-select-spec", function () { }); }); - describe("value with multiSelect", function () { + describe("value with allowsMultipleSelection", function () { beforeEach(function () { aSelect = new Select(); aSelect.content = content; - aSelect.multiSelect = true; + aSelect.allowsMultipleSelection = true; }); it("should have first of the values", function () { @@ -175,7 +175,7 @@ describe("test/base/abstract-select-spec", function () { beforeEach(function () { aSelect = new Select(); aSelect.content = content; - aSelect.multiSelect = true; + aSelect.allowsMultipleSelection = true; }); it("should change the selection of the content controller", function () { @@ -224,25 +224,25 @@ describe("test/base/abstract-select-spec", function () { }); }); - describe("multiSelect", function () { + describe("allowsMultipleSelection", function () { beforeEach(function () { aSelect = new Select(); aSelect.content = content; }); - it("should only have one item in the content controller's selection when multiSelect is off", function () { - aSelect.multiSelect = false; + it("should only have one item in the content controller's selection when allowsMultipleSelection is off", function () { + aSelect.allowsMultipleSelection = false; expect(aSelect.contentController.selection.length).toBe(1); aSelect.values = [content[1], content[2]]; expect(aSelect.contentController.selection.length).toBe(1); }); - it("should only have one selected item in content controller's selection when multiSelect is turned off", function () { - aSelect.multiSelect = true; + it("should only have one selected item in content controller's selection when allowsMultipleSelection is turned off", function () { + aSelect.allowsMultipleSelection = true; aSelect.values = [content[1], content[2]]; - aSelect.multiSelect = false; + aSelect.allowsMultipleSelection = false; expect(aSelect.contentController.selection.length).toBe(1); }); }); diff --git a/test/spec/composer/composer-spec.js b/test/spec/composer/composer-spec.js index bdb3612dc8..ee5ebe951e 100644 --- a/test/spec/composer/composer-spec.js +++ b/test/spec/composer/composer-spec.js @@ -98,43 +98,110 @@ TestPageLoader.queueTest("composer-programmatic-lazyload", {src: "spec/composer/ }); TestPageLoader.queueTest("swipe-composer", {src:"spec/composer/swipe/swipe.html", firstDraw: false}, function (testPage) { - var test; + var test, swipeElement; beforeEach(function () { test = testPage.test; + swipeElement = test.dummyComponent.element; }); + + //FIXME: should be removed when will provide a pollyfil for the pointer events + function dispatchPointerEvent(element, type, positionX, positionY) { + var eventInit = { + clientX: positionX, + clientY: positionY, + bubbles: true, + pointerType: "mouse" + }; + + if (window.PointerEvent) { + element.dispatchEvent(new PointerEvent(type, eventInit)); + } else if (window.MSPointerEvent && window.navigator.msPointerEnabled) { + if (type === "pointerdown") { + type = "MSPointerDown"; + } else if (type === "pointermove") { + type = "MSPointerMove"; + } else { + type = "MSPointerUp"; + } + element.dispatchEvent(new MSPointerEvent(type, eventInit)); + } else { + if (type === "pointerdown") { + type = "mousedown"; + } else if (type === "pointermove") { + type = "mousemove"; + } else { + type = "mouseup"; + } + element.dispatchEvent(new MouseEvent(type, eventInit)); + } + } + describe("composer-spec", function () { describe("swipe right",function () { it("shouldn't emit swipe event or swipemove event if no move", function () { - //simulate touch events spyOn(test, 'handleSwipe').and.callThrough(); - spyOn(test, 'handleSwipemove').and.callThrough(); - testPage.touchEvent(new EventInfo().initWithElementAndPosition(null, -100, 100), "touchstart", function () { - testPage.touchEvent(new EventInfo().initWithElementAndPosition(null, -100, 100), "touchmove", function () { - testPage.touchEvent(new EventInfo().initWithElementAndPosition(null, -100, 100), "touchend", function () { - expect(test.handleSwipemove).not.toHaveBeenCalled(); - expect(test.handleSwipe).not.toHaveBeenCalled(); - }); - }); - }); + dispatchPointerEvent(swipeElement, "pointerdown", -100, 100); + dispatchPointerEvent(swipeElement, "pointermove", -100, 100); + dispatchPointerEvent(swipeElement, "pointermove", -100, 100); + dispatchPointerEvent(swipeElement, "pointerup", -100, 100); + expect(test.handleSwipe).not.toHaveBeenCalled(); }); - it("should emit swipe event and swipemove event", function () { - //simulate touch events + it("should emit swipe event and swipeDown event", function () { spyOn(test, 'handleSwipe').and.callThrough(); - spyOn(test, 'handleSwipemove').and.callThrough(); - testPage.touchEvent(new EventInfo().initWithElementAndPosition(null, 0, 0), "touchstart", function () { - testPage.touchEvent(new EventInfo().initWithElementAndPosition(null, 0, 50), "touchmove", function () { - testPage.touchEvent(new EventInfo().initWithElementAndPosition(null, 0, 100), "touchmove", function () { - testPage.touchEvent(new EventInfo().initWithElementAndPosition(null, 50, 50), "touchend", function () { - expect(test.handleSwipemove).toHaveBeenCalled(); - expect(test.handleSwipe).toHaveBeenCalled(); - }); - }); - }); - }); + dispatchPointerEvent(swipeElement, "pointerdown", 0, 0); + dispatchPointerEvent(swipeElement, "pointermove", 0, 50); + dispatchPointerEvent(swipeElement, "pointermove", 0, 80); + dispatchPointerEvent(swipeElement, "pointerup", 0, 80); + expect(test.handleSwipe).toHaveBeenCalled(); + + var event = test.handleSwipe.calls.argsFor(0)[0]; + expect(event.direction).toBe('down'); + expect(event.angle).toBe(270); + expect(event.distance).toBe(80); + }); + + it("should emit swipe event and swipeUp event", function () { + spyOn(test, 'handleSwipe').and.callThrough(); + dispatchPointerEvent(swipeElement, "pointerdown", 50, 50); + dispatchPointerEvent(swipeElement, "pointermove", 50, 40); + dispatchPointerEvent(swipeElement, "pointermove", 50, 30); + dispatchPointerEvent(swipeElement, "pointerup", 50, 30); + expect(test.handleSwipe).toHaveBeenCalled(); + + var event = test.handleSwipe.calls.argsFor(0)[0]; + expect(event.direction).toBe('up'); + expect(event.angle).toBe(90); + expect(event.distance).toBe(20); + }); + + it("should emit swipe event and swipeRight event", function () { + spyOn(test, 'handleSwipe').and.callThrough(); + dispatchPointerEvent(swipeElement, "pointerdown", 0, 0); + dispatchPointerEvent(swipeElement, "pointermove", 50, 0); + dispatchPointerEvent(swipeElement, "pointermove", 80, 0); + dispatchPointerEvent(swipeElement, "pointerup", 80, 0); + expect(test.handleSwipe).toHaveBeenCalled(); + + var event = test.handleSwipe.calls.argsFor(0)[0]; + expect(event.direction).toBe('right'); + expect(event.angle).toBe(0); + expect(event.distance).toBe(80); + }); + + it("should emit swipe event and swipeLeft event", function () { + spyOn(test, 'handleSwipe').and.callThrough(); + dispatchPointerEvent(swipeElement, "pointerdown", 100, 0); + dispatchPointerEvent(swipeElement, "pointermove", 80, 0); + dispatchPointerEvent(swipeElement, "pointermove", 50, 0); + dispatchPointerEvent(swipeElement, "pointerup", 50, 0); + expect(test.handleSwipe).toHaveBeenCalled(); + + var event = test.handleSwipe.calls.argsFor(0)[0]; + expect(event.direction).toBe('left'); + expect(event.angle).toBe(180); + expect(event.distance).toBe(50); }); }); }); }); - - diff --git a/test/spec/composer/swipe/swipe.js b/test/spec/composer/swipe/swipe.js index d08bc43ada..3aff2b82e2 100644 --- a/test/spec/composer/swipe/swipe.js +++ b/test/spec/composer/swipe/swipe.js @@ -6,26 +6,23 @@ exports.Swipe = Montage.specialize( { deserializedFromTemplate: { value: function () { - var dummyComponent = new Component(); + var dummyComponent = this.dummyComponent = new Component(); dummyComponent.hasTemplate = false; dummyComponent.element = document.body; + dummyComponent.element.style.height = "400px"; + dummyComponent.element.style.width = "400px"; dummyComponent.attachToParentComponent(); dummyComponent.needsDraw = true; this.swipeComposer = new SwipeComposer(); this.swipeComposer.lazyLoad = false; dummyComponent.addComposer(this.swipeComposer); this.swipeComposer.addEventListener("swipe", this, false); - this.swipeComposer.addEventListener("swipemove", this, false); } }, handleSwipe: { value: function (event) { - } - }, - - handleSwipemove: { - value: function (event) { + console.log(event.direction) } } diff --git a/test/spec/core/drag/drag-spec.js b/test/spec/core/drag/drag-spec.js new file mode 100644 index 0000000000..05e2036920 --- /dev/null +++ b/test/spec/core/drag/drag-spec.js @@ -0,0 +1,57 @@ +// "use strict"; // TODO: causes q to throw, will reinstate when q is replaced by bluebird + +var TestPageLoader = require("montage-testing/testpageloader").TestPageLoader; + +TestPageLoader.queueTest("drag-test", function (testPage) { + describe("core/drag/drag-spec", function () { + var dragElement; + var dragComponent; + var dropElement; + var dropComponent; + + beforeEach(function () { + dragElement = testPage.getElementById("drag"); + dragComponent = dragElement.component; + dropElement = testPage.getElementById("drop"); + dropComponent = dropElement.component; + }); + + describe("Drag Manager", function () { + + describe("Drag Source", function () { + it("shoud be registered within the drag manager", function () { + expect(dragComponent.draggable).toEqual(true); + expect(dragComponent.dragManager._draggables.indexOf(dragComponent) > -1).toEqual(true); + }); + + it("shoud have the class name `montage-draggable`", function () { + expect(dragComponent.classList.has("montage-draggable")).toEqual(true); + }); + + it("shoud be unregistered when leaving the component tree", function () { + dragComponent._exitDocument(); + expect(dragComponent.dragManager._draggables.indexOf(dragComponent) === -1).toEqual(true); + expect(dragComponent.classList.has("montage-draggable")).toEqual(false); + }); + }); + + describe("Drag Destination", function () { + it("shoud be registered within the drag manager ", function () { + expect(dropComponent.droppable).toEqual(true); + expect(dropComponent.dragManager._droppables.indexOf(dropComponent) > -1).toEqual(true); + }); + + it("shoud have the class name `montage-droppable`", function () { + expect(dropComponent.classList.has("montage-droppable")).toEqual(true); + }); + + it("shoud be unregistered when leaving the component tree", function () { + dropComponent._exitDocument(); + expect(dropComponent.dragManager._droppables.indexOf(dropComponent) === -1).toEqual(true); + expect(dropComponent.classList.has("montage-droppable")).toEqual(false); + }); + }); + + }); + }); +}); diff --git a/test/spec/core/drag/drag-test.html b/test/spec/core/drag/drag-test.html new file mode 100644 index 0000000000..036d962047 --- /dev/null +++ b/test/spec/core/drag/drag-test.html @@ -0,0 +1,33 @@ + + + + + + + +
drag
+
+ + diff --git a/test/spec/core/drag/drag-test.js b/test/spec/core/drag/drag-test.js new file mode 100644 index 0000000000..7830a13883 --- /dev/null +++ b/test/spec/core/drag/drag-test.js @@ -0,0 +1,7 @@ +"use strict"; + +var TestController = require("montage-testing/test-controller").TestController; + +exports.DragTest = TestController.specialize({ + +}); diff --git a/test/spec/core/extras/map.js b/test/spec/core/extras/map.js new file mode 100644 index 0000000000..d2be0c4046 --- /dev/null +++ b/test/spec/core/extras/map.js @@ -0,0 +1,107 @@ +var Deserializer = require("montage/core/serialization/deserializer/montage-deserializer").MontageDeserializer, + Serializer = require("montage/core/serialization/serializer/montage-serializer").MontageSerializer; + +require("montage"); + +describe("core/extras/map", function () { + + describe("Map#deserializeSelf", function () { + + it("can deserialize with entries", function (done) { + + var deserializer = new Deserializer(), + entries = [], + serialization = { + "root": { + "prototype": "Map", + "values": { + "entries": entries + } + } + }, + string, i; + + for (i = 0; i < 5000; ++i) { + entries.push({key: i, value:{name: i}}); + } + + string = JSON.stringify(serialization); + deserializer.init(string, require); + return deserializer.deserializeObject().then(function (root) { + expect(root instanceof Map).toBeTruthy(); + done(); + }).catch(function(reason) { + console.warn(reason); + fail(reason); + }); + + }); + + it("can deserialize with keys and values", function (done) { + var deserializer = new Deserializer(), + keys = [], + values = [], + serialization = { + "root": { + "prototype": "Map", + "values": { + "keys": keys, + "values": values + } + } + }, + string, i; + + for (i = 0; i < 5000; ++i) { + keys.push(i); + values.push({name: i}); + } + string = JSON.stringify(serialization); + deserializer.init(string, require); + return deserializer.deserializeObject().then(function (root) { + expect(root instanceof Map).toBeTruthy(); + done(); + }).catch(function(reason) { + console.warn(reason); + fail(reason); + }); + + + }); + + }); + + describe("Map#serializeSelf", function () { + var serializer; + + beforeEach(function () { + originalUnits = Serializer._units; + Serializer._units = {}; + serializer = new Serializer().initWithRequire(require); + serializer.setSerializationIndentation(4); + }); + + it("can serialize", function () { + var map = new Map(), + serialization; + map.set("A", {name: "A"}); + map.set("B", {name: "B"}); + map.set("C", {name: "C"}); + + + serialization = serializer.serializeObject(map); + serialization = JSON.parse(serialization); + expect(serialization.root.prototype).toBe("Map"); + expect(serialization.root.values.keys[0]).toBe("A"); + expect(serialization.root.values.keys[1]).toBe("B"); + expect(serialization.root.values.keys[2]).toBe("C"); + expect(serialization.root.values.values[0].name).toBe("A"); + expect(serialization.root.values.values[1].name).toBe("B"); + expect(serialization.root.values.values[2].name).toBe("C"); + }); + + + }); + +}); + diff --git a/test/spec/core/extras/set.js b/test/spec/core/extras/set.js new file mode 100644 index 0000000000..f27494b0c1 --- /dev/null +++ b/test/spec/core/extras/set.js @@ -0,0 +1,72 @@ +var Deserializer = require("montage/core/serialization/deserializer/montage-deserializer").MontageDeserializer, + Serializer = require("montage/core/serialization/serializer/montage-serializer").MontageSerializer; + +require("montage"); + +describe("core/extras/set", function () { + + describe("Set#deserializeSelf", function () { + + it("can deserialize with entries", function (done) { + + var deserializer = new Deserializer(), + values = [], + serialization = { + "root": { + "prototype": "Set", + "values": { + "values": values + } + } + }, + string, i; + + for (i = 0; i < 5000; ++i) { + values.push({key: i, value:{name: i}}); + } + + string = JSON.stringify(serialization); + deserializer.init(string, require); + return deserializer.deserializeObject().then(function (root) { + expect(root instanceof Set).toBeTruthy(); + done(); + }).catch(function(reason) { + console.warn(reason); + fail(reason); + }); + + }); + + }); + + describe("Set#serializeSelf", function () { + var serializer; + + beforeEach(function () { + originalUnits = Serializer._units; + Serializer._units = {}; + serializer = new Serializer().initWithRequire(require); + serializer.setSerializationIndentation(4); + }); + + it("can serialize", function () { + var set = new Set(), + serialization; + + set.add({name: "A"}); + set.add({name: "B"}); + set.add({name: "C"}); + + + serialization = serializer.serializeObject(set); + serialization = JSON.parse(serialization); + expect(serialization.root.prototype).toBe("Set"); + expect(serialization.root.values.values[0].name).toBe("A"); + expect(serialization.root.values.values[1].name).toBe("B"); + expect(serialization.root.values.values[2].name).toBe("C"); + }); + + }); + +}); + diff --git a/test/spec/core/extras/weak-map.js b/test/spec/core/extras/weak-map.js new file mode 100644 index 0000000000..c2c4a2ad6d --- /dev/null +++ b/test/spec/core/extras/weak-map.js @@ -0,0 +1,108 @@ +var Deserializer = require("montage/core/serialization/deserializer/montage-deserializer").MontageDeserializer, + Serializer = require("montage/core/serialization/serializer/montage-serializer").MontageSerializer; + +require("montage"); + +describe("core/extras/weak-map", function () { + + describe("WeakMap#deserializeSelf", function () { + + it("can deserialize with entries", function (done) { + + var deserializer = new Deserializer(), + entries = [], + serialization = { + "root": { + "prototype": "WeakMap", + "values": { + "entries": entries + } + } + }, + string, i; + + for (i = 0; i < 5000; ++i) { + entries.push({key: {id: i}, value:{name: i}}); + } + + string = JSON.stringify(serialization); + deserializer.init(string, require); + return deserializer.deserializeObject().then(function (root) { + expect(root instanceof WeakMap).toBeTruthy(); + done(); + }).catch(function(reason) { + console.warn(reason); + fail(reason); + }); + + }); + + it("can deserialize with keys and values", function (done) { + var deserializer = new Deserializer(), + keys = [], + values = [], + serialization = { + "root": { + "prototype": "WeakMap", + "values": { + "keys": keys, + "values": values + } + } + }, + string, i; + + for (i = 0; i < 5000; ++i) { + keys.push({id: i}); + values.push({name: i}); + } + string = JSON.stringify(serialization); + deserializer.init(string, require); + return deserializer.deserializeObject().then(function (root) { + expect(root instanceof WeakMap).toBeTruthy(); + done(); + }).catch(function(reason) { + console.warn(reason); + fail(reason); + }); + + + }); + + }); + + describe("WeakMap#serializeSelf", function () { + + var serializer; + + beforeEach(function () { + originalUnits = Serializer._units; + Serializer._units = {}; + serializer = new Serializer().initWithRequire(require); + serializer.setSerializationIndentation(4); + }); + + it("can serialize", function () { + var map = new Map(), + serialization; + map.set("A", {name: "A"}); + map.set("B", {name: "B"}); + map.set("C", {name: "C"}); + + + serialization = serializer.serializeObject(map); + serialization = JSON.parse(serialization); + expect(serialization.root.prototype).toBe("Map"); + expect(serialization.root.values.keys[0]).toBe("A"); + expect(serialization.root.values.keys[1]).toBe("B"); + expect(serialization.root.values.keys[2]).toBe("C"); + expect(serialization.root.values.values[0].name).toBe("A"); + expect(serialization.root.values.values[1].name).toBe("B"); + expect(serialization.root.values.values[2].name).toBe("C"); + }); + + + }); + +}); + diff --git a/ui/base/abstract-select.js b/ui/base/abstract-select.js index b43c233878..238095bf99 100644 --- a/ui/base/abstract-select.js +++ b/ui/base/abstract-select.js @@ -48,7 +48,7 @@ var AbstractSelect = exports.AbstractSelect = AbstractControl.specialize( /** @l // "<->": "values.one()" // }, "contentController.allowsMultipleSelection": { - "<-": "multiSelect" + "<-": "allowsMultipleSelection" }, // classList management "classList.has('montage--disabled')": { @@ -154,7 +154,7 @@ var AbstractSelect = exports.AbstractSelect = AbstractControl.specialize( /** @l } }, - multiSelect: { + allowsMultipleSelection: { value: false }, @@ -265,4 +265,3 @@ var AbstractSelect = exports.AbstractSelect = AbstractControl.specialize( /** @l } }); - diff --git a/ui/base/abstract-select.meta b/ui/base/abstract-select.meta index 644bbd1c6e..3f8ac16bf7 100644 --- a/ui/base/abstract-select.meta +++ b/ui/base/abstract-select.meta @@ -59,10 +59,10 @@ } } }, - "multiSelect_property": { + "allowsMultipleSelection_property": { "prototype": "core/meta/property-descriptor", "values": { - "name": "multiSelect", + "name": "allowsMultipleSelection", "valueType": "boolean", "objectDescriptor": { "@": "root" @@ -99,7 +99,7 @@ "@": "values_property" }, { - "@": "multiSelect_property" + "@": "allowsMultipleSelection_property" } ], "propertyDescriptorGroups": { @@ -123,7 +123,7 @@ "@": "values_property" }, { - "@": "multiSelect_property" + "@": "allowsMultipleSelection_property" } ] }, diff --git a/ui/component.js b/ui/component.js index 99a394086e..713877f54f 100644 --- a/ui/component.js +++ b/ui/component.js @@ -21,7 +21,7 @@ var Montage = require("../core/core").Montage, Promise = require("../core/promise").Promise, defaultEventManager = require("../core/event/event-manager").defaultEventManager, Alias = require("../core/serialization/alias").Alias, - + DragManager = require("../core/drag/drag-manager").DragManager, logger = require("../core/logger").logger("component"), drawPerformanceLogger = require("../core/logger").logger("Drawing performance").color.green(), drawListLogger = require("../core/logger").logger("drawing list").color.blue(), @@ -39,7 +39,8 @@ var Montage = require("../core/core").Montage, var ATTR_LE_COMPONENT = "data-montage-le-component", ATTR_LE_ARG = "data-montage-le-arg", ATTR_LE_ARG_BEGIN = "data-montage-le-arg-begin", - ATTR_LE_ARG_END = "data-montage-le-arg-end"; + ATTR_LE_ARG_END = "data-montage-le-arg-end", + _defaultDragManager; function loggerToString (object) { @@ -744,6 +745,13 @@ var Component = exports.Component = Target.specialize(/** @lends Component.proto } }, + dragManager: { + get: function () { + return _defaultDragManager || + ((_defaultDragManager = new DragManager()).initWithComponent(this.rootComponent)); + } + }, + /** * TemplateArgumentProvider implementation */ @@ -1201,7 +1209,15 @@ var Component = exports.Component = Target.specialize(/** @lends Component.proto __exitDocument: { value: function () { - if (this._inDocument && typeof this.exitDocument === "function") { + if (this.draggable) { + this.unregisterDraggable(); + } + + if (this.droppable) { + this.unregisterDroppable(); + } + + if (this._inDocument && typeof this.exitDocument === "function") { this.exitDocument(); this._inDocument = false; } @@ -1266,6 +1282,14 @@ var Component = exports.Component = Target.specialize(/** @lends Component.proto } }, + draggable: { + value: false + }, + + droppable: { + value: false + }, + /** * The owner component is the owner of the template form which this * component was instantiated. @@ -1820,6 +1844,9 @@ var Component = exports.Component = Target.specialize(/** @lends Component.proto if(this._templateObjects) { this._setupTemplateObjects(documentPart.objects); } + + this.addOwnPropertyChangeListener("draggable", this); + this.addOwnPropertyChangeListener("droppable", this); } }, @@ -2904,6 +2931,87 @@ var Component = exports.Component = Target.specialize(/** @lends Component.proto } }, + // Drag & Drop operations + + handleDraggableChange: { + value: function (value) { + if (value) { + this.registerDraggable(); + } else { + this.unregisterDraggable(); + } + } + }, + + handleDroppableChange: { + value: function (value) { + if (value) { + this.registerDroppable(); + } else { + this.unregisterDroppable(); + } + } + }, + + /** + * Register a component for beeing a dragging source. + */ + registerDraggable: { + value: function () { + this.dragManager.registerDraggable(this); + this.classList.add("montage-draggable"); + } + }, + + /** + * unregister a component for beeing a dragging source. + */ + unregisterDraggable: { + value: function () { + this.dragManager.unregisterDraggable(this); + this.classList.remove("montage-draggable"); + } + }, + + /** + * Register a component for beeing a drag destination. + */ + registerDroppable: { + value: function () { + this.dragManager.registerDroppable(this); + this.classList.add("montage-droppable"); + } + }, + + /** + * Unregister a component for beeing a drag destination. + */ + unregisterDroppable: { + value: function () { + this.dragManager.unregisterDroppable(this); + this.classList.remove("montage-droppable"); + } + }, + + _draggableContainer: { + value: null + }, + + draggableContainer: { + set: function (element) { + if (element) { + if (element instanceof Element) { + this._draggableContainer = element; + } else if (element.element instanceof Element) { + this._draggableContainer = element.element; + } + } + }, + get: function () { + return this._draggableContainer; + } + }, + // // Attribute Handling // @@ -3223,6 +3331,14 @@ var Component = exports.Component = Target.specialize(/** @lends Component.proto value: function (firstTime) { var originalElement; + if (this.draggable) { + this.registerDraggable(); + } + + if (this.droppable) { + this.registerDroppable(); + } + if (firstTime) { // The element is now ready, so we can read the attributes that // have been set on it. @@ -4442,7 +4558,21 @@ var RootComponent = Component.specialize( /** @lends RootComponent.prototype */{ this._element = document.documentElement; this._documentResources = DocumentResources.getInstanceForDocument(document); } - } + }, + + willDraw: { + value: function () { + this.dragManager.willDraw(); + } + }, + + draw: { + value: function () { + this.dragManager.draw(); + } + } + + }); exports.__root__ = rootComponent = new RootComponent().init(); diff --git a/ui/html-fragment.reel/html-fragment.js b/ui/html-fragment.reel/html-fragment.js index d47654c3c0..2297b9da03 100644 --- a/ui/html-fragment.reel/html-fragment.js +++ b/ui/html-fragment.reel/html-fragment.js @@ -87,11 +87,11 @@ var HtmlFragment = exports.HtmlFragment = Component.specialize(/** @lends HtmlFr l--; } else { childAttributes = child.attributes; - shouldRemoveAttribute = false; allowedAttributesForTag = allowedAttributes[childTagName] || allowedAttributes['*']; for (ii = 0, ll = childAttributes.length; ii < ll; ii++) { + shouldRemoveAttribute = false; attribute = childAttributes[ii]; attributeName = attribute.name; attributeValue = attribute.value; diff --git a/ui/image.reel/image.css b/ui/image.reel/image.css index 945f8fdcd7..9639fc6d63 100644 --- a/ui/image.reel/image.css +++ b/ui/image.reel/image.css @@ -1,10 +1,3 @@ -.montage-image { - position: relative; - width: 100%; - height: 100%; - display: inline-block; -} - .montage-image[hidden] { display: none !important; } diff --git a/ui/list-item.reel/list-item.css b/ui/list-item.reel/list-item.css index dbd74c0f22..e8d69aea30 100755 --- a/ui/list-item.reel/list-item.css +++ b/ui/list-item.reel/list-item.css @@ -76,6 +76,13 @@ justify-content: center; } +.ListItem .ListItem-icon .montage-image { + position: relative; + height: 24px; + width: 24px; + display: inline-block; +} + /** Label + description **/ .ListItem .ListItem-text {