var _ = require("lodash");
var binder = require("../events/binder");
var hasEmitter = require("../events/hasemitter");
var GestureHandler = require("./gesturehandler");
var validate = require("../utils/validate");
var rect = require("../utils/rect");
/**
* Manages a group of {@link GestureHandler}s. When some types of gesture events are emitted
* by any {@link GestureHandler} in the group, the event will be applied to all over {@link GestureHandler}s
* in the group. E.g. if you pan or swipe one element 10 pixels to the left, all the other gesture handlers
* in the group will also be instructed to pan 10 pixels to the left.
*
* @class
* @param {?Object} options - options
* @return {GestureHandlerGroup}
*/
function GestureHandlerGroup(options) {
if (!(this instanceof GestureHandlerGroup)) {
return new GestureHandlerGroup(options);
}
options = _.merge({}, this.defaultOptions, options);
validate(options.containerElement, "options.containerElement", { isElement: true });
this.gestures = options.gestures;
this.paddingRight = options.paddingRight;
this.paddingBottom = options.paddingBottom;
this.gestureHandlers = [];
this.containerElement = options.containerElement;
this.setConfig(options.config);
this.setEmitter(options.emitter || {});
this.addGestureHandlers(options.gestureHandlers || []);
this.bind();
}
_.extend(GestureHandlerGroup.prototype, binder, hasEmitter, /** @lends GestureHandlerGroup.prototype */ {
defaultOptions: {
gestures: {
pan: {
enabled: true,
horizontal: true,
vertical: false
},
swipe: {
enabled: true,
horizontal: true,
vertical: false
},
tap: {
enabled: true
},
press: {
enabled: true
}
},
paddingRight: 60,
paddingBottom: 60
},
getEmitterEvents: function getEmitterEvents() {
var emitterEvents = {};
if (this.gestures.pan.enabled) {
emitterEvents["gesture:pan:start"] = "onGesturePanStart";
emitterEvents["gesture:pan:end"] = "onGesturePanEnd";
emitterEvents["gesture:pan:cancel"] = "onGesturePanCancel";
if (this.gestures.pan.horizontal && this.gestures.pan.vertical) {
emitterEvents["gesture:pan:any"] = "onGesturePanAny";
} else if (this.gestures.pan.vertical) {
emitterEvents["gesture:pan:y"] = "onGesturePanY";
} else if (this.gestures.pan.horizontal) {
emitterEvents["gesture:pan:x"] = "onGesturePanX";
}
}
if (this.gestures.swipe.enabled) {
if (this.gestures.swipe.horizontal && this.gestures.swipe.vertical) {
emitterEvents["gesture:swipe:any"] = "onGestureSwipeAny";
} else if (this.gestures.swipe.vertical) {
emitterEvents["gesture:swipe:y"] = "onGestureSwipeY";
} else if (this.gestures.swipe.horizontal) {
emitterEvents["gesture:swipe:x"] = "onGestureSwipeX";
}
}
if (this.gestures.tap.enabled) {
emitterEvents["gesture:tap"] = "onGestureTap";
}
if (this.gestures.press.enabled) {
emitterEvents["gesture:press"] = "onGesturePress";
}
return emitterEvents;
},
bind: function bind() {
var emitterEvents = this.getEmitterEvents();
// Setup event handler methods for all interested events
_.each(emitterEvents, function(methodName) {
if (!_.isFunction(this[methodName])) {
this[methodName] = this.applyGesture;
}
}, this);
this.bindEvents(this.emitter, this.getEmitterEvents());
_.each(this.gestureHandlers, function(gestureHandler) {
gestureHandler.bind();
}, this);
},
unbind: function unbind() {
this.unbindEvents(this.emitter, this.getEmitterEvents());
_.each(this.gestureHandlers, function(gestureHandler) {
gestureHandler.unbind();
}, this);
},
destroy: function destroy() {
// Unbind from all events
this.unbind();
// Destroy all the gesture handlers
_.each(this.gestureHandlers, function(gestureHandler) {
gestureHandler.destroy();
});
this.gestureHandlers = [];
},
getEventHandlerMethodName: function getEventHandlerMethodName(e) {
var emitterEvents = this.getEmitterEvents();
return emitterEvents[e.type];
},
setConfig: function setConfig(config) {
validate(config, "GestureHandlerGroup#setConfig: config", { isRequired: true, isNotSet: this.config });
this.config = config;
},
hasGestureHandlerForElement: function hasGestureHandlerForElement(element) {
return !!this.getGestureHandlerForElement(element);
},
getGestureHandlerForElement: function getGestureHandlerForElement(element) {
validate(element, "element", { isElement: true });
return _.find(this.gestureHandlers, function(gestureHandler) {
return gestureHandler.element === element;
});
},
removeGestureHandlerForElement: function removeGestureHandlerForElement(element, options) {
var gestureHandler = this.getGestureHandlerForElement(element);
if (!gestureHandler) {
return;
}
return this.removeGestureHandler(gestureHandler, options);
},
hasGestureHandler: function hasGestureHandler(gestureHandler) {
validate(gestureHandler, "gestureHandler", { isInstanceOf: GestureHandler });
return _.contains(this.gestureHandlers, gestureHandler);
},
addGestureHandler: function addGestureHandler(gestureHandler) {
validate(gestureHandler, "gestureHandler", { isInstanceOf: GestureHandler });
if (this.hasGestureHandler(gestureHandler)) {
return;
}
this.gestureHandlers.push(gestureHandler);
this.updateBounds();
return gestureHandler;
},
addGestureHandlers: function addGestureHandlers(gestureHandlers) {
validate(gestureHandlers, "gestureHandlers", { isArray: true });
return _.map(gestureHandlers, function(gestureHandler) {
return this.addGestureHandler(gestureHandler);
}, this);
},
removeGestureHandler: function removeGestureHandler(gestureHandler, options) {
validate(gestureHandler, "gestureHandler", { isInstanceOf: GestureHandler });
options = options || {};
if (!this.hasGestureHandler(gestureHandler)) {
return;
}
_.pull(this.gestureHandlers, gestureHandler);
if (options.destroy) {
gestureHandler.destroy();
}
this.updateBounds();
return gestureHandler;
},
applyGesture: function applyGesture(e) {
validate(e, "GestureHandlerGroup#applyGesture: e", { isRequired: true });
var methodName = this.getEventHandlerMethodName(e);
_.each(this.gestureHandlers, function(gestureHandler) {
// Don't apply the gesture to the element that originally emitted the event (it's already handled by that gesture handler)
// Don't apply the gesture to elements that aren't in this gesture handler group
if (gestureHandler.element === e.sender.element || !this.hasGestureHandlerForElement(e.sender.element)) {
return;
}
// Allows the event to be handled by a GestureHandler that does not match the element
// that emitted the event
var options = {
elementOverride: gestureHandler.element
};
gestureHandler[methodName](e, options);
}, this);
},
/**
* Gets all of the elements managed by this {@link GestureHandlerGroup}
*
* @return {Element[]}
*/
getElements: function() {
return _.map(this.gestureHandlers, function(gestureHandler) {
return gestureHandler.element;
});
},
/**
* Gets the bounding client rects for all the elements in this {@link GestureHandlerGroup}
*
* @return {Object[]}
*/
getElementRects: function() {
return _.map(this.getElements(), function(element) {
return rect.normalize(element);
});
},
/**
* Updates the bounds for all the {@link GestureHandler}s in the {@link GestureHandlerGroup}.
*
* @return {undefined}
*/
updateBounds: function() {
var elementRects = this.getElementRects();
var containerElementBounds = rect.normalize(this.containerElement);
var allElementBounds = rect.unionAll(elementRects);
_.each(this.gestureHandlers, function(gestureHandler, index) {
var elementRect = elementRects[index];
var bounds = rect.normalize({
left: elementRect.left - (allElementBounds.width - containerElementBounds.width) - this.paddingRight,
right: elementRect.right,
top: elementRect.top - (allElementBounds.height - containerElementBounds.height) - this.paddingBottom,
bottom: elementRect.bottom
});
// Disallow horizontal movement if the width of all elements is less than the width of the container
if (allElementBounds.width < containerElementBounds.width) {
bounds.left = elementRect.left;
}
// Disallow vertical movement if the height of all elements is less than the height of the container
if (allElementBounds.height < containerElementBounds.height) {
bounds.top = elementRect.top;
}
/*
console.log("------------");
console.log("element.id", gestureHandler.element.id);
console.log("elementRect", elementRect);
console.log("allElementBounds.width " + allElementBounds.width);
console.log("containerElementBounds.width", containerElementBounds.width);
console.log("bounds", bounds);
*/
gestureHandler.setBounds(bounds);
}, this);
}
});
module.exports = GestureHandlerGroup;