1 /*
  2  * vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix:
  3  *
  4  * Copyright (C) 2008, 2009 Mihai Şucan
  5  *
  6  * This file is part of libmacrame.
  7  *
  8  * Libmacrame is free software: you can redistribute it and/or modify
  9  * it under the terms of the GNU General Public License as published by
 10  * the Free Software Foundation, either version 3 of the License, or
 11  * (at your option) any later version.
 12  *
 13  * Libmacrame is distributed in the hope that it will be useful,
 14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16  * GNU General Public License for more details.
 17  *
 18  * You should have received a copy of the GNU General Public License
 19  * along with Libmacrame.  If not, see <http://www.gnu.org/licenses/>.
 20  *
 21  * $URL: http://code.google.com/p/libmacrame $
 22  * $Date: 2009-04-17 19:06:03 +0300 $
 23  *
 24  */
 25 
 26 /**
 27  * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a>
 28  * @version Pre-alpha release. In-design phase, unstable API.
 29  * @fileOverview The libmacrame core code.
 30  */
 31 
 32 
 33 (function () {
 34 
 35 /*
 36  * This defines the global alias to use for the libmacrame object. Set this to 
 37  * false if you don't want any global object.
 38  */
 39 var _alias = '$';
 40 
 41 /**
 42  * The global libmacrame object which can't be changed, and which must be used 
 43  * by any plugins extending the functionality of the library.
 44  *
 45  * @function
 46  * @name libmacrame
 47  * @see $ By default, the entire libmacrame object is aliased by $, for quicker 
 48  * access.
 49  * @see $.init The function being called, when you call libmacrame().
 50  *
 51  * @param {String} selector
 52  * @param {Document|Element} [context=document]
 53  */
 54 function libmacrame (selector, context) {
 55   return $.init(selector, context);
 56 }
 57 
 58 if (_alias) {
 59   window[_alias] = libmacrame;
 60 }
 61 
 62 window.libmacrame = libmacrame;
 63 
 64 /**
 65  * The $ symbol is simply an alias of {@link libmacrame}, for quicker access to 
 66  * the main namespace and main function, which is {@link $.init}.
 67  *
 68  * @class The libmacrame namespace.
 69  * @name $
 70  * @see libmacrame
 71  * @see $.init This is the function called when you call $()
 72  *
 73  * @param {String} selector
 74  * @param {Document|Element} [context=document]
 75  */
 76 var $ = libmacrame;
 77 
 78 /*
 79  * This regular expression used to quickly match ID and class name CSS 
 80  * selectors.
 81  */
 82 var re_id_class = /^(#|\.)?([a-zA-Z][a-zA-Z\d_-]*)$/;
 83 
 84 /**
 85  * Find DOM elements.
 86  * 
 87  * <p>This function currently allows you to use CSS selectors to find the DOM 
 88  * elements you want.
 89  *
 90  * <p>TODO: This is not done yet.
 91  *
 92  * @requires A Web browser which implements <a 
 93  * href="http://www.w3.org/TR/selectors-api/">Selectors API</a>. Currently this 
 94  * means <a href="http://opera.com">Opera</a> 10+, <a 
 95  * href="http://mozilla.com">Firefox</a> 3.1+ and <a 
 96  * href="http://apple.com/safari">Safari</a> 4+.
 97  *
 98  * @example
 99  * // Find the element with id='foo':
100  *
101  * <code>$('#foo');</code>
102  *
103  * @example
104  * // Find the elements having a class token 'foo':
105  *
106  * <code>$('.foo');</code>
107  *
108  * @example
109  * // Find all the <code><span></code> elements which are direct child
110  * // children of <code><p></code> elements:
111  *
112  * <code>$('p > span');</code>
113  *
114  * @param {String} selector The CSS selector. You can also use multiple 
115  * selectors, separated by comma. Basically, you can use anything supported by 
116  * querySelectorAll().
117  *
118  * @param {Document|Element} [context=document] The context where the CSS 
119  * selector will be used for finding the matching elements.
120  *
121  * @returns {Element|NodeList} The element found, or the list of elements 
122  * matching the selector.
123  */
124 $.init = function (selector, context) {
125   if (!selector || typeof selector != 'string') {
126     return selector;
127   }
128 
129   if (!context) {
130     context = document;
131   }
132 
133   var elem = false,
134       match = re_id_class.exec(selector);
135 
136   // If the selector is not an ID/class name, then just use the query selector.
137   if (!match) {
138     return context.querySelectorAll(selector);
139   }
140 
141   if (match[1] == '#' && match[2]) {
142     elem = context.getElementById(match[2]);
143   } else if (match[1] == '.' && match[2]) {
144     elem = context.getElementsByClassName(match[2]);
145   } else if (match[2]) {
146     elem = context.getElementsByTagName(match[2]);
147   } else {
148     elem = context.querySelectorAll(selector);
149   }
150 
151   return elem;
152 };
153 
154 /**
155  * @namespace Holds browser information.
156  */
157 $.browser = {};
158 
159 var ua = '', b = {};
160 
161 if (window.navigator && window.navigator.userAgent) {
162   ua = window.navigator.userAgent.toLowerCase();
163 }
164 
165 /**
166  * Determine the browser version.
167  *
168  * @param {String} name The browser name you want to search for, in the User 
169  * Agent string. The string is not escaped, thus you must use regular expression 
170  * syntax.
171  *
172  * @param {Number} [pos=1] The index of the matching parenthesis in the regular 
173  * expressiong. The value of the index will be returned by this function 
174  * (ua.match(regex)).
175  *
176  * @returns {String|null} The matching version number.
177  */
178 b.findVer = function (name, pos) {
179   var regex = '\\b' + name + '[\\/: ]([0-9a-z.+-]+)';
180   var res = ua.match(new RegExp(regex));
181   if (!pos) {
182     pos = 1;
183   }
184 
185   return res ? res[pos] : null;
186 };
187 
188 /**
189  * @name $.browser.opera
190  * @type Boolean
191  */
192 b.opera = window.opera ? true : /\bopera\b/.test(ua);
193 
194 /**
195  * @name $.browser.operaVersion
196  */
197 b.operaVersion = null;
198 
199 if (window.opera && window.opera.version) {
200   b.operaVersion = typeof window.opera.version == 'function' 
201     ? window.opera.version() : window.opera.version;
202 } else if (b.opera) {
203   b.operaVersion = b.findVer('opera');
204 }
205 
206 /**
207  * Webkit is the render engine used primarily by Safari. It's also used by 
208  * Google Chrome and GNOME Epiphany.
209  *
210  * @name $.browser.webkit
211  * @type Boolean
212  */
213 b.webkit = /\b(applewebkit|webkit)\b/.test(ua);
214 
215 /**
216  * @name $.browser.webkitVersion
217  */
218 b.webkitVersion = b.webkit ? b.findVer('(applewebkit|webkit)', 2) : null;
219 
220 /**
221  * Epiphany is the default Web browser of the GNOME desktop environment.
222  *
223  * @name $.browser.epiphany
224  * @type Boolean
225  */
226 b.epiphany = /\bepiphany\b/.test(ua);
227 
228 /**
229  * @name $.browser.epiphanyVersion
230  */
231 b.epiphanyVersion = b.epiphany ? b.findVer('epiphany') : null;
232 
233 /**
234  * Firefox uses the Gecko render engine.
235  *
236  *
237  * @name $.browser.firefox
238  * @type Boolean
239  */
240 // In some variations of the User Agent strings provided by Epiphany and Opera, 
241 // Firefox is mentioned.
242 b.firefox = /\bfirefox\b/.test(ua) && !b.opera && !b.epiphany;
243 
244 /**
245  * @name $.browser.firefoxVersion
246  */
247 b.firefoxVersion = b.firefox ? b.findVer('firefox') : null;
248 
249 /**
250  * Gecko is the render engine used by Firefox and related products.
251  *
252  * @name $.browser.gecko
253  * @type Boolean
254  */
255 // Typically, the user agent string of WebKit also mentions Gecko. Additionally, 
256 // Opera mentions Gecko for tricking some sites.
257 b.gecko = /\bgecko\b/.test(ua) && !b.opera && !b.webkit;
258 // TODO: b.geckoRevision
259 
260 /**
261  * @name $.browser.geckoBuild
262  */
263 b.geckoBuild = b.gecko ? b.findVer('gecko') : null;
264 
265 /**
266  * KHTML is the render engine used by Konqueror.
267  *
268  * @name $.browser.khtml
269  * @type Boolean
270  */
271 // Epiphany does mention KHTML...
272 b.khtml = /\bkhtml\b/.test(ua) && !b.epiphany;
273 
274 /**
275  * @name $.browser.khtmlVersion
276  */
277 b.khtmlVersion = b.khtml ? b.findVer('khtml') : null;
278 
279 /**
280  * Konqueror is the Web browser of the KDE desktop environment.
281  *
282  * @name $.browser.konqueror
283  * @type Boolean
284  */
285 b.konqueror = /\bkonqueror\b/.test(ua);
286 
287 /**
288  * @name $.browser.konquerorVersion
289  */
290 b.konquerorVersion = b.konqueror ? b.findVer('konqueror') : null;
291 
292 /**
293  * Google Chrome uses WebKit as the render engine.
294  *
295  * @name $.browser.chrome
296  * @type Boolean
297  */
298 b.chrome = /\bchrome\b/.test(ua);
299 
300 /**
301  * @name $.browser.chromeVersion
302  */
303 b.chromeVersion = b.chrome ? b.findVer('chrome') : null;
304 
305 /**
306  * Microsoft Internet Explorer. The future of computing.
307  *
308  * @name $.browser.msie
309  * @type Boolean
310  */
311 // Again, Opera allows users to easily fake the UA.
312 b.msie = /\bmsie\b/.test(ua) && !b.opera;
313 
314 /**
315  * @name $.browser.msieVersion
316  */
317 b.msieVersion = b.msie ? b.findVer('msie') : null;
318 
319 /**
320  * Presto is the render engine used by Opera.
321  *
322  * @name $.browser.presto
323  * @type Boolean
324  */
325 // Older versions of Opera did not mention Presto in the UA string.
326 b.presto = /\bpresto\b/.test(ua) || b.opera;
327 
328 /**
329  * @name $.browser.prestoVersion
330  */
331 b.prestoVersion = b.presto ? b.findVer('presto') : null;
332 
333 /**
334  * @name $.browser.mozilla
335  * @type Boolean
336  */
337 // Everyone mentions Mozilla. Hehe.
338 b.mozilla = /\bmozilla\b/.test(ua) && !b.opera && !b.webkit && !b.presto;
339 
340 /**
341  * Safari is the default Web browser of Mac OS, from Apple. It uses the WebKit 
342  * render engine.
343  *
344  * @name $.browser.safari
345  * @type Boolean
346  */
347 // Google Chrome mentions Safari and Epiphany does this as well.
348 b.safari = /\bsafari\b/.test(ua) && b.webkit && !b.epiphany && !b.chrome;
349 
350 /**
351  * @name $.browser.safariVersion
352  */
353 b.safariVersion = b.safari ? b.findVer('safari') : null;
354 
355 
356 /**
357  * The browser name. If the browser is unrecognized, it will contain the browser 
358  * engine name. If all fails, the property will be null.
359  *
360  * @name $.browser.name
361  * @type String
362  */
363 // Here the order is important: start with the browsers, and end with the engine 
364 // names.
365 b.name = b.epiphany  ? 'epiphany'  : (
366          b.firefox   ? 'firefox'   : (
367          b.konqueror ? 'konqueror' : (
368          b.opera     ? 'opera'     : (
369          b.msie      ? 'msie'      : (
370          b.chrome    ? 'chrome'    : (
371          b.safari    ? 'safari'    : (
372          b.presto    ? 'presto'    : (
373          b.khtml     ? 'khtml'     : (
374          b.webkit    ? 'webkit'    : (
375          b.gecko     ? 'gecko'     : (
376          b.mozilla   ? 'mozilla'   : null)))))))))));
377 
378 /**
379  * The version of the browser or render engine.
380  *
381  * @name $.browser.version
382  * @type String
383  */
384 b.version = b[b.name + 'Version'];
385 
386 /**
387  * Browser operating system
388  *
389  * @name $.browser.os
390  * @type String
391  */
392 b.os = (ua.match(/\b(windows|linux)\b/) || [])[1];
393 
394 delete b.findVer;
395 
396 $.browser = b;
397 
398 delete b, ua;
399 
400 /**
401  * This function extends objects.
402  *
403  * @example
404  * <code>var <var>obj1</var> = {a: 'a1', b: 'b1', d: 'd1'},
405  *     <var>obj2</var> = {a: 'a2', b: 'b2', c: 'c2'};
406  * 
407  * $.extend(<var>obj1</var>, <var>obj2</var>);</code>
408  * 
409  * // Now <var>obj1.c == 'c2'</var>, while <var>obj1.a</var>, <var>obj1.b</var>
410  * // and <var>obj1.d</var> remain the same.
411  *
412  * // If <code>$.extend(true, <var>obj1</var>, <var>obj2</var>)</code> is
413  * // called, then <var>obj1.a</var>, <var>obj1.b</var>, <var>obj1.c</var>
414  * // become all the same as in <var>obj2</var>.
415  *
416  * @example
417  * <code>var <var>obj1</var> = {a: 'a1', b: 'b1', extend: $.extend};
418  * <var>obj1</var>.extend({c: 'c1', d: 'd1'});</code>
419  *
420  * // In this case the destination object which is to be extend is
421  * // <var>obj1</var>.
422  *
423  * @param {Boolean} [overwrite=false] If the first argument is a boolean, then 
424  * it will be considered as a boolean flag for overwriting (or not) any existing 
425  * methods and properties in the destination object. Thus, any method and 
426  * property from the source object will take over those in the destination. The 
427  * argument is optional, and if it's omitted, then no method/property will be 
428  * overwritten.
429  *
430  * @param {Object} [destination=this] The second argument is the optional 
431  * destination object: the object which will be extended. By default, the 
432  * <var>this</var> object will be extended.
433  *
434  * @param {Object} source The third argument must provide list of methods and 
435  * properties which will be added to the destination object.
436  */
437 $.extend = function extend () {
438   var i = 0,
439       len = arguments.length,
440       name, src, sval, dval;
441 
442   if (typeof arguments[0] == 'boolean') {
443     force = arguments[0];
444     dest  = arguments[1];
445     src   = arguments[2];
446   } else {
447     force = false;
448     dest  = arguments[0];
449     src   = arguments[1];
450   }
451 
452   if (typeof src == 'undefined') {
453     src = dest;
454     dest = this;
455   }
456 
457   if (typeof dest == 'undefined') {
458     return;
459   }
460 
461   for (name in src) {
462     sval = src[name];
463     dval = dest[name];
464     if (force || typeof dval == 'undefined') {
465       dest[name] = sval;
466     }
467   }
468 };
469 
470 })();
471 
472