/************************************************************************************************************
@fileoverview
Standalone script from ludoJS - Javascript framework
Copyright (C) 2012-2012 ludoJS.com, Alf Magne Kalleland
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
ludoJS.com., hereby disclaims all copyright interest in this script
written by Alf Magne Kalleland.
Alf Magne Kalleland, 2012
Owner of ludoJS.com
************************************************************************************************************/
/**
* @module ludo
* @main ludo
*/
if (!window.ludo)window.ludo = {};
ludo.form = {
validator:{}
};
ludo.dashboard = {};
ludo.dialog = {};
ludo.remote = {};
ludo.tree = {};
ludo.model = {};
ludo.tpl = {};
ludo.video = {};
ludo.storage = {};
ludo.grid = {};
ludo.effect = {};
ludo.paging = {};
ludo.calendar = {};
ludo.layout = {};
ludo.progress = {};
ludo.dataSource = {};
ludo.controller = {};
ludo.card = {};
ludo.svg = {};
ludo.socket = {};
ludo.menu = {};
ludo.component = {};
ludo.audio = {};
if (Browser.ie) {
try {
document.execCommand("BackgroundImageCache", false, true);
} catch (e) {
}
}
ludo.SINGLETONS = {};
ludo.CmpMgrClass = new Class({
Extends:Events,
components:{},
formElements:{},
/**
* Reference to current active component
* @property object activeComponent
* @private
*/
activeComponent:undefined,
/**
* Reference to currently selected button
* @property object activeButton
* @private
*/
activeButton:undefined,
/** Array of available buttons for a component. Used for tab navigation
* @property array availableButtons
* @private
*/
availableButtons:undefined,
initialize:function () {
document.id(document.documentElement).addEvent('keypress', this.autoSubmit.bind(this));
},
autoSubmit:function (e) {
if (e.key == 'enter') {
if (e.target.tagName.toLowerCase() !== 'textarea') {
if (this.activeButton) {
this.activeButton.click();
}
}
}
if (e.key == 'tab') {
var tag = e.target.tagName.toLowerCase();
if (tag !== 'input' && tag !== 'textarea') {
this.selectNextButton();
}
}
},
registerComponent:function (component) {
this.components[component.id] = component;
if (component.buttonBar || component.buttons) {
component.addEvent('activate', this.selectFirstButton.bind(this));
component.addEvent('hide', this.clearButtons.bind(this));
}
if (component.singleton && component.type) {
ludo.SINGLETONS[component.type] = component;
}
},
selectFirstButton:function (cmp) {
if (cmp.isHidden() || !cmp.getButtons) {
return;
}
this.activeComponent = cmp;
if (this.activeButton) {
this.activeButton.deSelect();
}
this.activeButton = undefined;
var buttons = this.availableButtons = cmp.getButtons();
var i;
for (i = 0; i < buttons.length; i++) {
if (!buttons[i].isHidden() && buttons[i].selected) {
this.activeButton = buttons[i];
buttons[i].select();
return;
}
}
for (i = 0; i < buttons.length; i++) {
if (!buttons[i].isHidden() && buttons[i].type == 'form.SubmitButton') {
this.activeButton = buttons[i];
buttons[i].select();
return;
}
}
for (i = 0; i < buttons.length; i++) {
if (!buttons[i].isHidden() && buttons[i].type == 'form.CancelButton') {
this.activeButton = buttons[i];
buttons[i].select();
return;
}
}
},
selectNextButton:function () {
if (this.activeButton) {
this.activeButton.deSelect();
}
var index = this.availableButtons.indexOf(this.activeButton);
index++;
if (index >= this.availableButtons.length) {
index = 0;
}
this.activeButton = this.availableButtons[index];
this.activeButton.select();
},
clearButtons:function (cmp) {
if (this.activeComponent && this.activeComponent.getId() == cmp.getId()) {
this.activeComponent = undefined;
this.activeButton = undefined;
this.activeButton = undefined;
}
},
deleteComponent:function (component) {
this.clearButtons(component);
delete this.components[component.getId()];
},
get:function (id) {
return this.components[id];
},
zIndex:1,
getNewZIndex:function () {
this.zIndex++;
return this.zIndex;
},
newComponent:function (cmpConfig, parentComponent) {
cmpConfig = cmpConfig || {};
if (!this.isConfigObject(cmpConfig)) {
if (parentComponent) {
if (cmpConfig.getParent() && cmpConfig.getParent().removeChild) {
cmpConfig.getParent().removeChild(cmpConfig);
}
cmpConfig.setParentComponent(parentComponent);
}
return cmpConfig;
} else {
if (parentComponent) {
cmpConfig.els = cmpConfig.els || {};
if (!cmpConfig.els.parent && parentComponent.getEl())cmpConfig.els.parent = parentComponent.getEl();
cmpConfig.parentComponent = parentComponent;
}
var ret;
var cmpType = this.getViewType(cmpConfig, parentComponent);
if (cmpType.countNameSpaces > 1) {
var tokens = cmpConfig.type.split(/\./g);
var ns = tokens.join('.');
ret = eval('new window.ludo.' + ns + '(cmpConfig)');
if (!ret.type)ret.type = ns;
return ret;
}
else if (cmpType.nameSpace) {
if (!window.ludo[cmpType.nameSpace][cmpType.componentType] && parentComponent) {
parentComponent.log('Class ludo.' + cmpType.nameSpace + '.' + cmpType.componentType + ' does not exists');
}
ret = new window.ludo[cmpType.nameSpace][cmpType.componentType](cmpConfig);
if (!ret.type)ret.type = cmpType.nameSpace;
return ret;
} else {
if (!window.ludo[cmpType.componentType] && parentComponent) {
parentComponent.log('Cannot create object of type ' + cmpType.componentType);
}
return new window.ludo[cmpType.componentType](cmpConfig);
}
}
},
getViewType:function (config, parentComponent) {
var cmpType = '';
var nameSpace = '';
if (config.type) {
cmpType = config.type;
}
else if (config.cType) {
cmpType = config.cType;
} else {
cmpType = parentComponent.cType;
}
var countNS = 0;
if (cmpType.indexOf('.') >= 0) {
var tokens = cmpType.split(/\./g);
nameSpace = tokens[0];
cmpType = tokens[1];
countNS = tokens.length - 1;
}
return {
nameSpace:nameSpace,
componentType:cmpType,
countNameSpaces:countNS
}
},
isConfigObject:function (obj) {
return obj && obj.initialize ? false : true;
}
});
ludo.CmpMgr = new ludo.CmpMgrClass();
ludo.getView = function (id) {
return ludo.CmpMgr.get(id);
};
ludo.get = function (id) {
return ludo.CmpMgr.get(id);
};
ludo._new = function (config) {
if (config.type && ludo.SINGLETONS[config.type]) {
return ludo.SINGLETONS[config.type];
}
return ludo.CmpMgr.newComponent(config);
};
ludo.FormMgrClass = new Class({
formElements:{},
elementArray:[],
posArray:{},
forms:{},
add:function (item) {
var name = item.getName();
if (!this.formElements[name]) {
this.formElements[name] = item;
this.elementArray.push(item);
this.posArray[item.getId()] = this.elementArray.length - 1;
}
item.addEvent('focus', this.setFocus.bind(this));
item.addEvent('click', this.setFocus.bind(this));
},
getNext:function (formComponent) {
if (this.posArray[formComponent.getId()]) {
var index = this.posArray[formComponent.getId()];
if (index < this.elementArray.length - 1) {
return this.elementArray[index + 1];
}
}
return null;
},
get:function (name) {
return this.formElements[name] ? this.formElements[name] : null;
},
currentFocusedElement:undefined,
setFocus:function (value, component) {
if (component.isFormElement()) {
this.currentFocusedElement = component;
}
}
});
ludo.Form = new ludo.FormMgrClass();
ludo.Effect = new Class({
Extends: Events,
inProgress:false,
initialize:function(){
if(Browser.ie){
document.id(document.documentElement).addEvent('selectstart', this.cancelSelection.bind(this));
}
},
fireEvents:function(){
this.fireEvent('start');
this.fireEvent('end');
},
start:function(){
this.fireEvent('start');
this.inProgress = true;
this.disableSelection();
},
end:function(){
this.fireEvent('end');
this.inProgress = false;
this.enableSelection();
},
disableSelection:function(){
document.body.addClass('ludo-unselectable');
},
enableSelection:function(){
document.body.removeClass('ludo-unselectable');
},
cancelSelection:function(){
return !(this.inProgress);
}
});
ludo.EffectObject = new ludo.Effect();
/**
Words used by ludo JS. You can change words by creating a new ludo.language Object,
@module language
@type {Object}
@example
ludo.language = Object.merge(ludo.language,{
... Your own config
});
*/
ludo.language = {
'columns' : 'Columns'
};
ludo.storage.LocalStorage = new Class({
supported:false,
initialize:function(){
this.supported = typeof(Storage)!=="undefined";
},
save:function(key,value){
if(!this.supported)return;
var type = 'simple';
if(this.isObject(value)){
value = JSON.encode(value);
type = 'object';
}
localStorage[key] = value;
localStorage[this.getTypeKey(key)] = type;
},
get:function(key){
if(!this.supported)return;
var type = this.getType(key);
if(type==='object'){
return JSON.decode(localStorage[key]);
}
return localStorage[key];
},
isObject:function(value){
return typeof(value) == 'object';
},
getTypeKey:function(key){
return key + '___type';
},
getType:function(key){
key = this.getTypeKey(key);
if(localStorage[key]!==undefined){
return localStorage[key];
}
return 'simple';
},
clearLocalStore:function(){
localStorage.clear();
}
});
ludo.localStorage = undefined;
ludo.getLocalStorage = function(){
if(!ludo.localStorage)ludo.localStorage = new ludo.storage.LocalStorage();
return ludo.localStorage;
};
/**
* Base class for components and views in ludoJS. This class extends
* Mootools Events class.
* @class Core
*/
ludo.Core = new Class({
Extends:Events,
id:undefined,
/**
* NB. The config properties listed below are sent to the constructor when creating the component
* @attribute name
* @type string
* When creating children dynamically using config objects(see children) below, you can access a child
* by component.child[name] if a name is passed in the config object.
*/
name:undefined,
module:undefined,
submodule:undefined,
/**
Reference to a specific controller for the component.
The default way is to set useController to true and create a controller in
the same namespace as your component. Then that controller will be registered as controller
for the component.
The 'controller' property can be used to override this and assign a specific controller
If you create your own controller by extending ludo.controller.Controller,
you can control several views by adding events in the addView(component) method.
@attribute {Object} controller
@example
controller : 'idOfController'
@example
controller : { type : 'controller.MyController' }
A Controller can also be a singleton.
*/
controller:undefined,
/**
* Find controller and register this component to controller
* @attribute {Boolean} userController
* @default false
*/
useController:false,
/**
* Save states from session to session. This can be set to true
* for components and views where statefulProperties is defined. The component
* also needs an "id".
* @attribute stateful
* @type {Boolean}
* @default false
*/
stateful:false,
/**
* Array of stateful properties. These properties will be saved to
* local storage when "change" event is fired by the component
* @property statefulProperties
* @type Array
* @default undefined
*/
statefulProperties:undefined,
initialize:function (config) {
config = config || {};
this.lifeCycle(config);
},
lifeCycle:function(config){
this.__construct(config);
this.ludoEvents();
},
__construct:function(config){
if (config.url !== undefined)this.url = config.url;
if (config.name !== undefined)this.name = config.name;
if (config.listeners !== undefined)this.addEvents(config.listeners);
if (config.controller !== undefined)this.controller = config.controller;
if (this.controller !== undefined)ludo.controllerManager.assignSpecificControllerFor(this.controller, this);
if (config.module !== undefined)this.module = config.module;
if (config.submodule !== undefined)this.submodule = config.submodule;
if (config.useController !== undefined)this.useController = config.useController;
if (config.stateful !== undefined)this.stateful = config.stateful;
if (this.module || this.useController)ludo.controllerManager.registerComponent(this);
this.id = config.id || this.id;
if (this.stateful && this.statefulProperties !== undefined && this.id) {
config = this.appendPropertiesFromStore(config);
this.addEvent('state', this.saveStatefulProperties.bind(this));
}
if(!this.id)this.id = 'ludo-' + String.uniqueID();
ludo.CmpMgr.registerComponent(this);
},
ludoEvents:function(){
},
appendPropertiesFromStore:function (config) {
var c = ludo.getLocalStorage().get(this.getKeyForLocalStore());
if (c !== undefined) {
var keys = this.statefulProperties;
for (var i = 0; i < keys.length; i++) {
config[keys[i]] = c[keys[i]];
}
}
return config;
},
saveStatefulProperties:function () {
var obj = {};
var keys = this.statefulProperties;
for (var i = 0; i < keys.length; i++) {
obj[keys[i]] = this[keys[i]];
}
ludo.getLocalStorage().save(this.getKeyForLocalStore(), obj);
},
getKeyForLocalStore:function () {
return 'state_' + this.id;
},
/**
Return id of component
@method getId
@return String id
*/
getId:function () {
return this.id;
},
/**
Get name of component and form element
@method getName
@return String name
*/
getName:function () {
return this.name;
},
/**
* Get url for component
* @method getUrl
* @return string url
*/
getUrl:function () {
if (this.url) {
return this.url;
}
if (this.component) {
return this.component.getUrl();
}
if (this.applyTo) {
return this.applyTo.getUrl();
}
if (this.parentComponent) {
return this.parentComponent.getUrl();
}
if (window.LUDO_APP_CONFIG && LUDO_APP_CONFIG.url) {
return LUDO_APP_CONFIG.url;
}
return undefined;
},
isArray:function (obj) {
return typeof(obj) == 'object' && (obj instanceof Array);
},
isObject:function (obj) {
return typeof(obj) == 'object';
},
getEventEl:function () {
if (Browser.ie) {
return document.id(document.documentElement);
}
return document.id(window);
},
/**
* Send a JSON request
@method JSONRequest
@param {String} requestId
@param {Object} config
@return void
*/
JSONRequest:function (requestId, config) {
var proxy;
var url = config.url || this.getUrl();
if (proxy = ludo.remote.getProxy(url)) {
proxy.addRequest(requestId, config);
return;
}
var req = new Request.JSON(this.getRequestConfig(requestId, config));
req.send();
},
Request:function (requestId, config) {
var req = new Request(this.getRequestConfig(requestId, config));
req.send();
},
getRequestConfig:function (requestId, config) {
return {
url:config.url || this.getUrl(),
method:'post',
noCache:!this.isCacheEnabled(),
data:{
request:{
id:requestId,
data:config.data
}
},
evalScripts:true,
onSuccess:config.onSuccess.bind(this)
};
},
isCacheEnabled:function () {
return false
},
isMobilePlatform:function () {
return this.shouldUseTouchEvents();
},
shouldUseTouchEvents:function () {
var platform = navigator.platform;
if (platform.indexOf('iPad') >= 0) {
return true;
}
if (platform.indexOf('iPhone') >= 0) {
return true;
}
return Browser.Platform['android'];
},
log:function (txt) {
if (window.console && console.log && !Browser.ie) {
console.log(txt);
}
},
getDragStartEvent:function () {
if (this.shouldUseTouchEvents()) {
return 'touchstart';
}
return 'mousedown';
},
getDragMoveEvent:function () {
if (this.shouldUseTouchEvents()) {
return 'touchmove';
}
return 'mousemove';
},
getDragEndEvent:function () {
if (this.shouldUseTouchEvents()) {
return 'touchend';
}
return 'mouseup';
},
isConfigObject:function (obj) {
return obj.initialize === undefined;
},
ns:undefined,
/**
* Returns component type minus class name, example:
* type: calendar.View will return "calendar"
* @method getNamespace
* @return {String} namespace
*/
getNamespace:function () {
if (this.NS == undefined) {
if (this.type) {
var tokens = this.type.split(/\./g);
tokens = tokens.slice(0, tokens.length - 1);
this.NS = tokens.join('.');
} else {
this.NS = '';
}
}
return this.NS;
},
hasController:function () {
return this.controller ? true : false;
},
getController:function () {
return this.controller;
},
setController:function (controller) {
this.controller = controller;
this.addControllerEvents();
},
/**
Add events to controller
@method addControllerEvents
@return void
@example
this.controller.addEvent('eventname', this.methodName.bind(this));
*/
addControllerEvents:function () {
},
getModule:function () {
return this.getInheritedProperty('module');
},
getSubModule:function () {
return this.getInheritedProperty('submodule');
},
getInheritedProperty:function (key) {
if (this[key] !== undefined)return this[key];
if (this.parentComponent) {
return this.parentComponent.getInheritedProperty(key);
}
return undefined;
},
/**
Save state for stateful components and views. States are stored in localStorage which
is supported by all major browsers(IE from version 8).
@method saveState
@return void
@example
myComponent.saveState();
OR
@example
this.fireEvent('state');
which does the same.
*/
saveState:function () {
this.fireEvent('state');
}
});
ludo.dom = {
cache:{
PW:{}, PH:{},
BW:{}, BH:{},
MW:{}, MH:{}
},
/**
* Return Margin width (left and right) of DOM element
* Once retrieved, it will be cached for later lookup. Cache
* can be cleared by calling clearCacheStyles
* @method getMW
* @param {Object} el
*/
getMW:function (el) {
if (!el.id)el.id = 'el-' + String.uniqueID();
if (ludo.dom.cache.MW[el.id] === undefined) {
ludo.dom.cache.MW[el.id] = ludo.dom.getNumericStyle(el, 'margin-left') + ludo.dom.getNumericStyle(el, 'margin-right')
}
return ludo.dom.cache.MW[el.id];
},
/**
* Return Border width (left and right) of DOM element
* Once retrieved, it will be cached for later lookup. Cache
* can be cleared by calling clearCacheStyles
* @method getBW
* @param {Object} el DOMElement or id of DOMElement
*/
getBW:function (el) {
if (!el.id)el.id = 'el-' + String.uniqueID();
if (ludo.dom.cache.BW[el.id] === undefined) {
ludo.dom.cache.BW[el.id] = ludo.dom.getNumericStyle(el, 'border-left-width') + ludo.dom.getNumericStyle(el, 'border-right-width');
}
return ludo.dom.cache.BW[el.id];
},
/**
* Return Padding Width (left and right) of DOM element
* Once retrieved, it will be cached for later lookup. Cache
* can be cleared by calling clearCacheStyles
* @method getPW
* @param {Object} el
*/
getPW:function (el) {
if (!el.id)el.id = 'el-' + String.uniqueID();
if (ludo.dom.cache.PW[el.id] === undefined) {
ludo.dom.cache.PW[el.id] = ludo.dom.getNumericStyle(el, 'padding-left') + ludo.dom.getNumericStyle(el, 'padding-right');
}
return ludo.dom.cache.PW[el.id];
},
/**
* Return Margin height (top and bottom) of DOM element
* Once retrieved, it will be cached for later lookup. Cache
* can be cleared by calling clearCacheStyles
* @method getMH
* @param {Object} el
*/
getMH:function (el) {
if (!el.id)el.id = 'el-' + String.uniqueID();
if (ludo.dom.cache.MH[el.id] === undefined) {
ludo.dom.cache.MH[el.id] = ludo.dom.getNumericStyle(el, 'margin-top') + ludo.dom.getNumericStyle(el, 'margin-bottom')
}
return ludo.dom.cache.MH[el.id];
},
/**
* Return Border height (top and bottom) of DOM element
* Once retrieved, it will be cached for later lookup. Cache
* can be cleared by calling clearCacheStyles
* @method getBH
* @param {Object} el
*/
getBH:function (el) {
if (!el.id)el.id = 'el-' + String.uniqueID();
if (ludo.dom.cache.BH[el.id] === undefined) {
ludo.dom.cache.BH[el.id] = ludo.dom.getNumericStyle(el, 'border-top-width') + ludo.dom.getNumericStyle(el, 'border-bottom-width');
}
return ludo.dom.cache.BH[el.id];
},
/**
* Return Padding height (top and bottom) of DOM element
* Once retrieved, it will be cached for later lookup. Cache
* can be cleared by calling clearCacheStyles
* @method getPH
* @param {Object} el DOMElement or id of DOMElement
*/
getPH:function (el) {
if (!el.id)el.id = 'el-' + String.uniqueID();
if (ludo.dom.cache.PH[el.id] === undefined) {
ludo.dom.cache.PH[el.id] = ludo.dom.getNumericStyle(el, 'padding-top') + ludo.dom.getNumericStyle(el, 'padding-bottom');
}
return ludo.dom.cache.PH[el.id];
},
getMBPW:function (el) {
return ludo.dom.getPW(el) + ludo.dom.getMW(el) + ludo.dom.getBW(el);
},
getMBPH:function (el) {
return ludo.dom.getPH(el) + ludo.dom.getMH(el) + ludo.dom.getBH(el);
},
/**
* Return margin+border+padding width of elOne MINUS margin+border+padding width of elTwo
* @method getMBPWDiff
* @param elOne
* @param elTwo
* @return {Number}
*/
getMBPWDiff:function(elOne,elTwo){
return ludo.dom.getMBPW(elOne) - ludo.dom.getMBPW(elTwo);
},
/**
* Return margin+border+padding height of elOne MINUS margin+border+padding height of elTwo
* @method getMBPHDiff
* @param elOne
* @param elTwo
* @return {Number}
*/
getMBPHDiff:function(elOne,elTwo){
return ludo.dom.getMBPH(elOne) - ludo.dom.getMBPH(elTwo);
},
/**
* @method clearCacheStyles
* Clear cached padding,border and margins.
*/
clearCache:function () {
ludo.dom.cache = {
PW:{}, PH:{},
BW:{}, BH:{},
MW:{}, MH:{}
};
},
/**
* Return numeric style value,
* @method getNumericStyle
* @private
* @param {Object} el
* @param {String} style
*/
getNumericStyle:function (el, style) {
return parseInt(el.getStyle(style).replace('px', ''));
},
isInFamilies:function(el, ids){
for(var i=0;i<ids.length;i++){
if(ludo.dom.isInFamily(el, ids[i]))return true;
}
return false;
},
isInFamily:function(el, id){
if(el.id === id)return true;
if(el.getParent('#' + id))return true;
return false;
}
};
/**
* Base class for animations
* @namespace effect
* @class Effect
*/
ludo.effect.Effect = new Class({
Extends: ludo.Core,
/**
Fly/Slide DOM node to a position
@method fly
@param {Object} config
@example
<div id="myDiv" style="position:absolute;width:100px;height:100px;border:1px solid #000;background-color:#DEF;left:50px;top:50px"></div>
<script type="text/javascript">
new ludo.effect.Effect().fly({
el: 'myDiv',
duration:.5,
to:{ x:500, y: 300 },
onComplete:function(){
new ludo.effect.Effect().fly({
el: 'myDiv',
duration:1,
to:{ x:600, y: 50 }
});
}
});
</script>
Which will first move "myDiv" to position 500x300 on the screen, then to 600x50.
*/
fly:function(config){
config.el = document.id(config.el);
config.duration = config.duration || .2;
if(config.from == undefined){
config.from = config.el.getPosition();
}
var fx = this.getFx(config.el, config.duration, config.onComplete);
fx.start({
left : [config.from.x, config.to.x],
top : [config.from.y, config.to.y]
});
},
/**
Fly/Slide DOM node from current location to given x and y coordinats in given seconds.
@method flyTo
@param {HTMLElement} el
@param {Number} x
@param {Number} y
@param {Number} seconds
@example
You may also use this method like this:
@example
<div id="myDiv" style="position:absolute;width:100px;height:100px;border:1px solid #000;background-color:#DEF;left:50px;top:50px"></div>
<script type="text/javascript">
new ludo.effect.Effect().flyTo('myDiv', 500, 300, .5);
</script>
Which slides "myDiv" to position 500x300 in 0.5 seconds.
*/
flyTo:function(el, x, y, seconds){
this.fly({
el:el,
to:{x : x, y: y},
duration: seconds
});
},
getFx:function (el, duration, onComplete) {
duration*=1000;
var fx = new Fx.Morph(el, {
duration:duration
});
fx.addEvent('complete', this.animationComplete.bind(this, [onComplete, el]));
return fx;
},
animationComplete:function(onComplete, el){
/**
* Fired when animation is completed
* @event animationComplete
* @param {effect.Drag} this
*/
this.fireEvent('animationComplete', this);
if(onComplete !== undefined){
onComplete.call(this, el);
}
}
});
/**
Specification of a draggable node objects sent to {{#crossLink "effect.Drag/add"}}{{/crossLink}}. You will
never create objects of this class.
@namespace effect
@class DraggableNode
@type {Object|String}
*/
ludo.effect.DraggableNode = new Class({
/**
id of node. This attribute is optional
@property id
@type {String}
@default undefined
@optional
@example
var dragDrop = new ludo.effect.Drag();
var el = new Element('div');
dragDrop.add({
id: 'myId',
el : el
});
var ref = dragDrop.getById('myId');
Or you can use this code which does the same:
@example
var dragDrop = new ludo.effect.Drag();
var el = new Element('div');
el.id = 'myId';
dragDrop.add(el);
var ref = dragDrop.getById('myId');
Id's are only important if you need to access nodes later using {{#crossLink "effect.Drag/getById"}}{{/crossLink}}
*/
id: undefined,
/**
* Reference to dragable DOM node
* @property el
* @default undefined
* @type {String|HTMLDivElement}
*/
el:undefined,
/**
* Reference to handle for dragging. el will only be draggable by dragging the handle.
* @property handle
* @type {String|HTMLDivElement}
* @default undefined
* @optional
*/
handle:undefined,
/**
* Minimum x position. This is an optional argument. If not set, you will use the params
* set when creating the ludo.effect.Drag component if any.
* @property minX
* @type {Number}
* @default undefined
* @optional
*/
minX:undefined,
/**
* Maximum x position. This is an optional argument. If not set, you will use the params
* set when creating the ludo.effect.Drag component if any.
* @property maxX
* @type {Number}
* @default undefined
* @optional
*/
maxX:undefined,
/**
* Minimum x position. This is an optional argument. If not set, you will use the params
* set when creating the ludo.effect.Drag component if any.
* @property minY
* @type {Number}
* @default undefined
* @optional
*/
minY:undefined,
/**
* Maximum y position. This is an optional argument. If not set, you will use the params
* set when creating the ludo.effect.Drag component if any.
* @property maxY
* @type {Number}
* @default undefined
* @optional
*/
maxY:undefined,
/**
Allow dragging in these directions. This is an optional argument. If not set, you will use the params
set when creating the ludo.effect.Drag component if any.
@property directions
@type {String}
@default 'XY'
@optional
@example
directions:'XY' //
..
directions:'X' // Only allow dragging along x-axis
..
directions:'Y' // Only allow dragging along y-axis
*/
directions:undefined
});
/**
@namespace effect
@class Drag
@extends effect.Effect
@description Class for dragging DOM elements.
@constructor
@param {Object} config
@example
<style type="text/css">
.ludo-shim {
border: 15px solid #AAA;
background-color: #DEF;
margin: 5;
opacity: .5;
border-radius: 5px;
}
.draggable{
width:150px;
z-index:1000;
height:150px;
border-radius:5px;
border:1px solid #555;
background-color:#DEF
}
</style>
<div id="draggable" class="draggable">
I am draggable
</div>
<script type="text/javascript">
var d = new ludo.effect.Drag({
useShim:true,
listeners:{
endDrag:function(dragged, dragEffect){
dragEffect.getEl().setStyles({
left : dragEffect.getX(),
top: dragEffect.getY()
});
},
drag:function(pos, dragEffect){
dragEffect.setShimText(dragEffect.getX() + 'x' + dragEffect.getY());
}
}
});
d.add('draggable'); // "draggable" is the id of the div
</script>
*/
ludo.effect.Drag = new Class({
Extends:ludo.effect.Effect,
/**
* Reference to drag handle (Optional). If not set, "el" will be used
* @config handle
* @type Object|String
* @default undefined
*/
handle:undefined,
/**
* Reference to DOM element to be dragged
* @config el
* @type Object|String
* @default undefined
*/
el:undefined,
/**
* Minimum x position
* @config minX
* @type {Number}
* @default undefined
*/
minX:undefined,
/**
* Minimum y position
* @config minY
* @type {Number}
* @default undefined
*/
minY:undefined,
/**
* Maximum x position
* @config maxX
* @type {Number}
* @default undefined
*/
maxX:undefined,
/**
* config y position
* @attribute maxY
* @type {Number}
* @default undefined
*/
maxY:undefined,
/**
* minPos and maxPos can be used instead of minX,maxX,minY and maxY if
* you only accept dragging along x-axis or y-axis
* @config {Number} minPos
* @default undefined
*/
minPos:undefined,
/**
* @config maxPos
* @type {Number}
* @default undefined
*/
maxPos:undefined,
/**
* Accept dragging in these directions
* @config dragX
* @type String
* @default XY
*/
directions:'XY',
/**
* Unit used while dragging
* @config unit, example : "px", "%"
* @default px
*/
unit:'px',
dragProcess:{
active:false
},
/**
* Delay in seconds from mouse down to start drag. If mouse is released within this interval,
* the drag will be cancelled.
* @config delay
* @type {Number}
* @default 0
*/
delay:0,
startTime:undefined,
inDelayMode:false,
els:{},
/**
* True to use dynamically created shim while dragging. When true,
* the original DOM element will not be dragged.
* @config useShim
* @type {Boolean}
* @default false
*/
useShim:false,
/**
* True to automatically hide shim after drag is finished
* @config autohideShim
* @type {Boolean}
* @default true
*/
autoHideShim:true,
/**
CSS classes to add to shim
@config shimCls
@type Array
@default undefined
@example
shimCls:['myShim','myShim-2']
which will results in this shim :
@example
<div class="ludo-shim myShim myShim-2">
*/
shimCls:undefined,
/**
* While dragging, always show dragged element this amount of pixels below mouse cursor.
* @config mouseYOffset
* @type {Number} pixels
* @default undefined
*/
mouseYOffset:undefined,
/**
* While dragging, always show dragged element this amount of pixels right of mouse cursor.
* @config mouseXOffset
* @type {Number} pixels
* @default undefined
*/
mouseXOffset:undefined,
__construct:function (config) {
this.parent(config);
if (config.el !== undefined) {
this.add({
el:config.el,
handle:config.handle
})
}
if (config.useShim !== undefined)this.useShim = config.useShim;
if (config.autoHideShim !== undefined)this.autoHideShim = config.autoHideShim;
if (config.directions !== undefined)this.directions = config.directions;
if (config.delay !== undefined)this.delay = config.delay;
if (config.minX !== undefined)this.minX = config.minX;
if (config.maxX !== undefined)this.maxX = config.maxX;
if (config.minY !== undefined)this.minY = config.minY;
if (config.maxY !== undefined)this.maxY = config.maxY;
if (config.minPos !== undefined)this.minPos = config.minPos;
if (config.maxPos !== undefined)this.maxPos = config.maxPos;
if (config.unit !== undefined)this.unit = config.unit;
if (config.shimCls !== undefined)this.shimCls = config.shimCls;
if (config.mouseYOffset !== undefined)this.mouseYOffset = config.mouseYOffset;
if (config.mouseXOffset !== undefined)this.mouseXOffset = config.mouseXOffset;
},
ludoEvents:function () {
this.parent();
this.getEventEl().addEvent(this.getDragMoveEvent(), this.drag.bind(this));
this.getEventEl().addEvent(this.getDragEndEvent(), this.endDrag.bind(this));
if (this.useShim) {
this.addEvent('start', this.showShim.bind(this));
if(this.autoHideShim)this.addEvent('end', this.hideShim.bind(this));
}
},
/**
Add draggable object
@method add
@param {effect.DraggableNode|String|HTMLDivElement} node
@return {effect.DraggableNode}
@example
dragObject.add({
el: 'myDiv',
handle : 'myHandle'
});
handle is optional.
@example
dragObject.add('idOfMyDiv');
You can also add custom properties:
@example
dragobject.add({
id:'myReference',
el: 'myDiv',
column: 'city'
});
...
...
dragobject.addEvent('before', beforeDrag);
...
...
function beforeDrag(dragged){
console.log(dragged.el);
console.log(dragged.column);
}
*/
add:function (node) {
node = this.getValidNode(node);
var el = document.id(node.el);
this.setPositioning(el);
var handle;
if (node.handle !== undefined) {
handle = document.id(node.handle);
} else {
handle = el;
}
handle.id = handle.id || 'ludo-' + String.uniqueID();
handle.addClass('ludo-drag');
handle.addEvent(this.getDragStartEvent(), this.startDrag.bind(this));
handle.setProperty('forId', node.id);
this.els[node.id] = Object.merge(node, {
el:document.id(el),
handle:handle.id
});
return this.els[node.id];
},
/**
* Remove node
* @method remove
* @param {String} id
* @return {Boolean} success
*/
remove:function(id){
if(this.els[id]!==undefined){
var el = document.id(this.els[id].handle);
el.removeEvent(this.getDragStartEvent(), this.startDrag.bind(this));
this.els[id] = undefined;
return true;
}
return false;
},
removeAll:function(){
var keys = Object.keys(this.els);
for(var i=0;i<keys.length;i++){
this.remove(keys[i]);
}
this.els = {};
},
getValidNode:function(node){
if (!this.isElConfigObject(node)) {
node = {
el:document.id(node)
};
}
if(typeof node.el === 'string'){
node.el = document.id(node.el);
}
node.id = node.id || node.el.id || 'ludo-' + String.uniqueID();
if (!node.el.id)node.el.id = node.id;
node.el.setProperty('forId', node.id);
return node;
},
isElConfigObject:function (config) {
return config.el !== undefined || config.handle !== undefined;
},
setPositioning:function(el){
var pos = el.getStyle('position');
if (!this.useShim){
if(pos && pos === 'relative'){
}
el.style.position = 'absolute';
}else{
if(!pos || (pos!='relative' && pos!='absolute')){
el.style.position = 'relative';
}
}
},
getById:function(id){
return this.els[id];
},
getIdByEvent:function (e) {
var el = e.target;
if (!el.hasClass('ludo-drag')) {
el = el.getParent('.ludo-drag');
}
return el.getProperty('forId');
},
/**
* Returns reference to dragged object, i.e. object added in constructor or
* by use of add method
* @method getDragged
* @return {Object}
*/
getDragged:function(){
return this.els[this.dragProcess.dragged];
},
/**
* Returns reference to draggable DOM node
* @method getEl
* @return {Object} DOMNode
*/
getEl:function () {
return document.id(this.els[this.dragProcess.dragged].el);
},
getShimOrEl:function () {
return this.useShim ? this.getShim() : this.getEl();
},
startDrag:function (e) {
var id = this.getIdByEvent(e);
var el = document.id(this.getById(id).el);
var size = el.getSize();
var pos;
if(this.useShim){
pos = el.getPosition();
}else{
var parent = this.getPositionedParent(el);
if(parent){
pos = el.getPosition(parent);
}else{
pos = el.getPosition();
}
}
var x = pos.x;
var y = pos.y;
this.dragProcess = {
active:true,
dragged:id,
dragX:this.canDragAlongX(),
dragY:this.canDragAlongY(),
currentX:x,
currentY:y,
elX:x,
elY:y,
width:size.x,
height:size.y,
mouseX:e.page.x,
mouseY:e.page.y
};
this.dragProcess.minX = this.getMinX();
this.dragProcess.maxX = this.getMaxX();
this.dragProcess.minY = this.getMinY();
this.dragProcess.maxY = this.getMaxY();
this.dragProcess.el = this.getShimOrEl();
/**
* Event fired before drag
* @event {effect.DraggableNode}
* @param {Object} object to be dragged
* @param {ludo.effect.Drag} component
* @param {Object} pos(x and y)
*/
this.fireEvent('before', [this.els[id], this, {x:x,y:y}]);
if(!this.isActive()){
return;
}
if (this.delay) {
this.setActiveAfterDelay();
} else {
/**
* Event fired before dragging
* @event start
* @param {effect.DraggableNode} object to be dragged.
* @param {ludo.effect.Drag} component
* @param {Object} pos(x and y)
*/
this.fireEvent('start', [this.els[id], this, {x:x,y:y}]);
ludo.EffectObject.start();
}
return false;
},
getPositionedParent:function(el){
var parent = el.parentNode;
while(parent){
var pos = parent.getStyle('position');
if (pos === 'relative' || pos === 'absolute')return parent;
parent = parent.getParent();
}
return undefined;
},
/**
Cancel drag. This method is designed to be called from an event handler
attached to the "beforeDrag" event.
@method cancelDrag
@example
// Here, dd is a {{#crossLink "effect.Drag"}}{{/crossLink}} object
dd.addEvent('before', function(draggable, dd, pos){
if(pos.x > 1000 || pos.y > 500){
dd.cancelDrag();
}
});
In this example, dragging will be cancelled when the x position of the mouse
is greater than 1000 or if the y position is greater than 500. Another more
useful example is this:
@example
dd.addEvent('before', function(draggable, dd){
if(!this.isDraggable(draggable)){
dd.cancelDrag()
}
});
Here, we assume that we have an isDraggable method which returns true or false
for whether the given node is draggable or not. "draggable" in this example
is one of the {{#crossLink "effect.DraggableNode"}}{{/crossLink}} objects added
using the {{#crossLink "effect.Drag/add"}}{{/crossLink}} method.
*/
cancelDrag:function () {
this.dragProcess.active = false;
this.dragProcess.el = undefined;
ludo.EffectObject.end();
},
getShimFor:function (el) {
return el;
},
setActiveAfterDelay:function () {
this.inDelayMode = true;
this.dragProcess.active = false;
this.startIfMouseNotReleased.delay(this.delay * 1000, this);
},
startIfMouseNotReleased:function () {
if (this.inDelayMode) {
this.dragProcess.active = true;
this.inDelayMode = false;
this.fireEvent('start', [this.getDragged(), this, {x:this.getX(),y:this.getY()}]);
ludo.EffectObject.start();
}
},
drag:function (e) {
if (this.dragProcess.active && this.dragProcess.el) {
var pos = {
x:undefined,
y:undefined
};
if (this.dragProcess.dragX) {
pos.x = this.getXDrag(e);
}
if (this.dragProcess.dragY) {
pos.y = this.getYDrag(e);
}
this.move(pos);
/**
* Event fired while dragging. Sends position, example {x:100,y:50}
* and reference to effect.Drag as arguments
* @event drag
* @param {Object} x and y
* @param {effect.Drag} this
*/
this.fireEvent('drag', [pos, this.els[this.dragProcess.dragged], this]);
if (this.shouldUseTouchEvents())return false;
}
return undefined;
},
move:function (pos) {
if (pos.x !== undefined) {
this.dragProcess.currentX = pos.x;
this.dragProcess.el.style.left = pos.x + this.unit;
}
if (pos.y !== undefined) {
this.dragProcess.currentY = pos.y;
this.dragProcess.el.style.top = pos.y + this.unit;
}
},
/**
* Return current x pos
* @method getX
* @return {Number} x
*/
getX:function(){
return this.dragProcess.currentX;
},
/**
* Return current y pos
* @method getY
* @return {Number} y
*/
getY:function(){
return this.dragProcess.currentY;
},
getXDrag:function (e) {
var posX;
if(this.mouseXOffset !== undefined){
posX = e.page.x + this.mouseXOffset;
}else{
posX = e.page.x - this.dragProcess.mouseX + this.dragProcess.elX;
}
if (posX < this.dragProcess.minX) {
posX = this.dragProcess.minX;
}
if (posX > this.dragProcess.maxX) {
posX = this.dragProcess.maxX;
}
return posX;
},
getYDrag:function (e) {
var posY;
if(this.mouseYOffset !== undefined){
posY = e.page.y + this.mouseYOffset;
}else{
posY = e.page.y - this.dragProcess.mouseY + this.dragProcess.elY;
}
if (posY < this.dragProcess.minY) {
posY = this.dragProcess.minY;
}
if (posY > this.dragProcess.maxY) {
posY = this.dragProcess.maxY;
}
return posY;
},
endDrag:function () {
if (this.dragProcess.active) {
this.cancelDrag();
/**
* Event fired on drag end
* @event end
* @param {effect.DraggableNode} dragged
* @param {ludo.effect.Drag} this
* @param {Object} x and y
*/
this.fireEvent('end', [
this.getDragged(),
this,
{
x:this.getX(),
y:this.getY()
}
]);
}
if (this.inDelayMode)this.inDelayMode = false;
},
/**
* Set new max X pos
* @method setMaxX
* @param {Number} x
*/
setMaxX:function (x) {
this.maxX = x;
},
/**
* Set new min X pos
* @method setMinX
* @param {Number} x
*/
setMinX:function (x) {
this.minX = x;
},
/**
* Set new min Y pos
* @method setMinY
* @param {Number} y
*/
setMinY:function (y) {
this.minY = y;
},
/**
* Set new max Y pos
* @method setMaxY
* @param {Number} y
*/
setMaxY:function (y) {
this.maxY = y;
},
/**
* Set new min pos
* @method setMinPos
* @param {Number} pos
*/
setMinPos:function (pos) {
this.minPos = pos;
},
/**
* Set new max pos
* @method setMaxPos
* @param {Number} pos
*/
setMaxPos:function (pos) {
this.maxPos = pos;
},
getMaxX:function () {
var maxX = this.getConfigProperty('maxX');
if (maxX !== undefined)return maxX;
if (this.maxPos !== undefined)return this.maxPos;
return 100000;
},
getMaxY:function () {
var maxY = this.getConfigProperty('maxY');
if (maxY !== undefined)return maxY;
if (this.maxPos !== undefined)return this.maxPos;
return 100000;
},
getMinX:function () {
var minX = this.getConfigProperty('minX');
if (minX !== undefined)return minX;
return this.minPos;
},
getMinY:function () {
var dragged = this.getDragged();
if(dragged && dragged.minY!==undefined)return dragged.minY;
if (this.minY !== undefined)return this.minY;
return this.minPos;
},
canDragAlongX:function () {
return this.getConfigProperty('directions').indexOf('X') >= 0;
},
canDragAlongY:function () {
return this.getConfigProperty('directions').indexOf('Y') >= 0;
},
getConfigProperty:function(property){
var dragged = this.getDragged();
return dragged && dragged[property] !== undefined ? dragged[property] : this[property];
},
/**
* Returns width of dragged element
* @method getHeight
* @return {Number}
*/
getWidth:function () {
return this.dragProcess.width;
},
/**
* Returns height of dragged element
* @method getHeight
* @return {Number}
*/
getHeight:function () {
return this.dragProcess.height;
},
/**
* Returns current left position of dragged
* @method getLeft
* @return {Number}
*/
getLeft:function () {
return this.dragProcess.currentX;
},
/**
* Returns current top/y position of dragged.
* @method getTop
* @return {Number}
*/
getTop:function () {
return this.dragProcess.currentY;
},
/**
* Returns reference to DOM element of shim
* @method getShim
* @return {HTMLDivElement} shim
*/
getShim:function () {
if (this.shim === undefined) {
this.shim = new Element('div');
this.shim.addClass('ludo-shim');
this.shim.setStyles({
position:'absolute',
'z-index':50000,
display:'none'
});
document.body.adopt(this.shim);
if (this.shimCls !== undefined) {
for (var i = 0; i < this.shimCls.length; i++) {
this.shim.addClass(this.shimCls[i]);
}
}
/**
* Event fired when shim is created
* @event createShim
* @param {HTMLDivElement} shim
*/
this.fireEvent('createShim', this.shim);
}
return this.shim;
},
/**
* Show shim
* @method showShim
*/
showShim:function () {
this.getShim().setStyles({
display:'',
left:this.getShimX(),
top:this.getShimY(),
width:this.getWidth() + this.getShimWidthDiff(),
height:this.getHeight() + this.getShimHeightDiff()
});
this.fireEvent('showShim', [this.getShim(), this]);
},
getShimY:function(){
if(this.mouseYOffset!==undefined){
return this.dragProcess.mouseY + this.mouseYOffset;
}else{
return this.getTop() + ludo.dom.getMH(this.getEl()) - ludo.dom.getMW(this.shim);
}
},
getShimX:function(){
if(this.mouseXOffset!==undefined){
return this.dragProcess.mouseX + this.mouseXOffset;
}else{
return this.getLeft() + ludo.dom.getMW(this.getEl()) - ludo.dom.getMW(this.shim);
}
},
getShimWidthDiff:function(){
var e = this.getEl();
var s = this.shim;
return ludo.dom.getMW(e) - ludo.dom.getBW(s) - ludo.dom.getPW(s) - ludo.dom.getMW(s);
},
getShimHeightDiff:function(){
var e = this.getEl();
var s = this.shim;
return ludo.dom.getMH(e) - ludo.dom.getBH(s) - ludo.dom.getPH(s) - ludo.dom.getMH(s);
},
/**
* Hide shim
* @method hideShim
*/
hideShim:function () {
this.getShim().style.display = 'none';
},
/**
* Set text content of shim
* @method setShimText
* @param {String} text
*/
setShimText:function (text) {
this.getShim().set('html', text);
},
/**
* Fly/Slide dragged element back to it's original position
* @method flyBack
*/
flyBack:function (duration) {
this.fly({
el: this.getShimOrEl(),
duration: duration,
from:{ x: this.getLeft(), y : this.getTop() },
to:{ x: this.getStartX(), y : this.getStartY() },
onComplete : this.flyBackComplete.bind(this)
});
},
/**
* Fly/Slide dragged element to position of shim. This will only
* work when useShim is set to true.
* @method flyToShim
* @param {Number} duration in seconds(default = .2)
*/
flyToShim:function(duration){
this.fly({
el: this.getEl(),
duration: duration,
from:{ x: this.getStartX(), y : this.getStartY() },
to:{ x: this.getLeft(), y : this.getTop() },
onComplete : this.flyToShimComplete.bind(this)
});
},
getStartX:function () {
return this.dragProcess.elX;
},
getStartY:function () {
return this.dragProcess.elY;
},
flyBackComplete:function(){
/**
* Event fired after flyBack animation is complete
* @event flyBack
* @param {effect.Drag} this
* @param {HTMLElement} dom node
*/
this.fireEvent('flyBack', [this, this.getShimOrEl()]);
},
flyToShimComplete:function(){
/**
* Event fired after flyToShim animation is complete
* @event flyBack
* @param {effect.Drag} this
* @param {HTMLElement} dom node
*/
this.fireEvent('flyToShim', [this, this.getEl()]);
},
isActive:function(){
return this.dragProcess.active;
}
});
/**
Specification of a drop point node sent to {{#crossLink "effect.DragDrop/addDropTarget"}}{{/crossLink}}.
You may add your own properties in addition to the ones below.
@namespace effect
@class DropPoint
@constructor
@param {Object} config
@example
var dd = new ludo.effect.DragDrop();
var el = new Element('div');
dd.addDropTarget({
id:'myDropPoint',
el:el,
name:'John Doe'
});
var el = new Element('div');
dd.addDropTarget({
id:'myDropPoint',
el:el,
name:'Jane Doe'
});
dd.addEvent('enterDropTarget', function(node, dd){
if(node.name === 'John Doe'){
dd.setInvalid(); // Triggers an invalidDropTarget event
}
});
*/
ludo.effect.DropPoint = new Class({
/**
id of node. This attribute is optional
@property id
@type {String}
@default undefined
@optional
*/
id:undefined,
/**
* Reference to dragable DOM node
* @property el
* @default undefined
* @type {String|HTMLDivElement}
*/
el:undefined
});
/**
* effect.Drag with support for drop events.
* @namespace effect
* @class DragDrop
* @extends effect.Drag
*/
ludo.effect.DragDrop = new Class({
Extends:ludo.effect.Drag,
useShim:false,
currentDropPoint:undefined,
onValidDropPoint:undefined,
/**
Capture regions when moving over drop points
@config Boolean captureRegions
@default false
@example
captureRegions:true
*/
captureRegions:false,
/**
* While dragging, always show dragged element this amount of pixels below mouse cursor.
* @config mouseYOffset
* @type {Number} pixels
* @default undefined
*/
mouseYOffset:undefined,
__construct:function (config) {
this.parent(config);
if (config.captureRegions !== undefined)this.captureRegions = config.captureRegions;
},
ludoEvents:function () {
this.parent();
this.addEvent('start', this.setStartProperties.bind(this));
this.addEvent('end', this.drop.bind(this));
},
getDropIdByEvent:function (e) {
var el = e.target;
if (!el.hasClass('ludo-drop')) {
el = el.getParent('.ludo-drop');
}
return el.getProperty('forId');
},
/**
* Remove node
* @method remove
* @param {String} id
* @return {Boolean} success
*/
remove:function (id) {
if (this.els[id] !== undefined) {
var el = document.id(this.els[id].el);
el.removeEvent('mouseenter', this.enterDropTarget.bind(this));
el.removeEvent('mouseleave', this.leaveDropTarget.bind(this));
return this.parent(id);
}
return false;
},
/**
* Create new drop point.
* @method addDropTarget
* @param {effect.DropPoint} node
* @return {effect.DropPoint} node
*/
addDropTarget:function (node) {
node = this.getValidNode(node);
node.el.addClass('ludo-drop');
node.el.addEvent('mouseenter', this.enterDropTarget.bind(this));
node.el.addEvent('mouseleave', this.leaveDropTarget.bind(this));
var captureRegions = node.captureRegions !== undefined ? node.captureRegions : this.captureRegions;
if (captureRegions) {
node.el.addEvent('mousemove', this.captureRegion.bind(this));
}
node = this.els[node.id] = Object.merge(node, {
el:node.el,
captureRegions:captureRegions
});
return node;
},
enterDropTarget:function (e) {
if (this.isActive()) {
this.setCurrentDropPoint(e);
this.onValidDropPoint = true;
/**
Enter drop point event. This event is fired when dragging is active
and mouse enters a drop point
@event enterDropTarget
@param {effect.DraggableNode} node
@param {effect.DropPoint} node
@param {effect.DragDrop} this
@param {HTMLElement} target
*/
this.fireEvent('enterDropTarget', this.getDropEventArguments(e));
if (this.onValidDropPoint) {
if (this.shouldCaptureRegionsFor(this.currentDropPoint)) {
this.setMidPoint();
}
/**
Enters valid drop point.
@event validDropTarget
@param {effect.DraggableNode} dragged node
@param {effect.DropPoint} drop target
@param {effect.DragDrop} this
@param {HTMLElement} target
*/
this.fireEvent('validDropTarget', this.getDropEventArguments(e));
} else {
/**
Enters invalid drop point.
@event invalidDropTarget
@param {effect.DraggableNode} dragged node
@param {effect.DropPoint} drop target
@param {effect.DragDrop} this
@param {HTMLElement} target
*/
this.fireEvent('invalidDropTarget', this.getDropEventArguments(e));
}
return false;
}
return undefined;
},
setCurrentDropPoint:function (e) {
this.currentDropPoint = this.getById(this.getDropIdByEvent(e));
},
leaveDropTarget:function (e) {
if (this.isActive() && this.currentDropPoint) {
this.fireEvent('leaveDropTarget', this.getDropEventArguments(e));
this.onValidDropPoint = false;
this.currentDropPoint = undefined;
}
},
/**
* Return parent drop point if exists
* @method getParentDropPoint
* @param {effect.DropPoint}
* @return {effect.DropPoint}
*/
getParentDropPoint:function (node) {
var parent = node.el.getParent('.ludo-drop');
if (parent) {
return this.els[parent.id];
}
return undefined;
},
getDropEventArguments:function (e) {
return [this.getDragged(), this.currentDropPoint, this, e.target];
},
/**
Set drop point invalid. This method is usually used in connection with a listener
for the enterDropTarget event
@method setInvalid
@example
dd.addEvent('enterDropTarget', function(node, dd){
if(node.name === 'John Doe'){
dd.setInvalid(); // Triggers an invalidDropTarget event
}
});
*/
setInvalid:function () {
this.onValidDropPoint = false;
},
getCurrentDropPoint:function () {
return this.currentDropPoint;
},
drop:function (e) {
/**
drop event caused by mouseup on valid drop point.
@event drop
@param {effect.DraggableNode} dragged node
@param {effect.DropPoint} drop target
@param {effect.DragDrop} this
@param {HTMLElement} target
*/
if (this.onValidDropPoint)this.fireEvent('drop', this.getDropEventArguments(e));
},
setStartProperties:function () {
this.onValidDropPoint = false;
},
shouldCaptureRegionsFor:function (node) {
return this.els[node.id].captureRegions === true;
},
getDropPointCoordinates:function () {
if (this.currentDropPoint) {
return this.currentDropPoint.el.getCoordinates();
}
return undefined;
},
previousRegions:{
h:undefined,
v:undefined
},
captureRegion:function (e) {
if (this.isActive() && this.onValidDropPoint && this.shouldCaptureRegionsFor(this.currentDropPoint)) {
var midPoint = this.midPoint;
if (e.page.y < midPoint.y && this.previousRegions.v !== 'n') {
/**
Enter north region of a drop point
@event north
@param {effect.DraggableNode} dragged node
@param {effect.DropPoint} drop target
@param {effect.DragDrop} this
@param {HTMLElement} target
*/
this.fireEvent('north', this.getDropEventArguments(e));
this.previousRegions.v = 'n';
} else if (e.page.y >= midPoint.y && this.previousRegions.v !== 's') {
/**
Enter south region of a drop point
@event south
@param {effect.DraggableNode} dragged node
@param {effect.DropPoint} drop target
@param {effect.DragDrop} this
@param {HTMLElement} target
*/
this.fireEvent('south', this.getDropEventArguments(e));
this.previousRegions.v = 's';
}
if (e.page.x < midPoint.x && this.previousRegions.h !== 'w') {
/**
Enter west region of a drop point
@event west
@param {effect.DraggableNode} dragged node
@param {effect.DropPoint} drop target
@param {effect.DragDrop} this
@param {HTMLElement} target
*/
this.fireEvent('west', this.getDropEventArguments(e));
this.previousRegions.h = 'w';
} else if (e.page.x >= midPoint.x && this.previousRegions.h !== 'e') {
/**
Enter east region of a drop point
@event east
@param {effect.DraggableNode} dragged node
@param {effect.DropPoint} drop target
@param {effect.DragDrop} this
@param {HTMLElement} target
*/
this.fireEvent('east', this.getDropEventArguments(e));
this.previousRegions.h = 'e';
}
}
},
midPoint:undefined,
setMidPoint:function () {
var coords = this.getDropPointCoordinates();
this.midPoint = {
x:coords.left + (coords.width / 2),
y:coords.top + (coords.height / 2)
};
this.previousRegions = {
h:undefined,
v:undefined
};
}
});