beast rev 2066
[beast-modified.git] / public / javascripts / lowpro.js
bloba119f20a23a5d50b437be6ecba8ba6f80c8cce20
1 LowPro = {};
2 LowPro.Version = '0.1';
4 // Adapted from DOM Ready extension by Dan Webb
5 // http://www.vivabit.com/bollocks/2006/06/21/a-dom-ready-extension-for-prototype
6 // which was based on work by Matthias Miller, Dean Edwards and John Resig
7 //
8 // Usage:
9 //
10 // Event.onReady(callbackFunction);
11 Object.extend(Event, {
12   _domReady : function() {
13     if (arguments.callee.done) return;
14     arguments.callee.done = true;
16     if (Event._timer)  clearInterval(Event._timer);
17     
18     Event._readyCallbacks.each(function(f) { f() });
19     Event._readyCallbacks = null;
20     
21   },
22   onReady : function(f) {
23     if (!this._readyCallbacks) {
24       var domReady = this._domReady;
25       
26       if (domReady.done) return f();
27       
28       if (document.addEventListener)
29         document.addEventListener("DOMContentLoaded", domReady, false);
30         
31         /*@cc_on @*/
32         /*@if (@_win32)
33             document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
34             document.getElementById("__ie_onload").onreadystatechange = function() {
35                 if (this.readyState == "complete") { domReady(); }
36             };
37         /*@end @*/
38         
39         if (/WebKit/i.test(navigator.userAgent)) { 
40           this._timer = setInterval(function() {
41             if (/loaded|complete/.test(document.readyState)) domReady(); 
42           }, 10);
43         }
44         
45         Event.observe(window, 'load', domReady);
46         Event._readyCallbacks =  [];
47     }
48     Event._readyCallbacks.push(f);
49   }
50 });
52 if (!Element.addMethods) 
53   Element.addMethods = function(o) { Object.extend(Element.Methods, o) };
55 // Extend Element with observe and stopObserving.
56 Element.addMethods({
57   observe : function(el, event, callback) {
58     Event.observe(el, event, callback);
59   },
60   stopObserving : function(el, event, callback) {
61     Event.stopObserving(el, event, callback);
62   }
63 });
65 // Replace out existing event observe code with Dean Edwards' addEvent
66 // http://dean.edwards.name/weblog/2005/10/add-event/
67 Object.extend(Event, {
68   observe : function(el, type, func) {
69     el = $(el);
70     if (!func.$$guid) func.$$guid = Event._guid++;
71         if (!el.events) el.events = {};
72         var handlers = el.events[type];
73         if (!handlers) {
74                 handlers = el.events[type] = {};
75                 if (el["on" + type]) {
76                         handlers[0] = el["on" + type];
77                 }
78         }
79         handlers[func.$$guid] = func;
80         el["on" + type] = Event._handleEvent;
81         
82          if (!Event.observers) Event.observers = [];
83          Event.observers.push([el, name, func, false]);
84         },
85         stopObserving : function(el, type, func) {
86     if (el.events && el.events[type]) delete el.events[type][func.$$guid];
87   },
88   _handleEvent : function(e) {
89     var returnValue = true;
90     e = e || Event._fixEvent(window.event);
91     var handlers = this.events[e.type], el = $(this);
92     for (var i in handlers) {
93         el.$$handleEvent = handlers[i];
94         if (el.$$handleEvent(e) === false) returnValue = false;
95     }
96         return returnValue;
97   },
98   _fixEvent : function(e) {
99     e.preventDefault = Event._preventDefault;
100     e.stopPropagation = Event._stopPropagation;
101     return e;
102   },
103   _preventDefault : function() { this.returnValue = false },
104   _stopPropagation : function() { this.cancelBubble = true },
105   _guid : 1
108 // Allows you to trigger an event element.  
109 Object.extend(Event, {
110   trigger : function(element, event, fakeEvent) {
111     element = $(element);
112     fakeEvent = fakeEvent || { type :  event };
113     this.observers.each(function(cache) {
114       if (cache[0] == element && cache[1] == event)
115         cache[2].call(element, fakeEvent);
116     });
117   }
120 // Based on event:Selectors by Justin Palmer
121 // http://encytemedia.com/event-selectors/
123 // Usage:
125 // Event.addBehavior({
126 //      "selector:event" : function(event) { /* event handler.  this refers to the element. */ },
127 //      "selector" : function() { /* runs function on dom ready.  this refers to the element. */ }
128 //      ...
129 // });
131 // Multiple calls will add to exisiting rules.  Event.addBehavior.reassignAfterAjax and
132 // Event.addBehavior.autoTrigger can be adjusted to needs.
133 Event.addBehavior = function(rules) {
134   var ab = this.addBehavior;
135   Object.extend(ab.rules, rules);
136   
137   if (ab.autoTrigger) {
138     this.onReady(ab.load.bind(ab));
139   }
140   
141   Ajax.Responders.register({
142     onComplete : function() { 
143       if (Event.addBehavior.reassignAfterAjax) 
144         setTimeout(function() { ab.load() }, 10);
145     }
146   });
147   
150 Object.extend(Event.addBehavior, {
151   rules : {}, cache : [],
152   reassignAfterAjax : true,
153   autoTrigger : true,
154   
155   load : function() {
156     this.unload();
157     for (var selector in this.rules) {
158       var observer = this.rules[selector];
159       var sels = selector.split(',');
160       sels.each(function(sel) {
161         var parts = sel.split(/:(?=[a-z]+$)/), css = parts[0], event = parts[1];
162         $$(css).each(function(element) {
163           if (event) {
164             $(element).observe(event, observer);
165             Event.addBehavior.cache.push([element, event, observer]);
166           } else {
167             if (!element.$$assigned || !element.$$assigned.include(observer)) {
168               if (observer.attach) observer.attach(element);
169               else observer.call($(element));
170               element.$$assigned = element.$$assigned || [];
171               element.$$assigned.push(observer);
172             }
173           }
174         });
175       });
176     }
177   },
178   
179   unload : function() {
180     this.cache.each(function(c) {
181       Event.stopObserving.apply(Event, c);
182     });
183   }
184   
187 Event.observe(window, 'unload', Event.addBehavior.unload.bind(Event.addBehavior));
189 // Behaviors can be bound to elements to provide an object orientated way of controlling elements
190 // and their behavior.  Use Behavior.create() to make a new behavior class then use attach() to
191 // glue it to an element.  Each element then gets it's own instance of the behavior and any
192 // methods called onxxx are bound to the relevent event.
193 // 
194 // Usage:
195 // 
196 // var MyBehavior = Behavior.create({
197 //   onmouseover : function() { this.element.addClassName('bong') } 
198 // });
200 // Event.addBehavior({ 'a.rollover' : MyBehavior });
201 Behavior = {
202   create : function(members) {
203     var behavior = Class.create();
204     behavior.prototype.initialize = Prototype.K;
205     Object.extend(behavior.prototype, members);
206     Object.extend(behavior, Behavior.ClassMethods);
207     return behavior;
208   },
209   ClassMethods : {
210     attach : function(element) {
211       var bound = new this;
212       bound.element = $(element);
213       this._bindEvents(bound);
214       return bound;
215     },
216     _bindEvents : function(bound) {
217       for (var member in bound)
218         if (member.match(/^on(.+)/) && typeof bound[member] == 'function')
219           bound.element.observe(RegExp.$1, bound[member].bindAsEventListener(bound));
220     }
221   }
225 // Original code by Sylvian Zimmer
226 // http://www.sylvainzimmer.com/index.php/archives/2006/06/25/speeding-up-prototypes-selector/
227 // Optimises execution speed of the $$ function.  Rewritten for readability by Justin Palmer.
228 // 
229 // Turn off optimisation with LowPro.optimize$$ = false;
230 LowPro.SelectorLite = Class.create();
231 LowPro.SelectorLite.prototype = {
232   initialize: function(selectors) {
233     this.results = []; 
234     this.selectors = []; 
235     this.index = 0;
236     
237     for(var i = selectors.length -1; i >= 0; i--) {
238       var params = { tag: '*', id: null, classes: [] };
239       var selector = selectors[i];
240       var needle = selector.length - 1;
241       
242       do {
243         var id = selector.lastIndexOf("#");
244         var klass = selector.lastIndexOf(".");
245         var cursor = Math.max(id, klass);
246         
247         if(cursor == -1) params.tag = selector.toUpperCase();
248         else if(id == -1 || klass == cursor) params.classes.push(selector.substring(klass + 1))
249         else if(!params.id) params.id = selector.substring(id + 1);
250         
251         selector = selector.substring(0, cursor);
252       } while(cursor > 0);
253       this.selectors[i] = params;
254     }
255     
256   },
257   
258   get: function(root) {
259     this.findElements(root || document, this.index == (this.selectors.length - 1));
260     return this.results;
261   },
262   
263   findElements: function(parent, descendant) {
264     var selector = this.selectors[this.index], results = [], element;
265     if(selector.id) {
266       element = $(selector.id);
267       if(element && (selector.tag == '*' || element.tagName == selector.tag) && 
268         (element.childOf(parent))) {
269         results = [element];
270       }
271     } else {
272       results = $A(parent.getElementsByTagName(selector.tag));
273     }
274     
275     if(selector.classes.length == 1) {
276       results = results.select(function(target) {
277        return $(target).hasClassName(selector.classes[0]);
278       });
279     } else if(selector.classes.length > 1) {
280       results = results.select(function(target) {
281         var klasses = $(target).classNames();
282         return selector.classes.all(function(klass) {
283           return klasses.include(klass);
284         });
285       });
286     }
287     
288     if(descendant) {
289       this.results = this.results.concat(results);
290     } else {
291       ++this.index;
292       results.each(function(target) {
293         this.findElements(target, this.index == (this.selectors.length - 1));
294       }.bind(this));
295     }
296   }
299 LowPro.$$old=$$;
300 LowPro.optimize$$ = true;
302 function $$(a,b) {
303   if (LowPro.optimize$$ == false || b || a.indexOf("[")>=0) 
304     return LowPro.$$old.apply(this, arguments);
305   return new LowPro.SelectorLite(a.split(/\s+/)).get();