Created
October 25, 2010 10:16
-
-
Save shouvik/644722 to your computer and use it in GitHub Desktop.
Click to touch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// packager build Custom-Event/* Mobile/* Class-Extras/* History/* DynamicMatcher/* EventStack/* Form-AutoGrow/* Form-Placeholder/* Tree/* ScrollLoader/* Interface/* | |
/* | |
--- | |
name: Element.defineCustomEvent | |
description: Allows to create custom events based on other custom events. | |
authors: Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Core/Element.Event] | |
provides: Element.defineCustomEvent | |
... | |
*/ | |
(function(){ | |
[Element, Window, Document].invoke('implement', {hasEvent: function(event){ | |
var events = this.retrieve('events'), | |
list = (events && events[event]) ? events[event].values : null; | |
if (list){ | |
for (var i = list.length; i--;) if (i in list){ | |
return true; | |
} | |
} | |
return false; | |
}}); | |
var wrap = function(custom, method, extended, name){ | |
method = custom[method]; | |
extended = custom[extended]; | |
return function(fn, customName){ | |
if (!customName) customName = name; | |
if (extended && !this.hasEvent(customName)) extended.call(this, fn, customName); | |
if (method) method.call(this, fn, customName); | |
}; | |
}; | |
var inherit = function(custom, base, method, name){ | |
return function(fn, customName){ | |
base[method].call(this, fn, customName || name); | |
custom[method].call(this, fn, customName || name); | |
}; | |
}; | |
var events = Element.Events; | |
Element.defineCustomEvent = function(name, custom){ | |
var base = events[custom.base]; | |
custom.onAdd = wrap(custom, 'onAdd', 'onSetup', name); | |
custom.onRemove = wrap(custom, 'onRemove', 'onTeardown', name); | |
events[name] = base ? Object.append({}, custom, { | |
base: base.base, | |
condition: function(event){ | |
return (!base.condition || base.condition.call(this, event)) && | |
(!custom.condition || custom.condition.call(this, event)); | |
}, | |
onAdd: inherit(custom, base, 'onAdd', name), | |
onRemove: inherit(custom, base, 'onRemove', name) | |
}) : custom; | |
return this; | |
}; | |
var loop = function(name){ | |
var method = 'on' + name.capitalize(); | |
Element[name + 'CustomEvents'] = function(){ | |
Object.each(events, function(event, name){ | |
if (event[method]) event[method].call(event, name); | |
}); | |
}; | |
return loop; | |
}; | |
loop('enable')('disable'); | |
})(); | |
/* | |
--- | |
name: Browser.Mobile | |
description: Provides useful information about the browser environment | |
authors: Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Core/Browser] | |
provides: Browser.Mobile | |
... | |
*/ | |
(function(){ | |
Browser.Device = { | |
name: 'other' | |
}; | |
if (Browser.Platform.ios){ | |
var device = navigator.userAgent.toLowerCase().match(/(ip(ad|od|hone))/)[0]; | |
Browser.Device[device] = true; | |
Browser.Device.name = device; | |
} | |
if (this.devicePixelRatio == 2) | |
Browser.hasHighResolution = true; | |
Browser.isMobile = !['mac', 'linux', 'win'].contains(Browser.Platform.name); | |
})(); | |
/* | |
--- | |
name: Browser.Features.Touch | |
description: Checks whether the used Browser has touch events | |
authors: Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Core/Browser] | |
provides: Browser.Features.Touch | |
... | |
*/ | |
Browser.Features.Touch = (function(){ | |
try { | |
document.createEvent('TouchEvent').initTouchEvent('touchstart'); | |
return true; | |
} catch (exception){} | |
return false; | |
})(); | |
// Chrome 5 thinks it is touchy! | |
// Android doesn't have a touch delay and dispatchEvent does not fire the handler | |
Browser.Features.iOSTouch = (function(){ | |
var name = 'cantouch', // Name does not matter | |
html = document.html, | |
hasTouch = false; | |
var handler = function(){ | |
html.removeEventListener(name, handler, true); | |
hasTouch = true; | |
}; | |
try { | |
html.addEventListener(name, handler, true); | |
var event = document.createEvent('TouchEvent'); | |
event.initTouchEvent(name); | |
html.dispatchEvent(event); | |
return hasTouch; | |
} catch (exception){} | |
handler(); // Remove listener | |
return false; | |
})(); | |
/* | |
--- | |
name: Mouse | |
description: Maps mouse events to their touch counterparts | |
authors: Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Custom-Event/Element.defineCustomEvent, Browser.Features.Touch] | |
provides: Mouse | |
... | |
*/ | |
if (!Browser.Features.Touch) (function(){ | |
var condition = function(event){ | |
event.targetTouches = []; | |
event.changedTouches = event.touches = [{ | |
pageX: event.page.x, pageY: event.page.y, | |
clientX: event.client.x, clientY: event.client.y | |
}]; | |
return true; | |
}; | |
Element.defineCustomEvent('touchstart', { | |
base: 'mousedown', | |
condition: condition | |
}).defineCustomEvent('touchmove', { | |
base: 'mousemove', | |
condition: condition | |
}).defineCustomEvent('touchend', { | |
base: 'mouseup', | |
condition: condition | |
}); | |
})(); | |
/* | |
--- | |
name: Touch | |
description: Provides a custom touch event on mobile devices | |
authors: Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Core/Element.Event, Custom-Event/Element.defineCustomEvent, Browser.Features.Touch] | |
provides: Touch | |
... | |
*/ | |
(function(){ | |
var preventDefault = function(event){ | |
event.preventDefault(); | |
}; | |
var disabled; | |
Element.defineCustomEvent('touch', { | |
base: 'touchend', | |
condition: function(event){ | |
if (disabled || event.targetTouches.length != 0) return false; | |
var touch = event.changedTouches[0], | |
target = document.elementFromPoint(touch.clientX, touch.clientY); | |
do { | |
if (target == this) return true; | |
} while ((target = target.parentNode) && target); | |
return false; | |
}, | |
onSetup: function(){ | |
this.addEvent('touchstart', preventDefault); | |
}, | |
onTeardown: function(){ | |
this.removeEvent('touchstart', preventDefault); | |
}, | |
onEnable: function(){ | |
disabled = false; | |
}, | |
onDisable: function(){ | |
disabled = true; | |
} | |
}); | |
})(); | |
/* | |
--- | |
name: Click | |
description: Provides a replacement for click events on mobile devices | |
authors: Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Touch] | |
provides: Click | |
... | |
*/ | |
if (Browser.Features.iOSTouch) (function(){ | |
var name = 'click'; | |
delete Element.NativeEvents[name]; | |
Element.defineCustomEvent(name, { | |
base: 'touch' | |
}); | |
})(); | |
/* | |
--- | |
name: Pinch | |
description: Provides a custom pinch event for touch devices | |
authors: Christopher Beloch (@C_BHole), Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Core/Element.Event, Custom-Event/Element.defineCustomEvent, Browser.Features.Touch] | |
provides: Pinch | |
... | |
*/ | |
if (Browser.Features.Touch) (function(){ | |
var name = 'pinch', | |
thresholdKey = name + ':threshold', | |
disabled, active; | |
var events = { | |
touchstart: function(event){ | |
if (event.targetTouches.length == 2) active = true; | |
}, | |
touchmove: function(event){ | |
event.preventDefault(); | |
if (disabled || !active) return; | |
var threshold = this.retrieve(thresholdKey, 0.5); | |
if (event.scale < (1 + threshold) && event.scale > (1 - threshold)) return; | |
active = false; | |
event.pinch = (event.scale > 1) ? 'in' : 'out'; | |
this.fireEvent(name, event); | |
} | |
}; | |
Element.defineCustomEvent(name, { | |
onSetup: function(){ | |
this.addEvents(events); | |
}, | |
onTeardown: function(){ | |
this.removeEvents(events); | |
}, | |
onEnable: function(){ | |
disabled = false; | |
}, | |
onDisable: function(){ | |
disabled = true; | |
} | |
}); | |
})(); | |
/* | |
--- | |
name: Swipe | |
description: Provides a custom swipe event for touch devices | |
authors: Christopher Beloch (@C_BHole), Christoph Pojer (@cpojer), Ian Collins (@3n) | |
license: MIT-style license. | |
requires: [Core/Element.Event, Custom-Event/Element.defineCustomEvent, Browser.Features.Touch] | |
provides: Swipe | |
... | |
*/ | |
(function(){ | |
var name = 'swipe', | |
distanceKey = name + ':distance', | |
cancelKey = name + ':cancelVertical', | |
dflt = 50; | |
var start = {}, disabled, active; | |
var clean = function(){ | |
active = false; | |
}; | |
var events = { | |
touchstart: function(event){ | |
if (event.touches.length > 1) return; | |
var touch = event.touches[0]; | |
active = true; | |
start = {x: touch.pageX, y: touch.pageY}; | |
}, | |
touchmove: function(event){ | |
event.preventDefault(); | |
if (disabled || !active) return; | |
var touch = event.changedTouches[0]; | |
var end = {x: touch.pageX, y: touch.pageY}; | |
if (this.retrieve(cancelKey) && Math.abs(start.y - end.y) > Math.abs(start.x - end.x)){ | |
active = false; | |
return; | |
} | |
var distance = this.retrieve(distanceKey, dflt), | |
diff = end.x - start.x, | |
isLeftSwipe = diff < -distance, | |
isRightSwipe = diff > distance; | |
if (!isRightSwipe && !isLeftSwipe) | |
return; | |
active = false; | |
event.direction = (isLeftSwipe ? 'left' : 'right'); | |
event.start = start; | |
event.end = end; | |
this.fireEvent(name, event); | |
}, | |
touchend: clean, | |
touchcancel: clean | |
}; | |
Element.defineCustomEvent(name, { | |
onSetup: function(){ | |
this.addEvents(events); | |
}, | |
onTeardown: function(){ | |
this.removeEvents(events); | |
}, | |
onEnable: function(){ | |
disabled = false; | |
}, | |
onDisable: function(){ | |
disabled = true; | |
clean(); | |
} | |
}); | |
})(); | |
/* | |
--- | |
name: Touchhold | |
description: Provides a custom touchhold event for touch devices | |
authors: Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Core/Element.Event, Custom-Event/Element.defineCustomEvent, Browser.Features.Touch] | |
provides: Touchhold | |
... | |
*/ | |
(function(){ | |
var name = 'touchhold', | |
delayKey = name + ':delay', | |
disabled, timer; | |
var clear = function(e){ | |
clearTimeout(timer); | |
}; | |
var events = { | |
touchstart: function(event){ | |
if (event.touches.length > 1){ | |
clear(); | |
return; | |
} | |
timer = (function(){ | |
this.fireEvent(name, event); | |
}).delay(this.retrieve(delayKey) || 750, this); | |
}, | |
touchmove: clear, | |
touchcancel: clear, | |
touchend: clear | |
}; | |
Element.defineCustomEvent(name, { | |
onSetup: function(){ | |
this.addEvents(events); | |
}, | |
onTeardown: function(){ | |
this.removeEvents(events); | |
}, | |
onEnable: function(){ | |
disabled = false; | |
}, | |
onDisable: function(){ | |
disabled = true; | |
clear(); | |
} | |
}); | |
})(); | |
/* | |
--- | |
name: Class.Binds | |
description: Alternate Class.Binds Implementation | |
authors: Scott Kyle (@appden), Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Core/Class, Core/Function] | |
provides: Class.Binds | |
... | |
*/ | |
Class.Binds = new Class({ | |
$bound: {}, | |
bound: function(name){ | |
return this.$bound[name] ? this.$bound[name] : this.$bound[name] = this[name].bind(this); | |
} | |
}); | |
/* | |
--- | |
name: Class.Instantiate | |
description: Simple Wrapper for Mass-Class-Instantation | |
authors: Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Core/Class] | |
provides: Class.Instantiate | |
... | |
*/ | |
Class.Instantiate = function(klass, options){ | |
var create = function(object){ | |
new klass(object, options); | |
}; | |
return function(objects){ | |
objects.each(create); | |
}; | |
}; | |
/* | |
--- | |
name: Class.Singleton | |
description: Beautiful Singleton Implementation that is per-context or per-object/element | |
authors: Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Core/Class] | |
provides: Class.Singleton | |
... | |
*/ | |
(function(){ | |
var storage = { | |
storage: {}, | |
store: function(key, value){ | |
this.storage[key] = value; | |
}, | |
retrieve: function(key){ | |
return this.storage[key] || null; | |
} | |
}; | |
Class.Singleton = function(){ | |
this.$className = String.uniqueID(); | |
}; | |
Class.Singleton.prototype.check = function(item){ | |
if (!item) item = storage; | |
var instance = item.retrieve('single:' + this.$className); | |
if (!instance) item.store('single:' + this.$className, this); | |
return instance; | |
}; | |
var gIO = function(klass){ | |
var name = klass.prototype.$className; | |
return name ? this.retrieve('single:' + name) : null; | |
}; | |
if (('Element' in this) && Element.implement) Element.implement({getInstanceOf: gIO}); | |
Class.getInstanceOf = gIO.bind(storage); | |
}).call(this); | |
/* | |
--- | |
name: History | |
description: History Management via popstate or hashchange. | |
authors: Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Core/Events, Core/Element.Event, Class-Extras/Class.Binds] | |
provides: History | |
... | |
*/ | |
(function(){ | |
var events = Element.NativeEvents, | |
location = window.location, | |
history = window.history, | |
hasPushState = ('pushState' in history), | |
event = hasPushState ? 'popstate' : 'hashchange'; | |
this.History = new new Class({ | |
Implements: [Class.Binds, Events], | |
initialize: hasPushState ? function(){ | |
events[event] = 2; | |
window.addEvent(event, this.bound('pop')); | |
} : function(){ | |
events[event] = 1; | |
window.addEvent(event, this.bound('pop')); | |
this.hash = location.hash; | |
if ('onhashchange' in window) return; | |
this.timer = this.periodical.periodical(200, this); | |
}, | |
push: hasPushState ? function(url, title, state){ | |
history.pushState(state || null, title || null, url); | |
this.onChange(url, state); | |
} : function(url){ | |
location.hash = url; | |
}, | |
replace: hasPushState ? function(url, title, state){ | |
history.replaceState(state || null, title || null, url); | |
} : function(url){ | |
this.hash = '#' + url; | |
this.push(url); | |
}, | |
pop: hasPushState ? function(event){ | |
var url = location.pathname; | |
this.onChange(url, event.event.state); | |
} : function(){ | |
var hash = location.hash; | |
if (this.hash == hash) return; | |
this.hash = hash; | |
this.onChange(hash.substr(1)); | |
}, | |
onChange: function(url, state){ | |
this.fireEvent('change', [url, state]); | |
}, | |
back: function(){ | |
history.back(); | |
}, | |
forward: function(){ | |
history.forward(); | |
}, | |
getPath: function(){ | |
return hasPushState ? location.pathname : location.hash.substr(1); | |
}, | |
hasPushState: function(){ | |
return hasPushState; | |
}, | |
periodical: function(){ | |
if (this.hash != location.hash) this.pop(); | |
} | |
}); | |
})(); | |
/* | |
--- | |
name: DynamicMatcher | |
description: Searches elements via complex selectors and executes functions on them | |
authors: Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Core/Events, Core/Element] | |
provides: DynamicMatcher | |
... | |
*/ | |
(function(){ | |
this.DynamicMatcher = new Class({ | |
Implements: Events, | |
initialize: function(){ | |
this.expressions = []; | |
this.handlers = []; | |
}, | |
register: function(expression, fn){ | |
var index = this.handlers.indexOf(fn); | |
if (index != -1 && this.expressions[index] == expression) return this; | |
this.expressions.push(expression); | |
this.handlers.push(fn); | |
return this; | |
}, | |
unregister: function(expression, fn){ | |
var handlers = this.handlers, | |
expressions = this.expressions; | |
for (var i = 0, l = handlers.length; i < l; i++) if (expression == expressions[i] && fn == handlers[i]){ | |
delete handlers[i]; | |
delete expressions[i]; | |
break; | |
} | |
return this; | |
}, | |
update: function(element){ | |
element = document.id(element) || document; | |
var isDocument = (element == document), | |
handlers = this.handlers, | |
expressions = this.expressions; | |
for (var i = 0, l = handlers.length; i < l; i++){ | |
var expression = expressions[i]; | |
if (!expression) continue; | |
var elements = element.getElements(expression); | |
if (!isDocument && element.match(expression)) elements.push(element); | |
if (elements.length) handlers[i](elements); | |
} | |
this.fireEvent('update', [element]); | |
return this; | |
} | |
}); | |
})(); | |
/* | |
--- | |
name: EventStack | |
description: Helps you Escape. | |
authors: Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Core/Class.Extras, Core/Element.Event, Class-Extras/Class.Binds] | |
provides: EventStack | |
... | |
*/ | |
(function(){ | |
this.EventStack = new Class({ | |
Implements: [Options, Class.Binds], | |
options: { | |
event: 'keyup', | |
condition: function(event){ | |
return (event.key == 'esc'); | |
} | |
}, | |
initialize: function(options){ | |
this.setOptions(options); | |
this.stack = []; | |
this.data = []; | |
document.addEvent(this.options.event, this.bound('condition')); | |
}, | |
condition: function(event){ | |
if (this.options.condition.call(this, event, this.data.getLast())) | |
this.pop(event); | |
}, | |
erase: function(fn){ | |
this.data.erase(this.data[this.stack.indexOf(fn)]); | |
this.stack.erase(fn); | |
return this; | |
}, | |
push: function(fn, data){ | |
this.erase(fn); | |
this.data.push(data || null); | |
this.stack.push(fn); | |
return this; | |
}, | |
pop: function(event){ | |
var fn = this.stack.pop(), | |
data = this.data.pop(); | |
if (fn) fn.call(this, event, data); | |
return this; | |
} | |
}); | |
})(); | |
/* | |
--- | |
name: Form.AutoGrow | |
description: Automatically resizes textareas based on their content. | |
authors: Christoph Pojer (@cpojer) | |
credits: Based on a script by Gary Glass (www.bookballoon.com) | |
license: MIT-style license. | |
requires: [Core/Class.Extras, Core/Element, Core/Element.Event, Core/Element.Style, Core/Element.Dimensions, Class-Extras/Class.Binds, Class-Extras/Class.Singleton] | |
provides: Form.AutoGrow | |
... | |
*/ | |
(function(){ | |
var wrapper = new Element('div').setStyles({ | |
overflowX: 'hidden', | |
position: 'absolute', | |
top: 0, | |
left: -9999 | |
}); | |
var escapeHTML = function(string){ | |
return string.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); | |
}; | |
if (!this.Form) this.Form = {}; | |
var AutoGrow = this.Form.AutoGrow = new Class({ | |
Implements: [Options, Class.Singleton, Class.Binds], | |
options: { | |
minHeightFactor: 2, | |
margin: 0 | |
}, | |
initialize: function(element, options){ | |
this.setOptions(options); | |
element = this.element = document.id(element); | |
return this.check(element) || this.setup(); | |
}, | |
setup: function(){ | |
this.attach().focus().resize(); | |
}, | |
toElement: function(){ | |
return this.element; | |
}, | |
attach: function(){ | |
this.element.addEvents({ | |
focus: this.bound('focus'), | |
keydown: this.bound('keydown'), | |
scroll: this.bound('scroll') | |
}); | |
return this; | |
}, | |
detach: function(){ | |
this.element.removeEvents({ | |
focus: this.bound('focus'), | |
keydown: this.bound('keydown'), | |
scroll: this.bound('scroll') | |
}); | |
return this; | |
}, | |
focus: function(){ | |
wrapper.setStyles(this.element.getStyles('fontSize', 'fontFamily', 'width', 'lineHeight', 'padding')).inject(document.body); | |
this.minHeight = (wrapper.set('html', 'A').getHeight() + this.options.margin) * this.options.minHeightFactor; | |
return this; | |
}, | |
keydown: function(){ | |
this.resize.delay(15, this); | |
}, | |
resize: function(){ | |
var element = this.element, | |
html = escapeHTML(element.get('value')).replace(/\n|\r\n/g, '<br/>A'); | |
if (wrapper.get('html') == html) return this; | |
wrapper.set('html', html); | |
var height = wrapper.getHeight() + this.options.margin; | |
if (element.getHeight() != height){ | |
element.setStyle('height', this.minHeight.max(height)); | |
AutoGrow.fireEvent('resize', [this]); | |
} | |
return this; | |
}, | |
scroll: function(){ | |
this.element.scrollTo(0, 0); | |
} | |
}); | |
AutoGrow.extend(new Events); | |
})(); | |
/* | |
--- | |
name: Form.Placeholder | |
description: Provides a fallback for the placeholder property on input elements for older browsers. | |
authors: Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Core/Class.Extras, Core/Element, Core/Element.Event, Core/Element.Style, Class-Extras/Class.Binds, Class-Extras/Class.Singleton] | |
provides: Form.Placeholder | |
... | |
*/ | |
(function(){ | |
if (!this.Form) this.Form = {}; | |
var supportsPlaceholder = ('placeholder' in document.createElement('input')); | |
if (!('supportsPlaceholder' in this) && this.supportsPlaceholder !== false && supportsPlaceholder) return; | |
var wrapSet = function(self, set){ | |
return function(key, value){ | |
if (key == 'value' && !value){ | |
self.reset(); | |
return this; | |
} | |
return set.apply(this, arguments); | |
}; | |
}; | |
var wrapGet = function(get, self){ | |
return function(key){ | |
if (key == 'value' && !self.hasValue()) | |
return ''; | |
return get.apply(this, arguments); | |
}; | |
}; | |
this.Form.Placeholder = new Class({ | |
Implements: [Class.Singleton, Class.Binds, Options], | |
options: { | |
color: '#777' | |
}, | |
initialize: function(element, options){ | |
this.setOptions(options); | |
element = this.element = document.id(element); | |
return this.check(element) || this.setup(); | |
}, | |
setup: function(){ | |
var element = this.element; | |
this.defaultValue = element.get('placeholder') || element.value; | |
this.color = element.getStyle('color'); | |
element.erase('placeholder'); | |
element.set = wrapSet(element.set, this); | |
element.get = wrapGet(element.get, this); | |
this.attach(); | |
if (!this.hasValue()) this.reset(); | |
}, | |
attach: function(){ | |
this.element.addEvents({ | |
focus: this.bound('focus'), | |
blur: this.bound('blur') | |
}); | |
return this; | |
}, | |
detach: function(){ | |
this.element.removeEvents({ | |
focus: this.bound('focus'), | |
blur: this.bound('blur') | |
}); | |
return this; | |
}, | |
clear: function(){ | |
var element = this.element; | |
element.setStyle('color', this.color); | |
if (element.value == this.defaultValue) element.value = ''; | |
return this; | |
}, | |
reset: function(){ | |
this.element.setStyle('color', this.options.color).value = this.defaultValue; | |
return this; | |
}, | |
focus: function(){ | |
this.clear(); | |
}, | |
blur: function(){ | |
if (!this.element.value) this.reset(); | |
}, | |
hasValue: function(){ | |
var value = this.element.value; | |
return (value && value != this.defaultValue); | |
} | |
}); | |
})(); | |
/* | |
--- | |
name: Tree | |
description: Provides a way to sort and reorder a tree via drag&drop. | |
authors: Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Core/Events, Core/Element.Event, Core/Element.Style, Core/Element.Dimensions, Core/Fx.Tween, More/Drag.Move, More/Element.Delegation, Class-Extras/Class.Binds, Class-Extras/Class.Singleton] | |
provides: Tree | |
... | |
*/ | |
(function(){ | |
this.Tree = new Class({ | |
Implements: [Options, Events, Class.Binds, Class.Singleton], | |
options: { | |
/*onChange: function(){},*/ | |
indicatorOffset: 0, | |
cloneOffset: {x: 16, y: 16}, | |
cloneOpacity: 0.8, | |
checkDrag: Function.from(true), | |
checkDrop: Function.from(true) | |
}, | |
initialize: function(element, options){ | |
this.setOptions(options); | |
element = this.element = document.id(element); | |
return this.check(element) || this.setup(); | |
}, | |
setup: function(){ | |
this.indicator = new Element('div.treeIndicator'); | |
var self = this; | |
this.handler = function(e){ | |
self.mousedown(this, e); | |
}; | |
this.attach(); | |
}, | |
attach: function(){ | |
this.element.addEvent('mousedown:relay(li)', this.handler); | |
document.addEvent('mouseup', this.bound('mouseup')); | |
return this; | |
}, | |
detach: function(){ | |
this.element.removeEvent('mousedown:relay(li)', this.handler); | |
document.removeEvent('mouseup', this.bound('mouseup')); | |
return this; | |
}, | |
mousedown: function(element, event){ | |
event.preventDefault(); | |
this.padding = (this.element.getElement('li ul li') || this.element.getElement('li')).getLeft() - this.element.getLeft() + this.options.indicatorOffset; | |
if (this.collapse === undefined && typeof Collapse != 'undefined') | |
this.collapse = this.element.getInstanceOf(Collapse); | |
if(!this.options.checkDrag.call(this, element)) return; | |
if (this.collapse && Slick.match(event.target, this.collapse.options.selector)) return; | |
this.current = element; | |
this.clone = element.clone().setStyles({ | |
left: event.page.x + this.options.cloneOffset.x, | |
top: event.page.y + this.options.cloneOffset.y, | |
opacity: this.options.cloneOpacity | |
}).addClass('drag').inject(document.body); | |
this.clone.makeDraggable({ | |
droppables: this.element.getElements('li span'), | |
onLeave: this.bound('hideIndicator'), | |
onDrag: this.bound('onDrag'), | |
onDrop: this.bound('onDrop') | |
}).start(event); | |
}, | |
mouseup: function(){ | |
if (this.clone) this.clone.destroy(); | |
}, | |
onDrag: function(el, event){ | |
clearTimeout(this.timer); | |
if (this.previous) this.previous.fade(1); | |
this.previous = null; | |
if (!event || !event.target) return; | |
var droppable = (event.target.get('tag') == 'li') ? event.target : event.target.getParent('li'); | |
if (!droppable || this.element == droppable || !this.element.contains(droppable)) return; | |
if (this.collapse) this.expandCollapsed(droppable); | |
var coords = droppable.getCoordinates(), | |
marginTop = + droppable.getStyle('marginTop').toInt(), | |
center = coords.top + marginTop + (coords.height / 2), | |
isSubnode = (event.page.x > coords.left + this.padding), | |
position = { | |
x: coords.left + (isSubnode ? this.padding : 0), | |
y: coords.top + coords.height + marginTop + droppable.getStyle('marginBottom').toInt() | |
}; | |
var drop; | |
if ([droppable, droppable.getParent('li')].contains(this.current)){ | |
this.drop = {}; | |
} else if (event.page.y >= center){ | |
if (isSubnode) position.y -= coords.height; | |
drop = { | |
target: droppable, | |
where: 'after', | |
isSubnode: isSubnode | |
}; | |
if (!this.options.checkDrop.call(this, droppable, drop)) return; | |
this.setDropTarget(drop); | |
} else if (event.page.y < center){ | |
position.y -= coords.height; | |
position.x = coords.left; | |
drop = { | |
target: droppable, | |
where: 'before' | |
}; | |
if (!this.options.checkDrop.call(this, droppable, drop)) return; | |
this.setDropTarget(drop); | |
} | |
if (this.drop.target) this.showIndicator(position); | |
else this.hideIndicator(); | |
}, | |
onDrop: function(el){ | |
el.destroy(); | |
this.hideIndicator(); | |
var drop = this.drop, | |
current = this.current; | |
if (!drop || !drop.target) return; | |
var previous = current.getParent('li'); | |
if (drop.isSubnode) current.inject(drop.target.getElement('ul') || new Element('ul').inject(drop.target), 'bottom'); | |
else current.inject(drop.target, drop.where || 'after'); | |
if (this.collapse){ | |
if (previous) this.collapse.updateElement(previous); | |
this.collapse.updateElement(drop.target); | |
} | |
this.fireEvent('change'); | |
}, | |
setDropTarget: function(drop){ | |
this.drop = drop; | |
}, | |
showIndicator: function(position){ | |
this.indicator.setStyles({ | |
left: position.x + this.options.indicatorOffset, | |
top: position.y | |
}).inject(document.body); | |
}, | |
hideIndicator: function(){ | |
this.indicator.dispose(); | |
}, | |
expandCollapsed: function(element){ | |
var child = element.getElement('ul'); | |
if (!child || !this.collapse.isCollapsed(child)) return; | |
element.set('tween', {duration: 150}).fade(0.5); | |
this.previous = element; | |
this.timer = (function(){ | |
element.fade(1); | |
this.collapse.expand(element); | |
}).delay(300, this); | |
}, | |
serialize: function(fn, base){ | |
if (!base) base = this.element; | |
if (!fn) fn = function(el){ | |
return el.get('id'); | |
}; | |
var result = {}; | |
base.getChildren('li').each(function(el){ | |
var child = el.getElement('ul'); | |
result[fn(el)] = child ? this.serialize(fn, child) : true; | |
}, this); | |
return result; | |
} | |
}); | |
})(); | |
/* | |
--- | |
name: Collapse | |
description: Allows to expand or collapse a list or a tree. | |
authors: Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Core/Events, Core/Element.Event, Core/Element.Style, Core/Element.Dimensions, Core/Fx, More/Element.Delegation, Class-Extras/Class.Singleton] | |
provides: Collapse | |
... | |
*/ | |
(function(){ | |
this.Collapse = new Class({ | |
Implements: [Options, Class.Singleton], | |
options: { | |
animate: true, | |
fadeOpacity: 0.5, | |
className: 'collapse', | |
selector: 'a.expand', | |
listSelector: 'li', | |
childSelector: 'ul' | |
}, | |
initialize: function(element, options){ | |
this.setOptions(options); | |
element = this.element = document.id(element); | |
return this.check(element) || this.setup(); | |
}, | |
setup: function(){ | |
var self = this; | |
this.handler = function(e){ | |
self.toggle(this, e); | |
}; | |
this.mouseover = function(){ | |
if (self.hasChildren(this)) this.getElement(self.options.selector).fade(1); | |
}; | |
this.mouseout = function(){ | |
if (self.hasChildren(this)) this.getElement(self.options.selector).fade(self.options.fadeOpacity); | |
}; | |
this.prepare().attach(); | |
}, | |
attach: function(){ | |
var element = this.element; | |
element.addEvent('click:relay(' + this.options.selector + ')', this.handler); | |
if (this.options.animate){ | |
element.addEvent('mouseover:relay(' + this.options.listSelector + ')', this.mouseover); | |
element.addEvent('mouseout:relay(' + this.options.listSelector + ')', this.mouseout); | |
} | |
return this; | |
}, | |
detach: function(){ | |
this.element.removeEvent('click:relay(' + this.options.selector + ')', this.handler) | |
.removeEvent('mouseover:relay(' + this.options.listSelector + ')', this.mouseover) | |
.removeEvent('mouseout:relay(' + this.options.listSelector + ')', this.mouseout); | |
return this; | |
}, | |
prepare: function(){ | |
this.prepares = true; | |
this.element.getElements(this.options.listSelector).each(this.updateElement, this); | |
this.prepares = false; | |
return this; | |
}, | |
updateElement: function(element){ | |
var child = element.getElement(this.options.childSelector), | |
icon = element.getElement(this.options.selector); | |
if (!this.hasChildren(element)){ | |
if (!this.options.animate || this.prepares) icon.set('opacity', 0); | |
else icon.fade(0); | |
return; | |
} | |
if (this.options.animate) icon.fade(this.options.fadeOpacity); | |
else icon.set('opacity', this.options.fadeOpacity); | |
if (this.isCollapsed(child)) icon.removeClass('collapse'); | |
else icon.addClass('collapse'); | |
}, | |
hasChildren: function(element){ | |
var child = element.getElement(this.options.childSelector); | |
return (child && child.getChildren().length); | |
}, | |
isCollapsed: function(element){ | |
return (element.getStyle('display') == 'none'); | |
}, | |
toggle: function(element, event){ | |
if (event) event.preventDefault(); | |
if (!element.match(this.options.listSelector)) element = element.getParent(this.options.listSelector); | |
if (this.isCollapsed(element.getElement(this.options.childSelector))) this.expand(element); | |
else this.collapse(element); | |
return this; | |
}, | |
expand: function(element){ | |
element.getElement(this.options.childSelector).setStyle('display', 'block'); | |
element.getElement(this.options.selector).addClass(this.options.className); | |
return this; | |
}, | |
collapse: function(element){ | |
element.getElement(this.options.childSelector).setStyle('display', 'none'); | |
element.getElement(this.options.selector).removeClass(this.options.className); | |
return this; | |
} | |
}); | |
})(); | |
/* | |
--- | |
name: Collapse.Cookie | |
description: Automatically saves the collapsed/expanded state in a Cookie. | |
authors: Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Core/Cookie, Core/JSON, Collapse] | |
provides: Collapse.Cookie | |
... | |
*/ | |
(function(){ | |
this.Collapse.Cookie = new Class({ | |
Extends: this.Collapse, | |
options: { | |
getAttribute: function(element){ | |
return element.get('id'); | |
}, | |
getIdentifier: function(element){ | |
return 'collapse_' + element.get('id') + '_' + element.get('class').split(' ').join('_'); | |
} | |
}, | |
setup: function(){ | |
this.cookie = this.options.getIdentifier.call(this, this.element); | |
this.parent(); | |
}, | |
prepare: function(){ | |
var obj = this.getCookieData(); | |
this.element.getElements(this.options.listSelector).each(function(element){ | |
if (!element.getElement(this.options.childSelector)) return; | |
var state = obj[this.options.getAttribute.call(this, element)]; | |
if (state == 1) this.expand(element); | |
else if (state == 0) this.collapse(element); | |
}, this); | |
return this.parent(); | |
}, | |
getCookieData: function(){ | |
var self = this; | |
return Function.attempt(function(){ | |
return JSON.decode(Cookie.read(self.cookie)); | |
}) || {}; | |
}, | |
update: function(element, state){ | |
var obj = this.getCookieData(); | |
obj[this.options.getAttribute.call(this, element)] = state; | |
Cookie.write(this.cookie, JSON.encode(obj), {duration: 30}); | |
return this; | |
}, | |
expand: function(element){ | |
this.parent(element); | |
this.update(element, 1); | |
return this; | |
}, | |
collapse: function(element){ | |
this.parent(element); | |
this.update(element, 0); | |
return this; | |
} | |
}); | |
})(); | |
/* | |
--- | |
name: ScrollLoader | |
description: Fires an event when the user reaches a certain boundary. | |
authors: Christoph Pojer (@cpojer) | |
license: MIT-style license. | |
requires: [Core/Events, Core/Options, Core/Element.Event, Core/Element.Dimension, Class-Extras/Class.Binds] | |
provides: ScrollLoader | |
... | |
*/ | |
(function(){ | |
this.ScrollLoader = new Class({ | |
Implements: [Options, Events, Class.Binds], | |
options: { | |
/*onScroll: fn,*/ | |
area: 50, | |
mode: 'vertical', | |
container: null | |
}, | |
initialize: function(options){ | |
this.setOptions(options); | |
this.element = document.id(this.options.container) || window; | |
this.attach(); | |
}, | |
attach: function(){ | |
this.element.addEvent('scroll', this.bound('scroll')); | |
return this; | |
}, | |
detach: function(){ | |
this.element.removeEvent('scroll', this.bound('scroll')); | |
return this; | |
}, | |
scroll: function(){ | |
var z = (this.options.mode == 'vertical') ? 'y' : 'x'; | |
var element = this.element, | |
size = element.getSize()[z], | |
scroll = element.getScroll()[z], | |
scrollSize = element.getScrollSize()[z]; | |
if (scroll + size < scrollSize - this.options.area) return; | |
this.fireEvent('scroll'); | |
} | |
}); | |
})(); | |
/* | |
--- | |
name: Interface | |
description: Interfaces for Class to ensure certain properties are defined. | |
authors: Christoph Pojer (@cpojer), Luis Merino (@Rendez) | |
license: MIT-style license. | |
requires: [Core/Type, Core/Class] | |
provides: Interface | |
... | |
*/ | |
(function(context){ | |
this.Interface = new Type('Interface', function(object){ | |
if (object.Implements){ | |
Array.from(object.Implements).each(function(item){ | |
Object.append(this, item); | |
}, this); | |
delete object.Implements; | |
} | |
return Object.append(this, object); | |
}); | |
Class.Mutators.initialize = function(fn){ | |
return this.prototype.Interface ? function(){ | |
var result = fn.apply(this, arguments); | |
if (!this.Interface) return result; | |
var interfaces = Array.from(this.Interface); | |
for (var i = 0; i < interfaces.length; i++){ | |
var iface = interfaces[i]; | |
for (var key in iface){ | |
if (key.charAt(0) == '$') continue; // Skip Internal | |
if (!(key in this)) throw new Error('Instance does not implement "' + key + '"'); | |
var item = this[key], | |
object = iface[key]; | |
if (object == null) continue; | |
var type = typeOf(item), | |
oType = typeOf(object); | |
// Needs to be same datatype OR instance of the provided object | |
if (type != oType && !instanceOf(item, object)){ | |
var proto = object.prototype, | |
name = (proto && proto.$family) ? proto.$family().capitalize() : object.displayName; | |
throw new Error('Property "' + key + '" is implemented but not an instance of ' + (name ? '"' + name + '"' : 'the expected type')); | |
} | |
if (oType == 'function' && item.$origin.length < object.length) | |
throw new Error('Property "' + key + '" does not implement at least ' + object.length + ' parameter' + (object.length != 1 ? 's' : '')); | |
} | |
} | |
return result; | |
} : fn; | |
}; | |
}).call(typeof exports != 'undefined' ? exports : this); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment