1 /**
  2  * @fileOverview This file is the core of tQuery library. 
  3 */
  4 
  5 /**
  6  * Create a tQuery element
  7  *
  8  * @class root class
  9  * 
 10  * @param {} object
 11  * @param {THREE.Object3D} rootnode
 12  * @returns {tQuery.*} the tQuery object created
 13 */
 14 var tQuery	= function(object, root)
 15 {
 16 	// support for tQuery(geometry, material)
 17 	if( arguments.length === 2 && 
 18 			(arguments[0] instanceof THREE.Geometry
 19 				|| arguments[0] instanceof THREE.BufferGeometry
 20 				|| arguments[0] instanceof tQuery.Geometry)
 21 			&& 
 22 			(arguments[1] instanceof THREE.Material || arguments[1] instanceof tQuery.Material)
 23 			){
 24 		var tGeometry	= arguments[0] instanceof tQuery.Geometry ? arguments[0].get(0) : arguments[0];
 25 		var tMaterial	= arguments[1] instanceof tQuery.Material ? arguments[1].get(0) : arguments[1];
 26 		var tMesh	= new THREE.Mesh(tGeometry, tMaterial);
 27 		return tQuery( tMesh );
 28 	}
 29 
 30 // TODO make tthat cleaner
 31 // - there is a list of functions registered by each plugins
 32 //   - handle() object instanceof THREE.Mesh
 33 //   - create() return new tQuery(object)
 34 // - this list is processed in order here
 35 
 36 	// if the object is an array, compare only the first element
 37 	// - up to the subconstructor to check if the whole array has proper type
 38 	var instance	= Array.isArray(object) ? object[0] : object;
 39 
 40 	if( instance instanceof THREE.Mesh  && tQuery.Mesh){
 41 		return new tQuery.Mesh(object);
 42 	}else if( instance instanceof THREE.DirectionalLight && tQuery.DirectionalLight){
 43 		return new tQuery.DirectionalLight(object);
 44 	}else if( instance instanceof THREE.AmbientLight && tQuery.AmbientLight){
 45 		return new tQuery.AmbientLight(object);
 46 	}else if( instance instanceof THREE.Light && tQuery.Light){
 47 		return new tQuery.Light(object);
 48 
 49 	}else if( instance instanceof THREE.Object3D  && tQuery.Object3D){
 50 		return new tQuery.Object3D(object);
 51 	}else if( instance instanceof THREE.Geometry && tQuery.Geometry){
 52 		return new tQuery.Geometry(object);
 53 	}else if( instance instanceof THREE.Material && tQuery.Material){
 54 		return new tQuery.Material(object);
 55 	}else if( typeof instance === "string" && tQuery.Object3D){
 56 		return new tQuery.Object3D(object, root);
 57 	}else{
 58 		console.assert(false, "unsupported type")
 59 	}
 60 	return undefined;
 61 };
 62 
 63 /**
 64  * The version of tQuery
 65 */
 66 tQuery.VERSION	= "r52.0";
 67 
 68 //////////////////////////////////////////////////////////////////////////////////
 69 //										//
 70 //////////////////////////////////////////////////////////////////////////////////
 71 
 72 /**
 73  * generic getter/setter
 74  * 
 75  * @param {Object} object the object in which store the data
 76  * @param {String} key the key/name of the data to get/set
 77  * @param {*} value the value to set (optional)
 78  * @param {Boolean} mustNotExist if true, ensure that the key doesnt already exist, optional default to false
 79  * 
 80  * @returns {*} return the value stored in this object for this key
 81 */
 82 tQuery.data	= function(object, key, value, mustNotExist)
 83 {
 84 	// sanity check
 85 	console.assert( object, 'invalid parameters' );
 86 	console.assert( typeof key === 'string', 'invalid parameters');
 87 
 88 	// init _tqData
 89 	object['_tqData']	= object['_tqData']	|| {};
 90 	// honor mustNotExist
 91 	if( mustNotExist ){
 92 		console.assert(object['_tqData'][key] === undefined, "This key already exists "+key);
 93 	}
 94 	// set the value if any
 95 	if( value ){
 96 		object['_tqData'][key]	= value;
 97 	}
 98 	// return the value
 99 	return object['_tqData'][key];
100 };
101 
102 /**
103  * Same as jQuery.removeData()
104  *
105  * @param {Boolean} mustExist if true, ensure the key does exist, default to false
106 */
107 tQuery.removeData	= function(object, key, mustExist)
108 {
109 	// handle the 'key as Array' case
110 	if( key instanceof Array ){
111 		key.forEach(function(key){
112 			tQuery.removeData(object, key);
113 		})
114 		return;
115 	}
116 	// sanity check
117 	console.assert( typeof key === "string");
118 	// honor mustNotExist
119 	if( mustExist ){
120 		console.assert(object['_tqData'][key] !== undefined, "This key doesnt already exists "+key);
121 	}
122 	// do delete the key
123 	delete object['_tqData'][key];
124 	// TOTO remove object[_tqData] if empty now
125 }
126 
127 
128 //////////////////////////////////////////////////////////////////////////////////
129 //										//
130 //////////////////////////////////////////////////////////////////////////////////
131 
132 /**
133  * loop over a Array.
134  * 
135  * @param {Array} arr the array to traverse.
136  * @param {Function} callback the function to notify. function(element){ }.
137  * 			loop interrupted if it returns false
138  * 
139  * @returns {Boolean} return true if completed, false if interrupted
140 */
141 tQuery.each	= function(arr, callback){
142 	for(var i = 0; i < arr.length; i++){
143 		var keepLooping	= callback(arr[i])
144 		if( keepLooping === false )	return false;
145 	}
146 	return true;
147 };
148 
149 /**
150  * precise version of Date.now() -
151  * It provide submillisecond precision based on window.performance.now() when 
152  * available, fall back on Date.now()
153  * see http://updates.html5rocks.com/2012/05/requestAnimationFrame-API-now-with-sub-millisecond-precision 
154 */
155 tQuery.now	= (function(){
156 	var p			= window.performance	|| {};
157 	if( p.now )		return function(){ return p.timing.navigationStart + p.now();		};
158 	else if( p.mozNow )	return function(){ return p.timing.navigationStart + p.mozNow();	};
159 	else if( p.webkitNow )	return function(){ return p.timing.navigationStart + p.webkitNow()	};
160 	else if( p.mskitNow )	return function(){ return p.timing.navigationStart + p.msNow()		};
161 	else if( p.okitNow )	return function(){ return p.timing.navigationStart + p.oNow()		};
162 	else			return function(){ return Date.now;					};	
163 })();
164 
165 
166 /**
167  * Make a child Class inherit from the parent class.
168  *
169  * @param {Object} childClass the child class which gonna inherit
170  * @param {Object} parentClass the class which gonna be inherited
171 */
172 tQuery.inherit	= function(childClass, parentClass){
173 	// trick to avoid calling parentClass constructor
174 	var tempFn		= function() {};
175 	tempFn.prototype	= parentClass.prototype;
176 	childClass.prototype	= new tempFn();
177 
178 
179 	childClass.parent	= parentClass.prototype;
180 	childClass.prototype.constructor= childClass;	
181 };
182 
183 /**
184  * extend function. mainly aimed at handling default values - jme: im not sure at all it is the proper one.
185  * http://jsapi.info/_/extend
186  * similar to jquery one but much smaller
187 */
188 tQuery.extend = function(obj, base, deep){
189 	// handle parameter polymorphism
190 	deep		= deep !== undefined ? deep	: true;
191 	var extendFn	= deep ? deepExtend : shallowExtend;
192 	var result	= {};
193 	base	&& extendFn(result, base);
194 	obj	&& extendFn(result, obj);
195 	return result;
196 	
197 	function shallowExtend(dst, src){
198 		Object.keys(src).forEach(function(key){
199 			dst[key]	= src[key];
200 		})
201 	};
202 	// from http://andrewdupont.net/2009/08/28/deep-extending-objects-in-javascript/
203 	function deepExtend(dst, src){
204 		for (var property in src) {
205 			if (src[property] && src[property].constructor && src[property].constructor === Object) {
206 				dst[property] = dst[property] || {};
207 				arguments.callee(dst[property], src[property]);
208 			} else {
209 				dst[property] = src[property];
210 			}
211 		}
212 		return dst;
213 	};
214 };
215 
216 //////////////////////////////////////////////////////////////////////////////////
217 //										//
218 //////////////////////////////////////////////////////////////////////////////////
219 
220 /**
221  * Make an object pluginable
222  * 
223  * @param {Object} object the object on which you mixin function
224  * @param {Object} dest the object in which to register the plugin
225  * @param {string} suffix the suffix to add to the function name
226 */
227 tQuery._pluginsOn	= function(object, dest, fnNameSuffix){
228 	dest		= dest	|| object.prototype || object;
229 	fnNameSuffix	= fnNameSuffix || '';
230 	// sanity check 
231 	console.assert(object['register'+fnNameSuffix] === undefined);
232 	console.assert(object['unregister'+fnNameSuffix] === undefined);
233 	console.assert(object['registered'+fnNameSuffix] === undefined);
234 
235 	object['register'+fnNameSuffix]		= function(name, funct) {
236 		console.assert(dest[name] === undefined, 'Conflict! Already method called: ' + name);
237 		dest[name]	= funct;
238 	};
239 	object['unregister'+fnNameSuffix]	= function(name){
240 		if( dest.hasOwnProperty(name) === false ){
241 			throw new Error('Plugin not found: ' + name);
242 		}
243 		delete dest[name];
244 	};
245 	object['registered'+fnNameSuffix]	= function(name){
246 		return dest.hasOwnProperty(name) === true;
247 	}
248 };
249 
250 tQuery.pluginsInstanceOn= function(klass){
251 	tQuery._pluginsOn(klass, undefined, 'Instance');
252 };
253 tQuery.pluginsStaticOn	= function(klass){
254 	tQuery._pluginsOn(klass, klass, 'Static');
255 };
256 
257 // make it pluginable
258 tQuery.pluginsStaticOn(tQuery, tQuery);
259 
260 
261 //////////////////////////////////////////////////////////////////////////////////
262 //										//
263 //////////////////////////////////////////////////////////////////////////////////
264 
265 tQuery.mixinAttributes	= function(dstObject, properties){
266 	// mixin the new property
267 	// FIXME the inheritance should work now... not sure
268 	dstObject.prototype._attrProps	= tQuery.extend(dstObject.prototype._attrProps, properties);
269 
270 	dstObject.prototype.attr	= function(name, value){
271 		// handle parameters
272 		if( name instanceof Object && value === undefined ){
273 			Object.keys(name).forEach(function(key){
274 				this.attr(key, name[key]);
275 			}.bind(this));
276 		}else if( typeof(name) === 'string' ){
277 			console.assert( Object.keys(this._attrProps).indexOf(name) !== -1, 'invalid property name:'+name);
278 		}else	console.assert(false, 'invalid parameter');
279 
280 		// handle setter
281 		if( value !== undefined ){
282 			var convertFn	= this._attrProps[name];
283 			value		= convertFn(value);
284 			this.each(function(element){
285 				element[name]	= value;
286 			})
287 			return this;			
288 		}
289 		// handle getter
290 		if( this.length === 0 )	return undefined
291 		var element	= this.get(0);
292 		return element[name];
293 	};
294 
295 	// add shortcuts
296 	Object.keys(properties).forEach(function(name){
297 		dstObject.prototype[name]	= function(value){
298 			return this.attr(name, value);
299 		};
300 	}.bind(this));
301 };
302 
303 //////////////////////////////////////////////////////////////////////////////////
304 //		put some helpers						//
305 //////////////////////////////////////////////////////////////////////////////////
306 
307 /**
308  * Flow control - from https://github.com/jeromeetienne/gowiththeflow.js
309 */
310 tQuery.Flow	= function(){
311 	var self, stack = [], timerId = setTimeout(function(){ timerId = null; self._next(); }, 0);
312 	return self = {
313 		destroy	: function(){ timerId && clearTimeout(timerId);	},
314 		par	: function(callback, isSeq){
315 			if(isSeq || !(stack[stack.length-1] instanceof Array)) stack.push([]);
316 			stack[stack.length-1].push(callback);
317 			return self;
318 		},seq	: function(callback){ return self.par(callback, true);	},
319 		_next	: function(err, result){
320 			var errors = [], results = [], callbacks = stack.shift() || [], nbReturn = callbacks.length, isSeq = nbReturn == 1;
321 			callbacks && callbacks.forEach(function(fct, index){
322 				fct(function(error, result){
323 					errors[index]	= error;
324 					results[index]	= result;		
325 					if(--nbReturn == 0)	self._next(isSeq?errors[0]:errors, isSeq?results[0]:results)
326 				}, err, result)
327 			})
328 		}
329 	}
330 };
331 
332 /**
333  * microevents.js - https://github.com/jeromeetienne/microevent.js
334 */
335 tQuery.MicroeventMixin	= function(destObj){
336 	var bind	= function(event, fct){
337 		if(this._events === undefined) 	this._events	= {};
338 		this._events[event] = this._events[event]	|| [];
339 		this._events[event].push(fct);
340 		return fct;
341 	};
342 	var unbind	= function(event, fct){
343 		if(this._events === undefined) 	this._events	= {};
344 		if( event in this._events === false  )	return;
345 		this._events[event].splice(this._events[event].indexOf(fct), 1);
346 	};
347 	var trigger	= function(event /* , args... */){
348 		if(this._events === undefined) 	this._events	= {};
349 		if( this._events[event] === undefined )	return;
350 		var tmpArray	= this._events[event].slice(); 
351 		for(var i = 0; i < tmpArray.length; i++){
352 			tmpArray[i].apply(this, Array.prototype.slice.call(arguments, 1))
353 		}
354 	};
355 	
356 	// backward compatibility
357 	destObj.bind	= bind;
358 	destObj.unbind	= unbind;
359 	destObj.trigger	= trigger;
360 
361 	destObj.addEventListener	= function(event, fct){
362 		this.bind(event, fct)
363 		return this;	// for chained API
364 	}
365 	destObj.removeEventListener	= function(event, fct){
366 		this.unbind(event, fct)
367 		return this;	// for chained API
368 	}
369 	destObj.dispatchEvent		= function(event /* , args... */){
370 		this.trigger.apply(this, arguments)
371 		return this;
372 	}
373 };
374 
375