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