1(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.jade = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ 2'use strict'; 3 4/** 5 * Merge two attribute objects giving precedence 6 * to values in object `b`. Classes are special-cased 7 * allowing for arrays and merging/joining appropriately 8 * resulting in a string. 9 * 10 * @param {Object} a 11 * @param {Object} b 12 * @return {Object} a 13 * @api private 14 */ 15 16exports.merge = function merge(a, b) { 17 if (arguments.length === 1) { 18 var attrs = a[0]; 19 for (var i = 1; i < a.length; i++) { 20 attrs = merge(attrs, a[i]); 21 } 22 return attrs; 23 } 24 var ac = a['class']; 25 var bc = b['class']; 26 27 if (ac || bc) { 28 ac = ac || []; 29 bc = bc || []; 30 if (!Array.isArray(ac)) ac = [ac]; 31 if (!Array.isArray(bc)) bc = [bc]; 32 a['class'] = ac.concat(bc).filter(nulls); 33 } 34 35 for (var key in b) { 36 if (key != 'class') { 37 a[key] = b[key]; 38 } 39 } 40 41 return a; 42}; 43 44/** 45 * Filter null `val`s. 46 * 47 * @param {*} val 48 * @return {Boolean} 49 * @api private 50 */ 51 52function nulls(val) { 53 return val != null && val !== ''; 54} 55 56/** 57 * join array as classes. 58 * 59 * @param {*} val 60 * @return {String} 61 */
62exports.joinClasses = joinClasses; 63function joinClasses(val) { 64 return (Array.isArray(val) ? val.map(joinClasses) : 65 (val && typeof val === 'object') ? Object.keys(val).filter(function (key) { return val[key]; }) : 66 [val]).filter(nulls).join(' '); 67} 68 69/** 70 * Render the given classes. 71 * 72 * @param {Array} classes 73 * @param {Array.<Boolean>} escaped 74 * @return {String} 75 */ 76exports.cls = function cls(classes, escaped) { 77 var buf = []; 78 for (var i = 0; i < classes.length; i++) { 79 if (escaped && escaped[i]) { 80 buf.push(exports.escape(joinClasses([classes[i]]))); 81 } else { 82 buf.push(joinClasses(classes[i])); 83 } 84 } 85 var text = joinClasses(buf); 86 if (text.length) { 87 return ' class="' + text + '"'; 88 } else { 89 return ''; 90 } 91}; 92 93 94exports.style = function (val) { 95 if (val && typeof val === 'object') { 96 return Object.keys(val).map(function (style) { 97 return style + ':' + val[style]; 98 }).join(';'); 99 } else { 100 return val; 101 } 102}; 103/** 104 * Render the given attribute. 105 * 106 * @param {String} key 107 * @param {String} val 108 * @param {Boolean} escaped 109 * @param {Boolean} terse 110 * @return {String} 111 */ 112exports.attr = function attr(key, val, escaped, terse) { 113 if (key === 'style') { 114 val = exports.style(val); 115 } 116 if ('boolean' == typeof val || null == val) { 117 if (val) { 118 return ' ' + (terse ? key : key + '="' + key + '"'); 119 } else { 120 return ''; 121 } 122 } else if (0 == key.indexOf('data') && 'string' != typeof val) { 123 if (JSON.stringify(val).indexOf('&') !== -1) { 124 console.warn('Since Jade 2.0.0, ampersands (`&`) in data attributes ' + 125 'will be escaped to `&`'); 126 }; 127 if (val && typeof val.toISOString === 'function') { 128 console.warn('Jade will eliminate the double quotes around dates in ' + 129 'ISO form after 2.0.0'); 130 } 131 return ' ' + key + "='" + JSON.stringify(val).replace(/'/g, ''') + "'"; 132 } else if (escaped) { 133 if (val && typeof val.toISOString === 'function') { 134 console.warn('Jade will stringify dates in ISO form after 2.0.0'); 135 } 136 return ' ' + key + '="' + exports.escape(val) + '"'; 137 } else { 138 if (val && typeof val.toISOString === 'function') { 139 console.warn('Jade will stringify dates in ISO form after 2.0.0'); 140 } 141 return ' ' + key + '="' + val + '"'; 142 } 143}; 144 145/** 146 * Render the given attributes object. 147 * 148 * @param {Object} obj 149 * @param {Object} escaped 150 * @return {String} 151 */ 152exports.attrs = function attrs(obj, terse){ 153 var buf = []; 154 155 var keys = Object.keys(obj); 156 157 if (keys.length) { 158 for (var i = 0; i < keys.length; ++i) { 159 var key = keys[i] 160 , val = obj[key]; 161 162 if ('class' == key) {
163 if (val = joinClasses(val)) { 164 buf.push(' ' + key + '="' + val + '"'); 165 } 166 } else { 167 buf.push(exports.attr(key, val, false, terse)); 168 } 169 } 170 } 171 172 return buf.join(''); 173}; 174 175/** 176 * Escape the given string of `html`. 177 * 178 * @param {String} html 179 * @return {String} 180 * @api private 181 */ 182 183var jade_encode_html_rules = { 184 '&': '&', 185 '<': '<', 186 '>': '>', 187 '"': '"' 188}; 189var jade_match_html = /[&<>"]/g; 190 191function jade_encode_char(c) { 192 return jade_encode_html_rules[c] || c; 193} 194 195exports.escape = jade_escape; 196function jade_escape(html){ 197 var result = String(html).replace(jade_match_html, jade_encode_char); 198 if (result === '' + html) return html; 199 else return result; 200}; 201 202/** 203 * Re-throw the given `err` in context to the 204 * the jade in `filename` at the given `lineno`. 205 * 206 * @param {Error} err 207 * @param {String} filename 208 * @param {String} lineno 209 * @api private 210 */ 211 212exports.rethrow = function rethrow(err, filename, lineno, str){ 213 if (!(err instanceof Error)) throw err; 214 if ((typeof window != 'undefined' || !filename) && !str) { 215 err.message += ' on line ' + lineno; 216 throw err; 217 } 218 try { 219 str = str || require('fs').readFileSync(filename, 'utf8') 220 } catch (ex) { 221 rethrow(err, null, lineno) 222 } 223 var context = 3 224 , lines = str.split('\n') 225 , start = Math.max(lineno - context, 0) 226 , end = Math.min(lines.length, lineno + context); 227 228 // Error context 229 var context = lines.slice(start, end).map(function(line, i){ 230 var curr = i + start + 1; 231 return (curr == lineno ? ' > ' : ' ') 232 + curr 233 + '| ' 234 + line; 235 }).join('\n'); 236 237 // Alter exception message 238 err.path = filename; 239 err.message = (filename || 'Jade') + ':' + lineno 240 + '\n' + context + '\n\n' + err.message; 241 throw err; 242}; 243 244exports.DebugItem = function DebugItem(lineno, filename) { 245 this.lineno = lineno; 246 this.filename = filename; 247} 248 249},{"fs":2}],2:[function(require,module,exports){ 250 251},{}]},{},[1])(1) 252}); 253/** 254 * SWFObject v1.5: Flash Player detection and embed - http://blog.deconcept.com/swfobject/ 255 * 256 * SWFObject is (c) 2007 Geoff Stearns and is released under the MIT License: 257 * http://www.opensource.org/licenses/mit-license.php 258 * 259 */ 260if(typeof deconcept=="undefined"){var deconcept=new Object();}if(typeof deconcept.util=="undefined"){deconcept.util=new Object();}if(typeof deconcept.SWFObjectUtil=="undefined"){deconcept.SWFObjectUtil=new Object();}deconcept.SWFObject=function(_1,id,w,h,_5,c,_7,_8,_9,_a){if(!document.getElementById){return;}this.DETECT_KEY=_a?_a:"detectflash";this.skipDetect=deconcept.util.getRequestParameter(this.DETECT_KEY);this.params=new Object();this.variables=new Object();this.attributes=new Array();if(_1){this.setAttribute("swf",_1);}if(id){this.setAttribute("id",id);}if(w){this.setAttribute("width",w);}if(h){this.setAttribute("height",h);}if(_5){this.setAttribute("version",new deconcept.PlayerVersion(_5.toString().split(".")));}this.installedVer=deconcept.SWFObjectUtil.getPlayerVersion();if(!window.opera&&document.all&&this.installedVer.major>7){deconcept.SWFObject.doPrepUnload=true;}if(c){this.addParam("bgcolor",c);}var q=_7?_7:"high";this.addParam("quality",q);this.setAttribute("useExpressInstall",false);this.setAttribute("doExpressInstall",false);var _c=(_8)?_8:window.location;this.setAttribute("xiRedirectUrl",_c);this.setAttribute("redirectUrl","");if(_9){this.setAttribute("redirectUrl",_9);}};deconcept.SWFObject.prototype={useExpressInstall:function(_d){this.xiSWFPath=!_d?"expressinstall.swf":_d;this.setAttribute("useExpressInstall",true);},setAttribute:function(_e,_f){this.attributes[_e]=_f;},getAttribute:function(_10){return this.attributes[_10];},addParam:function(_11,_12){this.params[_11]=_12;},getParams:function(){return this.params;},addVariable:function(_13,_14){this.variables[_13]=_14;},getVariable:function(_15){return this.variables[_15];},getVariables:function(){return this.variables;},getVariablePairs:function(){var _16=new Array();var key;var _18=this.getVariables();for(key in _18){_16[_16.length]=key+"="+_18[key];}return _16;},getSWFHTML:function(){var _19="";if(navigator.plugins&&navigator.mimeTypes&&navigator.mimeTypes.length){if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","PlugIn");this.setAttribute("swf",this.xiSWFPath);}_19="<embed type=\"application/x-shockwave-flash\" src=\""+this.getAttribute("swf")+"\" width=\""+this.getAttribute("width")+"\" height=\""+this.getAttribute("height")+"\" style=\""+this.getAttribute("style")+"\"";_19+=" id=\""+this.getAttribute("id")+"\" name=\""+this.getAttribute("id")+"\" ";var _1a=this.getParams();for(var key in _1a){_19+=[key]+"=\""+_1a[key]+"\" ";}var _1c=this.getVariablePairs().join("&");if(_1c.length>0){_19+="flashvars=\""+_1c+"\"";}_19+="/>";}else{if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","ActiveX");this.setAttribute("swf",this.xiSWFPath);}_19="<object id=\""+this.getAttribute("id")+"\" classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" width=\""+this.getAttribute("width")+"\" height=\""+this.getAttribute("height")+"\" style=\""+this.getAttribute("style")+"\">";_19+="<param name=\"movie\" value=\""+this.getAttribute("swf")+"\" />";var _1d=this.getParams();for(var key in _1d){_19+="<param name=\""+key+"\" value=\""+_1d[key]+"\" />";}var _1f=this.getVariablePairs().join("&");if(_1f.length>0){_19+="<param name=\"flashvars\" value=\""+_1f+"\" />";}_19+="</object>";}return _19;},write:function(_20){if(this.getAttribute("useExpressInstall")){var _21=new deconcept.PlayerVersion([6,0,65]);if(this.installedVer.versionIsValid(_21)&&!this.installedVer.versionIsValid(this.getAttribute("version"))){this.setAttribute("doExpressInstall",true);this.addVariable("MMredirectURL",escape(this.getAttribute("xiRedirectUrl")));document.title=document.title.slice(0,47)+" - Flash Player Installation";this.addVariable("MMdoctitle",document.title);}}if(this.skipDetect||this.getAttribute("doExpressInstall")||this.installedVer.versionIsValid(this.getAttribute("version"))){var n=(typeof _20=="string")?document.getElementById(_20):_20;n.innerHTML=this.getSWFHTML();return true;}else{if(this.getAttribute("redirectUrl")!=""){document.location.replace(this.getAttribute("redirectUrl"));}}return false;}};deconcept.SWFObjectUtil.getPlayerVersion=function(){var _23=new deconcept.PlayerVersion([0,0,0]);if(navigator.plugins&&navigator.mimeTypes.length){var x=navigator.plugins["Shockwave Flash"];if(x&&x.description){_23=new deconcept.PlayerVersion(x.description.replace(/([a-zA-Z]|\s)+/,"").replace(/(\s+r|\s+b[0-9]+)/,".").split("."));}}else{if(navigator.userAgent&&navigator.userAgent.indexOf("Windows CE")>=0){var axo=1;var _26=3;while(axo){try{_26++;axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+_26);_23=new deconcept.PlayerVersion([_26,0,0]);}catch(e){axo=null;}}}else{try{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");}catch(e){try{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");_23=new deconcept.PlayerVersion([6,0,21]);axo.AllowScriptAccess="always";}catch(e){if(_23.major==6){return _23;}}try{axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");}catch(e){}}if(axo!=null){_23=new deconcept.PlayerVersion(axo.GetVariable("$version").split(" ")[1].split(","));}}}return _23;};deconcept.PlayerVersion=function(_29){this.major=_29[0]!=null?parseInt(_29[0]):0;this.minor=_29[1]!=null?parseInt(_29[1]):0;this.rev=_29[2]!=null?parseInt(_29[2]):0;};deconcept.PlayerVersion.prototype.versionIsValid=function(fv){if(this.major<fv.major){return false;}if(this.major>fv.major){return true;}if(this.minor<fv.minor){return false;}if(this.minor>fv.minor){return true;}if(this.rev<fv.rev){return false;}return true;};deconcept.util={getRequestParameter:function(_2b){var q=document.location.search||document.location.hash;if(_2b==null){return q;}if(q){var _2d=q.substring(1).split("&");for(var i=0;i<_2d.length;i++){if(_2d[i].substring(0,_2d[i].indexOf("="))==_2b){return _2d[i].substring((_2d[i].indexOf("=")+1));}}}return "";}};deconcept.SWFObjectUtil.cleanupSWFs=function(){var _2f=document.getElementsByTagName("OBJECT");for(var i=_2f.length-1;i>=0;i--){_2f[i].style.display="none";
260for(var x in _2f[i]){if(typeof _2f[i][x]=="function"){_2f[i][x]=function(){};}}}};if(deconcept.SWFObject.doPrepUnload){if(!deconcept.unloadSet){deconcept.SWFObjectUtil.prepUnload=function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){};window.attachEvent("onunload",deconcept.SWFObjectUtil.cleanupSWFs);};window.attachEvent("onbeforeunload",deconcept.SWFObjectUtil.prepUnload);deconcept.unloadSet=true;}}if(!document.getElementById&&document.all){document.getElementById=function(id){return document.all[id];};}var getQueryParamValue=deconcept.util.getRequestParameter;var FlashObject=deconcept.SWFObject;var SWFObject=deconcept.SWFObject; 261// 262// Copyright (c) 2008, 2009 Paul Duncan ([email protected]) 263// 264// Permission is hereby granted, free of charge, to any person obtaining a copy 265// of this software and associated documentation files (the "Software"), to deal 266// in the Software without restriction, including without limitation the rights 267// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 268// copies of the Software, and to permit persons to whom the Software is 269// furnished to do so, subject to the following conditions: 270// 271// The above copyright notice and this permission notice shall be included in 272// all copies or substantial portions of the Software. 273// 274// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 275// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 276// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 277// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 278// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
279// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 280// THE SOFTWARE. 281// 282 283 284/* 285 * The contents of gears_init.js; we need this because Chrome supports 286 * Gears out of the box, but still requires this constructor. Note that 287 * if you include gears_init.js then this function does nothing. 288 */ 289(function() { 290 // We are already defined. Hooray! 291 if (window.google && google.gears) 292 return; 293 294 // factory 295 var F = null; 296 297 // Firefox 298 if (typeof GearsFactory != 'undefined') { 299 F = new GearsFactory(); 300 } else { 301 // IE 302 try { 303 F = new ActiveXObject('Gears.Factory'); 304 // privateSetGlobalObject is only required and supported on WinCE. 305 if (F.getBuildInfo().indexOf('ie_mobile') != -1) 306 F.privateSetGlobalObject(this); 307 } catch (e) { 308 // Safari 309 if ((typeof navigator.mimeTypes != 'undefined') 310 && navigator.mimeTypes["application/x-googlegears"]) { 311 F = document.createElement("object"); 312 F.style.display = "none"; 313 F.width = 0; 314 F.height = 0; 315 F.type = "application/x-googlegears"; 316 document.documentElement.appendChild(F); 317 } 318 } 319 } 320 321 // *Do not* define any objects if Gears is not installed. This mimics the 322 // behavior of Gears defining the objects in the future. 323 if (!F) 324 return; 325 326 // Now set up the objects, being careful not to overwrite anything. 327 // 328 // Note: In Internet Explorer for Windows Mobile, you can't add properties to 329 // the window object. However, global objects are automatically added as 330 // properties of the window object in all browsers. 331 if (!window.google) 332 google = {}; 333 334 if (!google.gears) 335 google.gears = {factory: F}; 336})(); 337 338/** 339 * Persist - top-level namespace for Persist library. 340 * @namespace 341 */ 342Persist = (function() { 343 var VERSION = '0.2.0', P, B, C, esc, init, empty, ec; 344 345 // wrapper for Array.prototype.indexOf, since IE doesn't have it 346 var index_of = (function() { 347 if (Array.prototype.indexOf) 348 return function(ary, val) { 349 return Array.prototype.indexOf.call(ary, val); 350 }; 351 else 352 return function(ary, val) { 353 var i, l; 354 355 for (i = 0, l = ary.length; i < l; i++) 356 if (ary[i] == val) 357 return i; 358 359 return -1; 360 }; 361 })(); 362 363 364 // empty function 365 empty = function() { }; 366 367 /** 368 * Escape spaces and underscores in name. Used to generate a "safe" 369 * key from a name. 370 * 371 * @private 372 */ 373 esc = function(str) { 374 return 'PS' + str.replace(/_/g, '__').replace(/ /g, '_s'); 375 }; 376 377 C = { 378 /* 379 * Backend search order. 380 * 381 * Note that the search order is significant; the backends are 382 * listed in order of capacity, and many browsers 383 * support multiple backends, so changing the search order could 384 * result in a browser choosing a less capable backend. 385 */ 386 search_order: [ 387// 'gears', 388 'localstorage', 389 'globalstorage', 390 'whatwg_db', 391 'flash' 392 ], 393 394 // default flash configuration 395 flash: { 396 // ID of wrapper element 397 div_id: '_persist_flash_wrap', 398 399 // id of flash object/embed 400 id: '_persist_flash', 401 402 // default path to flash object 403 path: 'persist.swf', 404 size: { w:1, h:1 }, 405 406 // arguments passed to flash object 407 args: { 408 autostart: true 409 } 410 }, 411 412 // valid name regular expression 413 name_re: /^[a-z][a-z0-9_ -]+$/i, 414 415 // list of backend methods 416 methods: [ 417 'init', 418 'get', 419 'set', 420 'remove', 421 'load', 422 'save' 423 // TODO: clear method? 424 ], 425 426 // sql for db backends (gears and db) 427 sql: { 428 version: '1', // db schema version 429 430 // XXX: the "IF NOT EXISTS" is a sqlite-ism; fortunately all the 431 // known DB implementations (safari and gears) use sqlite 432 create: "CREATE TABLE IF NOT EXISTS persist_data (k TEXT UNIQUE NOT NULL PRIMARY KEY, v TEXT NOT NULL)", 433 get: "SELECT v FROM persist_data WHERE k = ?", 434 set: "INSERT INTO persist_data(k, v) VALUES (?, ?)",
435 remove: "DELETE FROM persist_data WHERE k = ?" 436 } 437 }; 438 439 // built-in backends 440 B = { 441 // gears db backend 442 // (src: http://code.google.com/apis/gears/api_database.html) 443 gears: { 444 // no known limit 445 size: -1, 446 447 test: function() { 448 // test for gears 449 return (window.google && window.google.gears) ? true : false; 450 }, 451 452 methods: { 453 transaction: function(fn) { 454 var db = this.db; 455 456 // begin transaction 457 db.execute('BEGIN').close(); 458 459 // call callback fn 460 fn.call(this, db); 461 462 // commit changes 463 db.execute('COMMIT').close(); 464 }, 465 466 init: function() { 467 var db; 468 469 // create database handle (TODO: add schema version?) 470 db = this.db = google.gears.factory.create('beta.database'); 471 472 // open database 473 // from gears ref: 474 // 475 // Currently the name, if supplied and of length greater than 476 // zero, must consist only of visible ASCII characters 477 // excluding the following characters: 478 // 479 // / \ : * ? " < > | ; , 480 // 481 // (this constraint is enforced in the Store constructor) 482 db.open(esc(this.name)); 483 484 // create table 485 db.execute(C.sql.create).close(); 486 }, 487 488 get: function(key, fn, scope) { 489 var r, sql = C.sql.get; 490 491 // if callback isn't defined, then return 492 if (!fn) 493 return; 494 495 // begin transaction 496 this.transaction(function (t) { 497 var is_valid, val; 498 // exec query 499 r = t.execute(sql, [key]); 500 501 // check result and get value 502 is_valid = r.isValidRow(); 503 val = is_valid ? r.field(0) : null; 504 505 // close result set 506 r.close(); 507 508 // call callback 509 fn.call(scope || this, is_valid, val); 510 }); 511 }, 512 513 set: function(key, val, fn, scope) { 514 var rm_sql = C.sql.remove, 515 sql = C.sql.set, r; 516 517 // begin set transaction 518 this.transaction(function(t) { 519 // exec remove query 520 t.execute(rm_sql, [key]).close(); 521 522 // exec set query 523 t.execute(sql, [key, val]).close(); 524 525 // run callback (TODO: get old value) 526 if (fn) 527 fn.call(scope || this, true, val); 528 }); 529 }, 530 531 remove: function(key, fn, scope) { 532 var get_sql = C.sql.get, 533 sql = C.sql.remove, 534 r, val = null, is_valid = false; 535 536 // begin remove transaction 537 this.transaction(function(t) { 538 // if a callback was defined, then get the old 539 // value before removing it 540 if (fn) { 541 // exec get query 542 r = t.execute(get_sql, [key]); 543 544 // check validity and get value 545 is_valid = r.isValidRow(); 546 val = is_valid ? r.field(0) : null; 547 548 // close result set 549 r.close(); 550 } 551 552 // exec remove query if no callback was defined, or if a 553 // callback was defined and there was an existing value 554 if (!fn || is_valid) { 555 // exec remove query 556 t.execute(sql, [key]).close(); 557 } 558 559 // exec callback 560 if (fn) 561 fn.call(scope || this, is_valid, val); 562 }); 563 } 564 } 565 }, 566 567 // whatwg db backend (webkit, Safari 3.1+) 568 // (src: whatwg and http://webkit.org/misc/DatabaseExample.html) 569 whatwg_db: { 570 // size based on DatabaseExample from above (should I increase 571 // this?) 572 size: 200 * 1024, 573 574 test: function() { 575 var name = 'PersistJS Test', 576 desc = 'Persistent database test.'; 577 578 // test for openDatabase 579 if (!window.openDatabase) 580 return false; 581 582 // make sure openDatabase works 583 // XXX: will this leak a db handle and/or waste space?
584 if (!window.openDatabase(name, C.sql.version, desc, B.whatwg_db.size)) 585 return false; 586 587 // return true 588 return true; 589 }, 590 591 methods: { 592 transaction: function(fn) { 593 // lazy create database table; 594 // this is done here because there is no way to 595 // prevent a race condition if the table is created in init() 596 if (!this.db_created) { 597 this.db.transaction(function(t) { 598 // create table 599 t.executeSql(C.sql.create, [], function() { 600 this.db_created = true; 601 }); 602 }, empty); // trap exception 603 } 604 605 // execute transaction 606 this.db.transaction(fn); 607 }, 608 609 init: function() { 610 // create database handle 611 this.db = openDatabase( 612 this.name, 613 C.sql.version, 614 this.o.about || ("Persistent storage for " + this.name), 615 this.o.size || B.whatwg_db.size 616 ); 617 }, 618 619 get: function(key, fn, scope) { 620 var sql = C.sql.get; 621 622 // if callback isn't defined, then return 623 if (!fn) 624 return; 625 626 // get callback scope 627 scope = scope || this; 628 629 // begin transaction 630 this.transaction(function (t) { 631 t.executeSql(sql, [key], function(t, r) { 632 if (r.rows.length > 0) 633 fn.call(scope, true, r.rows.item(0)['v']); 634 else 635 fn.call(scope, false, null); 636 }); 637 }); 638 }, 639 640 set: function(key, val, fn, scope) { 641 var rm_sql = C.sql.remove, 642 sql = C.sql.set; 643 644 // begin set transaction 645 this.transaction(function(t) { 646 // exec remove query 647 t.executeSql(rm_sql, [key], function() { 648 // exec set query 649 t.executeSql(sql, [key, val], function(t, r) { 650 // run callback 651 if (fn) 652 fn.call(scope || this, true, val); 653 }); 654 }); 655 }); 656 657 return val; 658 }, 659 660 // begin remove transaction 661 remove: function(key, fn, scope) { 662 var get_sql = C.sql.get, 663 sql = C.sql.remove; 664 665 this.transaction(function(t) { 666 // if a callback was defined, then get the old 667 // value before removing it 668 if (fn) { 669 // exec get query 670 t.executeSql(get_sql, [key], function(t, r) { 671 if (r.rows.length > 0) { 672 // key exists, get value 673 var val = r.rows.item(0)['v']; 674 675 // exec remove query 676 t.executeSql(sql, [key], function(t, r) { 677 // exec callback 678 fn.call(scope || this, true, val); 679 }); 680 } else { 681 // key does not exist, exec callback 682 fn.call(scope || this, false, null); 683 } 684 }); 685 } else { 686 // no callback was defined, so just remove the 687 // data without checking the old value 688 689 // exec remove query 690 t.executeSql(sql, [key]); 691 } 692 }); 693 } 694 } 695 }, 696 697 // globalstorage backend (globalStorage, FF2+, IE8+) 698 // (src: http://developer.mozilla.org/en/docs/DOM:Storage#globalStorage) 699 // https://developer.mozilla.org/En/DOM/Storage 700 // 701 // TODO: test to see if IE8 uses object literal semantics or 702 // getItem/setItem/removeItem semantics 703 globalstorage: { 704 // (5 meg limit, src: http://ejohn.org/blog/dom-storage-answers/) 705 size: 5 * 1024 * 1024, 706 707 test: function() { 708 try { 709 return !!window.globalStorage[location.hostname]; 710 } catch (e) { 711 return false; 712 } 713 }, 714 715 methods: { 716 key: function(key) { 717 return esc(this.name) + esc(key); 718 }, 719 720 init: function() { 721 this.store = globalStorage[this.o.domain]; 722 }, 723 724 get: function(key, fn, scope) { 725 // expand key 726 key = this.key(key); 727 728 var item = this.store.getItem(key), 729 val; 730 // FF3.0.x returns an object {security:undefined,value:_val_} 731 // and can raise security error. 732 try { 733 if (typeof item == 'object' && item != null) { 734 val = item.value; 735 } else { 736 val = item; 737 } 738 } catch (e) { 739 val = null; 740 } 741 742 if (val == 'null') val = null; 743 744 if (fn) 745 fn.call(scope || this, true, val); 746 }, 747 748 set: function(key, val, fn, scope) { 749 // expand key 750 751 key = this.key(key); 752 753 // set value 754 this.store.setItem(key, val); 755 756 if (fn) 757 fn.call(scope || this, true, val); 758 }, 759 760 remove: function(key, fn, scope) { 761 var val; 762 763 // expand key 764 key = this.key(key); 765 766 // get value 767 val = this.store[key]; 768 769 // delete value 770 this.store.removeItem(key); 771 772 if (fn) 773 fn.call(scope || this, (val !== null), val); 774 } 775 } 776 }, 777 778 // localstorage backend (globalStorage, FF2+, IE8+) 779 // (src: http://www.whatwg.org/specs/web-apps/current-work/#the-localstorage)
780 // also http://msdn.microsoft.com/en-us/library/cc197062(VS.85).aspx#_global 781 localstorage: { 782 // (unknown?) 783 // ie has the remainingSpace property, see: 784 // http://msdn.microsoft.com/en-us/library/cc197016(VS.85).aspx 785 size: -1, 786 787 test: function() { 788 try { 789 return !!window.localStorage; 790 } catch (e) { 791 return false; 792 } 793 }, 794 795 methods: { 796 key: function(key) { 797 return esc(this.name) + esc(key); 798 }, 799 800 init: function() { 801 this.store = localStorage; 802 }, 803 804 get: function(key, fn, scope) { 805 // expand key 806 key = this.key(key); 807 var val = this.store.getItem(key); 808 809 if (val == 'null') val = null; 810 811 if (fn) 812 fn.call(scope || this, true, val); 813 }, 814 815 set: function(key, val, fn, scope) { 816 // expand key 817 key = this.key(key); 818 819 // set value 820 this.store.setItem(key, val); 821 822 if (fn) 823 fn.call(scope || this, true, val); 824 }, 825 826 remove: function(key, fn, scope) { 827 var val; 828 829 // expand key 830 key = this.key(key); 831 832 // get value 833 val = this.store.getItem(key); 834 835 // delete value 836 this.store.removeItem(key); 837 838 if (fn) 839 fn.call(scope || this, (val !== null), val); 840 } 841 } 842 }, 843 // flash backend (requires flash 8 or newer) 844 // http://kb.adobe.com/selfservice/viewContent.do?externalId=tn_16194&sliceId=1 845 // http://livedocs.adobe.com/flash/8/main/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00002200.html 846 flash: { 847 test: function() { 848 // TODO: better flash detection 849 if (!deconcept || !deconcept.SWFObjectUtil) 850 return false; 851 852 // get the major version 853 var major = deconcept.SWFObjectUtil.getPlayerVersion().major; 854 855 // check flash version (require 8.0 or newer) 856 return (major >= 8) ? true : false; 857 }, 858 859 methods: { 860 init: function() { 861 if (!B.flash.el) { 862 var o, key, el, cfg = C.flash; 863 864 // create wrapper element 865 el = document.createElement('div'); 866 el.id = cfg.div_id; 867 868 // FIXME: hide flash element 869 // el.style.display = 'none'; 870 871 // append element to body 872 document.body.appendChild(el); 873 874 // create new swf object 875 o = new deconcept.SWFObject(this.o.swf_path || cfg.path, cfg.id, cfg.size.w, cfg.size.h, '8'); 876 877 // set parameters 878 for (key in cfg.args) 879 o.addVariable(key, cfg.args[key]); 880 881 // write flash object 882 o.write(el); 883 884 // save flash element 885 B.flash.el = document.getElementById(cfg.id); 886 } 887 888 // use singleton flash element 889 this.el = B.flash.el; 890 }, 891 892 get: function(key, fn, scope) { 893 var val; 894 895 // escape key 896 key = esc(key); 897 898 // get value 899 try { 900 val = this.el.get(this.name, key); 901 } catch (e) { 902 val = null; 903 } 904 905 if (val == 'null') val = null; 906 907 // call handler 908 if (fn) 909 fn.call(scope || this, val !== null, val); 910 }, 911 912 set: function(key, val, fn, scope) { 913 var old_val; 914 915 // escape key 916 key = esc(key); 917 918 // set value 919 old_val = this.el.set(this.name, key, val); 920 921 // call handler 922 if (fn) 923 fn.call(scope || this, true, val); 924 }, 925 926 remove: function(key, fn, scope) { 927 var val; 928 929 // get key 930 key = esc(key); 931 932 // remove old value 933 val = this.el.remove(this.name, key); 934 935 // call handler 936 if (fn) 937 fn.call(scope || this, true, val); 938 } 939 } 940 } 941 942 }; 943 944 /** 945 * Test for available backends and pick the best one. 946 * @private 947 */ 948 var init = function() { 949 var i, l, b, key, fns = C.methods, keys = C.search_order; 950 951 // set all functions to the empty function 952 for (i = 0, l = fns.length; i < l; i++) 953 P.Store.prototype[fns[i]] = empty; 954 955 // clear type and size 956 P.type = null; 957 P.size = -1; 958 959 // loop over all backends and test for each one
960 for (i = 0, l = keys.length; !P.type && i < l; i++) { 961 b = B[keys[i]]; 962 963 // test for backend 964 if (b.test()) { 965 // found backend, save type and size 966 P.type = keys[i]; 967 P.size = b.size; 968 969 // extend store prototype with backend methods 970 for (key in b.methods) 971 P.Store.prototype[key] = b.methods[key]; 972 } 973 } 974 975 // mark library as initialized 976 P._init = true; 977 }; 978 979 // create top-level namespace 980 P = { 981 // version of persist library 982 VERSION: VERSION, 983 984 // backend type and size limit 985 type: null, 986 size: 0, 987 988 // XXX: expose init function? 989 // init: init, 990 991 add: function(o) { 992 // add to backend hash 993 B[o.id] = o; 994 995 // add backend to front of search order 996 C.search_order = [o.id].concat(C.search_order); 997 998 // re-initialize library 999 init(); 1000 }, 1001 1002 remove: function(id) { 1003 var ofs = index_of(C.search_order, id); 1004 if (ofs < 0) 1005 return; 1006 1007 // remove from search order 1008 C.search_order.splice(ofs, 1); 1009 1010 // delete from lut 1011 delete B[id]; 1012 1013 // re-initialize library 1014 init(); 1015 }, 1016 1017 // store API 1018 Store: function(name, o) { 1019 // verify name 1020 if (!C.name_re.exec(name)) 1021 throw new Error("Invalid name"); 1022 1023 // XXX: should we lazy-load type? 1024 // if (!P._init) 1025 // init(); 1026 1027 // No suitable storage found 1028 if (!P.type) return false; //throw new Error("No suitable storage found"); 1029 1030 o = o || {}; 1031 this.name = name; 1032 1033 // get domain (XXX: does this localdomain fix work?) 1034 o.domain = o.domain || location.host || 'localhost'; 1035 1036 // strip port from domain (XXX: will this break ipv6?) 1037 o.domain = o.domain.replace(/:\d+$/, ''); 1038 1039 // append localdomain to domains w/o '." 1040 // (see https://bugzilla.mozilla.org/show_bug.cgi?id=357323) 1041 // (file://localhost/ works, see: 1042 // https://bugzilla.mozilla.org/show_bug.cgi?id=469192) 1043/* 1044 * if (!o.domain.match(/\./)) 1045 * o.domain += '.localdomain'; 1046 */ 1047 1048 this.o = o; 1049 1050 // expires in 2 years 1051 o.expires = o.expires || 365 * 2; 1052 1053 // set path to root 1054 o.path = o.path || '/'; 1055 1056 // call init function 1057 this.init(); 1058 } 1059 }; 1060 1061 // init persist 1062 init(); 1063 1064 // return top-level namespace 1065 return P; 1066})(); 1067/*! 1068 * jQuery JavaScript Library v1.5.2 1069 * http://jquery.com/ 1070 * 1071 * Copyright 2011, John Resig 1072 * Dual licensed under the MIT or GPL Version 2 licenses. 1073 * http://jquery.org/license 1074 * 1075 * Includes Sizzle.js 1076 * http://sizzlejs.com/ 1077 * Copyright 2011, The Dojo Foundation 1078 * Released under the MIT, BSD, and GPL Licenses. 1079 * 1080 * Date: Thu Mar 31 15:28:23 2011 -0400 1081 */ 1082(function( window, undefined ) { 1083 1084// Use the correct document accordingly with window argument (sandbox) 1085var document = window.document; 1086var jQuery = (function() { 1087 1088// Define a local copy of jQuery 1089var jQuery = function( selector, context ) { 1090 // The jQuery object is actually just the init constructor 'enhanced' 1091 return new jQuery.fn.init( selector, context, rootjQuery ); 1092 }, 1093 1094 // Map over jQuery in case of overwrite 1095 _jQuery = window.jQuery, 1096 1097 // Map over the $ in case of overwrite 1098 _$ = window.$, 1099 1100 // A central reference to the root jQuery(document) 1101 rootjQuery, 1102 1103 // A simple way to check for HTML strings or ID strings 1104 // (both of which we optimize for) 1105 quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/, 1106 1107 // Check if a string has a non-whitespace character in it 1108 rnotwhite = /\S/, 1109 1110 // Used for trimming whitespace 1111 trimLeft = /^\s+/, 1112 trimRight = /\s+$/, 1113 1114 // Check for digits 1115 rdigit = /\d/, 1116 1117 // Match a standalone tag 1118 rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, 1119 1120 // JSON RegExp 1121 rvalidchars = /^[\],:{}\s]*$/, 1122 rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, 1123 rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, 1124 rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, 1125 1126 // Useragent RegExp 1127 rwebkit = /(webkit)[ \/]([\w.]+)/, 1128 ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, 1129 rmsie = /(msie) ([\w.]+)/, 1130 rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, 1131 1132 // Keep a UserAgent string for use with jQuery.browser 1133 userAgent = navigator.userAgent, 1134 1135 // For matching the engine and version of the browser 1136 browserMatch, 1137 1138 // The deferred used on DOM ready 1139 readyList, 1140 1141 // The ready event handler 1142 DOMContentLoaded, 1143 1144 // Save a reference to some core methods 1145 toString = Object.prototype.toString, 1146 hasOwn = Object.prototype.hasOwnProperty, 1147 push = Array.prototype.push, 1148 slice = Array.prototype.slice, 1149 trim = String.prototype.trim, 1150 indexOf = Array.prototype.indexOf, 1151 1152 // [[Class]] -> type pairs 1153 class2type = {}; 1154 1155jQuery.fn = jQuery.prototype = { 1156 constructor: jQuery, 1157 init: function( selector, context, rootjQuery ) { 1158 var match, elem, ret, doc; 1159 1160 // Handle $(""), $(null), or $(undefined) 1161 if ( !selector ) { 1162 return this; 1163 } 1164 1165 // Handle $(DOMElement) 1166 if ( selector.nodeType ) { 1167 this.context = this[0] = selector; 1168 this.length = 1; 1169 return this; 1170 } 1171 1172 // The body element only exists once, optimize finding it 1173 if ( selector === "body" && !context && document.body ) { 1174 this.context = document; 1175 this[0] = document.body; 1176 this.selector = "body"; 1177 this.length = 1; 1178 return this; 1179 } 1180 1181 // Handle HTML strings 1182 if ( typeof selector === "string" ) { 1183 // Are we dealing with HTML string or an ID? 1184 match = quickExpr.exec( selector ); 1185 1186 // Verify a match, and that no context was specified for #id 1187 if ( match && (match[1] || !context) ) { 1188 1189 // HANDLE: $(html) -> $(array) 1190 if ( match[1] ) { 1191 context = context instanceof jQuery ? context[0] : context; 1192 doc = (context ? context.ownerDocument || context : document); 1193
1194 // If a single string is passed in and it's a single tag 1195 // just do a createElement and skip the rest 1196 ret = rsingleTag.exec( selector ); 1197 1198 if ( ret ) { 1199 if ( jQuery.isPlainObject( context ) ) { 1200 selector = [ document.createElement( ret[1] ) ]; 1201 jQuery.fn.attr.call( selector, context, true ); 1202 1203 } else { 1204 selector = [ doc.createElement( ret[1] ) ]; 1205 } 1206 1207 } else { 1208 ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); 1209 selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes; 1210 } 1211 1212 return jQuery.merge( this, selector ); 1213 1214 // HANDLE: $("#id") 1215 } else { 1216 elem = document.getElementById( match[2] ); 1217 1218 // Check parentNode to catch when Blackberry 4.6 returns 1219 // nodes that are no longer in the document #6963 1220 if ( elem && elem.parentNode ) { 1221 // Handle the case where IE and Opera return items 1222 // by name instead of ID 1223 if ( elem.id !== match[2] ) { 1224 return rootjQuery.find( selector ); 1225 } 1226 1227 // Otherwise, we inject the element directly into the jQuery object 1228 this.length = 1; 1229 this[0] = elem; 1230 } 1231 1232 this.context = document; 1233 this.selector = selector; 1234 return this; 1235 } 1236 1237 // HANDLE: $(expr, $(...)) 1238 } else if ( !context || context.jquery ) { 1239 return (context || rootjQuery).find( selector ); 1240 1241 // HANDLE: $(expr, context) 1242 // (which is just equivalent to: $(context).find(expr) 1243 } else { 1244 return this.constructor( context ).find( selector ); 1245 } 1246 1247 // HANDLE: $(function) 1248 // Shortcut for document ready 1249 } else if ( jQuery.isFunction( selector ) ) { 1250 return rootjQuery.ready( selector ); 1251 } 1252 1253 if (selector.selector !== undefined) { 1254 this.selector = selector.selector; 1255 this.context = selector.context; 1256 } 1257 1258 return jQuery.makeArray( selector, this ); 1259 }, 1260 1261 // Start with an empty selector 1262 selector: "", 1263 1264 // The current version of jQuery being used 1265 jquery: "1.5.2", 1266 1267 // The default length of a jQuery object is 0 1268 length: 0, 1269 1270 // The number of elements contained in the matched element set 1271 size: function() { 1272 return this.length; 1273 }, 1274 1275 toArray: function() { 1276 return slice.call( this, 0 ); 1277 }, 1278 1279 // Get the Nth element in the matched element set OR 1280 // Get the whole matched element set as a clean array 1281 get: function( num ) { 1282 return num == null ? 1283 1284 // Return a 'clean' array 1285 this.toArray() : 1286 1287 // Return just the object 1288 ( num < 0 ? this[ this.length + num ] : this[ num ] ); 1289 }, 1290 1291 // Take an array of elements and push it onto the stack 1292 // (returning the new matched element set) 1293 pushStack: function( elems, name, selector ) { 1294 // Build a new jQuery matched element set 1295 var ret = this.constructor(); 1296 1297 if ( jQuery.isArray( elems ) ) { 1298 push.apply( ret, elems ); 1299 1300 } else { 1301 jQuery.merge( ret, elems ); 1302 } 1303 1304 // Add the old object onto the stack (as a reference) 1305 ret.prevObject = this; 1306 1307 ret.context = this.context; 1308 1309 if ( name === "find" ) { 1310 ret.selector = this.selector + (this.selector ? " " : "") + selector; 1311 } else if ( name ) { 1312 ret.selector = this.selector + "." + name + "(" + selector + ")"; 1313 } 1314 1315 // Return the newly-formed element set 1316 return ret; 1317 }, 1318 1319 // Execute a callback for every element in the matched set. 1320 // (You can seed the arguments with an array of args, but this is 1321 // only used internally.) 1322 each: function( callback, args ) { 1323 return jQuery.each( this, callback, args ); 1324 }, 1325 1326 ready: function( fn ) { 1327 // Attach the listeners 1328 jQuery.bindReady(); 1329 1330 // Add the callback 1331 readyList.done( fn ); 1332 1333 return this; 1334 }, 1335 1336 eq: function( i ) { 1337 return i === -1 ? 1338 this.slice( i ) : 1339 this.slice( i, +i + 1 ); 1340 }, 1341 1342 first: function() { 1343 return this.eq( 0 ); 1344 }, 1345 1346 last: function() { 1347 return this.eq( -1 ); 1348 }, 1349 1350 slice: function() { 1351 return this.pushStack( slice.apply( this, arguments ), 1352 "slice", slice.call(arguments).join(",") ); 1353 }, 1354 1355 map: function( callback ) { 1356 return this.pushStack( jQuery.map(this, function( elem, i ) { 1357 return callback.call( elem, i, elem ); 1358 })); 1359 }, 1360 1361 end: function() { 1362 return this.prevObject || this.constructor(null); 1363 }, 1364 1365 // For internal use only. 1366 // Behaves like an Array's method, not like a jQuery method. 1367 push: push, 1368 sort: [].sort, 1369 splice: [].splice 1370}; 1371 1372// Give the init function the jQuery prototype for later instantiation 1373jQuery.fn.init.prototype = jQuery.fn; 1374 1375jQuery.extend = jQuery.fn.extend = function() { 1376 var options, name, src, copy, copyIsArray, clone, 1377 target = arguments[0] || {}, 1378 i = 1, 1379 length = arguments.length, 1380 deep = false;
1381 1382 // Handle a deep copy situation 1383 if ( typeof target === "boolean" ) { 1384 deep = target; 1385 target = arguments[1] || {}; 1386 // skip the boolean and the target 1387 i = 2; 1388 } 1389 1390 // Handle case when target is a string or something (possible in deep copy) 1391 if ( typeof target !== "object" && !jQuery.isFunction(target) ) { 1392 target = {}; 1393 } 1394 1395 // extend jQuery itself if only one argument is passed 1396 if ( length === i ) { 1397 target = this; 1398 --i; 1399 } 1400 1401 for ( ; i < length; i++ ) { 1402 // Only deal with non-null/undefined values 1403 if ( (options = arguments[ i ]) != null ) { 1404 // Extend the base object 1405 for ( name in options ) { 1406 src = target[ name ]; 1407 copy = options[ name ]; 1408 1409 // Prevent never-ending loop 1410 if ( target === copy ) { 1411 continue; 1412 } 1413 1414 // Recurse if we're merging plain objects or arrays 1415 if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { 1416 if ( copyIsArray ) { 1417 copyIsArray = false; 1418 clone = src && jQuery.isArray(src) ? src : []; 1419 1420 } else { 1421 clone = src && jQuery.isPlainObject(src) ? src : {}; 1422 } 1423 1424 // Never move original objects, clone them 1425 target[ name ] = jQuery.extend( deep, clone, copy ); 1426 1427 // Don't bring in undefined values 1428 } else if ( copy !== undefined ) { 1429 target[ name ] = copy; 1430 } 1431 } 1432 } 1433 } 1434 1435 // Return the modified object 1436 return target; 1437}; 1438 1439jQuery.extend({ 1440 noConflict: function( deep ) { 1441 window.$ = _$; 1442 1443 if ( deep ) { 1444 window.jQuery = _jQuery; 1445 } 1446 1447 return jQuery; 1448 }, 1449 1450 // Is the DOM ready to be used? Set to true once it occurs. 1451 isReady: false, 1452 1453 // A counter to track how many items to wait for before 1454 // the ready event fires. See #6781 1455 readyWait: 1, 1456 1457 // Handle when the DOM is ready 1458 ready: function( wait ) { 1459 // A third-party is pushing the ready event forwards 1460 if ( wait === true ) { 1461 jQuery.readyWait--; 1462 } 1463 1464 // Make sure that the DOM is not already loaded 1465 if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) { 1466 // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). 1467 if ( !document.body ) { 1468 return setTimeout( jQuery.ready, 1 ); 1469 } 1470 1471 // Remember that the DOM is ready 1472 jQuery.isReady = true; 1473 1474 // If a normal DOM Ready event fired, decrement, and wait if need be 1475 if ( wait !== true && --jQuery.readyWait > 0 ) { 1476 return; 1477 } 1478 1479 // If there are functions bound, to execute 1480 readyList.resolveWith( document, [ jQuery ] ); 1481 1482 // Trigger any bound ready events 1483 if ( jQuery.fn.trigger ) { 1484 jQuery( document ).trigger( "ready" ).unbind( "ready" ); 1485 } 1486 } 1487 }, 1488 1489 bindReady: function() { 1490 if ( readyList ) { 1491 return; 1492 } 1493 1494 readyList = jQuery._Deferred(); 1495 1496 // Catch cases where $(document).ready() is called after the 1497 // browser event has already occurred. 1498 if ( document.readyState === "complete" ) { 1499 // Handle it asynchronously to allow scripts the opportunity to delay ready 1500 return setTimeout( jQuery.ready, 1 ); 1501 } 1502 1503 // Mozilla, Opera and webkit nightlies currently support this event 1504 if ( document.addEventListener ) { 1505 // Use the handy event callback 1506 document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); 1507 1508 // A fallback to window.onload, that will always work 1509 window.addEventListener( "load", jQuery.ready, false ); 1510 1511 // If IE event model is used 1512 } else if ( document.attachEvent ) { 1513 // ensure firing before onload, 1514 // maybe late but safe also for iframes 1515 document.attachEvent("onreadystatechange", DOMContentLoaded); 1516 1517 // A fallback to window.onload, that will always work 1518 window.attachEvent( "onload", jQuery.ready ); 1519 1520 // If IE and not a frame 1521 // continually check to see if the document is ready 1522 var toplevel = false; 1523 1524 try { 1525 toplevel = window.frameElement == null; 1526 } catch(e) {} 1527 1528 if ( document.documentElement.doScroll && toplevel ) { 1529 doScrollCheck(); 1530 } 1531 } 1532 }, 1533 1534 // See test/unit/core.js for details concerning isFunction. 1535 // Since version 1.3, DOM methods and functions like alert 1536 // aren't supported. They return false on IE (#2968). 1537 isFunction: function( obj ) { 1538 return jQuery.type(obj) === "function"; 1539 }, 1540 1541 isArray: Array.isArray || function( obj ) { 1542 return jQuery.type(obj) === "array"; 1543 }, 1544 1545 // A crude way of determining if an object is a window 1546 isWindow: function( obj ) { 1547 return obj && typeof obj === "object" && "setInterval" in obj; 1548 }, 1549 1550 isNaN: function( obj ) { 1551 return obj == null || !rdigit.test( obj ) || isNaN( obj ); 1552 }, 1553 1554 type: function( obj ) { 1555 return obj == null ? 1556 String( obj ) : 1557 class2type[ toString.call(obj) ] || "object"; 1558 }, 1559 1560 isPlainObject: function( obj ) { 1561 // Must be an Object. 1562 // Because of IE, we also have to check the presence of the constructor property. 1563 // Make sure that DOM nodes and window objects don't pass through, as well 1564 if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { 1565 return false;
1566 } 1567 1568 // Not own constructor property must be Object 1569 if ( obj.constructor && 1570 !hasOwn.call(obj, "constructor") && 1571 !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { 1572 return false; 1573 } 1574 1575 // Own properties are enumerated firstly, so to speed up, 1576 // if last one is own, then all properties are own. 1577 1578 var key; 1579 for ( key in obj ) {} 1580 1581 return key === undefined || hasOwn.call( obj, key ); 1582 }, 1583 1584 isEmptyObject: function( obj ) { 1585 for ( var name in obj ) { 1586 return false; 1587 } 1588 return true; 1589 }, 1590 1591 error: function( msg ) { 1592 throw msg; 1593 }, 1594 1595 parseJSON: function( data ) { 1596 if ( typeof data !== "string" || !data ) { 1597 return null; 1598 } 1599 1600 // Make sure leading/trailing whitespace is removed (IE can't handle it) 1601 data = jQuery.trim( data ); 1602 1603 // Make sure the incoming data is actual JSON 1604 // Logic borrowed from http://json.org/json2.js 1605 if ( rvalidchars.test(data.replace(rvalidescape, "@") 1606 .replace(rvalidtokens, "]") 1607 .replace(rvalidbraces, "")) ) { 1608 1609 // Try to use the native JSON parser first 1610 return window.JSON && window.JSON.parse ? 1611 window.JSON.parse( data ) : 1612 (new Function("return " + data))(); 1613 1614 } else { 1615 jQuery.error( "Invalid JSON: " + data ); 1616 } 1617 }, 1618 1619 // Cross-browser xml parsing 1620 // (xml & tmp used internally) 1621 parseXML: function( data , xml , tmp ) { 1622 1623 if ( window.DOMParser ) { // Standard 1624 tmp = new DOMParser(); 1625 xml = tmp.parseFromString( data , "text/xml" ); 1626 } else { // IE 1627 xml = new ActiveXObject( "Microsoft.XMLDOM" ); 1628 xml.async = "false"; 1629 xml.loadXML( data ); 1630 } 1631 1632 tmp = xml.documentElement; 1633 1634 if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) { 1635 jQuery.error( "Invalid XML: " + data ); 1636 } 1637 1638 return xml; 1639 }, 1640 1641 noop: function() {}, 1642 1643 // Evalulates a script in a global context 1644 globalEval: function( data ) { 1645 if ( data && rnotwhite.test(data) ) { 1646 // Inspired by code by Andrea Giammarchi 1647 // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html 1648 var head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement, 1649 script = document.createElement( "script" ); 1650 1651 if ( jQuery.support.scriptEval() ) { 1652 script.appendChild( document.createTextNode( data ) ); 1653 } else { 1654 script.text = data; 1655 } 1656 1657 // Use insertBefore instead of appendChild to circumvent an IE6 bug. 1658 // This arises when a base node is used (#2709).
1659 head.insertBefore( script, head.firstChild ); 1660 head.removeChild( script ); 1661 } 1662 }, 1663 1664 nodeName: function( elem, name ) { 1665 return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); 1666 }, 1667 1668 // args is for internal usage only 1669 each: function( object, callback, args ) { 1670 var name, i = 0, 1671 length = object.length, 1672 isObj = length === undefined || jQuery.isFunction(object); 1673 1674 if ( args ) { 1675 if ( isObj ) { 1676 for ( name in object ) { 1677 if ( callback.apply( object[ name ], args ) === false ) { 1678 break; 1679 } 1680 } 1681 } else { 1682 for ( ; i < length; ) { 1683 if ( callback.apply( object[ i++ ], args ) === false ) { 1684 break; 1685 } 1686 } 1687 } 1688 1689 // A special, fast, case for the most common use of each 1690 } else { 1691 if ( isObj ) { 1692 for ( name in object ) { 1693 if ( callback.call( object[ name ], name, object[ name ] ) === false ) { 1694 break; 1695 } 1696 } 1697 } else { 1698 for ( var value = object[0]; 1699 i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} 1700 } 1701 } 1702 1703 return object; 1704 }, 1705 1706 // Use native String.trim function wherever possible 1707 trim: trim ? 1708 function( text ) { 1709 return text == null ? 1710 "" : 1711 trim.call( text ); 1712 } : 1713 1714 // Otherwise use our own trimming functionality 1715 function( text ) { 1716 return text == null ? 1717 "" : 1718 text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); 1719 }, 1720 1721 // results is for internal usage only 1722 makeArray: function( array, results ) { 1723 var ret = results || []; 1724 1725 if ( array != null ) { 1726 // The window, strings (and functions) also have 'length' 1727 // The extra typeof function check is to prevent crashes 1728 // in Safari 2 (See: #3039) 1729 // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 1730 var type = jQuery.type(array); 1731 1732 if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { 1733 push.call( ret, array ); 1734 } else { 1735 jQuery.merge( ret, array ); 1736 } 1737 } 1738 1739 return ret; 1740 }, 1741 1742 inArray: function( elem, array ) { 1743 if ( array.indexOf ) { 1744 return array.indexOf( elem ); 1745 } 1746 1747 for ( var i = 0, length = array.length; i < length; i++ ) { 1748 if ( array[ i ] === elem ) { 1749 return i; 1750 } 1751 } 1752 1753 return -1; 1754 }, 1755 1756 merge: function( first, second ) { 1757 var i = first.length, 1758 j = 0; 1759 1760 if ( typeof second.length === "number" ) { 1761 for ( var l = second.length; j < l; j++ ) { 1762 first[ i++ ] = second[ j ]; 1763 } 1764 1765 } else { 1766 while ( second[j] !== undefined ) { 1767 first[ i++ ] = second[ j++ ]; 1768 } 1769 } 1770 1771 first.length = i; 1772 1773 return first; 1774 }, 1775 1776 grep: function( elems, callback, inv ) { 1777 var ret = [], retVal; 1778 inv = !!inv; 1779 1780 // Go through the array, only saving the items 1781 // that pass the validator function 1782 for ( var i = 0, length = elems.length; i < length; i++ ) { 1783 retVal = !!callback( elems[ i ], i ); 1784 if ( inv !== retVal ) { 1785 ret.push( elems[ i ] ); 1786 } 1787 } 1788 1789 return ret; 1790 }, 1791 1792 // arg is for internal usage only 1793 map: function( elems, callback, arg ) { 1794 var ret = [], value; 1795 1796 // Go through the array, translating each of the items to their 1797 // new value (or values). 1798 for ( var i = 0, length = elems.length; i < length; i++ ) { 1799 value = callback( elems[ i ], i, arg ); 1800 1801 if ( value != null ) { 1802 ret[ ret.length ] = value; 1803 } 1804 } 1805 1806 // Flatten any nested arrays 1807 return ret.concat.apply( [], ret ); 1808 }, 1809 1810 // A global GUID counter for objects 1811 guid: 1, 1812 1813 proxy: function( fn, proxy, thisObject ) { 1814 if ( arguments.length === 2 ) { 1815 if ( typeof proxy === "string" ) { 1816 thisObject = fn; 1817 fn = thisObject[ proxy ]; 1818 proxy = undefined; 1819 1820 } else if ( proxy && !jQuery.isFunction( proxy ) ) { 1821 thisObject = proxy; 1822 proxy = undefined; 1823 } 1824 } 1825 1826 if ( !proxy && fn ) { 1827 proxy = function() { 1828 return fn.apply( thisObject || this, arguments ); 1829 }; 1830 } 1831 1832 // Set the guid of unique handler to the same of original handler, so it can be removed 1833 if ( fn ) { 1834 proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; 1835 } 1836 1837 // So proxy can be declared as an argument 1838 return proxy; 1839 }, 1840 1841 // Mutifunctional method to get and set values to a collection 1842 // The value/s can be optionally by executed if its a function 1843 access: function( elems, key, value, exec, fn, pass ) { 1844 var length = elems.length; 1845 1846 // Setting many attributes 1847 if ( typeof key === "object" ) { 1848 for ( var k in key ) { 1849 jQuery.access( elems, k, key[k], exec, fn, value ); 1850 } 1851 return elems; 1852 } 1853 1854 // Setting one attribute 1855 if ( value !== undefined ) { 1856 // Optionally, function values get executed if exec is true 1857 exec = !pass && exec && jQuery.isFunction(value); 1858 1859 for ( var i = 0; i < length; i++ ) { 1860 fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); 1861 } 1862 1863 return elems; 1864 } 1865 1866 // Getting an attribute 1867 return length ? fn( elems[0], key ) : undefined; 1868 }, 1869 1870 now: function() { 1871 return (new Date()).getTime(); 1872 }, 1873 1874 // Use of jQuery.browser is frowned upon. 1875 // More details: http://docs.jquery.com/Utilities/jQuery.browser 1876 uaMatch: function( ua ) { 1877 ua = ua.toLowerCase(); 1878 1879 var match = rwebkit.exec( ua ) || 1880 ropera.exec( ua ) || 1881 rmsie.exec( ua ) || 1882 ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || 1883 []; 1884 1885 return { browser: match[1] || "", version: match[2] || "0" }; 1886 }, 1887 1888 sub: function() { 1889 function jQuerySubclass( selector, context ) { 1890 return new jQuerySubclass.fn.init( selector, context ); 1891 } 1892 jQuery.extend( true, jQuerySubclass, this ); 1893 jQuerySubclass.superclass = this; 1894 jQuerySubclass.fn = jQuerySubclass.prototype = this(); 1895 jQuerySubclass.fn.constructor = jQuerySubclass; 1896 jQuerySubclass.subclass = this.subclass; 1897 jQuerySubclass.fn.init = function init( selector, context ) { 1898 if ( context && context instanceof jQuery && !(context instanceof jQuerySubclass) ) { 1899 context = jQuerySubclass(context); 1900 } 1901 1902 return jQuery.fn.init.call( this, selector, context, rootjQuerySubclass ); 1903 }; 1904 jQuerySubclass.fn.init.prototype = jQuerySubclass.fn; 1905 var rootjQuerySubclass = jQuerySubclass(document); 1906 return jQuerySubclass; 1907 }, 1908 1909 browser: {} 1910}); 1911 1912// Populate the class2type map 1913jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { 1914 class2type[ "[object " + name + "]" ] = name.toLowerCase(); 1915}); 1916 1917browserMatch = jQuery.uaMatch( userAgent ); 1918if ( browserMatch.browser ) { 1919 jQuery.browser[ browserMatch.browser ] = true; 1920 jQuery.browser.version = browserMatch.version; 1921} 1922 1923// Deprecated, use jQuery.browser.webkit instead 1924if ( jQuery.browser.webkit ) { 1925 jQuery.browser.safari = true; 1926} 1927 1928if ( indexOf ) { 1929 jQuery.inArray = function( elem, array ) { 1930 return indexOf.call( array, elem ); 1931 }; 1932} 1933
1934// IE doesn't match non-breaking spaces with \s 1935if ( rnotwhite.test( "\xA0" ) ) { 1936 trimLeft = /^[\s\xA0]+/; 1937 trimRight = /[\s\xA0]+$/; 1938} 1939 1940// All jQuery objects should point back to these 1941rootjQuery = jQuery(document); 1942 1943// Cleanup functions for the document ready method 1944if ( document.addEventListener ) { 1945 DOMContentLoaded = function() { 1946 document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); 1947 jQuery.ready(); 1948 }; 1949 1950} else if ( document.attachEvent ) { 1951 DOMContentLoaded = function() { 1952 // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). 1953 if ( document.readyState === "complete" ) { 1954 document.detachEvent( "onreadystatechange", DOMContentLoaded ); 1955 jQuery.ready(); 1956 } 1957 }; 1958} 1959 1960// The DOM ready check for Internet Explorer 1961function doScrollCheck() { 1962 if ( jQuery.isReady ) { 1963 return; 1964 } 1965 1966 try { 1967 // If IE is used, use the trick by Diego Perini 1968 // http://javascript.nwbox.com/IEContentLoaded/ 1969 document.documentElement.doScroll("left"); 1970 } catch(e) { 1971 setTimeout( doScrollCheck, 1 ); 1972 return; 1973 } 1974 1975 // and execute any waiting functions 1976 jQuery.ready(); 1977} 1978 1979// Expose jQuery to the global object 1980return jQuery; 1981 1982})(); 1983 1984 1985var // Promise methods 1986 promiseMethods = "then done fail isResolved isRejected promise".split( " " ), 1987 // Static reference to slice 1988 sliceDeferred = [].slice; 1989 1990jQuery.extend({ 1991 // Create a simple deferred (one callbacks list) 1992 _Deferred: function() { 1993 var // callbacks list 1994 callbacks = [], 1995 // stored [ context , args ] 1996 fired, 1997 // to avoid firing when already doing so 1998 firing, 1999 // flag to know if the deferred has been cancelled 2000 cancelled, 2001 // the deferred itself 2002 deferred = { 2003 2004 // done( f1, f2, ...) 2005 done: function() { 2006 if ( !cancelled ) { 2007 var args = arguments, 2008 i, 2009 length, 2010 elem, 2011 type, 2012 _fired; 2013 if ( fired ) { 2014 _fired = fired; 2015 fired = 0; 2016 } 2017 for ( i = 0, length = args.length; i < length; i++ ) { 2018 elem = args[ i ]; 2019 type = jQuery.type( elem ); 2020 if ( type === "array" ) { 2021 deferred.done.apply( deferred, elem ); 2022 } else if ( type === "function" ) { 2023 callbacks.push( elem ); 2024 } 2025 } 2026 if ( _fired ) { 2027 deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] ); 2028 } 2029 } 2030 return this; 2031 }, 2032 2033 // resolve with given context and args 2034 resolveWith: function( context, args ) { 2035 if ( !cancelled && !fired && !firing ) { 2036 // make sure args are available (#8421) 2037 args = args || []; 2038 firing = 1; 2039 try { 2040 while( callbacks[ 0 ] ) { 2041 callbacks.shift().apply( context, args ); 2042 } 2043 } 2044 finally { 2045 fired = [ context, args ]; 2046 firing = 0; 2047 } 2048 } 2049 return this; 2050 }, 2051 2052 // resolve with this as context and given arguments 2053 resolve: function() { 2054 deferred.resolveWith( this, arguments ); 2055 return this; 2056 }, 2057 2058 // Has this deferred been resolved? 2059 isResolved: function() { 2060 return !!( firing || fired ); 2061 }, 2062 2063 // Cancel 2064 cancel: function() { 2065 cancelled = 1; 2066 callbacks = []; 2067 return this; 2068 } 2069 }; 2070 2071 return deferred; 2072 }, 2073 2074 // Full fledged deferred (two callbacks list) 2075 Deferred: function( func ) { 2076 var deferred = jQuery._Deferred(), 2077 failDeferred = jQuery._Deferred(), 2078 promise; 2079 // Add errorDeferred methods, then and promise 2080 jQuery.extend( deferred, { 2081 then: function( doneCallbacks, failCallbacks ) { 2082 deferred.done( doneCallbacks ).fail( failCallbacks ); 2083 return this; 2084 }, 2085 fail: failDeferred.done, 2086 rejectWith: failDeferred.resolveWith, 2087 reject: failDeferred.resolve, 2088 isRejected: failDeferred.isResolved, 2089 // Get a promise for this deferred 2090 // If obj is provided, the promise aspect is added to the object 2091 promise: function( obj ) { 2092 if ( obj == null ) { 2093 if ( promise ) { 2094 return promise; 2095 } 2096 promise = obj = {}; 2097 } 2098 var i = promiseMethods.length; 2099 while( i-- ) { 2100 obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ]; 2101 } 2102 return obj; 2103 } 2104 } ); 2105 // Make sure only one callback list will be used 2106 deferred.done( failDeferred.cancel ).fail( deferred.cancel ); 2107 // Unexpose cancel 2108 delete deferred.cancel; 2109 // Call given func if any 2110 if ( func ) { 2111 func.call( deferred, deferred ); 2112 } 2113 return deferred; 2114 }, 2115 2116 // Deferred helper 2117 when: function( firstParam ) { 2118 var args = arguments, 2119 i = 0, 2120 length = args.length, 2121 count = length, 2122 deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? 2123 firstParam : 2124 jQuery.Deferred(); 2125 function resolveFunc( i ) { 2126 return function( value ) { 2127 args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; 2128 if ( !( --count ) ) { 2129 // Strange bug in FF4: 2130 // Values changed onto the arguments object sometimes end up as undefined values 2131 // outside the $.when method. Cloning the object into a fresh array solves the issue 2132 deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) ); 2133 } 2134 }; 2135 } 2136 if ( length > 1 ) { 2137 for( ; i < length; i++ ) { 2138 if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) { 2139 args[ i ].promise().then( resolveFunc(i), deferred.reject ); 2140 } else { 2141 --count; 2142 } 2143 } 2144 if ( !count ) { 2145 deferred.resolveWith( deferred, args ); 2146 } 2147 } else if ( deferred !== firstParam ) { 2148 deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); 2149 } 2150 return deferred.promise(); 2151 } 2152}); 2153 2154 2155 2156 2157(function() { 2158 2159 jQuery.support = {}; 2160 2161 var div = document.createElement("div"); 2162 2163 div.style.display = "none"; 2164 div.innerHTML = " <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a>
2164<input type='checkbox'/>"; 2165 2166 var all = div.getElementsByTagName("*"), 2167 a = div.getElementsByTagName("a")[0], 2168 select = document.createElement("select"), 2169 opt = select.appendChild( document.createElement("option") ), 2170 input = div.getElementsByTagName("input")[0]; 2171 2172 // Can't get basic test support 2173 if ( !all || !all.length || !a ) { 2174 return; 2175 } 2176 2177 jQuery.support = { 2178 // IE strips leading whitespace when .innerHTML is used 2179 leadingWhitespace: div.firstChild.nodeType === 3, 2180 2181 // Make sure that tbody elements aren't automatically inserted 2182 // IE will insert them into empty tables 2183 tbody: !div.getElementsByTagName("tbody").length, 2184 2185 // Make sure that link elements get serialized correctly by innerHTML 2186 // This requires a wrapper element in IE 2187 htmlSerialize: !!div.getElementsByTagName("link").length, 2188 2189 // Get the style information from getAttribute 2190 // (IE uses .cssText insted) 2191 style: /red/.test( a.getAttribute("style") ), 2192 2193 // Make sure that URLs aren't manipulated 2194 // (IE normalizes it by default) 2195 hrefNormalized: a.getAttribute("href") === "/a", 2196 2197 // Make sure that element opacity exists 2198 // (IE uses filter instead) 2199 // Use a regex to work around a WebKit issue. See #5145 2200 opacity: /^0.55$/.test( a.style.opacity ), 2201 2202 // Verify style float existence 2203 // (IE uses styleFloat instead of cssFloat) 2204 cssFloat: !!a.style.cssFloat, 2205 2206 // Make sure that if no value is specified for a checkbox 2207 // that it defaults to "on". 2208 // (WebKit defaults to "" instead) 2209 checkOn: input.value === "on", 2210 2211 // Make sure that a selected-by-default option has a working selected property. 2212 // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) 2213 optSelected: opt.selected, 2214 2215 // Will be defined later 2216 deleteExpando: true, 2217 optDisabled: false, 2218 checkClone: false, 2219 noCloneEvent: true, 2220 noCloneChecked: true, 2221 boxModel: null, 2222 inlineBlockNeedsLayout: false, 2223 shrinkWrapBlocks: false, 2224 reliableHiddenOffsets: true, 2225 reliableMarginRight: true 2226 }; 2227 2228 input.checked = true; 2229 jQuery.support.noCloneChecked = input.cloneNode( true ).checked; 2230 2231 // Make sure that the options inside disabled selects aren't marked as disabled 2232 // (WebKit marks them as diabled) 2233 select.disabled = true; 2234 jQuery.support.optDisabled = !opt.disabled; 2235 2236 var _scriptEval = null; 2237 jQuery.support.scriptEval = function() { 2238 if ( _scriptEval === null ) { 2239 var root = document.documentElement, 2240 script = document.createElement("script"), 2241 id = "script" + jQuery.now(); 2242 2243 // Make sure that the execution of code works by injecting a script 2244 // tag with appendChild/createTextNode 2245 // (IE doesn't support this, fails, and uses .text instead) 2246 try { 2247 script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); 2248 } catch(e) {} 2249 2250 root.insertBefore( script, root.firstChild ); 2251 2252 if ( window[ id ] ) { 2253 _scriptEval = true; 2254 delete window[ id ]; 2255 } else { 2256 _scriptEval = false; 2257 } 2258 2259 root.removeChild( script ); 2260 } 2261 2262 return _scriptEval; 2263 }; 2264 2265 // Test to see if it's possible to delete an expando from an element 2266 // Fails in Internet Explorer 2267 try { 2268 delete div.test; 2269 2270 } catch(e) { 2271 jQuery.support.deleteExpando = false; 2272 } 2273 2274 if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { 2275 div.attachEvent("onclick", function click() { 2276 // Cloning a node shouldn't copy over any 2277 // bound event handlers (IE does this) 2278 jQuery.support.noCloneEvent = false; 2279 div.detachEvent("onclick", click); 2280 }); 2281 div.cloneNode(true).fireEvent("onclick"); 2282 } 2283 2284 div = document.createElement("div"); 2285 div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>"; 2286 2287 var fragment = document.createDocumentFragment(); 2288 fragment.appendChild( div.firstChild ); 2289 2290 // WebKit doesn't clone checked state correctly in fragments 2291 jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; 2292 2293 // Figure out if the W3C box model works as expected 2294 // document.body must exist before we can do this 2295 jQuery(function() { 2296 var div = document.createElement("div"), 2297 body = document.getElementsByTagName("body")[0]; 2298 2299 // Frameset documents with no body should not run this code 2300 if ( !body ) { 2301 return; 2302 } 2303 2304 div.style.width = div.style.paddingLeft = "1px"; 2305 body.appendChild( div ); 2306 jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; 2307 2308 if ( "zoom" in div.style ) { 2309 // Check if natively block-level elements act like inline-block 2310 // elements when setting their display to 'inline' and giving 2311 // them layout 2312 // (IE < 8 does this) 2313 div.style.display = "inline"; 2314 div.style.zoom = 1; 2315 jQuery.support.inlineBlockNeedsLayout = div.offsetWidth === 2; 2316 2317 // Check if elements with layout shrink-wrap their children 2318 // (IE 6 does this) 2319 div.style.display = ""; 2320 div.innerHTML = "<div style='width:4px;'></div>"; 2321 jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2; 2322 } 2323 2324 div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>"; 2325 var tds = div.getElementsByTagName("td"); 2326 2327 // Check if table cells still have offsetWidth/Height when they are set 2328 // to display:none and there are still other visible table cells in a 2329 // table row; if so, offsetWidth/Height are not reliable for use when 2330 // determining if an element has been hidden directly using 2331 // display:none (it is still safe to use offsets if a parent element is 2332 // hidden; don safety goggles and see bug #4512 for more information). 2333 // (only IE 8 fails this test) 2334 jQuery.support.reliableHiddenOffsets = tds[0].offsetHeight === 0; 2335 2336 tds[0].style.display = ""; 2337 tds[1].style.display = "none"; 2338 2339 // Check if empty table cells still have offsetWidth/Height 2340 // (IE < 8 fail this test) 2341 jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0; 2342 div.innerHTML = ""; 2343 2344 // Check if div with explicit width and no margin-right incorrectly 2345 // gets computed margin-right based on width of container. For more 2346 // info see bug #3333 2347 // Fails in WebKit before Feb 2011 nightlies 2348 // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right 2349 if ( document.defaultView && document.defaultView.getComputedStyle ) { 2350 div.style.width = "1px"; 2351 div.style.marginRight = "0"; 2352 jQuery.support.reliableMarginRight = ( parseInt(document.defaultView.getComputedStyle(div, null).marginRight, 10) || 0 ) === 0; 2353 } 2354 2355 body.removeChild( div ).style.display = "none"; 2356 div = tds = null; 2357 }); 2358 2359 // Technique from Juriy Zaytsev 2360 // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ 2361 var eventSupported = function( eventName ) { 2362 var el = document.createElement("div"); 2363 eventName = "on" + eventName; 2364 2365 // We only care about the case where non-standard event systems 2366 // are used, namely in IE. Short-circuiting here helps us to 2367 // avoid an eval call (in setAttribute) which can cause CSP 2368 // to go haywire. See: https://developer.mozilla.org/en/Security/CSP 2369 if ( !el.attachEvent ) { 2370 return true; 2371 } 2372 2373 var isSupported = (eventName in el); 2374 if ( !isSupported ) { 2375 el.setAttribute(eventName, "return;"); 2376 isSupported = typeof el[eventName] === "function"; 2377 } 2378 return isSupported; 2379 }; 2380 2381 jQuery.support.submitBubbles = eventSupported("submit"); 2382 jQuery.support.changeBubbles = eventSupported("change"); 2383 2384 // release memory in IE 2385 div = all = a = null; 2386})(); 2387 2388 2389 2390var rbrace = /^(?:\{.*\}|\[.*\])$/; 2391 2392jQuery.extend({ 2393 cache: {}, 2394 2395 // Please use with caution 2396 uuid: 0, 2397 2398 // Unique for each copy of jQuery on the page 2399 // Non-digits removed to match rinlinejQuery 2400 expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), 2401 2402 // The following elements throw uncatchable exceptions if you 2403 // attempt to add expando properties to them. 2404 noData: { 2405 "embed": true, 2406 // Ban all objects except for Flash (which handle expandos) 2407 "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", 2408 "applet": true 2409 }, 2410 2411 hasData: function( elem ) { 2412 elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; 2413 2414 return !!elem && !isEmptyDataObject( elem ); 2415 }, 2416 2417 data: function( elem, name, data, pvt /* Internal Use Only */ ) { 2418 if ( !jQuery.acceptData( elem ) ) { 2419 return; 2420 } 2421 2422 var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache, 2423 2424 // We have to handle DOM nodes and JS objects differently because IE6-7 2425 // can't GC object references properly across the DOM-JS boundary 2426 isNode = elem.nodeType, 2427 2428 // Only DOM nodes need the global jQuery cache; JS object data is 2429 // attached directly to the object so GC can occur automatically 2430 cache = isNode ? jQuery.cache : elem, 2431 2432 // Only defining an ID for JS objects if its cache already exists allows 2433 // the code to shortcut on the same path as a DOM node with no cache 2434 id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando; 2435 2436 // Avoid doing any more work than we need to when trying to get data on an 2437 // object that has no data at all 2438 if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) { 2439 return; 2440 } 2441 2442 if ( !id ) { 2443 // Only DOM nodes need a new unique ID for each element since their data 2444 // ends up in the global cache 2445 if ( isNode ) { 2446 elem[ jQuery.expando ] = id = ++jQuery.uuid; 2447 } else { 2448 id = jQuery.expando; 2449 } 2450 } 2451 2452 if ( !cache[ id ] ) { 2453 cache[ id ] = {}; 2454 2455 // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery 2456 // metadata on plain JS objects when the object is serialized using 2457 // JSON.stringify 2458 if ( !isNode ) { 2459 cache[ id ].toJSON = jQuery.noop; 2460 } 2461 } 2462
2463 // An object can be passed to jQuery.data instead of a key/value pair; this gets 2464 // shallow copied over onto the existing cache 2465 if ( typeof name === "object" || typeof name === "function" ) { 2466 if ( pvt ) { 2467 cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name); 2468 } else { 2469 cache[ id ] = jQuery.extend(cache[ id ], name); 2470 } 2471 } 2472 2473 thisCache = cache[ id ]; 2474 2475 // Internal jQuery data is stored in a separate object inside the object's data 2476 // cache in order to avoid key collisions between internal data and user-defined 2477 // data 2478 if ( pvt ) { 2479 if ( !thisCache[ internalKey ] ) { 2480 thisCache[ internalKey ] = {}; 2481 } 2482 2483 thisCache = thisCache[ internalKey ]; 2484 } 2485 2486 if ( data !== undefined ) { 2487 thisCache[ name ] = data; 2488 } 2489 2490 // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should 2491 // not attempt to inspect the internal events object using jQuery.data, as this 2492 // internal data object is undocumented and subject to change. 2493 if ( name === "events" && !thisCache[name] ) { 2494 return thisCache[ internalKey ] && thisCache[ internalKey ].events; 2495 } 2496 2497 return getByName ? thisCache[ name ] : thisCache; 2498 }, 2499 2500 removeData: function( elem, name, pvt /* Internal Use Only */ ) { 2501 if ( !jQuery.acceptData( elem ) ) { 2502 return; 2503 } 2504 2505 var internalKey = jQuery.expando, isNode = elem.nodeType, 2506 2507 // See jQuery.data for more information 2508 cache = isNode ? jQuery.cache : elem, 2509 2510 // See jQuery.data for more information 2511 id = isNode ? elem[ jQuery.expando ] : jQuery.expando; 2512 2513 // If there is already no cache entry for this object, there is no 2514 // purpose in continuing 2515 if ( !cache[ id ] ) { 2516 return; 2517 } 2518 2519 if ( name ) { 2520 var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ]; 2521 2522 if ( thisCache ) { 2523 delete thisCache[ name ]; 2524 2525 // If there is no data left in the cache, we want to continue 2526 // and let the cache object itself get destroyed 2527 if ( !isEmptyDataObject(thisCache) ) { 2528 return; 2529 } 2530 } 2531 } 2532 2533 // See jQuery.data for more information 2534 if ( pvt ) { 2535 delete cache[ id ][ internalKey ]; 2536 2537 // Don't destroy the parent cache unless the internal data object 2538 // had been the only thing left in it 2539 if ( !isEmptyDataObject(cache[ id ]) ) { 2540 return; 2541 } 2542 } 2543 2544 var internalCache = cache[ id ][ internalKey ]; 2545 2546 // Browsers that fail expando deletion also refuse to delete expandos on 2547 // the window, but it will allow it on all other JS objects; other browsers 2548 // don't care 2549 if ( jQuery.support.deleteExpando || cache != window ) { 2550 delete cache[ id ]; 2551 } else { 2552 cache[ id ] = null; 2553 } 2554 2555 // We destroyed the entire user cache at once because it's faster than 2556 // iterating through each key, but we need to continue to persist internal 2557 // data if it existed 2558 if ( internalCache ) { 2559 cache[ id ] = {}; 2560 // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery 2561 // metadata on plain JS objects when the object is serialized using 2562 // JSON.stringify 2563 if ( !isNode ) { 2564 cache[ id ].toJSON = jQuery.noop; 2565 } 2566 2567 cache[ id ][ internalKey ] = internalCache; 2568 2569 // Otherwise, we need to eliminate the expando on the node to avoid 2570 // false lookups in the cache for entries that no longer exist 2571 } else if ( isNode ) { 2572 // IE does not allow us to delete expando properties from nodes, 2573 // nor does it have a removeAttribute function on Document nodes; 2574 // we must handle all of these cases 2575 if ( jQuery.support.deleteExpando ) { 2576 delete elem[ jQuery.expando ]; 2577 } else if ( elem.removeAttribute ) { 2578 elem.removeAttribute( jQuery.expando ); 2579 } else { 2580 elem[ jQuery.expando ] = null; 2581 } 2582 } 2583 }, 2584 2585 // For internal use only. 2586 _data: function( elem, name, data ) { 2587 return jQuery.data( elem, name, data, true ); 2588 }, 2589 2590 // A method for determining if a DOM node can handle the data expando 2591 acceptData: function( elem ) { 2592 if ( elem.nodeName ) { 2593 var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; 2594 2595 if ( match ) { 2596 return !(match === true || elem.getAttribute("classid") !== match); 2597 } 2598 } 2599 2600 return true; 2601 } 2602}); 2603 2604jQuery.fn.extend({ 2605 data: function( key, value ) { 2606 var data = null; 2607 2608 if ( typeof key === "undefined" ) { 2609 if ( this.length ) { 2610 data = jQuery.data( this[0] ); 2611 2612 if ( this[0].nodeType === 1 ) { 2613 var attr = this[0].attributes, name; 2614 for ( var i = 0, l = attr.length; i < l; i++ ) { 2615 name = attr[i].name; 2616
2617 if ( name.indexOf( "data-" ) === 0 ) { 2618 name = name.substr( 5 ); 2619 dataAttr( this[0], name, data[ name ] ); 2620 } 2621 } 2622 } 2623 } 2624 2625 return data; 2626 2627 } else if ( typeof key === "object" ) { 2628 return this.each(function() { 2629 jQuery.data( this, key ); 2630 }); 2631 } 2632 2633 var parts = key.split("."); 2634 parts[1] = parts[1] ? "." + parts[1] : ""; 2635 2636 if ( value === undefined ) { 2637 data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); 2638 2639 // Try to fetch any internally stored data first 2640 if ( data === undefined && this.length ) { 2641 data = jQuery.data( this[0], key ); 2642 data = dataAttr( this[0], key, data ); 2643 } 2644 2645 return data === undefined && parts[1] ? 2646 this.data( parts[0] ) : 2647 data; 2648 2649 } else { 2650 return this.each(function() { 2651 var $this = jQuery( this ), 2652 args = [ parts[0], value ]; 2653 2654 $this.triggerHandler( "setData" + parts[1] + "!", args ); 2655 jQuery.data( this, key, value ); 2656 $this.triggerHandler( "changeData" + parts[1] + "!", args ); 2657 }); 2658 } 2659 }, 2660 2661 removeData: function( key ) { 2662 return this.each(function() { 2663 jQuery.removeData( this, key ); 2664 }); 2665 } 2666}); 2667 2668function dataAttr( elem, key, data ) { 2669 // If nothing was found internally, try to fetch any 2670 // data from the HTML5 data-* attribute 2671 if ( data === undefined && elem.nodeType === 1 ) { 2672 data = elem.getAttribute( "data-" + key ); 2673 2674 if ( typeof data === "string" ) { 2675 try { 2676 data = data === "true" ? true : 2677 data === "false" ? false : 2678 data === "null" ? null : 2679 !jQuery.isNaN( data ) ? parseFloat( data ) : 2680 rbrace.test( data ) ? jQuery.parseJSON( data ) : 2681 data; 2682 } catch( e ) {} 2683 2684 // Make sure we set the data so it isn't changed later 2685 jQuery.data( elem, key, data ); 2686 2687 } else { 2688 data = undefined; 2689 } 2690 } 2691 2692 return data; 2693} 2694 2695// TODO: This is a hack for 1.5 ONLY to allow objects with a single toJSON 2696// property to be considered empty objects; this property always exists in 2697// order to make sure JSON.stringify does not expose internal metadata 2698function isEmptyDataObject( obj ) { 2699 for ( var name in obj ) { 2700 if ( name !== "toJSON" ) { 2701 return false; 2702 } 2703 } 2704 2705 return true; 2706} 2707 2708 2709 2710 2711jQuery.extend({ 2712 queue: function( elem, type, data ) { 2713 if ( !elem ) { 2714 return; 2715 } 2716 2717 type = (type || "fx") + "queue"; 2718 var q = jQuery._data( elem, type ); 2719 2720 // Speed up dequeue by getting out quickly if this is just a lookup 2721 if ( !data ) { 2722 return q || []; 2723 } 2724 2725 if ( !q || jQuery.isArray(data) ) { 2726 q = jQuery._data( elem, type, jQuery.makeArray(data) ); 2727 2728 } else { 2729 q.push( data ); 2730 } 2731 2732 return q; 2733 }, 2734 2735 dequeue: function( elem, type ) { 2736 type = type || "fx"; 2737 2738 var queue = jQuery.queue( elem, type ), 2739 fn = queue.shift(); 2740 2741 // If the fx queue is dequeued, always remove the progress sentinel 2742 if ( fn === "inprogress" ) { 2743 fn = queue.shift(); 2744 } 2745 2746 if ( fn ) { 2747 // Add a progress sentinel to prevent the fx queue from being 2748 // automatically dequeued 2749 if ( type === "fx" ) { 2750 queue.unshift("inprogress"); 2751 } 2752 2753 fn.call(elem, function() { 2754 jQuery.dequeue(elem, type); 2755 }); 2756 } 2757 2758 if ( !queue.length ) { 2759 jQuery.removeData( elem, type + "queue", true ); 2760 } 2761 } 2762}); 2763 2764jQuery.fn.extend({ 2765 queue: function( type, data ) { 2766 if ( typeof type !== "string" ) { 2767 data = type; 2768 type = "fx"; 2769 } 2770 2771 if ( data === undefined ) { 2772 return jQuery.queue( this[0], type ); 2773 } 2774 return this.each(function( i ) { 2775 var queue = jQuery.queue( this, type, data ); 2776 2777 if ( type === "fx" && queue[0] !== "inprogress" ) { 2778 jQuery.dequeue( this, type ); 2779 } 2780 }); 2781 }, 2782 dequeue: function( type ) { 2783 return this.each(function() { 2784 jQuery.dequeue( this, type ); 2785 }); 2786 }, 2787 2788 // Based off of the plugin by Clint Helfers, with permission. 2789 // http://blindsignals.com/index.php/2009/07/jquery-delay/ 2790 delay: function( time, type ) { 2791 time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; 2792 type = type || "fx"; 2793 2794 return this.queue( type, function() { 2795 var elem = this; 2796 setTimeout(function() { 2797 jQuery.dequeue( elem, type ); 2798 }, time ); 2799 }); 2800 }, 2801 2802 clearQueue: function( type ) { 2803 return this.queue( type || "fx", [] ); 2804 } 2805}); 2806 2807 2808 2809 2810var rclass = /[\n\t\r]/g, 2811 rspaces = /\s+/, 2812 rreturn = /\r/g, 2813 rspecialurl = /^(?:href|src|style)$/, 2814 rtype = /^(?:button|input)$/i, 2815 rfocusable = /^(?:button|input|object|select|textarea)$/i, 2816 rclickable = /^a(?:rea)?$/i, 2817 rradiocheck = /^(?:radio|checkbox)$/i; 2818 2819jQuery.props = { 2820 "for": "htmlFor", 2821 "class": "className", 2822 readonly: "readOnly", 2823 maxlength: "maxLength", 2824 cellspacing: "cellSpacing", 2825 rowspan: "rowSpan", 2826 colspan: "colSpan", 2827 tabindex: "tabIndex", 2828 usemap: "useMap", 2829 frameborder: "frameBorder" 2830}; 2831 2832jQuery.fn.extend({ 2833 attr: function( name, value ) { 2834 return jQuery.access( this, name, value, true, jQuery.attr ); 2835 }, 2836 2837 removeAttr: function( name, fn ) { 2838 return this.each(function(){ 2839 jQuery.attr( this, name, "" ); 2840 if ( this.nodeType === 1 ) { 2841 this.removeAttribute( name ); 2842 } 2843 }); 2844 }, 2845 2846 addClass: function( value ) { 2847 if ( jQuery.isFunction(value) ) { 2848 return this.each(function(i) { 2849 var self = jQuery(this); 2850 self.addClass( value.call(this, i, self.attr("class")) ); 2851 }); 2852 } 2853 2854 if ( value && typeof value === "string" ) { 2855 var classNames = (value || "").split( rspaces ); 2856 2857 for ( var i = 0, l = this.length; i < l; i++ ) { 2858 var elem = this[i]; 2859 2860 if ( elem.nodeType === 1 ) { 2861 if ( !elem.className ) { 2862 elem.className = value; 2863 2864 } else { 2865 var className = " " + elem.className + " ", 2866 setClass = elem.className; 2867 2868 for ( var c = 0, cl = classNames.length; c < cl; c++ ) { 2869 if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { 2870 setClass += " " + classNames[c]; 2871 } 2872 } 2873 elem.className = jQuery.trim( setClass ); 2874 } 2875 } 2876 } 2877 } 2878 2879 return this; 2880 }, 2881 2882 removeClass: function( value ) { 2883 if ( jQuery.isFunction(value) ) { 2884 return this.each(function(i) { 2885 var self = jQuery(this); 2886 self.removeClass( value.call(this, i, self.attr("class")) ); 2887 }); 2888 } 2889 2890 if ( (value && typeof value === "string") || value === undefined ) { 2891 var classNames = (value || "").split( rspaces ); 2892 2893 for ( var i = 0, l = this.length; i < l; i++ ) { 2894 var elem = this[i]; 2895 2896 if ( elem.nodeType === 1 && elem.className ) { 2897 if ( value ) { 2898 var className = (" " + elem.className + " ").replace(rclass, " "); 2899 for ( var c = 0, cl = classNames.length; c < cl; c++ ) { 2900 className = className.replace(" " + classNames[c] + " ", " "); 2901 } 2902 elem.className = jQuery.trim( className ); 2903 2904 } else { 2905 elem.className = ""; 2906 } 2907 } 2908 } 2909 } 2910 2911 return this; 2912 }, 2913 2914 toggleClass: function( value, stateVal ) { 2915 var type = typeof value, 2916 isBool = typeof stateVal === "boolean"; 2917 2918 if ( jQuery.isFunction( value ) ) { 2919 return this.each(function(i) { 2920 var self = jQuery(this); 2921 self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); 2922 }); 2923 } 2924 2925 return this.each(function() { 2926 if ( type === "string" ) { 2927 // toggle individual class names 2928 var className, 2929 i = 0, 2930 self = jQuery( this ), 2931 state = stateVal, 2932 classNames = value.split( rspaces ); 2933 2934 while ( (className = classNames[ i++ ]) ) { 2935 // check each className given, space seperated list 2936 state = isBool ? state : !self.hasClass( className ); 2937 self[ state ? "addClass" : "removeClass" ]( className ); 2938 } 2939 2940 } else if ( type === "undefined" || type === "boolean" ) { 2941 if ( this.className ) { 2942 // store className if set 2943 jQuery._data( this, "__className__", this.className ); 2944 } 2945 2946 // toggle whole className 2947 this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; 2948 } 2949 }); 2950 }, 2951 2952 hasClass: function( selector ) { 2953 var className = " " + selector + " "; 2954 for ( var i = 0, l = this.length; i < l; i++ ) { 2955 if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { 2956 return true; 2957 } 2958 } 2959 2960 return false;
2961 }, 2962 2963 val: function( value ) { 2964 if ( !arguments.length ) { 2965 var elem = this[0]; 2966 2967 if ( elem ) { 2968 if ( jQuery.nodeName( elem, "option" ) ) { 2969 // attributes.value is undefined in Blackberry 4.7 but 2970 // uses .value. See #6932 2971 var val = elem.attributes.value; 2972 return !val || val.specified ? elem.value : elem.text; 2973 } 2974 2975 // We need to handle select boxes special 2976 if ( jQuery.nodeName( elem, "select" ) ) { 2977 var index = elem.selectedIndex, 2978 values = [], 2979 options = elem.options, 2980 one = elem.type === "select-one"; 2981 2982 // Nothing was selected 2983 if ( index < 0 ) { 2984 return null; 2985 } 2986 2987 // Loop through all the selected options 2988 for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { 2989 var option = options[ i ]; 2990 2991 // Don't return options that are disabled or in a disabled optgroup 2992 if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && 2993 (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { 2994 2995 // Get the specific value for the option 2996 value = jQuery(option).val(); 2997 2998 // We don't need an array for one selects 2999 if ( one ) { 3000 return value; 3001 } 3002 3003 // Multi-Selects return an array 3004 values.push( value ); 3005 } 3006 } 3007 3008 // Fixes Bug #2551 -- select.val() broken in IE after form.reset() 3009 if ( one && !values.length && options.length ) { 3010 return jQuery( options[ index ] ).val(); 3011 } 3012 3013 return values; 3014 } 3015 3016 // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified 3017 if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { 3018 return elem.getAttribute("value") === null ? "on" : elem.value; 3019 } 3020 3021 // Everything else, we just grab the value 3022 return (elem.value || "").replace(rreturn, ""); 3023 3024 } 3025 3026 return undefined; 3027 } 3028 3029 var isFunction = jQuery.isFunction(value); 3030 3031 return this.each(function(i) { 3032 var self = jQuery(this), val = value; 3033 3034 if ( this.nodeType !== 1 ) { 3035 return; 3036 } 3037 3038 if ( isFunction ) { 3039 val = value.call(this, i, self.val()); 3040 } 3041 3042 // Treat null/undefined as ""; convert numbers to string 3043 if ( val == null ) { 3044 val = ""; 3045 } else if ( typeof val === "number" ) { 3046 val += ""; 3047 } else if ( jQuery.isArray(val) ) { 3048 val = jQuery.map(val, function (value) { 3049 return value == null ? "" : value + ""; 3050 }); 3051 } 3052 3053 if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { 3054 this.checked = jQuery.inArray( self.val(), val ) >= 0; 3055 3056 } else if ( jQuery.nodeName( this, "select" ) ) { 3057 var values = jQuery.makeArray(val); 3058 3059 jQuery( "option", this ).each(function() { 3060 this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; 3061 }); 3062 3063 if ( !values.length ) { 3064 this.selectedIndex = -1; 3065 } 3066 3067 } else { 3068 this.value = val; 3069 } 3070 }); 3071 } 3072}); 3073 3074jQuery.extend({ 3075 attrFn: { 3076 val: true, 3077 css: true, 3078 html: true, 3079 text: true, 3080 data: true, 3081 width: true, 3082 height: true, 3083 offset: true 3084 }, 3085 3086 attr: function( elem, name, value, pass ) { 3087 // don't get/set attributes on text, comment and attribute nodes 3088 if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || elem.nodeType === 2 ) { 3089 return undefined; 3090 } 3091 3092 if ( pass && name in jQuery.attrFn ) { 3093 return jQuery(elem)[name](value); 3094 } 3095 3096 var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), 3097 // Whether we are setting (or getting) 3098 set = value !== undefined; 3099 3100 // Try to normalize/fix the name 3101 name = notxml && jQuery.props[ name ] || name; 3102 3103 // Only do all the following if this is a node (faster for style) 3104 if ( elem.nodeType === 1 ) { 3105 // These attributes require special treatment 3106 var special = rspecialurl.test( name ); 3107 3108 // Safari mis-reports the default selected property of an option 3109 // Accessing the parent's selectedIndex property fixes it 3110 if ( name === "selected" && !jQuery.support.optSelected ) { 3111 var parent = elem.parentNode; 3112 if ( parent ) { 3113 parent.selectedIndex; 3114 3115 // Make sure that it also works with optgroups, see #5701 3116 if ( parent.parentNode ) { 3117 parent.parentNode.selectedIndex; 3118 } 3119 } 3120 } 3121 3122 // If applicable, access the attribute via the DOM 0 way 3123 // 'in' checks fail in Blackberry 4.7 #6931 3124 if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { 3125 if ( set ) { 3126 // We can't allow the type property to be changed (since it causes problems in IE)
3127 if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { 3128 jQuery.error( "type property can't be changed" ); 3129 } 3130 3131 if ( value === null ) { 3132 if ( elem.nodeType === 1 ) { 3133 elem.removeAttribute( name ); 3134 } 3135 3136 } else { 3137 elem[ name ] = value; 3138 } 3139 } 3140 3141 // browsers index elements by id/name on forms, give priority to attributes. 3142 if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { 3143 return elem.getAttributeNode( name ).nodeValue; 3144 } 3145 3146 // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set 3147 // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ 3148 if ( name === "tabIndex" ) { 3149 var attributeNode = elem.getAttributeNode( "tabIndex" ); 3150 3151 return attributeNode && attributeNode.specified ? 3152 attributeNode.value : 3153 rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? 3154 0 : 3155 undefined; 3156 } 3157 3158 return elem[ name ]; 3159 } 3160 3161 if ( !jQuery.support.style && notxml && name === "style" ) { 3162 if ( set ) { 3163 elem.style.cssText = "" + value; 3164 } 3165 3166 return elem.style.cssText; 3167 } 3168 3169 if ( set ) { 3170 // convert the value to a string (all browsers do this but IE) see #1070 3171 elem.setAttribute( name, "" + value ); 3172 } 3173 3174 // Ensure that missing attributes return undefined 3175 // Blackberry 4.7 returns "" from getAttribute #6938 3176 if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { 3177 return undefined; 3178 } 3179 3180 var attr = !jQuery.support.hrefNormalized && notxml && special ? 3181 // Some attributes require a special call on IE 3182 elem.getAttribute( name, 2 ) : 3183 elem.getAttribute( name ); 3184 3185 // Non-existent attributes return null, we normalize to undefined 3186 return attr === null ? undefined : attr; 3187 } 3188 // Handle everything which isn't a DOM element node 3189 if ( set ) { 3190 elem[ name ] = value; 3191 } 3192 return elem[ name ]; 3193 } 3194}); 3195 3196 3197 3198 3199var rnamespaces = /\.(.*)$/, 3200 rformElems = /^(?:textarea|input|select)$/i, 3201 rperiod = /\./g, 3202 rspace = / /g, 3203 rescape = /[^\w\s.|`]/g, 3204 fcleanup = function( nm ) { 3205 return nm.replace(rescape, "\\$&"); 3206 }; 3207 3208/* 3209 * A number of helper functions used for managing events. 3210 * Many of the ideas behind this code originated from 3211 * Dean Edwards' addEvent library. 3212 */ 3213jQuery.event = { 3214 3215 // Bind an event to an element 3216 // Original by Dean Edwards 3217 add: function( elem, types, handler, data ) { 3218 if ( elem.nodeType === 3 || elem.nodeType === 8 ) { 3219 return; 3220 } 3221 3222 // TODO :: Use a try/catch until it's safe to pull this out (likely 1.6) 3223 // Minor release fix for bug #8018 3224 try { 3225 // For whatever reason, IE has trouble passing the window object 3226 // around, causing it to be cloned in the process 3227 if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) { 3228 elem = window; 3229 } 3230 } 3231 catch ( e ) {} 3232 3233 if ( handler === false ) { 3234 handler = returnFalse; 3235 } else if ( !handler ) { 3236 // Fixes bug #7229. Fix recommended by jdalton 3237 return; 3238 } 3239 3240 var handleObjIn, handleObj; 3241 3242 if ( handler.handler ) { 3243 handleObjIn = handler; 3244 handler = handleObjIn.handler; 3245 } 3246 3247 // Make sure that the function being executed has a unique ID 3248 if ( !handler.guid ) { 3249 handler.guid = jQuery.guid++; 3250 } 3251 3252 // Init the element's event structure 3253 var elemData = jQuery._data( elem ); 3254 3255 // If no elemData is found then we must be trying to bind to one of the 3256 // banned noData elements 3257 if ( !elemData ) { 3258 return; 3259 } 3260 3261 var events = elemData.events, 3262 eventHandle = elemData.handle; 3263 3264 if ( !events ) { 3265 elemData.events = events = {}; 3266 } 3267 3268 if ( !eventHandle ) { 3269 elemData.handle = eventHandle = function( e ) { 3270 // Handle the second event of a trigger and when 3271 // an event is called after a page has unloaded 3272 return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? 3273 jQuery.event.handle.apply( eventHandle.elem, arguments ) : 3274 undefined; 3275 }; 3276 } 3277 3278 // Add elem as a property of the handle function 3279 // This is to prevent a memory leak with non-native events in IE. 3280 eventHandle.elem = elem; 3281 3282 // Handle multiple events separated by a space 3283 // jQuery(...).bind("mouseover mouseout", fn); 3284 types = types.split(" "); 3285 3286 var type, i = 0, namespaces; 3287 3288 while ( (type = types[ i++ ]) ) { 3289 handleObj = handleObjIn ? 3290 jQuery.extend({}, handleObjIn) : 3291 { handler: handler, data: data }; 3292 3293 // Namespaced event handlers 3294 if ( type.indexOf(".") > -1 ) { 3295 namespaces = type.split("."); 3296 type = namespaces.shift(); 3297 handleObj.namespace = namespaces.slice(0).sort().join("."); 3298 3299 } else { 3300 namespaces = []; 3301 handleObj.namespace = ""; 3302 } 3303 3304 handleObj.type = type; 3305 if ( !handleObj.guid ) { 3306 handleObj.guid = handler.guid; 3307 } 3308
3309 // Get the current list of functions bound to this event 3310 var handlers = events[ type ], 3311 special = jQuery.event.special[ type ] || {}; 3312 3313 // Init the event handler queue 3314 if ( !handlers ) { 3315 handlers = events[ type ] = []; 3316 3317 // Check for a special event handler 3318 // Only use addEventListener/attachEvent if the special 3319 // events handler returns false 3320 if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { 3321 // Bind the global event handler to the element 3322 if ( elem.addEventListener ) { 3323 elem.addEventListener( type, eventHandle, false ); 3324 3325 } else if ( elem.attachEvent ) { 3326 elem.attachEvent( "on" + type, eventHandle ); 3327 } 3328 } 3329 } 3330 3331 if ( special.add ) { 3332 special.add.call( elem, handleObj ); 3333 3334 if ( !handleObj.handler.guid ) { 3335 handleObj.handler.guid = handler.guid; 3336 } 3337 } 3338 3339 // Add the function to the element's handler list 3340 handlers.push( handleObj ); 3341 3342 // Keep track of which events have been used, for global triggering 3343 jQuery.event.global[ type ] = true; 3344 } 3345 3346 // Nullify elem to prevent memory leaks in IE 3347 elem = null; 3348 }, 3349 3350 global: {}, 3351 3352 // Detach an event or set of events from an element 3353 remove: function( elem, types, handler, pos ) { 3354 // don't do events on text and comment nodes 3355 if ( elem.nodeType === 3 || elem.nodeType === 8 ) { 3356 return; 3357 } 3358 3359 if ( handler === false ) { 3360 handler = returnFalse; 3361 } 3362 3363 var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, 3364 elemData = jQuery.hasData( elem ) && jQuery._data( elem ), 3365 events = elemData && elemData.events; 3366 3367 if ( !elemData || !events ) { 3368 return; 3369 } 3370 3371 // types is actually an event object here 3372 if ( types && types.type ) { 3373 handler = types.handler; 3374 types = types.type; 3375 } 3376 3377 // Unbind all events for the element 3378 if ( !types || typeof types === "string" && types.charAt(0) === "." ) { 3379 types = types || ""; 3380 3381 for ( type in events ) { 3382 jQuery.event.remove( elem, type + types ); 3383 } 3384 3385 return; 3386 } 3387 3388 // Handle multiple events separated by a space 3389 // jQuery(...).unbind("mouseover mouseout", fn); 3390 types = types.split(" "); 3391 3392 while ( (type = types[ i++ ]) ) { 3393 origType = type; 3394 handleObj = null; 3395 all = type.indexOf(".") < 0; 3396 namespaces = []; 3397 3398 if ( !all ) { 3399 // Namespaced event handlers 3400 namespaces = type.split("."); 3401 type = namespaces.shift(); 3402 3403 namespace = new RegExp("(^|\\.)" + 3404 jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); 3405 } 3406 3407 eventType = events[ type ]; 3408 3409 if ( !eventType ) { 3410 continue; 3411 } 3412 3413 if ( !handler ) { 3414 for ( j = 0; j < eventType.length; j++ ) { 3415 handleObj = eventType[ j ]; 3416 3417 if ( all || namespace.test( handleObj.namespace ) ) { 3418 jQuery.event.remove( elem, origType, handleObj.handler, j ); 3419 eventType.splice( j--, 1 ); 3420 } 3421 } 3422 3423 continue; 3424 } 3425 3426 special = jQuery.event.special[ type ] || {}; 3427 3428 for ( j = pos || 0; j < eventType.length; j++ ) { 3429 handleObj = eventType[ j ]; 3430 3431 if ( handler.guid === handleObj.guid ) { 3432 // remove the given handler for the given type 3433 if ( all || namespace.test( handleObj.namespace ) ) { 3434 if ( pos == null ) { 3435 eventType.splice( j--, 1 ); 3436 } 3437 3438 if ( special.remove ) { 3439 special.remove.call( elem, handleObj ); 3440 } 3441 } 3442 3443 if ( pos != null ) { 3444 break; 3445 } 3446 } 3447 } 3448 3449 // remove generic event handler if no more handlers exist 3450 if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { 3451 if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { 3452 jQuery.removeEvent( elem, type, elemData.handle ); 3453 } 3454 3455 ret = null; 3456 delete events[ type ]; 3457 } 3458 } 3459 3460 // Remove the expando if it's no longer used 3461 if ( jQuery.isEmptyObject( events ) ) { 3462 var handle = elemData.handle; 3463 if ( handle ) { 3464 handle.elem = null; 3465 } 3466 3467 delete elemData.events; 3468 delete elemData.handle; 3469 3470 if ( jQuery.isEmptyObject( elemData ) ) { 3471 jQuery.removeData( elem, undefined, true ); 3472 } 3473 } 3474 }, 3475 3476 // bubbling is internal 3477 trigger: function( event, data, elem /*, bubbling */ ) { 3478 // Event object or event type 3479 var type = event.type || event, 3480 bubbling = arguments[3]; 3481 3482 if ( !bubbling ) { 3483 event = typeof event === "object" ? 3484 // jQuery.Event object 3485 event[ jQuery.expando ] ? event : 3486 // Object literal 3487 jQuery.extend( jQuery.Event(type), event ) : 3488 // Just the event type (string) 3489 jQuery.Event(type); 3490 3491 if ( type.indexOf("!") >= 0 ) { 3492 event.type = type = type.slice(0, -1); 3493 event.exclusive = true; 3494 } 3495 3496 // Handle a global trigger 3497 if ( !elem ) { 3498 // Don't bubble custom events when global (to avoid too much overhead) 3499 event.stopPropagation(); 3500 3501 // Only trigger if we've ever bound an event for it 3502 if ( jQuery.event.global[ type ] ) { 3503 // XXX This code smells terrible. event.js should not be directly 3504 // inspecting the data cache 3505 jQuery.each( jQuery.cache, function() { 3506 // internalKey variable is just used to make it easier to find 3507 // and potentially change this stuff later; currently it just 3508 // points to jQuery.expando 3509 var internalKey = jQuery.expando, 3510 internalCache = this[ internalKey ]; 3511 if ( internalCache && internalCache.events && internalCache.events[ type ] ) { 3512 jQuery.event.trigger( event, data, internalCache.handle.elem ); 3513 } 3514 }); 3515 } 3516 } 3517 3518 // Handle triggering a single element 3519 3520 // don't do events on text and comment nodes 3521 if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { 3522 return undefined; 3523 } 3524 3525 // Clean up in case it is reused 3526 event.result = undefined; 3527 event.target = elem; 3528 3529 // Clone the incoming data, if any 3530 data = jQuery.makeArray( data ); 3531 data.unshift( event ); 3532 } 3533 3534 event.currentTarget = elem; 3535 3536 // Trigger the event, it is assumed that "handle" is a function 3537 var handle = jQuery._data( elem, "handle" ); 3538 3539 if ( handle ) { 3540 handle.apply( elem, data ); 3541 } 3542 3543 var parent = elem.parentNode || elem.ownerDocument; 3544 3545 // Trigger an inline bound script 3546 try { 3547 if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { 3548 if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { 3549 event.result = false;
3550 event.preventDefault(); 3551 } 3552 } 3553 3554 // prevent IE from throwing an error for some elements with some event types, see #3533 3555 } catch (inlineError) {} 3556 3557 if ( !event.isPropagationStopped() && parent ) { 3558 jQuery.event.trigger( event, data, parent, true ); 3559 3560 } else if ( !event.isDefaultPrevented() ) { 3561 var old, 3562 target = event.target, 3563 targetType = type.replace( rnamespaces, "" ), 3564 isClick = jQuery.nodeName( target, "a" ) && targetType === "click", 3565 special = jQuery.event.special[ targetType ] || {}; 3566 3567 if ( (!special._default || special._default.call( elem, event ) === false) && 3568 !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { 3569 3570 try { 3571 if ( target[ targetType ] ) { 3572 // Make sure that we don't accidentally re-trigger the onFOO events 3573 old = target[ "on" + targetType ]; 3574 3575 if ( old ) { 3576 target[ "on" + targetType ] = null; 3577 } 3578 3579 jQuery.event.triggered = event.type; 3580 target[ targetType ](); 3581 } 3582 3583 // prevent IE from throwing an error for some elements with some event types, see #3533 3584 } catch (triggerError) {} 3585 3586 if ( old ) { 3587 target[ "on" + targetType ] = old; 3588 } 3589 3590 jQuery.event.triggered = undefined; 3591 } 3592 } 3593 }, 3594 3595 handle: function( event ) { 3596 var all, handlers, namespaces, namespace_re, events, 3597 namespace_sort = [], 3598 args = jQuery.makeArray( arguments ); 3599 3600 event = args[0] = jQuery.event.fix( event || window.event ); 3601 event.currentTarget = this; 3602 3603 // Namespaced event handlers 3604 all = event.type.indexOf(".") < 0 && !event.exclusive; 3605 3606 if ( !all ) { 3607 namespaces = event.type.split("."); 3608 event.type = namespaces.shift(); 3609 namespace_sort = namespaces.slice(0).sort(); 3610 namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)"); 3611 } 3612 3613 event.namespace = event.namespace || namespace_sort.join("."); 3614 3615 events = jQuery._data(this, "events"); 3616 3617 handlers = (events || {})[ event.type ]; 3618 3619 if ( events && handlers ) { 3620 // Clone the handlers to prevent manipulation 3621 handlers = handlers.slice(0); 3622 3623 for ( var j = 0, l = handlers.length; j < l; j++ ) { 3624 var handleObj = handlers[ j ]; 3625 3626 // Filter the functions by class 3627 if ( all || namespace_re.test( handleObj.namespace ) ) { 3628 // Pass in a reference to the handler function itself 3629 // So that we can later remove it 3630 event.handler = handleObj.handler; 3631 event.data = handleObj.data; 3632 event.handleObj = handleObj; 3633 3634 var ret = handleObj.handler.apply( this, args ); 3635 3636 if ( ret !== undefined ) { 3637 event.result = ret; 3638 if ( ret === false ) { 3639 event.preventDefault(); 3640 event.stopPropagation(); 3641 } 3642 } 3643 3644 if ( event.isImmediatePropagationStopped() ) { 3645 break; 3646 } 3647 } 3648 } 3649 } 3650 3651 return event.result; 3652 }, 3653 3654 props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), 3655 3656 fix: function( event ) { 3657 if ( event[ jQuery.expando ] ) { 3658 return event; 3659 } 3660 3661 // store a copy of the original event object 3662 // and "clone" to set read-only properties 3663 var originalEvent = event; 3664 event = jQuery.Event( originalEvent ); 3665 3666 for ( var i = this.props.length, prop; i; ) { 3667 prop = this.props[ --i ]; 3668 event[ prop ] = originalEvent[ prop ]; 3669 } 3670 3671 // Fix target property, if necessary 3672 if ( !event.target ) { 3673 // Fixes #1925 where srcElement might not be defined either 3674 event.target = event.srcElement || document; 3675 } 3676 3677 // check if target is a textnode (safari) 3678 if ( event.target.nodeType === 3 ) { 3679 event.target = event.target.parentNode; 3680 } 3681 3682 // Add relatedTarget, if necessary 3683 if ( !event.relatedTarget && event.fromElement ) { 3684 event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; 3685 } 3686 3687 // Calculate pageX/Y if missing and clientX/Y available 3688 if ( event.pageX == null && event.clientX != null ) { 3689 var doc = document.documentElement, 3690 body = document.body; 3691 3692 event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); 3693 event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); 3694 } 3695 3696 // Add which for key events 3697 if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { 3698 event.which = event.charCode != null ? event.charCode : event.keyCode; 3699 } 3700 3701 // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) 3702 if ( !event.metaKey && event.ctrlKey ) { 3703 event.metaKey = event.ctrlKey; 3704 } 3705 3706 // Add which for click: 1 === left; 2 === middle; 3 === right 3707 // Note: button is not normalized, so don't use it 3708 if ( !event.which && event.button !== undefined ) { 3709 event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); 3710 } 3711 3712 return event; 3713 }, 3714 3715 // Deprecated, use jQuery.guid instead 3716 guid: 1E8, 3717 3718 // Deprecated, use jQuery.proxy instead 3719 proxy: jQuery.proxy, 3720 3721 special: { 3722 ready: { 3723 // Make sure the ready event is setup 3724 setup: jQuery.bindReady, 3725 teardown: jQuery.noop 3726 }, 3727 3728 live: { 3729 add: function( handleObj ) { 3730 jQuery.event.add( this, 3731 liveConvert( handleObj.origType, handleObj.selector ), 3732 jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); 3733 }, 3734 3735 remove: function( handleObj ) { 3736 jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); 3737 } 3738 }, 3739 3740 beforeunload: { 3741 setup: function( data, namespaces, eventHandle ) { 3742 // We only want to do this special case on windows 3743 if ( jQuery.isWindow( this ) ) { 3744 this.onbeforeunload = eventHandle; 3745 } 3746 }, 3747 3748 teardown: function( namespaces, eventHandle ) { 3749 if ( this.onbeforeunload === eventHandle ) { 3750 this.onbeforeunload = null; 3751 } 3752 } 3753 } 3754 } 3755}; 3756 3757jQuery.removeEvent = document.removeEventListener ? 3758 function( elem, type, handle ) { 3759 if ( elem.removeEventListener ) { 3760 elem.removeEventListener( type, handle, false ); 3761 } 3762 } : 3763 function( elem, type, handle ) { 3764 if ( elem.detachEvent ) { 3765 elem.detachEvent( "on" + type, handle ); 3766 } 3767 }; 3768 3769jQuery.Event = function( src ) { 3770 // Allow instantiation without the 'new' keyword 3771 if ( !this.preventDefault ) { 3772 return new jQuery.Event( src ); 3773 } 3774 3775 // Event object 3776 if ( src && src.type ) { 3777 this.originalEvent = src; 3778 this.type = src.type; 3779 3780 // Events bubbling up the document may have been marked as prevented 3781 // by a handler lower down the tree; reflect the correct value. 3782 this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || 3783 src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; 3784 3785 // Event type 3786 } else { 3787 this.type = src; 3788 } 3789 3790 // timeStamp is buggy for some events on Firefox(#3843) 3791 // So we won't rely on the native value 3792 this.timeStamp = jQuery.now(); 3793 3794 // Mark it as fixed 3795 this[ jQuery.expando ] = true; 3796}; 3797 3798function returnFalse() { 3799 return false;
3800} 3801function returnTrue() { 3802 return true; 3803} 3804 3805// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding 3806// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html 3807jQuery.Event.prototype = { 3808 preventDefault: function() { 3809 this.isDefaultPrevented = returnTrue; 3810 3811 var e = this.originalEvent; 3812 if ( !e ) { 3813 return; 3814 } 3815 3816 // if preventDefault exists run it on the original event 3817 if ( e.preventDefault ) { 3818 e.preventDefault(); 3819 3820 // otherwise set the returnValue property of the original event to false (IE) 3821 } else { 3822 e.returnValue = false; 3823 } 3824 }, 3825 stopPropagation: function() { 3826 this.isPropagationStopped = returnTrue; 3827 3828 var e = this.originalEvent; 3829 if ( !e ) { 3830 return; 3831 } 3832 // if stopPropagation exists run it on the original event 3833 if ( e.stopPropagation ) { 3834 e.stopPropagation(); 3835 } 3836 // otherwise set the cancelBubble property of the original event to true (IE) 3837 e.cancelBubble = true; 3838 }, 3839 stopImmediatePropagation: function() { 3840 this.isImmediatePropagationStopped = returnTrue; 3841 this.stopPropagation(); 3842 }, 3843 isDefaultPrevented: returnFalse, 3844 isPropagationStopped: returnFalse, 3845 isImmediatePropagationStopped: returnFalse 3846}; 3847 3848// Checks if an event happened on an element within another element 3849// Used in jQuery.event.special.mouseenter and mouseleave handlers 3850var withinElement = function( event ) { 3851 // Check if mouse(over|out) are still within the same parent element 3852 var parent = event.relatedTarget; 3853 3854 // Firefox sometimes assigns relatedTarget a XUL element 3855 // which we cannot access the parentNode property of 3856 try { 3857 3858 // Chrome does something similar, the parentNode property 3859 // can be accessed but is null. 3860 if ( parent && parent !== document && !parent.parentNode ) { 3861 return; 3862 } 3863 // Traverse up the tree 3864 while ( parent && parent !== this ) { 3865 parent = parent.parentNode; 3866 } 3867 3868 if ( parent !== this ) { 3869 // set the correct event type 3870 event.type = event.data; 3871 3872 // handle event if we actually just moused on to a non sub-element 3873 jQuery.event.handle.apply( this, arguments ); 3874 } 3875 3876 // assuming we've left the element since we most likely mousedover a xul element 3877 } catch(e) { } 3878}, 3879 3880// In case of event delegation, we only need to rename the event.type, 3881// liveHandler will take care of the rest. 3882delegate = function( event ) { 3883 event.type = event.data; 3884 jQuery.event.handle.apply( this, arguments ); 3885}; 3886 3887// Create mouseenter and mouseleave events 3888jQuery.each({ 3889 mouseenter: "mouseover", 3890 mouseleave: "mouseout" 3891}, function( orig, fix ) { 3892 jQuery.event.special[ orig ] = { 3893 setup: function( data ) { 3894 jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); 3895 }, 3896 teardown: function( data ) { 3897 jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); 3898 } 3899 }; 3900}); 3901 3902// submit delegation 3903if ( !jQuery.support.submitBubbles ) { 3904 3905 jQuery.event.special.submit = { 3906 setup: function( data, namespaces ) { 3907 if ( this.nodeName && this.nodeName.toLowerCase() !== "form" ) { 3908 jQuery.event.add(this, "click.specialSubmit", function( e ) { 3909 var elem = e.target, 3910 type = elem.type; 3911 3912 if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { 3913 trigger( "submit", this, arguments ); 3914 } 3915 }); 3916 3917 jQuery.event.add(this, "keypress.specialSubmit", function( e ) { 3918 var elem = e.target, 3919 type = elem.type; 3920 3921 if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { 3922 trigger( "submit", this, arguments ); 3923 } 3924 }); 3925 3926 } else { 3927 return false; 3928 } 3929 }, 3930 3931 teardown: function( namespaces ) { 3932 jQuery.event.remove( this, ".specialSubmit" ); 3933 } 3934 }; 3935 3936} 3937 3938// change delegation, happens here so we have bind. 3939if ( !jQuery.support.changeBubbles ) { 3940 3941 var changeFilters, 3942 3943 getVal = function( elem ) { 3944 var type = elem.type, val = elem.value; 3945 3946 if ( type === "radio" || type === "checkbox" ) { 3947 val = elem.checked; 3948 3949 } else if ( type === "select-multiple" ) { 3950 val = elem.selectedIndex > -1 ? 3951 jQuery.map( elem.options, function( elem ) { 3952 return elem.selected; 3953 }).join("-") : 3954 ""; 3955 3956 } else if ( elem.nodeName.toLowerCase() === "select" ) { 3957 val = elem.selectedIndex; 3958 } 3959 3960 return val; 3961 }, 3962 3963 testChange = function testChange( e ) { 3964 var elem = e.target, data, val; 3965 3966 if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) { 3967 return; 3968 } 3969 3970 data = jQuery._data( elem, "_change_data" ); 3971 val = getVal(elem); 3972 3973 // the current data will be also retrieved by beforeactivate 3974 if ( e.type !== "focusout" || elem.type !== "radio" ) { 3975 jQuery._data( elem, "_change_data", val ); 3976 } 3977 3978 if ( data === undefined || val === data ) { 3979 return; 3980 } 3981 3982 if ( data != null || val ) { 3983 e.type = "change"; 3984 e.liveFired = undefined; 3985 jQuery.event.trigger( e, arguments[1], elem ); 3986 } 3987 }; 3988 3989 jQuery.event.special.change = { 3990 filters: { 3991 focusout: testChange, 3992 3993 beforedeactivate: testChange, 3994 3995 click: function( e ) { 3996 var elem = e.target, type = elem.type; 3997 3998 if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { 3999 testChange.call( this, e ); 4000 } 4001 }, 4002 4003 // Change has to be called before submit 4004 // Keydown will be called before keypress, which is used in submit-event delegation 4005 keydown: function( e ) { 4006 var elem = e.target, type = elem.type; 4007 4008 if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || 4009 (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || 4010 type === "select-multiple" ) { 4011 testChange.call( this, e ); 4012 } 4013 }, 4014 4015 // Beforeactivate happens also before the previous element is blurred 4016 // with this event you can't trigger a change event, but you can store 4017 // information 4018 beforeactivate: function( e ) { 4019 var elem = e.target; 4020 jQuery._data( elem, "_change_data", getVal(elem) ); 4021 } 4022 }, 4023 4024 setup: function( data, namespaces ) { 4025 if ( this.type === "file" ) { 4026 return false;
4027 } 4028 4029 for ( var type in changeFilters ) { 4030 jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); 4031 } 4032 4033 return rformElems.test( this.nodeName ); 4034 }, 4035 4036 teardown: function( namespaces ) { 4037 jQuery.event.remove( this, ".specialChange" ); 4038 4039 return rformElems.test( this.nodeName ); 4040 } 4041 }; 4042 4043 changeFilters = jQuery.event.special.change.filters; 4044 4045 // Handle when the input is .focus()'d 4046 changeFilters.focus = changeFilters.beforeactivate; 4047} 4048 4049function trigger( type, elem, args ) { 4050 // Piggyback on a donor event to simulate a different one. 4051 // Fake originalEvent to avoid donor's stopPropagation, but if the 4052 // simulated event prevents default then we do the same on the donor. 4053 // Don't pass args or remember liveFired; they apply to the donor event. 4054 var event = jQuery.extend( {}, args[ 0 ] ); 4055 event.type = type; 4056 event.originalEvent = {}; 4057 event.liveFired = undefined; 4058 jQuery.event.handle.call( elem, event ); 4059 if ( event.isDefaultPrevented() ) { 4060 args[ 0 ].preventDefault(); 4061 } 4062} 4063 4064// Create "bubbling" focus and blur events 4065if ( document.addEventListener ) { 4066 jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { 4067 4068 // Attach a single capturing handler while someone wants focusin/focusout 4069 var attaches = 0; 4070 4071 jQuery.event.special[ fix ] = { 4072 setup: function() { 4073 if ( attaches++ === 0 ) { 4074 document.addEventListener( orig, handler, true ); 4075 } 4076 }, 4077 teardown: function() { 4078 if ( --attaches === 0 ) { 4079 document.removeEventListener( orig, handler, true ); 4080 } 4081 } 4082 }; 4083 4084 function handler( donor ) { 4085 // Donor event is always a native one; fix it and switch its type. 4086 // Let focusin/out handler cancel the donor focus/blur event. 4087 var e = jQuery.event.fix( donor ); 4088 e.type = fix; 4089 e.originalEvent = {}; 4090 jQuery.event.trigger( e, null, e.target ); 4091 if ( e.isDefaultPrevented() ) { 4092 donor.preventDefault(); 4093 } 4094 } 4095 }); 4096} 4097 4098jQuery.each(["bind", "one"], function( i, name ) { 4099 jQuery.fn[ name ] = function( type, data, fn ) { 4100 // Handle object literals 4101 if ( typeof type === "object" ) { 4102 for ( var key in type ) { 4103 this[ name ](key, data, type[key], fn); 4104 } 4105 return this; 4106 } 4107 4108 if ( jQuery.isFunction( data ) || data === false ) { 4109 fn = data; 4110 data = undefined; 4111 } 4112 4113 var handler = name === "one" ? jQuery.proxy( fn, function( event ) { 4114 jQuery( this ).unbind( event, handler ); 4115 return fn.apply( this, arguments ); 4116 }) : fn; 4117 4118 if ( type === "unload" && name !== "one" ) { 4119 this.one( type, data, fn ); 4120 4121 } else { 4122 for ( var i = 0, l = this.length; i < l; i++ ) { 4123 jQuery.event.add( this[i], type, handler, data ); 4124 } 4125 } 4126 4127 return this; 4128 }; 4129}); 4130 4131jQuery.fn.extend({ 4132 unbind: function( type, fn ) { 4133 // Handle object literals 4134 if ( typeof type === "object" && !type.preventDefault ) { 4135 for ( var key in type ) { 4136 this.unbind(key, type[key]); 4137 } 4138 4139 } else { 4140 for ( var i = 0, l = this.length; i < l; i++ ) { 4141 jQuery.event.remove( this[i], type, fn ); 4142 } 4143 } 4144 4145 return this; 4146 }, 4147 4148 delegate: function( selector, types, data, fn ) { 4149 return this.live( types, data, fn, selector ); 4150 }, 4151 4152 undelegate: function( selector, types, fn ) { 4153 if ( arguments.length === 0 ) { 4154 return this.unbind( "live" ); 4155 4156 } else { 4157 return this.die( types, null, fn, selector ); 4158 } 4159 }, 4160 4161 trigger: function( type, data ) { 4162 return this.each(function() { 4163 jQuery.event.trigger( type, data, this ); 4164 }); 4165 }, 4166 4167 triggerHandler: function( type, data ) { 4168 if ( this[0] ) { 4169 var event = jQuery.Event( type ); 4170 event.preventDefault(); 4171 event.stopPropagation(); 4172 jQuery.event.trigger( event, data, this[0] ); 4173 return event.result; 4174 } 4175 }, 4176 4177 toggle: function( fn ) { 4178 // Save reference to arguments for access in closure 4179 var args = arguments, 4180 i = 1; 4181 4182 // link all the functions, so any of them can unbind this click handler 4183 while ( i < args.length ) { 4184 jQuery.proxy( fn, args[ i++ ] ); 4185 } 4186 4187 return this.click( jQuery.proxy( fn, function( event ) { 4188 // Figure out which function to execute 4189 var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; 4190 jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); 4191 4192 // Make sure that clicks stop 4193 event.preventDefault(); 4194 4195 // and execute the function 4196 return args[ lastToggle ].apply( this, arguments ) || false; 4197 })); 4198 }, 4199 4200 hover: function( fnOver, fnOut ) { 4201 return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); 4202 } 4203}); 4204 4205var liveMap = { 4206 focus: "focusin", 4207 blur: "focusout", 4208 mouseenter: "mouseover", 4209 mouseleave: "mouseout" 4210}; 4211 4212jQuery.each(["live", "die"], function( i, name ) { 4213 jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { 4214 var type, i = 0, match, namespaces, preType, 4215 selector = origSelector || this.selector, 4216 context = origSelector ? this : jQuery( this.context ); 4217 4218 if ( typeof types === "object" && !types.preventDefault ) { 4219 for ( var key in types ) { 4220 context[ name ]( key, data, types[key], selector ); 4221 } 4222 4223 return this; 4224 } 4225 4226 if ( jQuery.isFunction( data ) ) { 4227 fn = data; 4228 data = undefined; 4229 } 4230 4231 types = (types || "").split(" "); 4232 4233 while ( (type = types[ i++ ]) != null ) { 4234 match = rnamespaces.exec( type ); 4235 namespaces = ""; 4236 4237 if ( match ) { 4238 namespaces = match[0]; 4239 type = type.replace( rnamespaces, "" ); 4240 } 4241 4242 if ( type === "hover" ) { 4243 types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); 4244 continue; 4245 } 4246 4247 preType = type; 4248 4249 if ( type === "focus" || type === "blur" ) { 4250 types.push( liveMap[ type ] + namespaces ); 4251 type = type + namespaces; 4252 4253 } else { 4254 type = (liveMap[ type ] || type) + namespaces; 4255 } 4256
4257 if ( name === "live" ) { 4258 // bind live handler 4259 for ( var j = 0, l = context.length; j < l; j++ ) { 4260 jQuery.event.add( context[j], "live." + liveConvert( type, selector ), 4261 { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); 4262 } 4263 4264 } else { 4265 // unbind live handler 4266 context.unbind( "live." + liveConvert( type, selector ), fn ); 4267 } 4268 } 4269 4270 return this; 4271 }; 4272}); 4273 4274function liveHandler( event ) { 4275 var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, 4276 elems = [], 4277 selectors = [], 4278 events = jQuery._data( this, "events" ); 4279 4280 // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911) 4281 if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) { 4282 return; 4283 } 4284 4285 if ( event.namespace ) { 4286 namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); 4287 } 4288 4289 event.liveFired = this; 4290 4291 var live = events.live.slice(0); 4292 4293 for ( j = 0; j < live.length; j++ ) { 4294 handleObj = live[j]; 4295 4296 if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { 4297 selectors.push( handleObj.selector ); 4298 4299 } else { 4300 live.splice( j--, 1 ); 4301 } 4302 } 4303 4304 match = jQuery( event.target ).closest( selectors, event.currentTarget ); 4305 4306 for ( i = 0, l = match.length; i < l; i++ ) { 4307 close = match[i]; 4308 4309 for ( j = 0; j < live.length; j++ ) { 4310 handleObj = live[j]; 4311 4312 if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) { 4313 elem = close.elem; 4314 related = null; 4315 4316 // Those two events require additional checking 4317 if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { 4318 event.type = handleObj.preType; 4319 related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; 4320 } 4321 4322 if ( !related || related !== elem ) { 4323 elems.push({ elem: elem, handleObj: handleObj, level: close.level }); 4324 } 4325 } 4326 } 4327 } 4328 4329 for ( i = 0, l = elems.length; i < l; i++ ) { 4330 match = elems[i]; 4331 4332 if ( maxLevel && match.level > maxLevel ) { 4333 break; 4334 } 4335 4336 event.currentTarget = match.elem; 4337 event.data = match.handleObj.data; 4338 event.handleObj = match.handleObj; 4339 4340 ret = match.handleObj.origHandler.apply( match.elem, arguments ); 4341 4342 if ( ret === false || event.isPropagationStopped() ) { 4343 maxLevel = match.level; 4344 4345 if ( ret === false ) { 4346 stop = false; 4347 } 4348 if ( event.isImmediatePropagationStopped() ) { 4349 break; 4350 } 4351 } 4352 } 4353 4354 return stop; 4355} 4356 4357function liveConvert( type, selector ) { 4358 return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspace, "&"); 4359} 4360 4361jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + 4362 "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + 4363 "change select submit keydown keypress keyup error").split(" "), function( i, name ) { 4364 4365 // Handle event binding 4366 jQuery.fn[ name ] = function( data, fn ) { 4367 if ( fn == null ) { 4368 fn = data; 4369 data = null; 4370 } 4371 4372 return arguments.length > 0 ? 4373 this.bind( name, data, fn ) : 4374 this.trigger( name ); 4375 }; 4376 4377 if ( jQuery.attrFn ) { 4378 jQuery.attrFn[ name ] = true; 4379 } 4380}); 4381 4382 4383/*! 4384 * Sizzle CSS Selector Engine 4385 * Copyright 2011, The Dojo Foundation 4386 * Released under the MIT, BSD, and GPL Licenses. 4387 * More information: http://sizzlejs.com/ 4388 */ 4389(function(){ 4390 4391var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, 4392 done = 0, 4393 toString = Object.prototype.toString, 4394 hasDuplicate = false, 4395 baseHasDuplicate = true, 4396 rBackslash = /\\/g, 4397 rNonWord = /\W/; 4398 4399// Here we check if the JavaScript engine is using some sort of 4400// optimization where it does not always call our comparision 4401// function. If that is the case, discard the hasDuplicate value. 4402// Thus far that includes Google Chrome. 4403[0, 0].sort(function() { 4404 baseHasDuplicate = false; 4405 return 0; 4406}); 4407 4408var Sizzle = function( selector, context, results, seed ) { 4409 results = results || []; 4410 context = context || document; 4411 4412 var origContext = context; 4413 4414 if ( context.nodeType !== 1 && context.nodeType !== 9 ) { 4415 return []; 4416 } 4417 4418 if ( !selector || typeof selector !== "string" ) { 4419 return results; 4420 } 4421 4422 var m, set, checkSet, extra, ret, cur, pop, i, 4423 prune = true, 4424 contextXML = Sizzle.isXML( context ), 4425 parts = [], 4426 soFar = selector; 4427 4428 // Reset the position of the chunker regexp (start from head) 4429 do { 4430 chunker.exec( "" ); 4431 m = chunker.exec( soFar ); 4432 4433 if ( m ) { 4434 soFar = m[3]; 4435 4436 parts.push( m[1] ); 4437 4438 if ( m[2] ) { 4439 extra = m[3]; 4440 break; 4441 } 4442 } 4443 } while ( m ); 4444 4445 if ( parts.length > 1 && origPOS.exec( selector ) ) { 4446 4447 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { 4448 set = posProcess( parts[0] + parts[1], context ); 4449 4450 } else { 4451 set = Expr.relative[ parts[0] ] ? 4452 [ context ] : 4453 Sizzle( parts.shift(), context ); 4454 4455 while ( parts.length ) { 4456 selector = parts.shift(); 4457 4458 if ( Expr.relative[ selector ] ) { 4459 selector += parts.shift(); 4460 } 4461 4462 set = posProcess( selector, set ); 4463 } 4464 } 4465 4466 } else { 4467 // Take a shortcut and set the context if the root selector is an ID 4468 // (but not if it'll be faster if the inner selector is an ID) 4469 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && 4470 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { 4471 4472 ret = Sizzle.find( parts.shift(), context, contextXML ); 4473 context = ret.expr ? 4474 Sizzle.filter( ret.expr, ret.set )[0] : 4475 ret.set[0]; 4476 } 4477 4478 if ( context ) { 4479 ret = seed ? 4480 { expr: parts.pop(), set: makeArray(seed) } : 4481 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); 4482 4483 set = ret.expr ? 4484 Sizzle.filter( ret.expr, ret.set ) : 4485 ret.set; 4486 4487 if ( parts.length > 0 ) { 4488 checkSet = makeArray( set ); 4489 4490 } else { 4491 prune = false;
4492 } 4493 4494 while ( parts.length ) { 4495 cur = parts.pop(); 4496 pop = cur; 4497 4498 if ( !Expr.relative[ cur ] ) { 4499 cur = ""; 4500 } else { 4501 pop = parts.pop(); 4502 } 4503 4504 if ( pop == null ) { 4505 pop = context; 4506 } 4507 4508 Expr.relative[ cur ]( checkSet, pop, contextXML ); 4509 } 4510 4511 } else { 4512 checkSet = parts = []; 4513 } 4514 } 4515 4516 if ( !checkSet ) { 4517 checkSet = set; 4518 } 4519 4520 if ( !checkSet ) { 4521 Sizzle.error( cur || selector ); 4522 } 4523 4524 if ( toString.call(checkSet) === "[object Array]" ) { 4525 if ( !prune ) { 4526 results.push.apply( results, checkSet ); 4527 4528 } else if ( context && context.nodeType === 1 ) { 4529 for ( i = 0; checkSet[i] != null; i++ ) { 4530 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { 4531 results.push( set[i] ); 4532 } 4533 } 4534 4535 } else { 4536 for ( i = 0; checkSet[i] != null; i++ ) { 4537 if ( checkSet[i] && checkSet[i].nodeType === 1 ) { 4538 results.push( set[i] ); 4539 } 4540 } 4541 } 4542 4543 } else { 4544 makeArray( checkSet, results ); 4545 } 4546 4547 if ( extra ) { 4548 Sizzle( extra, origContext, results, seed ); 4549 Sizzle.uniqueSort( results ); 4550 } 4551 4552 return results; 4553}; 4554 4555Sizzle.uniqueSort = function( results ) { 4556 if ( sortOrder ) { 4557 hasDuplicate = baseHasDuplicate; 4558 results.sort( sortOrder ); 4559 4560 if ( hasDuplicate ) { 4561 for ( var i = 1; i < results.length; i++ ) { 4562 if ( results[i] === results[ i - 1 ] ) { 4563 results.splice( i--, 1 ); 4564 } 4565 } 4566 } 4567 } 4568 4569 return results; 4570}; 4571 4572Sizzle.matches = function( expr, set ) { 4573 return Sizzle( expr, null, null, set ); 4574}; 4575 4576Sizzle.matchesSelector = function( node, expr ) { 4577 return Sizzle( expr, null, null, [node] ).length > 0; 4578}; 4579 4580Sizzle.find = function( expr, context, isXML ) { 4581 var set; 4582 4583 if ( !expr ) { 4584 return []; 4585 } 4586 4587 for ( var i = 0, l = Expr.order.length; i < l; i++ ) { 4588 var match, 4589 type = Expr.order[i]; 4590 4591 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { 4592 var left = match[1]; 4593 match.splice( 1, 1 ); 4594 4595 if ( left.substr( left.length - 1 ) !== "\\" ) { 4596 match[1] = (match[1] || "").replace( rBackslash, "" ); 4597 set = Expr.find[ type ]( match, context, isXML ); 4598 4599 if ( set != null ) { 4600 expr = expr.replace( Expr.match[ type ], "" ); 4601 break; 4602 } 4603 } 4604 } 4605 } 4606 4607 if ( !set ) { 4608 set = typeof context.getElementsByTagName !== "undefined" ? 4609 context.getElementsByTagName( "*" ) : 4610 []; 4611 } 4612 4613 return { set: set, expr: expr }; 4614}; 4615 4616Sizzle.filter = function( expr, set, inplace, not ) { 4617 var match, anyFound, 4618 old = expr, 4619 result = [], 4620 curLoop = set, 4621 isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); 4622 4623 while ( expr && set.length ) { 4624 for ( var type in Expr.filter ) { 4625 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { 4626 var found, item, 4627 filter = Expr.filter[ type ], 4628 left = match[1]; 4629 4630 anyFound = false; 4631 4632 match.splice(1,1); 4633 4634 if ( left.substr( left.length - 1 ) === "\\" ) { 4635 continue; 4636 } 4637 4638 if ( curLoop === result ) { 4639 result = []; 4640 } 4641 4642 if ( Expr.preFilter[ type ] ) { 4643 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); 4644 4645 if ( !match ) { 4646 anyFound = found = true; 4647 4648 } else if ( match === true ) { 4649 continue; 4650 } 4651 } 4652 4653 if ( match ) { 4654 for ( var i = 0; (item = curLoop[i]) != null; i++ ) { 4655 if ( item ) { 4656 found = filter( item, match, i, curLoop ); 4657 var pass = not ^ !!found; 4658 4659 if ( inplace && found != null ) { 4660 if ( pass ) { 4661 anyFound = true; 4662 4663 } else { 4664 curLoop[i] = false; 4665 } 4666 4667 } else if ( pass ) { 4668 result.push( item ); 4669 anyFound = true; 4670 } 4671 } 4672 } 4673 } 4674 4675 if ( found !== undefined ) { 4676 if ( !inplace ) { 4677 curLoop = result; 4678 } 4679 4680 expr = expr.replace( Expr.match[ type ], "" ); 4681 4682 if ( !anyFound ) { 4683 return []; 4684 } 4685 4686 break; 4687 } 4688 } 4689 } 4690 4691 // Improper expression 4692 if ( expr === old ) { 4693 if ( anyFound == null ) { 4694 Sizzle.error( expr ); 4695 4696 } else { 4697 break; 4698 } 4699 } 4700 4701 old = expr; 4702 } 4703 4704 return curLoop; 4705}; 4706 4707Sizzle.error = function( msg ) { 4708 throw "Syntax error, unrecognized expression: " + msg; 4709}; 4710
4711var Expr = Sizzle.selectors = { 4712 order: [ "ID", "NAME", "TAG" ], 4713 4714 match: { 4715 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 4716 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 4717 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, 4718 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, 4719 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, 4720 CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, 4721 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, 4722 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ 4723 }, 4724 4725 leftMatch: {}, 4726 4727 attrMap: { 4728 "class": "className", 4729 "for": "htmlFor" 4730 }, 4731 4732 attrHandle: { 4733 href: function( elem ) { 4734 return elem.getAttribute( "href" ); 4735 }, 4736 type: function( elem ) { 4737 return elem.getAttribute( "type" ); 4738 } 4739 }, 4740 4741 relative: { 4742 "+": function(checkSet, part){ 4743 var isPartStr = typeof part === "string", 4744 isTag = isPartStr && !rNonWord.test( part ), 4745 isPartStrNotTag = isPartStr && !isTag; 4746 4747 if ( isTag ) { 4748 part = part.toLowerCase(); 4749 } 4750 4751 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { 4752 if ( (elem = checkSet[i]) ) { 4753 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} 4754 4755 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? 4756 elem || false : 4757 elem === part; 4758 } 4759 } 4760 4761 if ( isPartStrNotTag ) { 4762 Sizzle.filter( part, checkSet, true ); 4763 } 4764 }, 4765 4766 ">": function( checkSet, part ) { 4767 var elem, 4768 isPartStr = typeof part === "string", 4769 i = 0, 4770 l = checkSet.length; 4771 4772 if ( isPartStr && !rNonWord.test( part ) ) { 4773 part = part.toLowerCase(); 4774 4775 for ( ; i < l; i++ ) { 4776 elem = checkSet[i]; 4777 4778 if ( elem ) { 4779 var parent = elem.parentNode; 4780 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; 4781 } 4782 } 4783 4784 } else { 4785 for ( ; i < l; i++ ) { 4786 elem = checkSet[i]; 4787 4788 if ( elem ) { 4789 checkSet[i] = isPartStr ? 4790 elem.parentNode : 4791 elem.parentNode === part; 4792 } 4793 } 4794 4795 if ( isPartStr ) { 4796 Sizzle.filter( part, checkSet, true ); 4797 } 4798 } 4799 }, 4800 4801 "": function(checkSet, part, isXML){ 4802 var nodeCheck, 4803 doneName = done++, 4804 checkFn = dirCheck; 4805 4806 if ( typeof part === "string" && !rNonWord.test( part ) ) { 4807 part = part.toLowerCase(); 4808 nodeCheck = part; 4809 checkFn = dirNodeCheck; 4810 } 4811 4812 checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); 4813 }, 4814 4815 "~": function( checkSet, part, isXML ) { 4816 var nodeCheck, 4817 doneName = done++, 4818 checkFn = dirCheck; 4819 4820 if ( typeof part === "string" && !rNonWord.test( part ) ) { 4821 part = part.toLowerCase(); 4822 nodeCheck = part; 4823 checkFn = dirNodeCheck; 4824 } 4825 4826 checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); 4827 } 4828 }, 4829 4830 find: { 4831 ID: function( match, context, isXML ) { 4832 if ( typeof context.getElementById !== "undefined" && !isXML ) { 4833 var m = context.getElementById(match[1]); 4834 // Check parentNode to catch when Blackberry 4.6 returns 4835 // nodes that are no longer in the document #6963 4836 return m && m.parentNode ? [m] : []; 4837 } 4838 }, 4839 4840 NAME: function( match, context ) { 4841 if ( typeof context.getElementsByName !== "undefined" ) { 4842 var ret = [], 4843 results = context.getElementsByName( match[1] ); 4844 4845 for ( var i = 0, l = results.length; i < l; i++ ) { 4846 if ( results[i].getAttribute("name") === match[1] ) { 4847 ret.push( results[i] ); 4848 } 4849 } 4850 4851 return ret.length === 0 ? null : ret; 4852 } 4853 }, 4854 4855 TAG: function( match, context ) { 4856 if ( typeof context.getElementsByTagName !== "undefined" ) { 4857 return context.getElementsByTagName( match[1] ); 4858 } 4859 } 4860 }, 4861 preFilter: { 4862 CLASS: function( match, curLoop, inplace, result, not, isXML ) { 4863 match = " " + match[1].replace( rBackslash, "" ) + " "; 4864 4865 if ( isXML ) { 4866 return match; 4867 } 4868 4869 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { 4870 if ( elem ) { 4871 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { 4872 if ( !inplace ) { 4873 result.push( elem ); 4874 } 4875 4876 } else if ( inplace ) { 4877 curLoop[i] = false;
4878 } 4879 } 4880 } 4881 4882 return false; 4883 }, 4884 4885 ID: function( match ) { 4886 return match[1].replace( rBackslash, "" ); 4887 }, 4888 4889 TAG: function( match, curLoop ) { 4890 return match[1].replace( rBackslash, "" ).toLowerCase(); 4891 }, 4892 4893 CHILD: function( match ) { 4894 if ( match[1] === "nth" ) { 4895 if ( !match[2] ) { 4896 Sizzle.error( match[0] ); 4897 } 4898 4899 match[2] = match[2].replace(/^\+|\s*/g, ''); 4900 4901 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' 4902 var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( 4903 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || 4904 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); 4905 4906 // calculate the numbers (first)n+(last) including if they are negative 4907 match[2] = (test[1] + (test[2] || 1)) - 0; 4908 match[3] = test[3] - 0; 4909 } 4910 else if ( match[2] ) { 4911 Sizzle.error( match[0] ); 4912 } 4913 4914 // TODO: Move to normal caching system 4915 match[0] = done++; 4916 4917 return match; 4918 }, 4919 4920 ATTR: function( match, curLoop, inplace, result, not, isXML ) { 4921 var name = match[1] = match[1].replace( rBackslash, "" ); 4922 4923 if ( !isXML && Expr.attrMap[name] ) { 4924 match[1] = Expr.attrMap[name]; 4925 } 4926 4927 // Handle if an un-quoted value was used 4928 match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); 4929 4930 if ( match[2] === "~=" ) { 4931 match[4] = " " + match[4] + " "; 4932 } 4933 4934 return match; 4935 }, 4936 4937 PSEUDO: function( match, curLoop, inplace, result, not ) { 4938 if ( match[1] === "not" ) { 4939 // If we're dealing with a complex expression, or a simple one 4940 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { 4941 match[3] = Sizzle(match[3], null, null, curLoop); 4942 4943 } else { 4944 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); 4945 4946 if ( !inplace ) { 4947 result.push.apply( result, ret ); 4948 } 4949 4950 return false; 4951 } 4952 4953 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { 4954 return true; 4955 } 4956 4957 return match; 4958 }, 4959 4960 POS: function( match ) { 4961 match.unshift( true ); 4962 4963 return match; 4964 } 4965 }, 4966 4967 filters: { 4968 enabled: function( elem ) { 4969 return elem.disabled === false && elem.type !== "hidden"; 4970 }, 4971 4972 disabled: function( elem ) { 4973 return elem.disabled === true; 4974 }, 4975 4976 checked: function( elem ) { 4977 return elem.checked === true; 4978 }, 4979 4980 selected: function( elem ) { 4981 // Accessing this property makes selected-by-default 4982 // options in Safari work properly 4983 if ( elem.parentNode ) { 4984 elem.parentNode.selectedIndex; 4985 } 4986 4987 return elem.selected === true; 4988 }, 4989 4990 parent: function( elem ) { 4991 return !!elem.firstChild; 4992 }, 4993 4994 empty: function( elem ) { 4995 return !elem.firstChild; 4996 }, 4997 4998 has: function( elem, i, match ) { 4999 return !!Sizzle( match[3], elem ).length; 5000 }, 5001 5002 header: function( elem ) { 5003 return (/h\d/i).test( elem.nodeName ); 5004 }, 5005 5006 text: function( elem ) { 5007 var attr = elem.getAttribute( "type" ), type = elem.type; 5008 // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) 5009 // use getAttribute instead to test this case 5010 return "text" === type && ( attr === type || attr === null ); 5011 }, 5012 5013 radio: function( elem ) { 5014 return "radio" === elem.type; 5015 }, 5016 5017 checkbox: function( elem ) { 5018 return "checkbox" === elem.type; 5019 }, 5020 5021 file: function( elem ) { 5022 return "file" === elem.type; 5023 }, 5024 password: function( elem ) { 5025 return "password" === elem.type; 5026 }, 5027 5028 submit: function( elem ) { 5029 return "submit" === elem.type; 5030 }, 5031 5032 image: function( elem ) { 5033 return "image" === elem.type; 5034 }, 5035 5036 reset: function( elem ) { 5037 return "reset" === elem.type; 5038 }, 5039 5040 button: function( elem ) { 5041 return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; 5042 }, 5043 5044 input: function( elem ) { 5045 return (/input|select|textarea|button/i).test( elem.nodeName ); 5046 } 5047 }, 5048 setFilters: { 5049 first: function( elem, i ) { 5050 return i === 0; 5051 }, 5052 5053 last: function( elem, i, match, array ) { 5054 return i === array.length - 1; 5055 }, 5056 5057 even: function( elem, i ) { 5058 return i % 2 === 0; 5059 }, 5060 5061 odd: function( elem, i ) { 5062 return i % 2 === 1; 5063 }, 5064 5065 lt: function( elem, i, match ) { 5066 return i < match[3] - 0; 5067 }, 5068 5069 gt: function( elem, i, match ) { 5070 return i > match[3] - 0; 5071 }, 5072 5073 nth: function( elem, i, match ) { 5074 return match[3] - 0 === i; 5075 }, 5076 5077 eq: function( elem, i, match ) { 5078 return match[3] - 0 === i; 5079 } 5080 }, 5081 filter: { 5082 PSEUDO: function( elem, match, i, array ) { 5083 var name = match[1], 5084 filter = Expr.filters[ name ]; 5085 5086 if ( filter ) { 5087 return filter( elem, i, match, array ); 5088 5089 } else if ( name === "contains" ) { 5090 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; 5091 5092 } else if ( name === "not" ) { 5093 var not = match[3]; 5094 5095 for ( var j = 0, l = not.length; j < l; j++ ) { 5096 if ( not[j] === elem ) { 5097 return false;
5098 } 5099 } 5100 5101 return true; 5102 5103 } else { 5104 Sizzle.error( name ); 5105 } 5106 }, 5107 5108 CHILD: function( elem, match ) { 5109 var type = match[1], 5110 node = elem; 5111 5112 switch ( type ) { 5113 case "only": 5114 case "first": 5115 while ( (node = node.previousSibling) ) { 5116 if ( node.nodeType === 1 ) { 5117 return false; 5118 } 5119 } 5120 5121 if ( type === "first" ) { 5122 return true; 5123 } 5124 5125 node = elem; 5126 5127 case "last": 5128 while ( (node = node.nextSibling) ) { 5129 if ( node.nodeType === 1 ) { 5130 return false; 5131 } 5132 } 5133 5134 return true; 5135 5136 case "nth": 5137 var first = match[2], 5138 last = match[3]; 5139 5140 if ( first === 1 && last === 0 ) { 5141 return true; 5142 } 5143 5144 var doneName = match[0], 5145 parent = elem.parentNode; 5146 5147 if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { 5148 var count = 0; 5149 5150 for ( node = parent.firstChild; node; node = node.nextSibling ) { 5151 if ( node.nodeType === 1 ) { 5152 node.nodeIndex = ++count; 5153 } 5154 } 5155 5156 parent.sizcache = doneName; 5157 } 5158 5159 var diff = elem.nodeIndex - last; 5160 5161 if ( first === 0 ) { 5162 return diff === 0; 5163 5164 } else { 5165 return ( diff % first === 0 && diff / first >= 0 ); 5166 } 5167 } 5168 }, 5169 5170 ID: function( elem, match ) { 5171 return elem.nodeType === 1 && elem.getAttribute("id") === match; 5172 }, 5173 5174 TAG: function( elem, match ) { 5175 return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; 5176 }, 5177 5178 CLASS: function( elem, match ) { 5179 return (" " + (elem.className || elem.getAttribute("class")) + " ") 5180 .indexOf( match ) > -1; 5181 }, 5182 5183 ATTR: function( elem, match ) { 5184 var name = match[1], 5185 result = Expr.attrHandle[ name ] ? 5186 Expr.attrHandle[ name ]( elem ) : 5187 elem[ name ] != null ? 5188 elem[ name ] : 5189 elem.getAttribute( name ), 5190 value = result + "", 5191 type = match[2], 5192 check = match[4]; 5193 5194 return result == null ? 5195 type === "!=" : 5196 type === "=" ? 5197 value === check : 5198 type === "*=" ? 5199 value.indexOf(check) >= 0 : 5200 type === "~=" ? 5201 (" " + value + " ").indexOf(check) >= 0 : 5202 !check ? 5203 value && result !== false : 5204 type === "!=" ? 5205 value !== check : 5206 type === "^=" ? 5207 value.indexOf(check) === 0 : 5208 type === "$=" ? 5209 value.substr(value.length - check.length) === check : 5210 type === "|=" ? 5211 value === check || value.substr(0, check.length + 1) === check + "-" : 5212 false; 5213 }, 5214 5215 POS: function( elem, match, i, array ) { 5216 var name = match[2], 5217 filter = Expr.setFilters[ name ]; 5218 5219 if ( filter ) { 5220 return filter( elem, i, match, array ); 5221 } 5222 } 5223 } 5224}; 5225 5226var origPOS = Expr.match.POS, 5227 fescape = function(all, num){ 5228 return "\\" + (num - 0 + 1); 5229 }; 5230 5231for ( var type in Expr.match ) { 5232 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); 5233 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); 5234} 5235 5236var makeArray = function( array, results ) { 5237 array = Array.prototype.slice.call( array, 0 ); 5238 5239 if ( results ) { 5240 results.push.apply( results, array ); 5241 return results; 5242 } 5243 5244 return array; 5245}; 5246 5247// Perform a simple check to determine if the browser is capable of 5248// converting a NodeList to an array using builtin methods. 5249// Also verifies that the returned array holds DOM nodes 5250// (which is not the case in the Blackberry browser) 5251try { 5252 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; 5253 5254// Provide a fallback method if it does not work 5255} catch( e ) { 5256 makeArray = function( array, results ) { 5257 var i = 0, 5258 ret = results || []; 5259 5260 if ( toString.call(array) === "[object Array]" ) { 5261 Array.prototype.push.apply( ret, array ); 5262 5263 } else { 5264 if ( typeof array.length === "number" ) { 5265 for ( var l = array.length; i < l; i++ ) { 5266 ret.push( array[i] ); 5267 } 5268 5269 } else { 5270 for ( ; array[i]; i++ ) { 5271 ret.push( array[i] ); 5272 } 5273 } 5274 } 5275 5276 return ret; 5277 }; 5278} 5279 5280var sortOrder, siblingCheck; 5281 5282if ( document.documentElement.compareDocumentPosition ) { 5283 sortOrder = function( a, b ) { 5284 if ( a === b ) { 5285 hasDuplicate = true; 5286 return 0; 5287 } 5288 5289 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { 5290 return a.compareDocumentPosition ? -1 : 1; 5291 } 5292 5293 return a.compareDocumentPosition(b) & 4 ? -1 : 1; 5294 }; 5295 5296} else { 5297 sortOrder = function( a, b ) { 5298 var al, bl, 5299 ap = [], 5300 bp = [], 5301 aup = a.parentNode, 5302 bup = b.parentNode, 5303 cur = aup; 5304 5305 // The nodes are identical, we can exit early 5306 if ( a === b ) { 5307 hasDuplicate = true; 5308 return 0; 5309 5310 // If the nodes are siblings (or identical) we can do a quick check 5311 } else if ( aup === bup ) { 5312 return siblingCheck( a, b ); 5313 5314 // If no parents were found then the nodes are disconnected 5315 } else if ( !aup ) { 5316 return -1; 5317 5318 } else if ( !bup ) { 5319 return 1; 5320 } 5321 5322 // Otherwise they're somewhere else in the tree so we need 5323 // to build up a full list of the parentNodes for comparison 5324 while ( cur ) { 5325 ap.unshift( cur ); 5326 cur = cur.parentNode; 5327 } 5328 5329 cur = bup; 5330 5331 while ( cur ) { 5332 bp.unshift( cur ); 5333 cur = cur.parentNode; 5334 } 5335 5336 al = ap.length; 5337 bl = bp.length; 5338 5339 // Start walking down the tree looking for a discrepancy 5340 for ( var i = 0; i < al && i < bl; i++ ) { 5341 if ( ap[i] !== bp[i] ) { 5342 return siblingCheck( ap[i], bp[i] ); 5343 } 5344 } 5345 5346 // We ended someplace up the tree so do a sibling check 5347 return i === al ?
5348 siblingCheck( a, bp[i], -1 ) : 5349 siblingCheck( ap[i], b, 1 ); 5350 }; 5351 5352 siblingCheck = function( a, b, ret ) { 5353 if ( a === b ) { 5354 return ret; 5355 } 5356 5357 var cur = a.nextSibling; 5358 5359 while ( cur ) { 5360 if ( cur === b ) { 5361 return -1; 5362 } 5363 5364 cur = cur.nextSibling; 5365 } 5366 5367 return 1; 5368 }; 5369} 5370 5371// Utility function for retreiving the text value of an array of DOM nodes 5372Sizzle.getText = function( elems ) { 5373 var ret = "", elem; 5374 5375 for ( var i = 0; elems[i]; i++ ) { 5376 elem = elems[i]; 5377 5378 // Get the text from text nodes and CDATA nodes 5379 if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 5380 ret += elem.nodeValue; 5381 5382 // Traverse everything else, except comment nodes 5383 } else if ( elem.nodeType !== 8 ) { 5384 ret += Sizzle.getText( elem.childNodes ); 5385 } 5386 } 5387 5388 return ret; 5389}; 5390 5391// Check to see if the browser returns elements by name when 5392// querying by getElementById (and provide a workaround) 5393(function(){ 5394 // We're going to inject a fake input element with a specified name 5395 var form = document.createElement("div"), 5396 id = "script" + (new Date()).getTime(), 5397 root = document.documentElement; 5398 5399 form.innerHTML = "<a name='" + id + "'/>"; 5400 5401 // Inject it into the root element, check its status, and remove it quickly 5402 root.insertBefore( form, root.firstChild ); 5403 5404 // The workaround has to do additional checks after a getElementById 5405 // Which slows things down for other browsers (hence the branching) 5406 if ( document.getElementById( id ) ) { 5407 Expr.find.ID = function( match, context, isXML ) { 5408 if ( typeof context.getElementById !== "undefined" && !isXML ) { 5409 var m = context.getElementById(match[1]); 5410 5411 return m ? 5412 m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? 5413 [m] : 5414 undefined : 5415 []; 5416 } 5417 }; 5418 5419 Expr.filter.ID = function( elem, match ) { 5420 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); 5421 5422 return elem.nodeType === 1 && node && node.nodeValue === match; 5423 }; 5424 } 5425 5426 root.removeChild( form ); 5427 5428 // release memory in IE 5429 root = form = null; 5430})(); 5431 5432(function(){ 5433 // Check to see if the browser returns only elements 5434 // when doing getElementsByTagName("*") 5435 5436 // Create a fake element 5437 var div = document.createElement("div"); 5438 div.appendChild( document.createComment("") ); 5439 5440 // Make sure no comments are found 5441 if ( div.getElementsByTagName("*").length > 0 ) { 5442 Expr.find.TAG = function( match, context ) { 5443 var results = context.getElementsByTagName( match[1] ); 5444 5445 // Filter out possible comments 5446 if ( match[1] === "*" ) { 5447 var tmp = []; 5448 5449 for ( var i = 0; results[i]; i++ ) { 5450 if ( results[i].nodeType === 1 ) { 5451 tmp.push( results[i] ); 5452 } 5453 } 5454 5455 results = tmp; 5456 } 5457 5458 return results; 5459 }; 5460 } 5461 5462 // Check to see if an attribute returns normalized href attributes 5463 div.innerHTML = "<a href='#'></a>"; 5464 5465 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && 5466 div.firstChild.getAttribute("href") !== "#" ) { 5467 5468 Expr.attrHandle.href = function( elem ) { 5469 return elem.getAttribute( "href", 2 ); 5470 }; 5471 } 5472 5473 // release memory in IE 5474 div = null; 5475})(); 5476 5477if ( document.querySelectorAll ) { 5478 (function(){ 5479 var oldSizzle = Sizzle, 5480 div = document.createElement("div"), 5481 id = "__sizzle__"; 5482 5483 div.innerHTML = "<p class='TEST'></p>"; 5484 5485 // Safari can't handle uppercase or unicode characters when 5486 // in quirks mode. 5487 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { 5488 return; 5489 } 5490 5491 Sizzle = function( query, context, extra, seed ) { 5492 context = context || document; 5493 5494 // Only use querySelectorAll on non-XML documents 5495 // (ID selectors don't work in non-HTML documents) 5496 if ( !seed && !Sizzle.isXML(context) ) { 5497 // See if we find a selector to speed up 5498 var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); 5499 5500 if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { 5501 // Speed-up: Sizzle("TAG") 5502 if ( match[1] ) { 5503 return makeArray( context.getElementsByTagName( query ), extra ); 5504 5505 // Speed-up: Sizzle(".CLASS") 5506 } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { 5507 return makeArray( context.getElementsByClassName( match[2] ), extra ); 5508 } 5509 } 5510 5511 if ( context.nodeType === 9 ) { 5512 // Speed-up: Sizzle("body") 5513 // The body element only exists once, optimize finding it 5514 if ( query === "body" && context.body ) { 5515 return makeArray( [ context.body ], extra ); 5516 5517 // Speed-up: Sizzle("#ID") 5518 } else if ( match && match[3] ) { 5519 var elem = context.getElementById( match[3] ); 5520 5521 // Check parentNode to catch when Blackberry 4.6 returns 5522 // nodes that are no longer in the document #6963 5523 if ( elem && elem.parentNode ) { 5524 // Handle the case where IE and Opera return items 5525 // by name instead of ID 5526 if ( elem.id === match[3] ) { 5527 return makeArray( [ elem ], extra ); 5528 } 5529 5530 } else { 5531 return makeArray( [], extra ); 5532 } 5533 } 5534 5535 try { 5536 return makeArray( context.querySelectorAll(query), extra ); 5537 } catch(qsaError) {} 5538 5539 // qSA works strangely on Element-rooted queries 5540 // We can work around this by specifying an extra ID on the root 5541 // and working up from there (Thanks to Andrew Dupont for the technique) 5542 // IE 8 doesn't work on object elements 5543 } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { 5544 var oldContext = context, 5545 old = context.getAttribute( "id" ), 5546 nid = old || id, 5547 hasParent = context.parentNode, 5548 relativeHierarchySelector = /^\s*[+~]/.test( query ); 5549 5550 if ( !old ) { 5551 context.setAttribute( "id", nid ); 5552 } else { 5553 nid = nid.replace( /'/g, "\\$&" ); 5554 } 5555 if ( relativeHierarchySelector && hasParent ) { 5556 context = context.parentNode; 5557 } 5558 5559 try { 5560 if ( !relativeHierarchySelector || hasParent ) { 5561 return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); 5562 } 5563 5564 } catch(pseudoError) { 5565 } finally { 5566 if ( !old ) { 5567 oldContext.removeAttribute( "id" ); 5568 } 5569 } 5570 } 5571 } 5572 5573 return oldSizzle(query, context, extra, seed); 5574 }; 5575 5576 for ( var prop in oldSizzle ) { 5577 Sizzle[ prop ] = oldSizzle[ prop ]; 5578 } 5579 5580 // release memory in IE 5581 div = null; 5582 })(); 5583} 5584 5585(function(){ 5586 var html = document.documentElement, 5587 matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; 5588 5589 if ( matches ) { 5590 // Check to see if it's possible to do matchesSelector 5591 // on a disconnected node (IE 9 fails this) 5592 var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), 5593 pseudoWorks = false;
5594 5595 try { 5596 // This should fail with an exception 5597 // Gecko does not error, returns false instead 5598 matches.call( document.documentElement, "[test!='']:sizzle" ); 5599 5600 } catch( pseudoError ) { 5601 pseudoWorks = true; 5602 } 5603 5604 Sizzle.matchesSelector = function( node, expr ) { 5605 // Make sure that attribute selectors are quoted 5606 expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); 5607 5608 if ( !Sizzle.isXML( node ) ) { 5609 try { 5610 if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { 5611 var ret = matches.call( node, expr ); 5612 5613 // IE 9's matchesSelector returns false on disconnected nodes 5614 if ( ret || !disconnectedMatch || 5615 // As well, disconnected nodes are said to be in a document 5616 // fragment in IE 9, so check for that 5617 node.document && node.document.nodeType !== 11 ) { 5618 return ret; 5619 } 5620 } 5621 } catch(e) {} 5622 } 5623 5624 return Sizzle(expr, null, null, [node]).length > 0; 5625 }; 5626 } 5627})(); 5628 5629(function(){ 5630 var div = document.createElement("div"); 5631 5632 div.innerHTML = "<div class='test e'></div><div class='test'></div>"; 5633 5634 // Opera can't find a second classname (in 9.6) 5635 // Also, make sure that getElementsByClassName actually exists 5636 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { 5637 return; 5638 } 5639 5640 // Safari caches class attributes, doesn't catch changes (in 3.2) 5641 div.lastChild.className = "e"; 5642 5643 if ( div.getElementsByClassName("e").length === 1 ) { 5644 return; 5645 } 5646 5647 Expr.order.splice(1, 0, "CLASS"); 5648 Expr.find.CLASS = function( match, context, isXML ) { 5649 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { 5650 return context.getElementsByClassName(match[1]); 5651 } 5652 }; 5653 5654 // release memory in IE 5655 div = null; 5656})(); 5657 5658function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 5659 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 5660 var elem = checkSet[i]; 5661 5662 if ( elem ) { 5663 var match = false; 5664 5665 elem = elem[dir]; 5666 5667 while ( elem ) { 5668 if ( elem.sizcache === doneName ) { 5669 match = checkSet[elem.sizset]; 5670 break; 5671 } 5672 5673 if ( elem.nodeType === 1 && !isXML ){ 5674 elem.sizcache = doneName; 5675 elem.sizset = i; 5676 } 5677 5678 if ( elem.nodeName.toLowerCase() === cur ) { 5679 match = elem; 5680 break; 5681 } 5682 5683 elem = elem[dir]; 5684 } 5685 5686 checkSet[i] = match; 5687 } 5688 } 5689} 5690 5691function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 5692 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 5693 var elem = checkSet[i]; 5694 5695 if ( elem ) { 5696 var match = false; 5697 5698 elem = elem[dir]; 5699 5700 while ( elem ) { 5701 if ( elem.sizcache === doneName ) { 5702 match = checkSet[elem.sizset]; 5703 break; 5704 } 5705 5706 if ( elem.nodeType === 1 ) { 5707 if ( !isXML ) { 5708 elem.sizcache = doneName; 5709 elem.sizset = i; 5710 } 5711 5712 if ( typeof cur !== "string" ) { 5713 if ( elem === cur ) { 5714 match = true; 5715 break; 5716 } 5717 5718 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { 5719 match = elem; 5720 break; 5721 } 5722 } 5723 5724 elem = elem[dir]; 5725 } 5726 5727 checkSet[i] = match; 5728 } 5729 } 5730} 5731 5732if ( document.documentElement.contains ) { 5733 Sizzle.contains = function( a, b ) { 5734 return a !== b && (a.contains ? a.contains(b) : true); 5735 }; 5736 5737} else if ( document.documentElement.compareDocumentPosition ) { 5738 Sizzle.contains = function( a, b ) { 5739 return !!(a.compareDocumentPosition(b) & 16); 5740 }; 5741 5742} else { 5743 Sizzle.contains = function() { 5744 return false; 5745 }; 5746} 5747 5748Sizzle.isXML = function( elem ) { 5749 // documentElement is verified for cases where it doesn't yet exist 5750 // (such as loading iframes in IE - #4833) 5751 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; 5752 5753 return documentElement ? documentElement.nodeName !== "HTML" : false; 5754}; 5755 5756var posProcess = function( selector, context ) { 5757 var match, 5758 tmpSet = [], 5759 later = "", 5760 root = context.nodeType ? [context] : context; 5761 5762 // Position selectors must be done after the filter 5763 // And so must :not(positional) so we move all PSEUDOs to the end 5764 while ( (match = Expr.match.PSEUDO.exec( selector )) ) { 5765 later += match[0]; 5766 selector = selector.replace( Expr.match.PSEUDO, "" ); 5767 } 5768 5769 selector = Expr.relative[selector] ? selector + "*" : selector; 5770 5771 for ( var i = 0, l = root.length; i < l; i++ ) { 5772 Sizzle( selector, root[i], tmpSet ); 5773 } 5774 5775 return Sizzle.filter( later, tmpSet ); 5776}; 5777 5778// EXPOSE 5779jQuery.find = Sizzle; 5780jQuery.expr = Sizzle.selectors; 5781jQuery.expr[":"] = jQuery.expr.filters; 5782jQuery.unique = Sizzle.uniqueSort; 5783jQuery.text = Sizzle.getText; 5784jQuery.isXMLDoc = Sizzle.isXML; 5785jQuery.contains = Sizzle.contains; 5786 5787 5788})(); 5789 5790 5791var runtil = /Until$/, 5792 rparentsprev = /^(?:parents|prevUntil|prevAll)/, 5793 // Note: This RegExp should be improved, or likely pulled from Sizzle 5794 rmultiselector = /,/, 5795 isSimple = /^.[^:#\[\.,]*$/, 5796 slice = Array.prototype.slice, 5797 POS = jQuery.expr.match.POS, 5798 // methods guaranteed to produce a unique set when starting from a unique set 5799 guaranteedUnique = { 5800 children: true, 5801 contents: true, 5802 next: true, 5803 prev: true 5804 }; 5805 5806jQuery.fn.extend({ 5807 find: function( selector ) { 5808 var ret = this.pushStack( "", "find", selector ), 5809 length = 0; 5810 5811 for ( var i = 0, l = this.length; i < l; i++ ) { 5812 length = ret.length; 5813 jQuery.find( selector, this[i], ret ); 5814 5815 if ( i > 0 ) { 5816 // Make sure that the results are unique 5817 for ( var n = length; n < ret.length; n++ ) { 5818 for ( var r = 0; r < length; r++ ) { 5819 if ( ret[r] === ret[n] ) { 5820 ret.splice(n--, 1); 5821 break; 5822 } 5823 } 5824 } 5825 } 5826 } 5827 5828 return ret; 5829 }, 5830 5831 has: function( target ) { 5832 var targets = jQuery( target ); 5833 return this.filter(function() { 5834 for ( var i = 0, l = targets.length; i < l; i++ ) { 5835 if ( jQuery.contains( this, targets[i] ) ) { 5836 return true; 5837 } 5838 } 5839 }); 5840 }, 5841 5842 not: function( selector ) { 5843 return this.pushStack( winnow(this, selector, false), "not", selector); 5844 }, 5845 5846 filter: function( selector ) {
5847 return this.pushStack( winnow(this, selector, true), "filter", selector ); 5848 }, 5849 5850 is: function( selector ) { 5851 return !!selector && jQuery.filter( selector, this ).length > 0; 5852 }, 5853 5854 closest: function( selectors, context ) { 5855 var ret = [], i, l, cur = this[0]; 5856 5857 if ( jQuery.isArray( selectors ) ) { 5858 var match, selector, 5859 matches = {}, 5860 level = 1; 5861 5862 if ( cur && selectors.length ) { 5863 for ( i = 0, l = selectors.length; i < l; i++ ) { 5864 selector = selectors[i]; 5865 5866 if ( !matches[selector] ) { 5867 matches[selector] = jQuery.expr.match.POS.test( selector ) ? 5868 jQuery( selector, context || this.context ) : 5869 selector; 5870 } 5871 } 5872 5873 while ( cur && cur.ownerDocument && cur !== context ) { 5874 for ( selector in matches ) { 5875 match = matches[selector]; 5876 5877 if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { 5878 ret.push({ selector: selector, elem: cur, level: level }); 5879 } 5880 } 5881 5882 cur = cur.parentNode; 5883 level++; 5884 } 5885 } 5886 5887 return ret; 5888 } 5889 5890 var pos = POS.test( selectors ) ? 5891 jQuery( selectors, context || this.context ) : null; 5892 5893 for ( i = 0, l = this.length; i < l; i++ ) { 5894 cur = this[i]; 5895 5896 while ( cur ) { 5897 if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { 5898 ret.push( cur ); 5899 break; 5900 5901 } else { 5902 cur = cur.parentNode; 5903 if ( !cur || !cur.ownerDocument || cur === context ) { 5904 break; 5905 } 5906 } 5907 } 5908 } 5909 5910 ret = ret.length > 1 ? jQuery.unique(ret) : ret; 5911 5912 return this.pushStack( ret, "closest", selectors ); 5913 }, 5914 5915 // Determine the position of an element within 5916 // the matched set of elements 5917 index: function( elem ) { 5918 if ( !elem || typeof elem === "string" ) { 5919 return jQuery.inArray( this[0], 5920 // If it receives a string, the selector is used 5921 // If it receives nothing, the siblings are used 5922 elem ? jQuery( elem ) : this.parent().children() ); 5923 } 5924 // Locate the position of the desired element 5925 return jQuery.inArray( 5926 // If it receives a jQuery object, the first element is used 5927 elem.jquery ? elem[0] : elem, this ); 5928 }, 5929 5930 add: function( selector, context ) { 5931 var set = typeof selector === "string" ? 5932 jQuery( selector, context ) : 5933 jQuery.makeArray( selector ), 5934 all = jQuery.merge( this.get(), set ); 5935 5936 return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? 5937 all : 5938 jQuery.unique( all ) ); 5939 }, 5940 5941 andSelf: function() { 5942 return this.add( this.prevObject ); 5943 } 5944}); 5945 5946// A painfully simple check to see if an element is disconnected 5947// from a document (should be improved, where feasible). 5948function isDisconnected( node ) { 5949 return !node || !node.parentNode || node.parentNode.nodeType === 11; 5950} 5951 5952jQuery.each({ 5953 parent: function( elem ) { 5954 var parent = elem.parentNode; 5955 return parent && parent.nodeType !== 11 ? parent : null; 5956 }, 5957 parents: function( elem ) { 5958 return jQuery.dir( elem, "parentNode" ); 5959 }, 5960 parentsUntil: function( elem, i, until ) { 5961 return jQuery.dir( elem, "parentNode", until ); 5962 }, 5963 next: function( elem ) { 5964 return jQuery.nth( elem, 2, "nextSibling" ); 5965 }, 5966 prev: function( elem ) { 5967 return jQuery.nth( elem, 2, "previousSibling" ); 5968 }, 5969 nextAll: function( elem ) { 5970 return jQuery.dir( elem, "nextSibling" ); 5971 }, 5972 prevAll: function( elem ) { 5973 return jQuery.dir( elem, "previousSibling" ); 5974 }, 5975 nextUntil: function( elem, i, until ) { 5976 return jQuery.dir( elem, "nextSibling", until ); 5977 }, 5978 prevUntil: function( elem, i, until ) { 5979 return jQuery.dir( elem, "previousSibling", until ); 5980 }, 5981 siblings: function( elem ) { 5982 return jQuery.sibling( elem.parentNode.firstChild, elem ); 5983 }, 5984 children: function( elem ) { 5985 return jQuery.sibling( elem.firstChild ); 5986 }, 5987 contents: function( elem ) { 5988 return jQuery.nodeName( elem, "iframe" ) ? 5989 elem.contentDocument || elem.contentWindow.document : 5990 jQuery.makeArray( elem.childNodes ); 5991 } 5992}, function( name, fn ) { 5993 jQuery.fn[ name ] = function( until, selector ) { 5994 var ret = jQuery.map( this, fn, until ), 5995 // The variable 'args' was introduced in 5996 // https://github.com/jquery/jquery/commit/52a0238 5997 // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed. 5998 // http://code.google.com/p/v8/issues/detail?id=1050 5999 args = slice.call(arguments); 6000 6001 if ( !runtil.test( name ) ) { 6002 selector = until; 6003 } 6004 6005 if ( selector && typeof selector === "string" ) { 6006 ret = jQuery.filter( selector, ret ); 6007 } 6008 6009 ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; 6010 6011 if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { 6012 ret = ret.reverse(); 6013 } 6014 6015 return this.pushStack( ret, name, args.join(",") ); 6016 }; 6017}); 6018 6019jQuery.extend({ 6020 filter: function( expr, elems, not ) { 6021 if ( not ) { 6022 expr = ":not(" + expr + ")"; 6023 } 6024 6025 return elems.length === 1 ? 6026 jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : 6027 jQuery.find.matches(expr, elems); 6028 }, 6029 6030 dir: function( elem, dir, until ) { 6031 var matched = [], 6032 cur = elem[ dir ]; 6033 6034 while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { 6035 if ( cur.nodeType === 1 ) { 6036 matched.push( cur ); 6037 } 6038 cur = cur[dir]; 6039 } 6040 return matched; 6041 }, 6042 6043 nth: function( cur, result, dir, elem ) { 6044 result = result || 1; 6045 var num = 0; 6046 6047 for ( ; cur; cur = cur[dir] ) { 6048 if ( cur.nodeType === 1 && ++num === result ) { 6049 break; 6050 } 6051 } 6052 6053 return cur; 6054 }, 6055 6056 sibling: function( n, elem ) { 6057 var r = []; 6058 6059 for ( ; n; n = n.nextSibling ) { 6060 if ( n.nodeType === 1 && n !== elem ) { 6061 r.push( n ); 6062 } 6063 } 6064 6065 return r; 6066 } 6067}); 6068 6069// Implement the identical functionality for filter and not 6070function winnow( elements, qualifier, keep ) { 6071 if ( jQuery.isFunction( qualifier ) ) { 6072 return jQuery.grep(elements, function( elem, i ) { 6073 var retVal = !!qualifier.call( elem, i, elem ); 6074 return retVal === keep; 6075 }); 6076 6077 } else if ( qualifier.nodeType ) { 6078 return jQuery.grep(elements, function( elem, i ) { 6079 return (elem === qualifier) === keep; 6080 }); 6081 6082 } else if ( typeof qualifier === "string" ) { 6083 var filtered = jQuery.grep(elements, function( elem ) { 6084 return elem.nodeType === 1; 6085 }); 6086 6087 if ( isSimple.test( qualifier ) ) { 6088 return jQuery.filter(qualifier, filtered, !keep); 6089 } else { 6090 qualifier = jQuery.filter( qualifier, filtered ); 6091 } 6092 } 6093 6094 return jQuery.grep(elements, function( elem, i ) { 6095 return (jQuery.inArray( elem, qualifier ) >= 0) === keep; 6096 }); 6097} 6098 6099 6100 6101 6102var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, 6103 rleadingWhitespace = /^\s+/, 6104 rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, 6105 rtagName = /<([\w:]+)/, 6106 rtbody = /<tbody/i,
6107 rhtml = /<|&#?\w+;/, 6108 rnocache = /<(?:script|object|embed|option|style)/i, 6109 // checked="checked" or checked 6110 rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, 6111 wrapMap = { 6112 option: [ 1, "<select multiple='multiple'>", "</select>" ], 6113 legend: [ 1, "<fieldset>", "</fieldset>" ], 6114 thead: [ 1, "<table>", "</table>" ], 6115 tr: [ 2, "<table><tbody>", "</tbody></table>" ], 6116 td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], 6117 col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ], 6118 area: [ 1, "<map>", "</map>" ], 6119 _default: [ 0, "", "" ] 6120 }; 6121 6122wrapMap.optgroup = wrapMap.option; 6123wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 6124wrapMap.th = wrapMap.td; 6125 6126// IE can't serialize <link> and <script> tags normally 6127if ( !jQuery.support.htmlSerialize ) { 6128 wrapMap._default = [ 1, "div<div>", "</div>" ]; 6129} 6130 6131jQuery.fn.extend({ 6132 text: function( text ) { 6133 if ( jQuery.isFunction(text) ) { 6134 return this.each(function(i) { 6135 var self = jQuery( this ); 6136 6137 self.text( text.call(this, i, self.text()) ); 6138 }); 6139 } 6140 6141 if ( typeof text !== "object" && text !== undefined ) { 6142 return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); 6143 } 6144 6145 return jQuery.text( this ); 6146 }, 6147 6148 wrapAll: function( html ) { 6149 if ( jQuery.isFunction( html ) ) { 6150 return this.each(function(i) { 6151 jQuery(this).wrapAll( html.call(this, i) ); 6152 }); 6153 } 6154 6155 if ( this[0] ) { 6156 // The elements to wrap the target around 6157 var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); 6158 6159 if ( this[0].parentNode ) { 6160 wrap.insertBefore( this[0] ); 6161 } 6162 6163 wrap.map(function() { 6164 var elem = this; 6165 6166 while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { 6167 elem = elem.firstChild; 6168 } 6169 6170 return elem; 6171 }).append(this); 6172 } 6173 6174 return this; 6175 }, 6176 6177 wrapInner: function( html ) { 6178 if ( jQuery.isFunction( html ) ) { 6179 return this.each(function(i) { 6180 jQuery(this).wrapInner( html.call(this, i) ); 6181 }); 6182 } 6183 6184 return this.each(function() { 6185 var self = jQuery( this ), 6186 contents = self.contents(); 6187 6188 if ( contents.length ) { 6189 contents.wrapAll( html ); 6190 6191 } else { 6192 self.append( html ); 6193 } 6194 }); 6195 }, 6196 6197 wrap: function( html ) { 6198 return this.each(function() { 6199 jQuery( this ).wrapAll( html ); 6200 }); 6201 }, 6202 6203 unwrap: function() { 6204 return this.parent().each(function() { 6205 if ( !jQuery.nodeName( this, "body" ) ) { 6206 jQuery( this ).replaceWith( this.childNodes ); 6207 } 6208 }).end(); 6209 }, 6210 6211 append: function() { 6212 return this.domManip(arguments, true, function( elem ) { 6213 if ( this.nodeType === 1 ) { 6214 this.appendChild( elem ); 6215 } 6216 }); 6217 }, 6218
6219 prepend: function() { 6220 return this.domManip(arguments, true, function( elem ) { 6221 if ( this.nodeType === 1 ) { 6222 this.insertBefore( elem, this.firstChild ); 6223 } 6224 }); 6225 }, 6226 6227 before: function() { 6228 if ( this[0] && this[0].parentNode ) { 6229 return this.domManip(arguments, false, function( elem ) { 6230 this.parentNode.insertBefore( elem, this ); 6231 }); 6232 } else if ( arguments.length ) { 6233 var set = jQuery(arguments[0]); 6234 set.push.apply( set, this.toArray() ); 6235 return this.pushStack( set, "before", arguments ); 6236 } 6237 }, 6238 6239 after: function() { 6240 if ( this[0] && this[0].parentNode ) { 6241 return this.domManip(arguments, false, function( elem ) { 6242 this.parentNode.insertBefore( elem, this.nextSibling ); 6243 }); 6244 } else if ( arguments.length ) { 6245 var set = this.pushStack( this, "after", arguments ); 6246 set.push.apply( set, jQuery(arguments[0]).toArray() ); 6247 return set; 6248 } 6249 }, 6250 6251 // keepData is for internal use only--do not document 6252 remove: function( selector, keepData ) { 6253 for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { 6254 if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { 6255 if ( !keepData && elem.nodeType === 1 ) { 6256 jQuery.cleanData( elem.getElementsByTagName("*") ); 6257 jQuery.cleanData( [ elem ] ); 6258 } 6259 6260 if ( elem.parentNode ) { 6261 elem.parentNode.removeChild( elem ); 6262 } 6263 } 6264 } 6265 6266 return this; 6267 }, 6268 6269 empty: function() { 6270 for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { 6271 // Remove element nodes and prevent memory leaks 6272 if ( elem.nodeType === 1 ) { 6273 jQuery.cleanData( elem.getElementsByTagName("*") ); 6274 } 6275 6276 // Remove any remaining nodes 6277 while ( elem.firstChild ) { 6278 elem.removeChild( elem.firstChild ); 6279 } 6280 } 6281 6282 return this; 6283 }, 6284 6285 clone: function( dataAndEvents, deepDataAndEvents ) { 6286 dataAndEvents = dataAndEvents == null ? false : dataAndEvents; 6287 deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; 6288 6289 return this.map( function() { 6290 return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); 6291 }); 6292 }, 6293 6294 html: function( value ) { 6295 if ( value === undefined ) { 6296 return this[0] && this[0].nodeType === 1 ? 6297 this[0].innerHTML.replace(rinlinejQuery, "") : 6298 null; 6299 6300 // See if we can take a shortcut and just use innerHTML 6301 } else if ( typeof value === "string" && !rnocache.test( value ) && 6302 (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) && 6303 !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) { 6304 6305 value = value.replace(rxhtmlTag, "<$1></$2>"); 6306 6307 try { 6308 for ( var i = 0, l = this.length; i < l; i++ ) { 6309 // Remove element nodes and prevent memory leaks 6310 if ( this[i].nodeType === 1 ) { 6311 jQuery.cleanData( this[i].getElementsByTagName("*") ); 6312 this[i].innerHTML = value; 6313 } 6314 } 6315 6316 // If using innerHTML throws an exception, use the fallback method 6317 } catch(e) { 6318 this.empty().append( value ); 6319 } 6320 6321 } else if ( jQuery.isFunction( value ) ) { 6322 this.each(function(i){ 6323 var self = jQuery( this ); 6324 6325 self.html( value.call(this, i, self.html()) ); 6326 }); 6327 6328 } else { 6329 this.empty().append( value ); 6330 } 6331 6332 return this; 6333 }, 6334 6335 replaceWith: function( value ) { 6336 if ( this[0] && this[0].parentNode ) { 6337 // Make sure that the elements are removed from the DOM before they are inserted 6338 // this can help fix replacing a parent with child elements 6339 if ( jQuery.isFunction( value ) ) { 6340 return this.each(function(i) { 6341 var self = jQuery(this), old = self.html(); 6342 self.replaceWith( value.call( this, i, old ) ); 6343 }); 6344 } 6345 6346 if ( typeof value !== "string" ) { 6347 value = jQuery( value ).detach(); 6348 } 6349 6350 return this.each(function() { 6351 var next = this.nextSibling, 6352 parent = this.parentNode; 6353 6354 jQuery( this ).remove(); 6355 6356 if ( next ) { 6357 jQuery(next).before( value ); 6358 } else { 6359 jQuery(parent).append( value ); 6360 } 6361 }); 6362 } else { 6363 return this.length ? 6364 this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) : 6365 this; 6366 } 6367 }, 6368 6369 detach: function( selector ) { 6370 return this.remove( selector, true ); 6371 }, 6372 6373 domManip: function( args, table, callback ) { 6374 var results, first, fragment, parent, 6375 value = args[0], 6376 scripts = []; 6377 6378 // We can't cloneNode fragments that contain checked, in WebKit 6379 if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) { 6380 return this.each(function() { 6381 jQuery(this).domManip( args, table, callback, true ); 6382 }); 6383 } 6384 6385 if ( jQuery.isFunction(value) ) { 6386 return this.each(function(i) { 6387 var self = jQuery(this); 6388 args[0] = value.call(this, i, table ? self.html() : undefined); 6389 self.domManip( args, table, callback ); 6390 }); 6391 } 6392 6393 if ( this[0] ) { 6394 parent = value && value.parentNode; 6395 6396 // If we're in a fragment, just use that instead of building a new one 6397 if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) { 6398 results = { fragment: parent }; 6399 6400 } else { 6401 results = jQuery.buildFragment( args, this, scripts ); 6402 } 6403 6404 fragment = results.fragment; 6405 6406 if ( fragment.childNodes.length === 1 ) { 6407 first = fragment = fragment.firstChild; 6408 } else { 6409 first = fragment.firstChild; 6410 } 6411 6412 if ( first ) { 6413 table = table && jQuery.nodeName( first, "tr" ); 6414 6415 for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) { 6416 callback.call( 6417 table ? 6418 root(this[i], first) : 6419 this[i], 6420 // Make sure that we do not leak memory by inadvertently discarding 6421 // the original fragment (which might have attached data) instead of 6422 // using it; in addition, use the original fragment object for the last 6423 // item instead of first because it can end up being emptied incorrectly 6424 // in certain situations (Bug #8070). 6425 // Fragments from the fragment cache must always be cloned and never used 6426 // in place. 6427 results.cacheable || (l > 1 && i < lastIndex) ? 6428 jQuery.clone( fragment, true, true ) : 6429 fragment 6430 ); 6431 } 6432 } 6433 6434 if ( scripts.length ) { 6435 jQuery.each( scripts, evalScript ); 6436 } 6437 } 6438 6439 return this; 6440 } 6441}); 6442 6443function root( elem, cur ) { 6444 return jQuery.nodeName(elem, "table") ? 6445 (elem.getElementsByTagName("tbody")[0] || 6446 elem.appendChild(elem.ownerDocument.createElement("tbody"))) : 6447 elem; 6448} 6449 6450function cloneCopyEvent( src, dest ) { 6451
6452 if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { 6453 return; 6454 } 6455 6456 var internalKey = jQuery.expando, 6457 oldData = jQuery.data( src ), 6458 curData = jQuery.data( dest, oldData ); 6459 6460 // Switch to use the internal data object, if it exists, for the next 6461 // stage of data copying 6462 if ( (oldData = oldData[ internalKey ]) ) { 6463 var events = oldData.events; 6464 curData = curData[ internalKey ] = jQuery.extend({}, oldData); 6465 6466 if ( events ) { 6467 delete curData.handle; 6468 curData.events = {}; 6469 6470 for ( var type in events ) { 6471 for ( var i = 0, l = events[ type ].length; i < l; i++ ) { 6472 jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data ); 6473 } 6474 } 6475 } 6476 } 6477} 6478 6479function cloneFixAttributes(src, dest) { 6480 // We do not need to do anything for non-Elements 6481 if ( dest.nodeType !== 1 ) { 6482 return; 6483 } 6484 6485 var nodeName = dest.nodeName.toLowerCase(); 6486 6487 // clearAttributes removes the attributes, which we don't want, 6488 // but also removes the attachEvent events, which we *do* want 6489 dest.clearAttributes(); 6490 6491 // mergeAttributes, in contrast, only merges back on the 6492 // original attributes, not the events 6493 dest.mergeAttributes(src); 6494 6495 // IE6-8 fail to clone children inside object elements that use 6496 // the proprietary classid attribute value (rather than the type 6497 // attribute) to identify the type of content to display 6498 if ( nodeName === "object" ) { 6499 dest.outerHTML = src.outerHTML; 6500 6501 } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) { 6502 // IE6-8 fails to persist the checked state of a cloned checkbox 6503 // or radio button. Worse, IE6-7 fail to give the cloned element 6504 // a checked appearance if the defaultChecked value isn't also set 6505 if ( src.checked ) { 6506 dest.defaultChecked = dest.checked = src.checked; 6507 } 6508 6509 // IE6-7 get confused and end up setting the value of a cloned 6510 // checkbox/radio button to an empty string instead of "on" 6511 if ( dest.value !== src.value ) { 6512 dest.value = src.value; 6513 } 6514 6515 // IE6-8 fails to return the selected option to the default selected 6516 // state when cloning options 6517 } else if ( nodeName === "option" ) { 6518 dest.selected = src.defaultSelected; 6519 6520 // IE6-8 fails to set the defaultValue to the correct value when 6521 // cloning other types of input fields 6522 } else if ( nodeName === "input" || nodeName === "textarea" ) { 6523 dest.defaultValue = src.defaultValue; 6524 } 6525 6526 // Event data gets referenced instead of copied if the expando 6527 // gets copied too 6528 dest.removeAttribute( jQuery.expando ); 6529} 6530 6531jQuery.buildFragment = function( args, nodes, scripts ) { 6532 var fragment, cacheable, cacheresults, 6533 doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document); 6534 6535 // Only cache "small" (1/2 KB) HTML strings that are associated with the main document 6536 // Cloning options loses the selected state, so don't cache them 6537 // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment 6538 // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache 6539 if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document && 6540 args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) { 6541 6542 cacheable = true; 6543 cacheresults = jQuery.fragments[ args[0] ]; 6544 if ( cacheresults ) { 6545 if ( cacheresults !== 1 ) { 6546 fragment = cacheresults; 6547 } 6548 } 6549 } 6550 6551 if ( !fragment ) { 6552 fragment = doc.createDocumentFragment(); 6553 jQuery.clean( args, doc, fragment, scripts ); 6554 } 6555 6556 if ( cacheable ) { 6557 jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1; 6558 } 6559 6560 return { fragment: fragment, cacheable: cacheable }; 6561}; 6562 6563jQuery.fragments = {}; 6564 6565jQuery.each({ 6566 appendTo: "append", 6567 prependTo: "prepend", 6568 insertBefore: "before", 6569 insertAfter: "after", 6570 replaceAll: "replaceWith" 6571}, function( name, original ) { 6572 jQuery.fn[ name ] = function( selector ) { 6573 var ret = [], 6574 insert = jQuery( selector ), 6575 parent = this.length === 1 && this[0].parentNode; 6576 6577 if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { 6578 insert[ original ]( this[0] ); 6579 return this; 6580 6581 } else { 6582 for ( var i = 0, l = insert.length; i < l; i++ ) { 6583 var elems = (i > 0 ? this.clone(true) : this).get(); 6584 jQuery( insert[i] )[ original ]( elems ); 6585 ret = ret.concat( elems ); 6586 } 6587 6588 return this.pushStack( ret, name, insert.selector ); 6589 } 6590 }; 6591}); 6592 6593function getAll( elem ) { 6594 if ( "getElementsByTagName" in elem ) { 6595 return elem.getElementsByTagName( "*" ); 6596 6597 } else if ( "querySelectorAll" in elem ) { 6598 return elem.querySelectorAll( "*" ); 6599 6600 } else { 6601 return []; 6602 } 6603} 6604 6605jQuery.extend({ 6606 clone: function( elem, dataAndEvents, deepDataAndEvents ) { 6607 var clone = elem.cloneNode(true), 6608 srcElements, 6609 destElements, 6610 i; 6611 6612 if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && 6613 (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { 6614 // IE copies events bound via attachEvent when using cloneNode. 6615 // Calling detachEvent on the clone will also remove the events 6616 // from the original. In order to get around this, we use some 6617 // proprietary methods to clear the events. Thanks to MooTools 6618 // guys for this hotness. 6619 6620 cloneFixAttributes( elem, clone ); 6621 6622 // Using Sizzle here is crazy slow, so we use getElementsByTagName 6623 // instead 6624 srcElements = getAll( elem ); 6625 destElements = getAll( clone ); 6626 6627 // Weird iteration because IE will replace the length property 6628 // with an element if you are cloning the body and one of the 6629 // elements on the page has a name or id of "length" 6630 for ( i = 0; srcElements[i]; ++i ) { 6631 cloneFixAttributes( srcElements[i], destElements[i] ); 6632 } 6633 } 6634 6635 // Copy the events from the original to the clone 6636 if ( dataAndEvents ) { 6637 cloneCopyEvent( elem, clone ); 6638 6639 if ( deepDataAndEvents ) { 6640 srcElements = getAll( elem ); 6641 destElements = getAll( clone ); 6642 6643 for ( i = 0; srcElements[i]; ++i ) { 6644 cloneCopyEvent( srcElements[i], destElements[i] ); 6645 } 6646 } 6647 } 6648 6649 // Return the cloned set 6650 return clone; 6651}, 6652 clean: function( elems, context, fragment, scripts ) { 6653 context = context || document; 6654 6655 // !context.createElement fails in IE with an error but returns typeof 'object' 6656 if ( typeof context.createElement === "undefined" ) { 6657 context = context.ownerDocument || context[0] && context[0].ownerDocument || document; 6658 } 6659 6660 var ret = []; 6661 6662 for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { 6663 if ( typeof elem === "number" ) { 6664 elem += ""; 6665 } 6666 6667 if ( !elem ) { 6668 continue; 6669 } 6670 6671 // Convert html string into DOM nodes 6672 if ( typeof elem === "string" && !rhtml.test( elem ) ) { 6673 elem = context.createTextNode( elem ); 6674 6675 } else if ( typeof elem === "string" ) { 6676 // Fix "XHTML"-style tags in all browsers 6677 elem = elem.replace(rxhtmlTag, "<$1></$2>"); 6678 6679 // Trim whitespace, otherwise indexOf won't work as expected 6680 var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(), 6681 wrap = wrapMap[ tag ] || wrapMap._default, 6682 depth = wrap[0], 6683 div = context.createElement("div"); 6684 6685 // Go to html and back, then peel off extra wrappers 6686 div.innerHTML = wrap[1] + elem + wrap[2]; 6687 6688 // Move to the right depth 6689 while ( depth-- ) {
6690 div = div.lastChild; 6691 } 6692 6693 // Remove IE's autoinserted <tbody> from table fragments 6694 if ( !jQuery.support.tbody ) { 6695 6696 // String was a <table>, *may* have spurious <tbody> 6697 var hasBody = rtbody.test(elem), 6698 tbody = tag === "table" && !hasBody ? 6699 div.firstChild && div.firstChild.childNodes : 6700 6701 // String was a bare <thead> or <tfoot> 6702 wrap[1] === "<table>" && !hasBody ? 6703 div.childNodes : 6704 []; 6705 6706 for ( var j = tbody.length - 1; j >= 0 ; --j ) { 6707 if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { 6708 tbody[ j ].parentNode.removeChild( tbody[ j ] ); 6709 } 6710 } 6711 6712 } 6713 6714 // IE completely kills leading whitespace when innerHTML is used 6715 if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { 6716 div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); 6717 } 6718 6719 elem = div.childNodes; 6720 } 6721 6722 if ( elem.nodeType ) { 6723 ret.push( elem ); 6724 } else { 6725 ret = jQuery.merge( ret, elem ); 6726 } 6727 } 6728 6729 if ( fragment ) { 6730 for ( i = 0; ret[i]; i++ ) { 6731 if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { 6732 scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); 6733 6734 } else { 6735 if ( ret[i].nodeType === 1 ) { 6736 ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) ); 6737 } 6738 fragment.appendChild( ret[i] ); 6739 } 6740 } 6741 } 6742 6743 return ret; 6744 }, 6745 6746 cleanData: function( elems ) { 6747 var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special, 6748 deleteExpando = jQuery.support.deleteExpando; 6749 6750 for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { 6751 if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { 6752 continue; 6753 } 6754 6755 id = elem[ jQuery.expando ]; 6756 6757 if ( id ) { 6758 data = cache[ id ] && cache[ id ][ internalKey ]; 6759 6760 if ( data && data.events ) { 6761 for ( var type in data.events ) { 6762 if ( special[ type ] ) { 6763 jQuery.event.remove( elem, type ); 6764 6765 // This is a shortcut to avoid jQuery.event.remove's overhead 6766 } else { 6767 jQuery.removeEvent( elem, type, data.handle ); 6768 } 6769 } 6770 6771 // Null the DOM reference to avoid IE6/7/8 leak (#7054) 6772 if ( data.handle ) { 6773 data.handle.elem = null; 6774 } 6775 } 6776 6777 if ( deleteExpando ) { 6778 delete elem[ jQuery.expando ]; 6779 6780 } else if ( elem.removeAttribute ) { 6781 elem.removeAttribute( jQuery.expando ); 6782 } 6783 6784 delete cache[ id ]; 6785 } 6786 } 6787 } 6788}); 6789 6790function evalScript( i, elem ) { 6791 if ( elem.src ) { 6792 jQuery.ajax({ 6793 url: elem.src, 6794 async: false, 6795 dataType: "script" 6796 }); 6797 } else { 6798 jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); 6799 } 6800 6801 if ( elem.parentNode ) { 6802 elem.parentNode.removeChild( elem ); 6803 } 6804} 6805 6806 6807 6808 6809var ralpha = /alpha\([^)]*\)/i, 6810 ropacity = /opacity=([^)]*)/, 6811 rdashAlpha = /-([a-z])/ig, 6812 // fixed for IE9, see #8346 6813 rupper = /([A-Z]|^ms)/g, 6814 rnumpx = /^-?\d+(?:px)?$/i, 6815 rnum = /^-?\d/, 6816 6817 cssShow = { position: "absolute", visibility: "hidden", display: "block" }, 6818 cssWidth = [ "Left", "Right" ], 6819 cssHeight = [ "Top", "Bottom" ], 6820 curCSS, 6821 6822 getComputedStyle, 6823 currentStyle, 6824 6825 fcamelCase = function( all, letter ) { 6826 return letter.toUpperCase(); 6827 }; 6828 6829jQuery.fn.css = function( name, value ) { 6830 // Setting 'undefined' is a no-op 6831 if ( arguments.length === 2 && value === undefined ) { 6832 return this; 6833 } 6834 6835 return jQuery.access( this, name, value, true, function( elem, name, value ) { 6836 return value !== undefined ? 6837 jQuery.style( elem, name, value ) : 6838 jQuery.css( elem, name ); 6839 }); 6840}; 6841 6842jQuery.extend({ 6843 // Add in style property hooks for overriding the default 6844 // behavior of getting and setting a style property 6845 cssHooks: { 6846 opacity: { 6847 get: function( elem, computed ) { 6848 if ( computed ) { 6849 // We should always get a number back from opacity 6850 var ret = curCSS( elem, "opacity", "opacity" ); 6851 return ret === "" ? "1" : ret; 6852 6853 } else { 6854 return elem.style.opacity; 6855 } 6856 } 6857 } 6858 }, 6859 6860 // Exclude the following css properties to add px 6861 cssNumber: { 6862 "zIndex": true, 6863 "fontWeight": true, 6864 "opacity": true, 6865 "zoom": true, 6866 "lineHeight": true 6867 }, 6868 6869 // Add in properties whose names you wish to fix before 6870 // setting or getting the value 6871 cssProps: { 6872 // normalize float css property 6873 "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" 6874 }, 6875 6876 // Get and set the style property on a DOM Node 6877 style: function( elem, name, value, extra ) { 6878 // Don't set styles on text and comment nodes 6879 if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { 6880 return; 6881 } 6882 6883 // Make sure that we're working with the right name 6884 var ret, origName = jQuery.camelCase( name ), 6885 style = elem.style, hooks = jQuery.cssHooks[ origName ]; 6886 6887 name = jQuery.cssProps[ origName ] || origName; 6888 6889 // Check if we're setting a value 6890 if ( value !== undefined ) { 6891 // Make sure that NaN and null values aren't set. See: #7116 6892 if ( typeof value === "number" && isNaN( value ) || value == null ) { 6893 return; 6894 } 6895 6896 // If a number was passed in, add 'px' to the (except for certain CSS properties) 6897 if ( typeof value === "number" && !jQuery.cssNumber[ origName ] ) { 6898 value += "px"; 6899 } 6900 6901 // If a hook was provided, use that value, otherwise just set the specified value 6902 if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
6903 // Wrapped to prevent IE from throwing errors when 'invalid' values are provided 6904 // Fixes bug #5509 6905 try { 6906 style[ name ] = value; 6907 } catch(e) {} 6908 } 6909 6910 } else { 6911 // If a hook was provided get the non-computed value from there 6912 if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { 6913 return ret; 6914 } 6915 6916 // Otherwise just get the value from the style object 6917 return style[ name ]; 6918 } 6919 }, 6920 6921 css: function( elem, name, extra ) { 6922 // Make sure that we're working with the right name 6923 var ret, origName = jQuery.camelCase( name ), 6924 hooks = jQuery.cssHooks[ origName ]; 6925 6926 name = jQuery.cssProps[ origName ] || origName; 6927 6928 // If a hook was provided get the computed value from there 6929 if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) { 6930 return ret; 6931 6932 // Otherwise, if a way to get the computed value exists, use that 6933 } else if ( curCSS ) { 6934 return curCSS( elem, name, origName ); 6935 } 6936 }, 6937 6938 // A method for quickly swapping in/out CSS properties to get correct calculations 6939 swap: function( elem, options, callback ) { 6940 var old = {}; 6941 6942 // Remember the old values, and insert the new ones 6943 for ( var name in options ) { 6944 old[ name ] = elem.style[ name ]; 6945 elem.style[ name ] = options[ name ]; 6946 } 6947 6948 callback.call( elem ); 6949 6950 // Revert the old values 6951 for ( name in options ) { 6952 elem.style[ name ] = old[ name ]; 6953 } 6954 }, 6955 6956 camelCase: function( string ) { 6957 return string.replace( rdashAlpha, fcamelCase ); 6958 } 6959}); 6960 6961// DEPRECATED, Use jQuery.css() instead 6962jQuery.curCSS = jQuery.css; 6963 6964jQuery.each(["height", "width"], function( i, name ) { 6965 jQuery.cssHooks[ name ] = { 6966 get: function( elem, computed, extra ) { 6967 var val; 6968 6969 if ( computed ) { 6970 if ( elem.offsetWidth !== 0 ) { 6971 val = getWH( elem, name, extra ); 6972 6973 } else { 6974 jQuery.swap( elem, cssShow, function() { 6975 val = getWH( elem, name, extra ); 6976 }); 6977 } 6978 6979 if ( val <= 0 ) { 6980 val = curCSS( elem, name, name ); 6981 6982 if ( val === "0px" && currentStyle ) { 6983 val = currentStyle( elem, name, name ); 6984 } 6985 6986 if ( val != null ) {
6987 // Should return "auto" instead of 0, use 0 for 6988 // temporary backwards-compat 6989 return val === "" || val === "auto" ? "0px" : val; 6990 } 6991 } 6992 6993 if ( val < 0 || val == null ) { 6994 val = elem.style[ name ]; 6995 6996 // Should return "auto" instead of 0, use 0 for 6997 // temporary backwards-compat 6998 return val === "" || val === "auto" ? "0px" : val; 6999 } 7000 7001 return typeof val === "string" ? val : val + "px"; 7002 } 7003 }, 7004 7005 set: function( elem, value ) { 7006 if ( rnumpx.test( value ) ) { 7007 // ignore negative width and height values #1599 7008 value = parseFloat(value); 7009 7010 if ( value >= 0 ) { 7011 return value + "px"; 7012 } 7013 7014 } else { 7015 return value; 7016 } 7017 } 7018 }; 7019}); 7020 7021if ( !jQuery.support.opacity ) { 7022 jQuery.cssHooks.opacity = { 7023 get: function( elem, computed ) { 7024 // IE uses filters for opacity 7025 return ropacity.test((computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "") ? 7026 (parseFloat(RegExp.$1) / 100) + "" : 7027 computed ? "1" : ""; 7028 }, 7029 7030 set: function( elem, value ) { 7031 var style = elem.style; 7032 7033 // IE has trouble with opacity if it does not have layout 7034 // Force it by setting the zoom level 7035 style.zoom = 1; 7036 7037 // Set the alpha filter to set the opacity 7038 var opacity = jQuery.isNaN(value) ? 7039 "" : 7040 "alpha(opacity=" + value * 100 + ")", 7041 filter = style.filter || ""; 7042 7043 style.filter = ralpha.test(filter) ? 7044 filter.replace(ralpha, opacity) : 7045 style.filter + ' ' + opacity; 7046 } 7047 }; 7048} 7049 7050jQuery(function() { 7051 // This hook cannot be added until DOM ready because the support test 7052 // for it is not run until after DOM ready 7053 if ( !jQuery.support.reliableMarginRight ) { 7054 jQuery.cssHooks.marginRight = { 7055 get: function( elem, computed ) { 7056 // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right 7057 // Work around by temporarily setting element display to inline-block 7058 var ret; 7059 jQuery.swap( elem, { "display": "inline-block" }, function() { 7060 if ( computed ) { 7061 ret = curCSS( elem, "margin-right", "marginRight" ); 7062 } else { 7063 ret = elem.style.marginRight; 7064 } 7065 }); 7066 return ret; 7067 } 7068 }; 7069 } 7070}); 7071 7072if ( document.defaultView && document.defaultView.getComputedStyle ) { 7073 getComputedStyle = function( elem, newName, name ) { 7074 var ret, defaultView, computedStyle; 7075 7076 name = name.replace( rupper, "-$1" ).toLowerCase(); 7077 7078 if ( !(defaultView = elem.ownerDocument.defaultView) ) { 7079 return undefined; 7080 } 7081 7082 if ( (computedStyle = defaultView.getComputedStyle( elem, null )) ) { 7083 ret = computedStyle.getPropertyValue( name ); 7084 if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) { 7085 ret = jQuery.style( elem, name ); 7086 } 7087 } 7088 7089 return ret; 7090 }; 7091} 7092 7093if ( document.documentElement.currentStyle ) { 7094 currentStyle = function( elem, name ) { 7095 var left, 7096 ret = elem.currentStyle && elem.currentStyle[ name ], 7097 rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ], 7098 style = elem.style; 7099 7100 // From the awesome hack by Dean Edwards 7101 // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 7102 7103 // If we're not dealing with a regular pixel number 7104 // but a number that has a weird ending, we need to convert it to pixels 7105 if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { 7106 // Remember the original values 7107 left = style.left; 7108 7109 // Put in the new values to get a computed value out 7110 if ( rsLeft ) { 7111 elem.runtimeStyle.left = elem.currentStyle.left; 7112 } 7113 style.left = name === "fontSize" ? "1em" : (ret || 0); 7114 ret = style.pixelLeft + "px"; 7115 7116 // Revert the changed values 7117 style.left = left; 7118 if ( rsLeft ) { 7119 elem.runtimeStyle.left = rsLeft; 7120 } 7121 } 7122 7123 return ret === "" ? "auto" : ret; 7124 }; 7125} 7126 7127curCSS = getComputedStyle || currentStyle; 7128 7129function getWH( elem, name, extra ) { 7130 var which = name === "width" ? cssWidth : cssHeight, 7131 val = name === "width" ? elem.offsetWidth : elem.offsetHeight; 7132 7133 if ( extra === "border" ) { 7134 return val; 7135 } 7136 7137 jQuery.each( which, function() { 7138 if ( !extra ) { 7139 val -= parseFloat(jQuery.css( elem, "padding" + this )) || 0; 7140 } 7141 7142 if ( extra === "margin" ) { 7143 val += parseFloat(jQuery.css( elem, "margin" + this )) || 0; 7144 7145 } else { 7146 val -= parseFloat(jQuery.css( elem, "border" + this + "Width" )) || 0; 7147 } 7148 }); 7149 7150 return val; 7151} 7152 7153if ( jQuery.expr && jQuery.expr.filters ) { 7154 jQuery.expr.filters.hidden = function( elem ) { 7155 var width = elem.offsetWidth, 7156 height = elem.offsetHeight; 7157 7158 return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "dis
7158play" )) === "none"); 7159 }; 7160 7161 jQuery.expr.filters.visible = function( elem ) { 7162 return !jQuery.expr.filters.hidden( elem ); 7163 }; 7164} 7165 7166 7167 7168 7169var r20 = /%20/g, 7170 rbracket = /\[\]$/, 7171 rCRLF = /\r?\n/g, 7172 rhash = /#.*$/, 7173 rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL 7174 rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, 7175 // #7653, #8125, #8152: local protocol detection 7176 rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|widget):$/, 7177 rnoContent = /^(?:GET|HEAD)$/, 7178 rprotocol = /^\/\//, 7179 rquery = /\?/, 7180 rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, 7181 rselectTextarea = /^(?:select|textarea)/i, 7182 rspacesAjax = /\s+/, 7183 rts = /([?&])_=[^&]*/, 7184 rucHeaders = /(^|\-)([a-z])/g, 7185 rucHeadersFunc = function( _, $1, $2 ) { 7186 return $1 + $2.toUpperCase(); 7187 }, 7188 rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/, 7189 7190 // Keep a copy of the old load method 7191 _load = jQuery.fn.load, 7192 7193 /* Prefilters 7194 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) 7195 * 2) These are called: 7196 * - BEFORE asking for a transport 7197 * - AFTER param serialization (s.data is a string if s.processData is true) 7198 * 3) key is the dataType 7199 * 4) the catchall symbol "*" can be used 7200 * 5) execution will start with transport dataType and THEN continue down to "*" if needed 7201 */ 7202 prefilters = {}, 7203 7204 /* Transports bindings 7205 * 1) key is the dataType 7206 * 2) the catchall symbol "*" can be used 7207 * 3) selection will start with transport dataType and THEN go to "*" if needed 7208 */ 7209 transports = {}, 7210 7211 // Document location 7212 ajaxLocation, 7213 7214 // Document location segments 7215 ajaxLocParts; 7216 7217// #8138, IE may throw an exception when accessing 7218// a field from document.location if document.domain has been set 7219try { 7220 ajaxLocation = document.location.href; 7221} catch( e ) { 7222 // Use the href attribute of an A element 7223 // since IE will modify it given document.location 7224 ajaxLocation = document.createElement( "a" ); 7225 ajaxLocation.href = ""; 7226 ajaxLocation = ajaxLocation.href; 7227} 7228 7229// Segment location into parts 7230ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || []; 7231 7232// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport 7233function addToPrefiltersOrTransports( structure ) { 7234 7235 // dataTypeExpression is optional and defaults to "*" 7236 return function( dataTypeExpression, func ) { 7237 7238 if ( typeof dataTypeExpression !== "string" ) { 7239 func = dataTypeExpression; 7240 dataTypeExpression = "*"; 7241 } 7242 7243 if ( jQuery.isFunction( func ) ) { 7244 var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ), 7245 i = 0, 7246 length = dataTypes.length, 7247 dataType, 7248 list, 7249 placeBefore; 7250 7251 // For each dataType in the dataTypeExpression 7252 for(; i < length; i++ ) { 7253 dataType = dataTypes[ i ]; 7254 // We control if we're asked to add before 7255 // any existing element 7256 placeBefore = /^\+/.test( dataType ); 7257 if ( placeBefore ) { 7258 dataType = dataType.substr( 1 ) || "*"; 7259 } 7260 list = structure[ dataType ] = structure[ dataType ] || []; 7261 // then we add to the structure accordingly 7262 list[ placeBefore ? "unshift" : "push" ]( func ); 7263 } 7264 } 7265 }; 7266} 7267 7268//Base inspection function for prefilters and transports 7269function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, 7270 dataType /* internal */, inspected /* internal */ ) { 7271 7272 dataType = dataType || options.dataTypes[ 0 ]; 7273 inspected = inspected || {}; 7274 7275 inspected[ dataType ] = true; 7276 7277 var list = structure[ dataType ], 7278 i = 0, 7279 length = list ? list.length : 0, 7280 executeOnly = ( structure === prefilters ), 7281 selection; 7282 7283 for(; i < length && ( executeOnly || !selection ); i++ ) { 7284 selection = list[ i ]( options, originalOptions, jqXHR ); 7285 // If we got redirected to another dataType 7286 // we try there if executing only and not done already 7287 if ( typeof selection === "string" ) { 7288 if ( !executeOnly || inspected[ selection ] ) { 7289 selection = undefined; 7290 } else { 7291 options.dataTypes.unshift( selection ); 7292 selection = inspectPrefiltersOrTransports( 7293 structure, options, originalOptions, jqXHR, selection, inspected ); 7294 } 7295 } 7296 } 7297 // If we're only executing or nothing was selected 7298 // we try the catchall dataType if not done already 7299 if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) { 7300 selection = inspectPrefiltersOrTransports( 7301 structure, options, originalOptions, jqXHR, "*", inspected ); 7302 } 7303 // unnecessary when only executing (prefilters) 7304 // but it'll be ignored by the caller in that case 7305 return selection; 7306} 7307 7308jQuery.fn.extend({ 7309 load: function( url, params, callback ) { 7310 if ( typeof url !== "string" && _load ) { 7311 return _load.apply( this, arguments ); 7312 7313 // Don't do a request if no elements are being requested 7314 } else if ( !this.length ) { 7315 return this; 7316 } 7317 7318 var off = url.indexOf( " " ); 7319 if ( off >= 0 ) { 7320 var selector = url.slice( off, url.length ); 7321 url = url.slice( 0, off ); 7322 } 7323
7324 // Default to a GET request 7325 var type = "GET"; 7326 7327 // If the second parameter was provided 7328 if ( params ) { 7329 // If it's a function 7330 if ( jQuery.isFunction( params ) ) { 7331 // We assume that it's the callback 7332 callback = params; 7333 params = undefined; 7334 7335 // Otherwise, build a param string 7336 } else if ( typeof params === "object" ) { 7337 params = jQuery.param( params, jQuery.ajaxSettings.traditional ); 7338 type = "POST"; 7339 } 7340 } 7341 7342 var self = this; 7343 7344 // Request the remote document 7345 jQuery.ajax({ 7346 url: url, 7347 type: type, 7348 dataType: "html", 7349 data: params, 7350 // Complete callback (responseText is used internally) 7351 complete: function( jqXHR, status, responseText ) { 7352 // Store the response as specified by the jqXHR object 7353 responseText = jqXHR.responseText; 7354 // If successful, inject the HTML into all the matched elements 7355 if ( jqXHR.isResolved() ) { 7356 // #4825: Get the actual response in case 7357 // a dataFilter is present in ajaxSettings 7358 jqXHR.done(function( r ) { 7359 responseText = r; 7360 }); 7361 // See if a selector was specified 7362 self.html( selector ? 7363 // Create a dummy div to hold the results 7364 jQuery("<div>") 7365 // inject the contents of the document in, removing the scripts 7366 // to avoid any 'Permission Denied' errors in IE 7367 .append(responseText.replace(rscript, "")) 7368 7369 // Locate the specified elements 7370 .find(selector) : 7371 7372 // If not, just inject the full result 7373 responseText ); 7374 } 7375 7376 if ( callback ) { 7377 self.each( callback, [ responseText, status, jqXHR ] ); 7378 } 7379 } 7380 }); 7381 7382 return this; 7383 }, 7384 7385 serialize: function() { 7386 return jQuery.param( this.serializeArray() ); 7387 }, 7388 7389 serializeArray: function() { 7390 return this.map(function(){ 7391 return this.elements ? jQuery.makeArray( this.elements ) : this; 7392 }) 7393 .filter(function(){ 7394 return this.name && !this.disabled && 7395 ( this.checked || rselectTextarea.test( this.nodeName ) || 7396 rinput.test( this.type ) ); 7397 }) 7398 .map(function( i, elem ){ 7399 var val = jQuery( this ).val(); 7400 7401 return val == null ? 7402 null : 7403 jQuery.isArray( val ) ? 7404 jQuery.map( val, function( val, i ){ 7405 return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; 7406 }) : 7407 { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; 7408 }).get(); 7409 } 7410}); 7411 7412// Attach a bunch of functions for handling common AJAX events 7413jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){ 7414 jQuery.fn[ o ] = function( f ){ 7415 return this.bind( o, f ); 7416 }; 7417} ); 7418 7419jQuery.each( [ "get", "post" ], function( i, method ) { 7420 jQuery[ method ] = function( url, data, callback, type ) { 7421 // shift arguments if data argument was omitted 7422 if ( jQuery.isFunction( data ) ) { 7423 type = type || callback; 7424 callback = data; 7425 data = undefined; 7426 } 7427 7428 return jQuery.ajax({ 7429 type: method, 7430 url: url, 7431 data: data, 7432 success: callback, 7433 dataType: type 7434 }); 7435 }; 7436} ); 7437 7438jQuery.extend({ 7439 7440 getScript: function( url, callback ) { 7441 return jQuery.get( url, undefined, callback, "script" ); 7442 }, 7443 7444 getJSON: function( url, data, callback ) { 7445 return jQuery.get( url, data, callback, "json" ); 7446 }, 7447 7448 // Creates a full fledged settings object into target 7449 // with both ajaxSettings and settings fields. 7450 // If target is omitted, writes into ajaxSettings. 7451 ajaxSetup: function ( target, settings ) { 7452 if ( !settings ) { 7453 // Only one parameter, we extend ajaxSettings 7454 settings = target; 7455 target = jQuery.extend( true, jQuery.ajaxSettings, settings ); 7456 } else { 7457 // target was provided, we extend into it 7458 jQuery.extend( true, target, jQuery.ajaxSettings, settings ); 7459 } 7460 // Flatten fields we don't want deep extended 7461 for( var field in { context: 1, url: 1 } ) { 7462 if ( field in settings ) { 7463 target[ field ] = settings[ field ]; 7464 } else if( field in jQuery.ajaxSettings ) { 7465 target[ field ] = jQuery.ajaxSettings[ field ]; 7466 } 7467 } 7468 return target; 7469 }, 7470 7471 ajaxSettings: { 7472 url: ajaxLocation, 7473 isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ), 7474 global: true, 7475 type: "GET", 7476 contentType: "application/x-www-form-urlencoded", 7477 processData: true, 7478 async: true, 7479 /* 7480 timeout: 0, 7481 data: null, 7482 dataType: null, 7483 username: null, 7484 password: null, 7485 cache: null, 7486 traditional: false, 7487 headers: {}, 7488 */ 7489 7490 accepts: { 7491 xml: "application/xml, text/xml", 7492 html: "text/html", 7493 text: "text/plain", 7494 json: "application/json, text/javascript", 7495 "*": "*/*" 7496 }, 7497 7498 contents: { 7499 xml: /xml/, 7500 html: /html/, 7501 json: /json/ 7502 }, 7503 7504 responseFields: { 7505 xml: "responseXML", 7506 text: "responseText" 7507 }, 7508 7509 // List of data converters 7510 // 1) key format is "source_type destination_type" (a single space in-between) 7511 // 2) the catchall symbol "*" can be used for source_type 7512 converters: { 7513 7514 // Convert anything to text 7515 "* text": window.String, 7516 7517 // Text to html (true = no transformation) 7518 "text html": true, 7519 7520 // Evaluate text as a json expression 7521 "text json": jQuery.parseJSON, 7522 7523 // Parse text as xml 7524 "text xml": jQuery.parseXML 7525 } 7526 }, 7527 7528 ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), 7529 ajaxTransport: addToPrefiltersOrTransports( transports ), 7530 7531 // Main method 7532 ajax: function( url, options ) { 7533 7534 // If url is an object, simulate pre-1.5 signature 7535 if ( typeof url === "object" ) { 7536 options = url; 7537 url = undefined; 7538 } 7539 7540 // Force options to be an object 7541 options = options || {}; 7542 7543 var // Create the final options object 7544 s = jQuery.ajaxSetup( {}, options ), 7545 // Callbacks context 7546 callbackContext = s.context || s, 7547 // Context for global events 7548 // It's the callbackContext if one was provided in the options 7549 // and if it's a DOM node or a jQuery collection 7550 globalEventContext = callbackContext !== s && 7551 ( callbackContext.nodeType || callbackContext instanceof jQuery ) ? 7552 jQuery( callbackContext ) : jQuery.event, 7553 // Deferreds 7554 deferred = jQuery.Deferred(), 7555 completeDeferred = jQuery._Deferred(), 7556 // Status-dependent callbacks 7557 statusCode = s.statusCode || {}, 7558 // ifModified key 7559 ifModifiedKey, 7560 // Headers (they are sent all at once) 7561 requestHeaders = {}, 7562 // Response headers 7563 responseHeadersString, 7564 responseHeaders, 7565 // transport 7566 transport, 7567 // timeout handle 7568 timeoutTimer, 7569 // Cross-domain detection vars 7570 parts, 7571 // The jqXHR state 7572 state = 0, 7573 // To know if global events are to be dispatched 7574 fireGlobals, 7575 // Loop variable 7576 i, 7577 // Fake xhr 7578 jqXHR = { 7579 7580 readyState: 0, 7581 7582 // Caches the header 7583 setRequestHeader: function( name, value ) { 7584 if ( !state ) { 7585 requestHeaders[ name.toLowerCase().replace( rucHeaders, rucHeadersFunc ) ] = value; 7586 } 7587 return this; 7588 }, 7589 7590 // Raw string 7591 getAllResponseHeaders: function() { 7592 return state === 2 ? responseHeadersString : null; 7593 }, 7594 7595 // Builds headers hashtable if needed 7596 getResponseHeader: function( key ) { 7597 var match; 7598 if ( state === 2 ) { 7599 if ( !responseHeaders ) { 7600 responseHeaders = {}; 7601 while( ( match = rheaders.exec( responseHeadersString ) ) ) { 7602 responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; 7603 } 7604 } 7605 match = responseHeaders[ key.toLowerCase() ]; 7606 } 7607 return match === undefined ? null : match; 7608 }, 7609 7610 // Overrides response content-type header 7611 overrideMimeType: function( type ) { 7612 if ( !state ) { 7613 s.mimeType = type; 7614 } 7615 return this; 7616 }, 7617 7618 // Cancel the request 7619 abort: function( statusText ) { 7620 statusText = statusText || "abort"; 7621 if ( transport ) { 7622 transport.abort( statusText ); 7623 } 7624 done( 0, statusText ); 7625 return this; 7626 } 7627 }; 7628 7629 // Callback for when everything is done 7630 // It is defined here because jslint complains if it is declared 7631 // at the end of the function (which would be more logical and readable) 7632 function done( status, statusText, responses, headers ) { 7633 7634 // Called once 7635 if ( state === 2 ) { 7636 return; 7637 } 7638 7639 // State is "done" now 7640 state = 2; 7641 7642 // Clear timeout if it exists 7643 if ( timeoutTimer ) { 7644 clearTimeout( timeoutTimer ); 7645 } 7646 7647 // Dereference transport for early garbage collection 7648 // (no matter how long the jqXHR object will be used) 7649 transport = undefined; 7650 7651 // Cache response headers 7652 responseHeadersString = headers || ""; 7653 7654 // Set readyState 7655 jqXHR.readyState = status ? 4 : 0; 7656 7657 var isSuccess, 7658 success, 7659 error, 7660 response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined, 7661 lastModified, 7662 etag; 7663 7664 // If successful, handle type chaining 7665 if ( status >= 200 && status < 300 || status === 304 ) { 7666 7667 // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. 7668 if ( s.ifModified ) { 7669 7670 if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) { 7671 jQuery.lastModified[ ifModifiedKey ] = lastModified; 7672 } 7673 if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) { 7674 jQuery.etag[ ifModifiedKey ] = etag; 7675 } 7676 } 7677 7678 // If not modified 7679 if ( status === 304 ) { 7680 7681 statusText = "notmodified"; 7682 isSuccess = true; 7683 7684 // If we have data 7685 } else { 7686 7687 try { 7688 success = ajaxConvert( s, response ); 7689 statusText = "success"; 7690 isSuccess = true; 7691 } catch(e) { 7692 // We have a parsererror 7693 statusText = "parsererror"; 7694 error = e; 7695 } 7696 } 7697 } else { 7698 // We extract error from statusText 7699 // then normalize statusText and status for non-aborts 7700 error = statusText; 7701 if( !statusText || status ) { 7702 statusText = "error"; 7703 if ( status < 0 ) { 7704 status = 0; 7705 } 7706 } 7707 } 7708 7709 // Set data for the fake xhr object 7710 jqXHR.status = status; 7711 jqXHR.statusText = statusText; 7712 7713 // Success/Error 7714 if ( isSuccess ) { 7715 deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); 7716 } else { 7717 deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); 7718 } 7719 7720 // Status-dependent callbacks 7721 jqXHR.statusCode( statusCode ); 7722 statusCode = undefined; 7723 7724 if ( fireGlobals ) { 7725 globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ), 7726 [ jqXHR, s, isSuccess ? success : error ] ); 7727 } 7728 7729 // Complete 7730 completeDeferred.resolveWith( callbackContext, [ jqXHR, statusText ] ); 7731 7732 if ( fireGlobals ) { 7733 globalEventContext.trigger( "ajaxComplete", [ jqXHR, s] ); 7734 // Handle the global AJAX counter 7735 if ( !( --jQuery.active ) ) { 7736 jQuery.event.trigger( "ajaxStop" ); 7737 } 7738 } 7739 } 7740 7741 // Attach deferreds 7742 deferred.promise( jqXHR ); 7743 jqXHR.success = jqXHR.done; 7744 jqXHR.error = jqXHR.fail; 7745 jqXHR.complete = completeDeferred.done; 7746 7747 // Status-dependent callbacks 7748 jqXHR.statusCode = function( map ) { 7749 if ( map ) { 7750 var tmp; 7751 if ( state < 2 ) { 7752 for( tmp in map ) { 7753 statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ]; 7754 } 7755 } else { 7756 tmp = map[ jqXHR.status ]; 7757 jqXHR.then( tmp, tmp ); 7758 } 7759 } 7760 return this; 7761 }; 7762 7763 // Remove hash character (#7531: and string promotion) 7764 // Add protocol if not provided (#5866: IE7 issue with protocol-less urls) 7765 // We also use the url parameter if available 7766 s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" ); 7767 7768 // Extract dataTypes list 7769 s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax ); 7770 7771 // Determine if a cross-domain request is in order 7772 if ( s.crossDomain == null ) { 7773 parts = rurl.exec( s.url.toLowerCase() ); 7774 s.crossDomain = !!( parts && 7775 ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] || 7776 ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) != 7777 ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) ) 7778 ); 7779 } 7780 7781 // Convert data if not already a string 7782 if ( s.data && s.processData && typeof s.data !== "string" ) { 7783 s.data = jQuery.param( s.data, s.traditional ); 7784 } 7785 7786 // Apply prefilters 7787 inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); 7788 7789 // If request was aborted inside a prefiler, stop there 7790 if ( state === 2 ) { 7791 return false;
7792 } 7793 7794 // We can fire global events as of now if asked to 7795 fireGlobals = s.global; 7796 7797 // Uppercase the type 7798 s.type = s.type.toUpperCase(); 7799 7800 // Determine if request has content 7801 s.hasContent = !rnoContent.test( s.type ); 7802 7803 // Watch for a new set of requests 7804 if ( fireGlobals && jQuery.active++ === 0 ) { 7805 jQuery.event.trigger( "ajaxStart" ); 7806 } 7807 7808 // More options handling for requests with no content 7809 if ( !s.hasContent ) { 7810 7811 // If data is available, append data to url 7812 if ( s.data ) { 7813 s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; 7814 } 7815 7816 // Get ifModifiedKey before adding the anti-cache parameter 7817 ifModifiedKey = s.url; 7818 7819 // Add anti-cache in url if needed 7820 if ( s.cache === false ) { 7821 7822 var ts = jQuery.now(), 7823 // try replacing _= if it is there 7824 ret = s.url.replace( rts, "$1_=" + ts ); 7825 7826 // if nothing was replaced, add timestamp to the end 7827 s.url = ret + ( (ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" ); 7828 } 7829 } 7830 7831 // Set the correct header, if data is being sent 7832 if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { 7833 requestHeaders[ "Content-Type" ] = s.contentType; 7834 } 7835 7836 // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. 7837 if ( s.ifModified ) { 7838 ifModifiedKey = ifModifiedKey || s.url; 7839 if ( jQuery.lastModified[ ifModifiedKey ] ) { 7840 requestHeaders[ "If-Modified-Since" ] = jQuery.lastModified[ ifModifiedKey ]; 7841 } 7842 if ( jQuery.etag[ ifModifiedKey ] ) { 7843 requestHeaders[ "If-None-Match" ] = jQuery.etag[ ifModifiedKey ]; 7844 } 7845 } 7846 7847 // Set the Accepts header for the server, depending on the dataType 7848 requestHeaders.Accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? 7849 s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) : 7850 s.accepts[ "*" ]; 7851 7852 // Check for headers option 7853 for ( i in s.headers ) { 7854 jqXHR.setRequestHeader( i, s.headers[ i ] ); 7855 } 7856 7857 // Allow custom headers/mimetypes and early abort 7858 if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { 7859 // Abort if not done already 7860 jqXHR.abort(); 7861 return false; 7862 7863 } 7864 7865 // Install callbacks on deferreds 7866 for ( i in { success: 1, error: 1, complete: 1 } ) { 7867 jqXHR[ i ]( s[ i ] ); 7868 } 7869 7870 // Get transport 7871 transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); 7872 7873 // If no transport, we auto-abort 7874 if ( !transport ) { 7875 done( -1, "No Transport" ); 7876 } else { 7877 jqXHR.readyState = 1; 7878 // Send global event 7879 if ( fireGlobals ) { 7880 globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); 7881 } 7882 // Timeout 7883 if ( s.async && s.timeout > 0 ) { 7884 timeoutTimer = setTimeout( function(){ 7885 jqXHR.abort( "timeout" ); 7886 }, s.timeout ); 7887 } 7888 7889 try { 7890 state = 1; 7891 transport.send( requestHeaders, done ); 7892 } catch (e) { 7893 // Propagate exception as error if not done 7894 if ( status < 2 ) { 7895 done( -1, e ); 7896 // Simply rethrow otherwise 7897 } else { 7898 jQuery.error( e ); 7899 } 7900 } 7901 } 7902 7903 return jqXHR; 7904 }, 7905 7906 // Serialize an array of form elements or a set of 7907 // key/values into a query string 7908 param: function( a, traditional ) { 7909 var s = [], 7910 add = function( key, value ) { 7911 // If value is a function, invoke it and return its value 7912 value = jQuery.isFunction( value ) ? value() : value; 7913 s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); 7914 }; 7915 7916 // Set traditional to true for jQuery <= 1.3.2 behavior. 7917 if ( traditional === undefined ) { 7918 traditional = jQuery.ajaxSettings.traditional; 7919 } 7920 7921 // If an array was passed in, assume that it is an array of form elements. 7922 if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { 7923 // Serialize the form elements 7924 jQuery.each( a, function() { 7925 add( this.name, this.value ); 7926 } ); 7927 7928 } else { 7929 // If traditional, encode the "old" way (the way 1.3.2 or older 7930 // did it), otherwise encode params recursively. 7931 for ( var prefix in a ) { 7932 buildParams( prefix, a[ prefix ], traditional, add ); 7933 } 7934 } 7935 7936 // Return the resulting serialization 7937 return s.join( "&" ).replace( r20, "+" ); 7938 } 7939}); 7940 7941function buildParams( prefix, obj, traditional, add ) { 7942 if ( jQuery.isArray( obj ) && obj.length ) { 7943 // Serialize array item. 7944 jQuery.each( obj, function( i, v ) { 7945 if ( traditional || rbracket.test( prefix ) ) { 7946 // Treat each array item as a scalar. 7947 add( prefix, v ); 7948 7949 } else { 7950 // If array item is non-scalar (array or object), encode its 7951 // numeric index to resolve deserialization ambiguity issues. 7952 // Note that rack (as of 1.0.0) can't currently deserialize 7953 // nested arrays properly, and attempting to do so may cause 7954 // a server error. Possible fixes are to modify rack's 7955 // deserialization algorithm or to provide an option or flag 7956 // to force array serialization to be shallow. 7957 buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add ); 7958 } 7959 }); 7960 7961 } else if ( !traditional && obj != null && typeof obj === "object" ) { 7962 // If we see an array here, it is empty and should be treated as an empty 7963 // object 7964 if ( jQuery.isArray( obj ) || jQuery.isEmptyObject( obj ) ) { 7965 add( prefix, "" ); 7966 7967 // Serialize object item. 7968 } else { 7969 for ( var name in obj ) { 7970 buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); 7971 } 7972 } 7973 7974 } else { 7975 // Serialize scalar item. 7976 add( prefix, obj ); 7977 } 7978} 7979 7980// This is still on the jQuery object... for now 7981// Want to move this to jQuery.ajax some day 7982jQuery.extend({ 7983 7984 // Counter for holding the number of active queries 7985 active: 0, 7986 7987 // Last-Modified header cache for next request 7988 lastModified: {}, 7989 etag: {} 7990 7991}
7991); 7992 7993/* Handles responses to an ajax request: 7994 * - sets all responseXXX fields accordingly 7995 * - finds the right dataType (mediates between content-type and expected dataType) 7996 * - returns the corresponding response 7997 */ 7998function ajaxHandleResponses( s, jqXHR, responses ) { 7999 8000 var contents = s.contents, 8001 dataTypes = s.dataTypes, 8002 responseFields = s.responseFields, 8003 ct, 8004 type, 8005 finalDataType, 8006 firstDataType; 8007 8008 // Fill responseXXX fields 8009 for( type in responseFields ) { 8010 if ( type in responses ) { 8011 jqXHR[ responseFields[type] ] = responses[ type ]; 8012 } 8013 } 8014 8015 // Remove auto dataType and get content-type in the process 8016 while( dataTypes[ 0 ] === "*" ) { 8017 dataTypes.shift(); 8018 if ( ct === undefined ) { 8019 ct = s.mimeType || jqXHR.getResponseHeader( "content-type" ); 8020 } 8021 } 8022 8023 // Check if we're dealing with a known content-type 8024 if ( ct ) { 8025 for ( type in contents ) { 8026 if ( contents[ type ] && contents[ type ].test( ct ) ) { 8027 dataTypes.unshift( type ); 8028 break; 8029 } 8030 } 8031 } 8032 8033 // Check to see if we have a response for the expected dataType 8034 if ( dataTypes[ 0 ] in responses ) { 8035 finalDataType = dataTypes[ 0 ]; 8036 } else { 8037 // Try convertible dataTypes 8038 for ( type in responses ) { 8039 if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) { 8040 finalDataType = type; 8041 break; 8042 } 8043 if ( !firstDataType ) { 8044 firstDataType = type; 8045 } 8046 } 8047 // Or just use first one 8048 finalDataType = finalDataType || firstDataType; 8049 } 8050 8051 // If we found a dataType 8052 // We add the dataType to the list if needed 8053 // and return the corresponding response 8054 if ( finalDataType ) { 8055 if ( finalDataType !== dataTypes[ 0 ] ) { 8056 dataTypes.unshift( finalDataType ); 8057 } 8058 return responses[ finalDataType ]; 8059 } 8060} 8061 8062// Chain conversions given the request and the original response 8063function ajaxConvert( s, response ) { 8064 8065 // Apply the dataFilter if provided 8066 if ( s.dataFilter ) { 8067 response = s.dataFilter( response, s.dataType ); 8068 } 8069 8070 var dataTypes = s.dataTypes, 8071 converters = {}, 8072 i, 8073 key, 8074 length = dataTypes.length, 8075 tmp, 8076 // Current and previous dataTypes 8077 current = dataTypes[ 0 ], 8078 prev, 8079 // Conversion expression 8080 conversion, 8081 // Conversion function 8082 conv, 8083 // Conversion functions (transitive conversion) 8084 conv1, 8085 conv2; 8086 8087 // For each dataType in the chain 8088 for( i = 1; i < length; i++ ) { 8089 8090 // Create converters map 8091 // with lowercased keys 8092 if ( i === 1 ) { 8093 for( key in s.converters ) { 8094 if( typeof key === "string" ) { 8095 converters[ key.toLowerCase() ] = s.converters[ key ]; 8096 } 8097 } 8098 } 8099 8100 // Get the dataTypes 8101 prev = current; 8102 current = dataTypes[ i ]; 8103 8104 // If current is auto dataType, update it to prev 8105 if( current === "*" ) { 8106 current = prev; 8107 // If no auto and dataTypes are actually different 8108 } else if ( prev !== "*" && prev !== current ) { 8109 8110 // Get the converter 8111 conversion = prev + " " + current; 8112 conv = converters[ conversion ] || converters[ "* " + current ]; 8113 8114 // If there is no direct converter, search transitively 8115 if ( !conv ) { 8116 conv2 = undefined; 8117 for( conv1 in converters ) { 8118 tmp = conv1.split( " " ); 8119 if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) { 8120 conv2 = converters[ tmp[1] + " " + current ]; 8121 if ( conv2 ) { 8122 conv1 = converters[ conv1 ]; 8123 if ( conv1 === true ) { 8124 conv = conv2; 8125 } else if ( conv2 === true ) { 8126 conv = conv1; 8127 } 8128 break; 8129 } 8130 } 8131 } 8132 } 8133 // If we found no converter, dispatch an error 8134 if ( !( conv || conv2 ) ) { 8135 jQuery.error( "No conversion from " + conversion.replace(" "," to ") ); 8136 } 8137 // If found converter is not an equivalence 8138 if ( conv !== true ) { 8139 // Convert with 1 or 2 converters accordingly 8140 response = conv ? conv( response ) : conv2( conv1(response) ); 8141 } 8142 } 8143 } 8144 return response; 8145} 8146 8147 8148 8149 8150var jsc = jQuery.now(), 8151 jsre = /(\=)\?(&|$)|\?\?/i; 8152 8153// Default jsonp settings 8154jQuery.ajaxSetup({ 8155 jsonp: "callback", 8156 jsonpCallback: function() { 8157 return jQuery.expando + "_" + ( jsc++ ); 8158 } 8159}); 8160 8161// Detect, normalize options and install callbacks for jsonp requests 8162jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { 8163 8164 var dataIsString = ( typeof s.data === "string" ); 8165 8166 if ( s.dataTypes[ 0 ] === "jsonp" || 8167 originalSettings.jsonpCallback || 8168 originalSettings.jsonp != null || 8169 s.jsonp !== false && ( jsre.test( s.url ) || 8170 dataIsString && jsre.test( s.data ) ) ) { 8171 8172 var responseContainer, 8173 jsonpCallback = s.jsonpCallback = 8174 jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, 8175 previous = window[ jsonpCallback ], 8176 url = s.url, 8177 data = s.data, 8178 replace = "$1" + jsonpCallback + "$2", 8179 cleanUp = function() { 8180 // Set callback back to previous value 8181 window[ jsonpCallback ] = previous; 8182 // Call if it was a function and we have a response 8183 if ( responseContainer && jQuery.isFunction( previous ) ) { 8184 window[ jsonpCallback ]( responseContainer[ 0 ] ); 8185 } 8186 }; 8187 8188 if ( s.jsonp !== false ) { 8189 url = url.replace( jsre, replace ); 8190 if ( s.url === url ) { 8191 if ( dataIsString ) { 8192 data = data.replace( jsre, replace ); 8193 } 8194 if ( s.data === data ) { 8195 // Add callback manually 8196 url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback; 8197 } 8198 } 8199 } 8200 8201 s.url = url; 8202 s.data = data; 8203 8204 // Install callback 8205 window[ jsonpCallback ] = function( response ) { 8206 responseContainer = [ response ]; 8207 }; 8208 8209 // Install cleanUp function 8210 jqXHR.then( cleanUp, cleanUp ); 8211 8212 // Use data converter to retrieve json after script execution 8213 s.converters["script json"] = function() { 8214 if ( !responseContainer ) { 8215 jQuery.error( jsonpCallback + " was not called" ); 8216 } 8217 return responseContainer[ 0 ]; 8218 }; 8219 8220 // force json dataType 8221 s.dataTypes[ 0 ] = "json"; 8222 8223 // Delegate to script 8224 return "script"; 8225 } 8226} ); 8227 8228 8229 8230 8231// Install script dataType 8232jQuery.ajaxSetup({ 8233 accepts: { 8234 script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" 8235 }, 8236 contents: { 8237 script: /javascript|ecmascript/ 8238 }, 8239 converters: { 8240 "text script": function( text ) { 8241 jQuery.globalEval( text ); 8242 return text; 8243 } 8244 } 8245}); 8246 8247// Handle cache's special case and global 8248jQuery.ajaxPrefilter( "script", function( s ) { 8249 if ( s.cache === undefined ) { 8250 s.cache = false;
8251 } 8252 if ( s.crossDomain ) { 8253 s.type = "GET"; 8254 s.global = false; 8255 } 8256} ); 8257 8258// Bind script tag hack transport 8259jQuery.ajaxTransport( "script", function(s) { 8260 8261 // This transport only deals with cross domain requests 8262 if ( s.crossDomain ) { 8263 8264 var script, 8265 head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement; 8266 8267 return { 8268 8269 send: function( _, callback ) { 8270 8271 script = document.createElement( "script" ); 8272 8273 script.async = "async"; 8274 8275 if ( s.scriptCharset ) { 8276 script.charset = s.scriptCharset; 8277 } 8278 8279 script.src = s.url; 8280 8281 // Attach handlers for all browsers 8282 script.onload = script.onreadystatechange = function( _, isAbort ) { 8283 8284 if ( !script.readyState || /loaded|complete/.test( script.readyState ) ) { 8285 8286 // Handle memory leak in IE 8287 script.onload = script.onreadystatechange = null; 8288 8289 // Remove the script 8290 if ( head && script.parentNode ) { 8291 head.removeChild( script ); 8292 } 8293 8294 // Dereference the script 8295 script = undefined; 8296 8297 // Callback if not abort 8298 if ( !isAbort ) { 8299 callback( 200, "success" ); 8300 } 8301 } 8302 }; 8303 // Use insertBefore instead of appendChild to circumvent an IE6 bug. 8304 // This arises when a base node is used (#2709 and #4378). 8305 head.insertBefore( script, head.firstChild ); 8306 }, 8307 8308 abort: function() { 8309 if ( script ) { 8310 script.onload( 0, 1 ); 8311 } 8312 } 8313 }; 8314 } 8315} ); 8316 8317 8318 8319 8320var // #5280: next active xhr id and list of active xhrs' callbacks 8321 xhrId = jQuery.now(), 8322 xhrCallbacks, 8323 8324 // XHR used to determine supports properties 8325 testXHR; 8326 8327// #5280: Internet Explorer will keep connections alive if we don't abort on unload 8328function xhrOnUnloadAbort() { 8329 jQuery( window ).unload(function() { 8330 // Abort all pending requests 8331 for ( var key in xhrCallbacks ) { 8332 xhrCallbacks[ key ]( 0, 1 ); 8333 } 8334 }); 8335} 8336 8337// Functions to create xhrs 8338function createStandardXHR() { 8339 try { 8340 return new window.XMLHttpRequest(); 8341 } catch( e ) {} 8342} 8343 8344function createActiveXHR() { 8345 try { 8346 return new window.ActiveXObject( "Microsoft.XMLHTTP" ); 8347 } catch( e ) {} 8348} 8349 8350// Create the request object 8351// (This is still attached to ajaxSettings for backward compatibility) 8352jQuery.ajaxSettings.xhr = window.ActiveXObject ? 8353 /* Microsoft failed to properly 8354 * implement the XMLHttpRequest in IE7 (can't request local files), 8355 * so we use the ActiveXObject when it is available 8356 * Additionally XMLHttpRequest can be disabled in IE7/IE8 so 8357 * we need a fallback. 8358 */ 8359 function() { 8360 return !this.isLocal && createStandardXHR() || createActiveXHR(); 8361 } : 8362 // For all other browsers, use the standard XMLHttpRequest object 8363 createStandardXHR; 8364 8365// Test if we can create an xhr object 8366testXHR = jQuery.ajaxSettings.xhr(); 8367jQuery.support.ajax = !!testXHR; 8368 8369// Does this browser support crossDomain XHR requests 8370jQuery.support.cors = testXHR && ( "withCredentials" in testXHR ); 8371 8372// No need for the temporary xhr anymore 8373testXHR = undefined; 8374 8375// Create transport if the browser can provide an xhr 8376if ( jQuery.support.ajax ) { 8377 8378 jQuery.ajaxTransport(function( s ) { 8379 // Cross domain only allowed if supported through XMLHttpRequest 8380 if ( !s.crossDomain || jQuery.support.cors ) { 8381 8382 var callback; 8383 8384 return { 8385 send: function( headers, complete ) { 8386 8387 // Get a new xhr 8388 var xhr = s.xhr(), 8389 handle, 8390 i; 8391 8392 // Open the socket 8393 // Passing null username, generates a login popup on Opera (#2865) 8394 if ( s.username ) { 8395 xhr.open( s.type, s.url, s.async, s.username, s.password ); 8396 } else { 8397 xhr.open( s.type, s.url, s.async ); 8398 } 8399 8400 // Apply custom fields if provided 8401 if ( s.xhrFields ) { 8402 for ( i in s.xhrFields ) { 8403 xhr[ i ] = s.xhrFields[ i ]; 8404 } 8405 } 8406 8407 // Override mime type if needed 8408 if ( s.mimeType && xhr.overrideMimeType ) { 8409 xhr.overrideMimeType( s.mimeType ); 8410 } 8411 8412 // X-Requested-With header 8413 // For cross-domain requests, seeing as conditions for a preflight are 8414 // akin to a jigsaw puzzle, we simply never set it to be sure. 8415 // (it can always be set on a per-request basis or even using ajaxSetup)
8416 // For same-domain requests, won't change header if already provided. 8417 if ( !s.crossDomain && !headers["X-Requested-With"] ) { 8418 headers[ "X-Requested-With" ] = "XMLHttpRequest"; 8419 } 8420 8421 // Need an extra try/catch for cross domain requests in Firefox 3 8422 try { 8423 for ( i in headers ) { 8424 xhr.setRequestHeader( i, headers[ i ] ); 8425 } 8426 } catch( _ ) {} 8427 8428 // Do send the request 8429 // This may raise an exception which is actually 8430 // handled in jQuery.ajax (so no try/catch here) 8431 xhr.send( ( s.hasContent && s.data ) || null ); 8432 8433 // Listener 8434 callback = function( _, isAbort ) { 8435 8436 var status, 8437 statusText, 8438 responseHeaders, 8439 responses, 8440 xml; 8441 8442 // Firefox throws exceptions when accessing properties 8443 // of an xhr when a network error occured 8444 // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE) 8445 try { 8446 8447 // Was never called and is aborted or complete 8448 if ( callback && ( isAbort || xhr.readyState === 4 ) ) { 8449 8450 // Only called once 8451 callback = undefined; 8452 8453 // Do not keep as active anymore 8454 if ( handle ) { 8455 xhr.onreadystatechange = jQuery.noop; 8456 delete xhrCallbacks[ handle ]; 8457 } 8458 8459 // If it's an abort 8460 if ( isAbort ) { 8461 // Abort it manually if needed 8462 if ( xhr.readyState !== 4 ) { 8463 xhr.abort(); 8464 } 8465 } else { 8466 status = xhr.status; 8467 responseHeaders = xhr.getAllResponseHeaders(); 8468 responses = {}; 8469 xml = xhr.responseXML; 8470 8471 // Construct response list 8472 if ( xml && xml.documentElement /* #4958 */ ) { 8473 responses.xml = xml; 8474 } 8475 responses.text = xhr.responseText; 8476 8477 // Firefox throws an exception when accessing 8478 // statusText for faulty cross-domain requests 8479 try { 8480 statusText = xhr.statusText; 8481 } catch( e ) { 8482 // We normalize with Webkit giving an empty statusText 8483 statusText = ""; 8484 } 8485 8486 // Filter status for non standard behaviors 8487 8488 // If the request is local and we have data: assume a success 8489 // (success with no data won't get notified, that's the best we 8490 // can do given current implementations) 8491 if ( !status && s.isLocal && !s.crossDomain ) { 8492 status = responses.text ? 200 : 404; 8493 // IE - #1450: sometimes returns 1223 when it should be 204 8494 } else if ( status === 1223 ) { 8495 status = 204; 8496 } 8497 } 8498 } 8499 } catch( firefoxAccessException ) { 8500 if ( !isAbort ) { 8501 complete( -1, firefoxAccessException ); 8502 } 8503 } 8504 8505 // Call complete if needed 8506 if ( responses ) { 8507 complete( status, statusText, responses, responseHeaders ); 8508 } 8509 }; 8510 8511 // if we're in sync mode or it's in cache 8512 // and has been retrieved directly (IE6 & IE7) 8513 // we need to manually fire the callback 8514 if ( !s.async || xhr.readyState === 4 ) { 8515 callback(); 8516 } else { 8517 // Create the active xhrs callbacks list if needed 8518 // and attach the unload handler 8519 if ( !xhrCallbacks ) { 8520 xhrCallbacks = {}; 8521 xhrOnUnloadAbort(); 8522 } 8523 // Add to list of active xhrs callbacks 8524 handle = xhrId++; 8525 xhr.onreadystatechange = xhrCallbacks[ handle ] = callback; 8526 } 8527 }, 8528 8529 abort: function() { 8530 if ( callback ) { 8531 callback(0,1); 8532 } 8533 } 8534 }; 8535 } 8536 }); 8537} 8538 8539 8540 8541 8542var elemdisplay = {}, 8543 rfxtypes = /^(?:toggle|show|hide)$/, 8544 rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i, 8545 timerId, 8546 fxAttrs = [ 8547 // height animations 8548 [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], 8549 // width animations 8550 [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], 8551 // opacity animations 8552 [ "opacity" ] 8553 ]; 8554 8555jQuery.fn.extend({ 8556 show: function( speed, easing, callback ) { 8557 var elem, display; 8558 8559 if ( speed || speed === 0 ) { 8560 return this.animate( genFx("show", 3), speed, easing, callback); 8561 8562 } else { 8563 for ( var i = 0, j = this.length; i < j; i++ ) { 8564 elem = this[i]; 8565 display = elem.style.display; 8566 8567 // Reset the inline display of this element to learn if it is 8568 // being hidden by cascaded rules or not 8569 if ( !jQuery._data(elem, "olddisplay") && display === "none" ) { 8570 display = elem.style.display = ""; 8571 } 8572 8573 // Set elements which have been overridden with display: none 8574 // in a stylesheet to whatever the default browser style is 8575 // for such an element
8576 if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { 8577 jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName)); 8578 } 8579 } 8580 8581 // Set the display of most of the elements in a second loop 8582 // to avoid the constant reflow 8583 for ( i = 0; i < j; i++ ) { 8584 elem = this[i]; 8585 display = elem.style.display; 8586 8587 if ( display === "" || display === "none" ) { 8588 elem.style.display = jQuery._data(elem, "olddisplay") || ""; 8589 } 8590 } 8591 8592 return this; 8593 } 8594 }, 8595 8596 hide: function( speed, easing, callback ) { 8597 if ( speed || speed === 0 ) { 8598 return this.animate( genFx("hide", 3), speed, easing, callback); 8599 8600 } else { 8601 for ( var i = 0, j = this.length; i < j; i++ ) { 8602 var display = jQuery.css( this[i], "display" ); 8603 8604 if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) { 8605 jQuery._data( this[i], "olddisplay", display ); 8606 } 8607 } 8608 8609 // Set the display of the elements in a second loop 8610 // to avoid the constant reflow 8611 for ( i = 0; i < j; i++ ) { 8612 this[i].style.display = "none"; 8613 } 8614 8615 return this; 8616 } 8617 }, 8618 8619 // Save the old toggle function 8620 _toggle: jQuery.fn.toggle, 8621 8622 toggle: function( fn, fn2, callback ) { 8623 var bool = typeof fn === "boolean"; 8624 8625 if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) { 8626 this._toggle.apply( this, arguments ); 8627 8628 } else if ( fn == null || bool ) { 8629 this.each(function() { 8630 var state = bool ? fn : jQuery(this).is(":hidden"); 8631 jQuery(this)[ state ? "show" : "hide" ](); 8632 }); 8633 8634 } else { 8635 this.animate(genFx("toggle", 3), fn, fn2, callback); 8636 } 8637 8638 return this; 8639 }, 8640 8641 fadeTo: function( speed, to, easing, callback ) { 8642 return this.filter(":hidden").css("opacity", 0).show().end() 8643 .animate({opacity: to}, speed, easing, callback); 8644 }, 8645 8646 animate: function( prop, speed, easing, callback ) { 8647 var optall = jQuery.speed(speed, easing, callback); 8648 8649 if ( jQuery.isEmptyObject( prop ) ) { 8650 return this.each( optall.complete ); 8651 } 8652 8653 return this[ optall.queue === false ? "each" : "queue" ](function() { 8654 // XXX 'this' does not always have a nodeName when running the 8655 // test suite 8656 8657 var opt = jQuery.extend({}, optall), p, 8658 isElement = this.nodeType === 1, 8659 hidden = isElement && jQuery(this).is(":hidden"), 8660 self = this; 8661 8662 for ( p in prop ) { 8663 var name = jQuery.camelCase( p ); 8664 8665 if ( p !== name ) { 8666 prop[ name ] = prop[ p ]; 8667 delete prop[ p ]; 8668 p = name; 8669 } 8670 8671 if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) { 8672 return opt.complete.call(this); 8673 } 8674 8675 if ( isElement && ( p === "height" || p === "width" ) ) { 8676 // Make sure that nothing sneaks out 8677 // Record all 3 overflow attributes because IE does not 8678 // change the overflow attribute when overflowX and 8679 // overflowY are set to the same value 8680 opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ]; 8681 8682 // Set display property to inline-block for height/width 8683 // animations on inline elements that are having width/height 8684 // animated 8685 if ( jQuery.css( this, "display" ) === "inline" && 8686 jQuery.css( this, "float" ) === "none" ) { 8687 if ( !jQuery.support.inlineBlockNeedsLayout ) { 8688 this.style.display = "inline-block"; 8689 8690 } else { 8691 var display = defaultDisplay(this.nodeName); 8692 8693 // inline-level elements accept inline-block; 8694 // block-level elements need to be inline with layout 8695 if ( display === "inline" ) { 8696 this.style.display = "inline-block"; 8697 8698 } else { 8699 this.style.display = "inline"; 8700 this.style.zoom = 1; 8701 } 8702 } 8703 } 8704 } 8705 8706 if ( jQuery.isArray( prop[p] ) ) { 8707 // Create (if needed) and add to specialEasing 8708 (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1]; 8709 prop[p] = prop[p][0]; 8710 } 8711 } 8712 8713 if ( opt.overflow != null ) { 8714 this.style.overflow = "hidden"; 8715 } 8716 8717 opt.curAnim = jQuery.extend({}, prop); 8718 8719 jQuery.each( prop, function( name, val ) { 8720 var e = new jQuery.fx( self, opt, name ); 8721 8722 if ( rfxtypes.test(val) ) { 8723 e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop ); 8724 8725 } else { 8726 var parts = rfxnum.exec(val), 8727 start = e.cur(); 8728 8729 if ( parts ) { 8730 var end = parseFloat( parts[2] ), 8731 unit = parts[3] || ( jQuery.cssNumber[ name ] ? "" : "px" ); 8732 8733 // We need to compute starting value 8734 if ( unit !== "px" ) {
8735 jQuery.style( self, name, (end || 1) + unit); 8736 start = ((end || 1) / e.cur()) * start; 8737 jQuery.style( self, name, start + unit); 8738 } 8739 8740 // If a +=/-= token was provided, we're doing a relative animation 8741 if ( parts[1] ) { 8742 end = ((parts[1] === "-=" ? -1 : 1) * end) + start; 8743 } 8744 8745 e.custom( start, end, unit ); 8746 8747 } else { 8748 e.custom( start, val, "" ); 8749 } 8750 } 8751 }); 8752 8753 // For JS strict compliance 8754 return true; 8755 }); 8756 }, 8757 8758 stop: function( clearQueue, gotoEnd ) { 8759 var timers = jQuery.timers; 8760 8761 if ( clearQueue ) { 8762 this.queue([]); 8763 } 8764 8765 this.each(function() { 8766 // go in reverse order so anything added to the queue during the loop is ignored 8767 for ( var i = timers.length - 1; i >= 0; i-- ) { 8768 if ( timers[i].elem === this ) { 8769 if (gotoEnd) { 8770 // force the next step to be the last 8771 timers[i](true); 8772 } 8773 8774 timers.splice(i, 1); 8775 } 8776 } 8777 }); 8778 8779 // start the next in the queue if the last step wasn't forced 8780 if ( !gotoEnd ) { 8781 this.dequeue(); 8782 } 8783 8784 return this; 8785 } 8786 8787}); 8788 8789function genFx( type, num ) { 8790 var obj = {}; 8791 8792 jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() { 8793 obj[ this ] = type; 8794 }); 8795 8796 return obj; 8797} 8798 8799// Generate shortcuts for custom animations 8800jQuery.each({ 8801 slideDown: genFx("show", 1), 8802 slideUp: genFx("hide", 1), 8803 slideToggle: genFx("toggle", 1), 8804 fadeIn: { opacity: "show" }, 8805 fadeOut: { opacity: "hide" }, 8806 fadeToggle: { opacity: "toggle" } 8807}, function( name, props ) { 8808 jQuery.fn[ name ] = function( speed, easing, callback ) { 8809 return this.animate( props, speed, easing, callback ); 8810 }; 8811}); 8812 8813jQuery.extend({ 8814 speed: function( speed, easing, fn ) { 8815 var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : { 8816 complete: fn || !fn && easing || 8817 jQuery.isFunction( speed ) && speed, 8818 duration: speed, 8819 easing: fn && easing || easing && !jQuery.isFunction(easing) && easing 8820 }; 8821 8822 opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : 8823 opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default; 8824 8825 // Queueing 8826 opt.old = opt.complete; 8827 opt.complete = function() { 8828 if ( opt.queue !== false ) { 8829 jQuery(this).dequeue(); 8830 } 8831 if ( jQuery.isFunction( opt.old ) ) { 8832 opt.old.call( this ); 8833 } 8834 }; 8835 8836 return opt; 8837 }, 8838 8839 easing: { 8840 linear: function( p, n, firstNum, diff ) { 8841 return firstNum + diff * p; 8842 }, 8843 swing: function( p, n, firstNum, diff ) { 8844 return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; 8845 } 8846 }, 8847 8848 timers: [], 8849 8850 fx: function( elem, options, prop ) { 8851 this.options = options; 8852 this.elem = elem; 8853 this.prop = prop; 8854 8855 if ( !options.orig ) { 8856 options.orig = {}; 8857 } 8858 } 8859 8860}); 8861 8862jQuery.fx.prototype = { 8863 // Simple function for setting a style value 8864 update: function() { 8865 if ( this.options.step ) { 8866 this.options.step.call( this.elem, this.now, this ); 8867 } 8868 8869 (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this ); 8870 }, 8871 8872 // Get the current size 8873 cur: function() { 8874 if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) { 8875 return this.elem[ this.prop ]; 8876 } 8877 8878 var parsed, 8879 r = jQuery.css( this.elem, this.prop ); 8880 // Empty strings, null, undefined and "auto" are converted to 0, 8881 // complex values such as "rotate(1rad)" are returned as is, 8882 // simple values such as "10px" are parsed to Float. 8883 return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed; 8884 }, 8885 8886 // Start an animation from one number to another 8887 custom: function( from, to, unit ) { 8888 var self = this, 8889 fx = jQuery.fx; 8890 8891 this.startTime = jQuery.now(); 8892 this.start = from; 8893 this.end = to; 8894 this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" ); 8895 this.now = this.start; 8896 this.pos = this.state = 0; 8897 8898 function t( gotoEnd ) { 8899 return self.step(gotoEnd); 8900 } 8901 8902 t.elem = this.elem; 8903 8904 if ( t() && jQuery.timers.push(t) && !timerId ) { 8905 timerId = setInterval(fx.tick, fx.interval); 8906 } 8907 }, 8908 8909 // Simple 'show' function 8910 show: function() { 8911 // Remember where we started, so that we can go back to it later 8912 this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); 8913 this.options.show = true; 8914 8915 // Begin the animation 8916 // Make sure that we start at a small width/height to avoid any 8917 // flash of content 8918 this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur()); 8919 8920 // Start by showing the element 8921 jQuery( this.elem ).show(); 8922 }, 8923 8924 // Simple 'hide' function 8925 hide: function() { 8926 // Remember where we started, so that we can go back to it later 8927 this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); 8928 this.options.hide = true; 8929 8930 // Begin the animation 8931 this.custom(this.cur(), 0); 8932 }, 8933
8934 // Each step of an animation 8935 step: function( gotoEnd ) { 8936 var t = jQuery.now(), done = true; 8937 8938 if ( gotoEnd || t >= this.options.duration + this.startTime ) { 8939 this.now = this.end; 8940 this.pos = this.state = 1; 8941 this.update(); 8942 8943 this.options.curAnim[ this.prop ] = true; 8944 8945 for ( var i in this.options.curAnim ) { 8946 if ( this.options.curAnim[i] !== true ) { 8947 done = false; 8948 } 8949 } 8950 8951 if ( done ) { 8952 // Reset the overflow 8953 if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { 8954 var elem = this.elem, 8955 options = this.options; 8956 8957 jQuery.each( [ "", "X", "Y" ], function (index, value) { 8958 elem.style[ "overflow" + value ] = options.overflow[index]; 8959 } ); 8960 } 8961 8962 // Hide the element if the "hide" operation was done 8963 if ( this.options.hide ) { 8964 jQuery(this.elem).hide(); 8965 } 8966 8967 // Reset the properties, if the item has been hidden or shown 8968 if ( this.options.hide || this.options.show ) { 8969 for ( var p in this.options.curAnim ) { 8970 jQuery.style( this.elem, p, this.options.orig[p] ); 8971 } 8972 } 8973 8974 // Execute the complete function 8975 this.options.complete.call( this.elem ); 8976 } 8977 8978 return false; 8979 8980 } else { 8981 var n = t - this.startTime; 8982 this.state = n / this.options.duration; 8983 8984 // Perform the easing function, defaults to swing 8985 var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop]; 8986 var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear"); 8987 this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration);
8988 this.now = this.start + ((this.end - this.start) * this.pos); 8989 8990 // Perform the next step of the animation 8991 this.update(); 8992 } 8993 8994 return true; 8995 } 8996}; 8997 8998jQuery.extend( jQuery.fx, { 8999 tick: function() { 9000 var timers = jQuery.timers; 9001 9002 for ( var i = 0; i < timers.length; i++ ) { 9003 if ( !timers[i]() ) { 9004 timers.splice(i--, 1); 9005 } 9006 } 9007 9008 if ( !timers.length ) { 9009 jQuery.fx.stop(); 9010 } 9011 }, 9012 9013 interval: 13, 9014 9015 stop: function() { 9016 clearInterval( timerId ); 9017 timerId = null; 9018 }, 9019 9020 speeds: { 9021 slow: 600, 9022 fast: 200, 9023 // Default speed 9024 _default: 400 9025 }, 9026 9027 step: { 9028 opacity: function( fx ) { 9029 jQuery.style( fx.elem, "opacity", fx.now ); 9030 }, 9031 9032 _default: function( fx ) { 9033 if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { 9034 fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit; 9035 } else { 9036 fx.elem[ fx.prop ] = fx.now; 9037 } 9038 } 9039 } 9040}); 9041 9042if ( jQuery.expr && jQuery.expr.filters ) { 9043 jQuery.expr.filters.animated = function( elem ) { 9044 return jQuery.grep(jQuery.timers, function( fn ) { 9045 return elem === fn.elem; 9046 }).length; 9047 }; 9048} 9049 9050function defaultDisplay( nodeName ) { 9051 if ( !elemdisplay[ nodeName ] ) { 9052 var elem = jQuery("<" + nodeName + ">").appendTo("body"), 9053 display = elem.css("display"); 9054 9055 elem.remove(); 9056 9057 if ( display === "none" || display === "" ) { 9058 display = "block"; 9059 } 9060 9061 elemdisplay[ nodeName ] = display; 9062 } 9063 9064 return elemdisplay[ nodeName ]; 9065} 9066 9067 9068 9069 9070var rtable = /^t(?:able|d|h)$/i, 9071 rroot = /^(?:body|html)$/i; 9072 9073if ( "getBoundingClientRect" in document.documentElement ) { 9074 jQuery.fn.offset = function( options ) { 9075 var elem = this[0], box; 9076 9077 if ( options ) { 9078 return this.each(function( i ) { 9079 jQuery.offset.setOffset( this, options, i ); 9080 }); 9081 } 9082 9083 if ( !elem || !elem.ownerDocument ) { 9084 return null; 9085 } 9086 9087 if ( elem === elem.ownerDocument.body ) { 9088 return jQuery.offset.bodyOffset( elem ); 9089 } 9090 9091 try { 9092 box = elem.getBoundingClientRect(); 9093 } catch(e) {} 9094 9095 var doc = elem.ownerDocument, 9096 docElem = doc.documentElement; 9097 9098 // Make sure we're not dealing with a disconnected DOM node 9099 if ( !box || !jQuery.contains( docElem, elem ) ) { 9100 return box ? { top: box.top, left: box.left } : { top: 0, left: 0 }; 9101 } 9102 9103 var body = doc.body, 9104 win = getWindow(doc), 9105 clientTop = docElem.clientTop || body.clientTop || 0, 9106 clientLeft = docElem.clientLeft || body.clientLeft || 0, 9107 scrollTop = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop, 9108 scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft, 9109 top = box.top + scrollTop - clientTop, 9110 left = box.left + scrollLeft - clientLeft; 9111 9112 return { top: top, left: left }; 9113 }; 9114 9115} else { 9116 jQuery.fn.offset = function( options ) { 9117 var elem = this[0]; 9118 9119 if ( options ) { 9120 return this.each(function( i ) { 9121 jQuery.offset.setOffset( this, options, i ); 9122 }); 9123 } 9124 9125 if ( !elem || !elem.ownerDocument ) { 9126 return null; 9127 } 9128 9129 if ( elem === elem.ownerDocument.body ) { 9130 return jQuery.offset.bodyOffset( elem ); 9131 } 9132 9133 jQuery.offset.initialize(); 9134 9135 var computedStyle, 9136 offsetParent = elem.offsetParent, 9137 prevOffsetParent = elem, 9138 doc = elem.ownerDocument, 9139 docElem = doc.documentElement, 9140 body = doc.body, 9141 defaultView = doc.defaultView, 9142 prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle, 9143 top = elem.offsetTop, 9144 left = elem.offsetLeft; 9145 9146 while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { 9147 if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { 9148 break; 9149 } 9150 9151 computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle; 9152 top -= elem.scrollTop; 9153 left -= elem.scrollLeft; 9154 9155 if ( elem === offsetParent ) { 9156 top += elem.offsetTop; 9157 left += elem.offsetLeft; 9158 9159 if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) { 9160 top += parseFloat( computedStyle.borderTopWidth ) || 0; 9161 left += parseFloat( computedStyle.borderLeftWidth ) || 0; 9162 } 9163 9164 prevOffsetParent = offsetParent; 9165 offsetParent = elem.offsetParent; 9166 } 9167 9168 if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { 9169 top += parseFloat( computedStyle.borderTopWidth ) || 0; 9170 left += parseFloat( computedStyle.borderLeftWidth ) || 0; 9171 } 9172 9173 prevComputedStyle = computedStyle; 9174 } 9175 9176 if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) { 9177 top += body.offsetTop; 9178 left += body.offsetLeft; 9179 } 9180 9181 if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { 9182 top += Math.max( docElem.scrollTop, body.scrollTop ); 9183 left += Math.max( docElem.scrollLeft, body.scrollLeft ); 9184 } 9185 9186 return { top: top, left: left }; 9187 }; 9188} 9189 9190jQuery.offset = { 9191 initialize: function() { 9192 var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0, 9193 html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>"; 9194 9195 jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } ); 9196 9197 container.innerHTML = html; 9198 body.insertBefore( container, body.firstChild ); 9199 innerDiv = container.firstChild; 9200 checkDiv = innerDiv.firstChild; 9201 td = innerDiv.nextSibling.firstChild.firstChild; 9202 9203 this.doesNotAddBorder = (checkDiv.offsetTop !== 5); 9204 this.doesAddBorderForTableAndCells = (td.offsetTop === 5); 9205 9206 checkDiv.style.position = "fixed"; 9207 checkDiv.style.top = "20px"; 9208 9209 // safari subtracts parent border width here which is 5px 9210 this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15); 9211 checkDiv.style.position = checkDiv.style.top = ""; 9212 9213 innerDiv.style.overflow = "hidden"; 9214 innerDiv.style.position = "relative"; 9215 9216 this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5); 9217 9218 this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop); 9219 9220 body.removeChild( container ); 9221 jQuery.offset.initialize = jQuery.noop; 9222 }, 9223 9224 bodyOffset: function( body ) { 9225 var top = body.offsetTop, 9226 left = body.offsetLeft; 9227 9228 jQuery.offset.initialize(); 9229 9230 if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) { 9231 top += parseFloat( jQuery.css(body, "marginTop") ) || 0; 9232 left += parseFloat( jQuery.css(body, "marginLeft") ) || 0; 9233 } 9234 9235 return { top: top, left: left }; 9236 }, 9237 9238 setOffset: function( elem, options, i ) { 9239 var position = jQuery.css( elem, "position" ); 9240 9241 // set position first, in-case top/left are set even on static elem 9242 if ( position === "static" ) { 9243 elem.style.position = "relative"; 9244 } 9245 9246 var curElem = jQuery( elem ), 9247 curOffset = curElem.offset(), 9248 curCSSTop = jQuery.css( elem, "top" ), 9249 curCSSLeft = jQuery.css( elem, "left" ), 9250 calculatePosition = (position === "absolute" || position === "fixed") && jQuery.inArray('auto', [curCSSTop, curCSSLeft]) > -1, 9251 props = {}, curPosition = {}, curTop, curLeft; 9252 9253 // need to be able to calculate position if either top or left is auto and position is either absolute or fixed 9254 if ( calculatePosition ) { 9255 curPosition = curElem.position(); 9256 } 9257 9258 curTop = calculatePosition ? curPosition.top : parseInt( curCSSTop, 10 ) || 0; 9259 curLeft = calculatePosition ? curPosition.left : parseInt( curCSSLeft, 10 ) || 0; 9260 9261 if ( jQuery.isFunction( options ) ) { 9262 options = options.call( elem, i, curOffset ); 9263 } 9264 9265 if (options.top != null) { 9266 props.top = (options.top - curOffset.top) + curTop; 9267 } 9268 if (options.left != null) { 9269 props.left = (options.left - curOffset.left) + curLeft; 9270 } 9271 9272 if ( "using" in options ) { 9273 options.using.call( elem, props ); 9274 } else { 9275 curElem.css( props ); 9276 } 9277 } 9278}; 9279 9280 9281jQuery.fn.extend({ 9282 position: function() { 9283 if ( !this[0] ) { 9284 return null; 9285 } 9286 9287 var elem = this[0], 9288 9289 // Get *real* offsetParent 9290 offsetParent = this.offsetParent(), 9291 9292 // Get correct offsets 9293 offset = this.offset(), 9294 parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset(); 9295 9296 // Subtract element margins 9297 // note: when an element has margin: auto the offsetLeft and marginLeft 9298 // are the same in Safari causing offset.left to incorrectly be 0 9299 offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0; 9300 offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0; 9301 9302 // Add offsetParent borders 9303 parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0; 9304 parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0; 9305 9306 // Subtract the two offsets 9307 return { 9308 top: offset.top - parentOffset.top, 9309 left: offset.left - parentOffset.left 9310 }; 9311 }, 9312 9313 offsetParent: function() { 9314 return this.map(function() { 9315 var offsetParent = this.offsetParent || document.body;
9316 while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) { 9317 offsetParent = offsetParent.offsetParent; 9318 } 9319 return offsetParent; 9320 }); 9321 } 9322}); 9323 9324 9325// Create scrollLeft and scrollTop methods 9326jQuery.each( ["Left", "Top"], function( i, name ) { 9327 var method = "scroll" + name; 9328 9329 jQuery.fn[ method ] = function(val) { 9330 var elem = this[0], win; 9331 9332 if ( !elem ) { 9333 return null; 9334 } 9335 9336 if ( val !== undefined ) { 9337 // Set the scroll offset 9338 return this.each(function() { 9339 win = getWindow( this ); 9340 9341 if ( win ) { 9342 win.scrollTo( 9343 !i ? val : jQuery(win).scrollLeft(), 9344 i ? val : jQuery(win).scrollTop() 9345 ); 9346 9347 } else { 9348 this[ method ] = val; 9349 } 9350 }); 9351 } else { 9352 win = getWindow( elem ); 9353 9354 // Return the scroll offset 9355 return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] : 9356 jQuery.support.boxModel && win.document.documentElement[ method ] || 9357 win.document.body[ method ] : 9358 elem[ method ]; 9359 } 9360 }; 9361}); 9362 9363function getWindow( elem ) { 9364 return jQuery.isWindow( elem ) ? 9365 elem : 9366 elem.nodeType === 9 ? 9367 elem.defaultView || elem.parentWindow : 9368 false; 9369} 9370 9371 9372 9373 9374// Create innerHeight, innerWidth, outerHeight and outerWidth methods 9375jQuery.each([ "Height", "Width" ], function( i, name ) { 9376 9377 var type = name.toLowerCase(); 9378 9379 // innerHeight and innerWidth 9380 jQuery.fn["inner" + name] = function() { 9381 return this[0] ? 9382 parseFloat( jQuery.css( this[0], type, "padding" ) ) : 9383 null; 9384 }; 9385 9386 // outerHeight and outerWidth 9387 jQuery.fn["outer" + name] = function( margin ) { 9388 return this[0] ? 9389 parseFloat( jQuery.css( this[0], type, margin ? "margin" : "border" ) ) : 9390 null; 9391 }; 9392 9393 jQuery.fn[ type ] = function( size ) { 9394 // Get window width or height 9395 var elem = this[0]; 9396 if ( !elem ) { 9397 return size == null ? null : this; 9398 } 9399 9400 if ( jQuery.isFunction( size ) ) { 9401 return this.each(function( i ) { 9402 var self = jQuery( this ); 9403 self[ type ]( size.call( this, i, self[ type ]() ) ); 9404 }); 9405 } 9406 9407 if ( jQuery.isWindow( elem ) ) { 9408 // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode 9409 // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat 9410 var docElemProp = elem.document.documentElement[ "client" + name ]; 9411 return elem.document.compatMode === "CSS1Compat" && docElemProp || 9412 elem.document.body[ "client" + name ] || docElemProp; 9413 9414 // Get document width or height 9415 } else if ( elem.nodeType === 9 ) { 9416 // Either scroll[Width/Height] or offset[Width/Height], whichever is greater 9417 return Math.max( 9418 elem.documentElement["client" + name], 9419 elem.body["scroll" + name], elem.documentElement["scroll" + name], 9420 elem.body["offset" + name], elem.documentElement["offset" + name] 9421 ); 9422 9423 // Get or set width or height on the element 9424 } else if ( size === undefined ) { 9425 var orig = jQuery.css( elem, type ), 9426 ret = parseFloat( orig ); 9427 9428 return jQuery.isNaN( ret ) ? orig : ret; 9429 9430 // Set the width or height on the element (default to pixels if value is unitless) 9431 } else { 9432 return this.css( type, typeof size === "string" ? size : size + "px" ); 9433 } 9434 }; 9435 9436}); 9437 9438 9439window.jQuery = window.$ = jQuery; 9440})(window); 9441/*! 9442 * jQuery-ajaxTransport-XDomainRequest - v1.0.4 - 2015-03-05 9443 * https://github.com/MoonScript/jQuery-ajaxTransport-XDomainRequest 9444 * Copyright (c) 2015 Jason Moon (@JSONMOON) 9445 * Licensed MIT (/blob/master/LICENSE.txt) 9446 */ 9447(function(factory) { 9448 if (typeof define === 'function' && define.amd) { 9449 // AMD. Register as anonymous module. 9450 define(['jquery'], factory); 9451 } else if (typeof exports === 'object') { 9452 // CommonJS 9453 module.exports = factory(require('jquery')); 9454 } else { 9455 // Browser globals. 9456 factory(jQuery); 9457 } 9458}(function($) { 9459 9460// Only continue if we're on IE8/IE9 with jQuery 1.5+ (contains the ajaxTransport function)
9461if ($.support.cors || !$.ajaxTransport || !window.XDomainRequest) { 9462 return $; 9463} 9464 9465var httpRegEx = /^(https?:)?\/\//i; 9466var getOrPostRegEx = /^get|post$/i; 9467var sameSchemeRegEx = new RegExp('^(\/\/|' + location.protocol + ')', 'i'); 9468 9469// ajaxTransport exists in jQuery 1.5+ 9470$.ajaxTransport('* text html xml json', function(options, userOptions, jqXHR) { 9471 9472 // Only continue if the request is: asynchronous, uses GET or POST method, has HTTP or HTTPS protocol, and has the same scheme as the calling page 9473 if (!options.crossDomain || !options.async || !getOrPostRegEx.test(options.type) || !httpRegEx.test(options.url) || !sameSchemeRegEx.test(options.url)) { 9474 return; 9475 } 9476 9477 var xdr = null; 9478 9479 return { 9480 send: function(headers, complete) { 9481 var postData = ''; 9482 var userType = (userOptions.dataType || '').toLowerCase(); 9483 9484 xdr = new XDomainRequest(); 9485 if (/^\d+$/.test(userOptions.timeout)) { 9486 xdr.timeout = userOptions.timeout; 9487 } 9488 9489 xdr.ontimeout = function() { 9490 complete(500, 'timeout'); 9491 }; 9492 9493 xdr.onload = function() { 9494 var allResponseHeaders = 'Content-Length: ' + xdr.responseText.length + '\r\nContent-Type: ' + xdr.contentType; 9495 var status = { 9496 code: 200, 9497 message: 'success' 9498 }; 9499 var responses = { 9500 text: xdr.responseText 9501 }; 9502 try { 9503 if (userType === 'html' || /text\/html/i.test(xdr.contentType)) { 9504 responses.html = xdr.responseText; 9505 } else if (userType === 'json' || (userType !== 'text' && /\/json/i.test(xdr.contentType))) { 9506 try { 9507 responses.json = $.parseJSON(xdr.responseText); 9508 } catch(e) { 9509 status.code = 500; 9510 status.message = 'parseerror'; 9511 //throw 'Invalid JSON: ' + xdr.responseText; 9512 } 9513 } else if (userType === 'xml' || (userType !== 'text' && /\/xml/i.test(xdr.contentType))) { 9514 var doc = new ActiveXObject('Microsoft.XMLDOM'); 9515 doc.async = false; 9516 try { 9517 doc.loadXML(xdr.responseText); 9518 } catch(e) { 9519 doc = undefined; 9520 } 9521 if (!doc || !doc.documentElement || doc.getElementsByTagName('parsererror').length) { 9522 status.code = 500; 9523 status.message = 'parseerror'; 9524 throw 'Invalid XML: ' + xdr.responseText; 9525 } 9526 responses.xml = doc; 9527 } 9528 } catch(parseMessage) { 9529 throw parseMessage; 9530 } finally { 9531 complete(status.code, status.message, responses, allResponseHeaders); 9532 } 9533 }; 9534 9535 // set an empty handler for 'onprogress' so requests don't get aborted 9536 xdr.onprogress = function(){}; 9537 xdr.onerror = function() { 9538 complete(500, 'error', { 9539 text: xdr.responseText 9540 }); 9541 }; 9542 9543 if (userOptions.data) { 9544 postData = ($.type(userOptions.data) === 'string') ? userOptions.data : $.param(userOptions.data); 9545 } 9546 xdr.open(options.type, options.url); 9547 xdr.send(postData); 9548 }, 9549 abort: function() { 9550 if (xdr) { 9551 xdr.abort(); 9552 } 9553 } 9554 }; 9555}); 9556 9557return $; 9558 9559})); 9560/*! 9561 * jQuery Cookie Plugin v1.3.1 9562 * https://github.com/carhartl/jquery-cookie 9563 * 9564 * Copyright 2013 Klaus Hartl 9565 * Released under the MIT license 9566 */ 9567(function (factory) { 9568 if (typeof define === 'function' && define.amd) { 9569 // AMD. Register as anonymous module. 9570 define(['jquery'], factory); 9571 } else { 9572 // Browser globals. 9573 factory(jQuery); 9574 } 9575}(function ($) { 9576 9577 var pluses = /\+/g; 9578 9579 function raw(s) { 9580 return s; 9581 } 9582 9583 function decoded(s) { 9584 return decodeURIComponent(s.replace(pluses, ' ')); 9585 } 9586 9587 function converted(s) { 9588 if (s.indexOf('"') === 0) { 9589 // This is a quoted cookie as according to RFC2068, unescape 9590 s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); 9591 } 9592 try { 9593 return config.json ? JSON.parse(s) : s; 9594 } catch(er) {} 9595 } 9596 9597 var config = $.cookie = function (key, value, options) { 9598 9599 // write 9600 if (value !== undefined) { 9601 options = $.extend({}, config.defaults, options); 9602 9603 if (typeof options.expires === 'number') { 9604 var days = options.expires, t = options.expires = new Date(); 9605 t.setDate(t.getDate() + days); 9606 } 9607 9608 value = config.json ? JSON.stringify(value) : String(value); 9609 9610 return (document.cookie = [ 9611 config.raw ? key : encodeURIComponent(key), 9612 '=', 9613 config.raw ? value : encodeURIComponent(value), 9614 options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE 9615 '; path=/', 9616 options.domain ? '; domain=' + (options.domain) : '', 9617 options.secure ? '; secure' : '' 9618 ].join('')); 9619 } 9620 9621 // read 9622 var decode = config.raw ? raw : decoded; 9623 var cookies = document.cookie.split('; '); 9624 var result = key ? undefined : {}; 9625 for (var i = 0, l = cookies.length; i < l; i++) { 9626 var parts = cookies[i].split('='); 9627 var name = decode(parts.shift()); 9628 var cookie = decode(parts.join('=')); 9629 9630 if (key && key === name) { 9631 result = converted(cookie); 9632 break; 9633 } 9634 9635 if (!key) { 9636 result[name] = converted(cookie); 9637 } 9638 } 9639 9640 return result; 9641 }; 9642 9643 config.defaults = {}; 9644 9645 $.removeCookie = function (key, options) { 9646 if ($.cookie(key) !== undefined) {
9647 // Must not alter options, thus extending a fresh object... 9648 $.cookie(key, '', $.extend({}, options, { expires: -1 })); 9649 return true; 9650 } 9651 return false; 9652 }; 9653 9654})); 9655/*! Copyright (c) 2009 Brandon Aaron (http://brandonaaron.net) 9656 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 9657 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. 9658 * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. 9659 * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. 9660 * 9661 * Version: 3.0.2 9662 * 9663 * Requires: 1.2.2+ 9664 */ 9665 9666(function($) { 9667 9668var types = ['DOMMouseScroll', 'mousewheel']; 9669 9670$.event.special.mousewheel = { 9671 setup: function() { 9672 if ( this.addEventListener ) 9673 for ( var i=types.length; i; ) 9674 this.addEventListener( types[--i], handler, false ); 9675 else 9676 this.onmousewheel = handler; 9677 }, 9678 9679 teardown: function() { 9680 if ( this.removeEventListener ) 9681 for ( var i=types.length; i; ) 9682 this.removeEventListener( types[--i], handler, false ); 9683 else 9684 this.onmousewheel = null; 9685 } 9686}; 9687 9688$.fn.extend({ 9689 mousewheel: function(fn) { 9690 return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel"); 9691 }, 9692 9693 unmousewheel: function(fn) { 9694 return this.unbind("mousewheel", fn); 9695 } 9696}); 9697 9698 9699function handler(event) { 9700 var args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true; 9701 9702 event = $.event.fix(event || window.event); 9703 event.type = "mousewheel"; 9704 9705 if ( event.wheelDelta ) delta = event.wheelDelta/120; 9706 if ( event.detail ) delta = -event.detail/3; 9707 9708 // Add events and delta to the front of the arguments 9709 args.unshift(event, delta); 9710 9711 return $.event.handle.apply(this, args); 9712} 9713 9714})(jQuery); 9715/* 9716 json2.js 9717 2008-03-14 9718 9719 Public Domain 9720 9721 No warranty expressed or implied. Use at your own risk. 9722 9723 See http://www.JSON.org/js.html 9724 9725 This is a reference implementation. You are free to copy, modify, or 9726 redistribute. 9727 9728 Use your own copy. It is extremely unwise to load third party 9729 code into your pages. 9730*/ 9731 9732if (!this.JSON) { 9733 9734 this.JSON = function() { 9735 9736 function f(n) { // Format integers to have at least two digits. 9737 return n < 10 ? '0' + n : n; 9738 } 9739 9740 Date.prototype.toJSON = function() { 9741 return this.getUTCFullYear() + '-' + 9742 f(this.getUTCMonth() + 1) + '-' + 9743 f(this.getUTCDate()) + 'T' + 9744 f(this.getUTCHours()) + ':' + 9745 f(this.getUTCMinutes()) + ':' + 9746 f(this.getUTCSeconds()) + 'Z'; 9747 }; 9748 9749 9750 var m = { // table of character substitutions 9751 '\b': '\\b', 9752 '\t': '\\t', 9753 '\n': '\\n', 9754 '\f': '\\f', 9755 '\r': '\\r', 9756 '"' : '\\"', 9757 '\\': '\\\\' 9758 }; 9759 9760 function stringify(value, whitelist) { 9761 var a, // The array holding the partial texts. 9762 i, // The loop counter. 9763 k, // The member key. 9764 l, // Length. 9765 r = /["\\\x00-\x1f\x7f-\x9f]/g, 9766 v; // The member value. 9767 9768 switch (typeof value) { 9769 case 'string': 9770 9771 return r.test(value) ? 9772 '"' + value.replace(r, function (a) { 9773 var c = m[a]; 9774 if (c) { 9775 return c; 9776 } 9777 c = a.charCodeAt(); 9778 return '\\u00' + Math.floor(c / 16).toString(16) + 9779 (c % 16).toString(16); 9780 }) + '"' : 9781 '"' + value + '"'; 9782 9783 case 'number': 9784 9785 return isFinite(value) ? String(value) : 'null'; 9786 9787 case 'boolean': 9788 case 'null': 9789 return String(value); 9790 9791 case 'object': 9792 9793 if (!value) { 9794 return 'null'; 9795 } 9796 9797 if (typeof value.toJSON === 'function') { 9798 return stringify(value.toJSON()); 9799 } 9800 a = []; 9801 if (typeof value.length === 'number' && 9802 !(value.propertyIsEnumerable('length'))) { 9803 9804 l = value.length; 9805 for (i = 0; i < l; i += 1) { 9806 a.push(stringify(value[i], whitelist) || 'null'); 9807 } 9808 9809 return '[' + a.join(',') + ']'; 9810 } 9811 if (whitelist) { 9812 l = whitelist.length; 9813 for (i = 0; i < l; i += 1) { 9814 k = whitelist[i]; 9815 if (typeof k === 'string') { 9816 v = stringify(value[k], whitelist); 9817 if (v) { 9818 a.push(stringify(k) + ':' + v); 9819 } 9820 } 9821 } 9822 } else { 9823 9824 for (k in value) { 9825 if (typeof k === 'string') { 9826 v = stringify(value[k], whitelist); 9827 if (v) { 9828 a.push(stringify(k) + ':' + v); 9829 } 9830 } 9831 } 9832 } 9833 9834 return '{' + a.join(',') + '}'; 9835 } 9836 } 9837 9838 return {
9839 stringify: stringify, 9840 parse: function (text, filter) { 9841 var j; 9842 9843 function walk(k, v) { 9844 var i, n; 9845 if (v && typeof v === 'object') { 9846 for (i in v) { 9847 if (Object.prototype.hasOwnProperty.apply(v, [i])) { 9848 n = walk(i, v[i]); 9849 if (n !== undefined) { 9850 v[i] = n; 9851 } else { 9852 delete v[i]; 9853 } 9854 } 9855 } 9856 } 9857 return filter(k, v); 9858 } 9859 9860 if (/^[\],:{}\s]*$/.test(text.replace(/\\["\\\/bfnrtu]/g, '@'). 9861replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). 9862replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 9863 9864 j = eval('(' + text + ')'); 9865 9866 return typeof filter === 'function' ? walk('', j) : j; 9867 } 9868 9869 throw new SyntaxError('parseJSON'); 9870 } 9871 }; 9872 }(); 9873}; 9874/** 9875 * @fileOverview Migration workarounds to keep sitewide inline JS working with 9876 * bothm prototype and jQuery. 9877 * Mostly the functions ported from prototype or mapping of old function names to 9878 * jQuery analogs. 9879 */ 9880 9881jQuery.ajaxSetup({'traditional': true}); 9882 9883addLoadEvent = $; 9884myDeco.readCookie = $.cookie; 9885myDeco.createCookie = function(name, value, days, path, domain) { 9886 return $.cookie(name, value, {expires: days, path: path, domain: domain}); 9887}; 9888 9889myDeco.eraseCookie = function(name, path, domain) { 9890 this.createCookie(name, "", -1, path, domain); 9891}; 9892 9893// Some methods from prototype library implemented for jQ. 9894$A = $.makeArray; 9895 9896jQuery.extend({ 9897 keys: function(object) { 9898 var k = []; 9899 for (var p in object) 9900 k.push(p); 9901 return k; 9902 }, 9903 9904 values: function(object) { 9905 var k = []; 9906 for (var p in object) 9907 k.push(object[p]); 9908 return k; 9909 }, 9910 first: function(list, callback) { 9911 var ret, i, 9912 isFunction = typeof callback === 'function'; 9913 9914 for (i = list.length - 1; i >= 0; i--) { 9915 if (isFunction && callback(list[i], i) == true || 9916 callback === list[i]) { 9917 ret = list[i]; 9918 break; 9919 } 9920 } 9921 9922 return ret; 9923 }, 9924 any: function(list, callback) { 9925 return (typeof this.first(list, callback) !== 'undefined'); 9926 }, 9927 firstElementKey: function(object) { 9928 for (var first in object) break; 9929 return first; 9930 }, 9931 /** 9932 * Extract specified element from list 9933 * @param {Any} element Element to extract 9934 * @param {Array} list List extract from 9935 * @returns Extracted object or undefined if not found 9936 */ 9937 extract: function(element, list) { 9938 var idx = $.inArray(element, list), 9939 ret; 9940 if (idx != -1) 9941 ret = list.splice(idx, 1).shift(); 9942 9943 return ret; 9944 }, 9945 9946 /** 9947 * Returns common elements for both given arrays 9948 * @param {Array} array1 9949 * @param {Array} array2 9950 * @returns {Array} 9951 */ 9952 intersect: function (array1, array2) { 9953 var i, result = []; 9954 9955 for (i = array1.length - 1; i >= 0; i--) { 9956 if (array2.indexOf(array1[i]) !== -1) { 9957 result.push(array1[i]); 9958 } 9959 } 9960 9961 return result; 9962 }, 9963 9964 /** 9965 * Substract to lists 9966 * @param {Array} list List to substract from 9967 * @param {Array} toSubstract Items to substract 9968 * @returns {Array} List without elements from toSubstract 9969 */ 9970 substract: function(list, toSubstract) { 9971 var ret = [], 9972 i, ilen = list.length; 9973 for (i = 0; i < ilen; i++) 9974 if ($.inArray(list[i], toSubstract) == -1) 9975 ret.push(list[i]); 9976 9977 return ret; 9978 }, 9979 9980 /** 9981 * Minify given HTML by removed spaces, tabs, CRLFs between tags and from beginning/ending 9982 * @param {String} html HTML to minify 9983 * @returns {String} Minified HTML 9984 */ 9985 minifyHtml: function(html) { 9986 return $.trim(html.replace(/>[\s\t\r\n]*</g, '><')); 9987 } 9988}); 9989 9990Function.prototype.bindAsEventListener = function() { 9991 var __method = this, args = $.makeArray(arguments), object = args.shift(); 9992 return function(event) { 9993 return __method.apply(object, [event || window.event].concat(args)); 9994 } 9995}; 9996 9997Function.prototype.bind = function() { 9998 if (arguments.length < 2 && typeof(arguments[0]) == "undefined") 9999 return this; 10000 10001 var __method = this, args = $.makeArray(arguments), object = args.shift(); 10002 return function() { 10003 return __method.apply(object, args.concat($A(arguments))); 10004 } 10005}; 10006 10007String.prototype.toQueryParams = function(separator, excludeEmpty) { 10008 var i, key, value, s, hash = {}, 10009 str = $.trim(this), 10010 start = str.indexOf('?'), 10011 end = str.indexOf('#'), 10012 chunks = []; 10013 10014 start = start === -1 ? 0 : start + 1; 10015 end = end === -1 ? str.length : end; 10016 10017 if (end > start && (str = str.substring(start, end)).indexOf('=') !== -1) { 10018 chunks = str.replace(/\+/g, ' ').split(separator || '&'); 10019 for (i = chunks.length - 1; i != -1; i--) { 10020 if (chunks[i].length === 0) 10021 continue; 10022 s = chunks[i].indexOf('='); 10023 key = decodeURIComponent(chunks[i].substring(0, s)); 10024 value = decodeURIComponent(chunks[i].substring(s + 1)); 10025 if (!excludeEmpty || excludeEmpty && value !== '') { 10026 if (key in hash) { 10027 if (typeof hash[key] === 'string') 10028 hash[key] = [hash[key]]; 10029 10030 hash[key].push(value); 10031 } else { 10032 hash[key] = value; 10033 } 10034 } 10035 } 10036 } 10037 10038 return hash; 10039}; 10040 10041// Simple and fast function for regexp based param extraction 10042String.prototype.getURIParam = function(param) { 10043 var uriRe = new RegExp('[\?&;]?(' + param + '\=)([^&]*)', 'g'), 10044 parse, 10045 result = []; 10046 10047 while ((parse = uriRe.exec(this)) != null) 10048 result.push(decodeURIComponent(parse[2].replace('+', ' '))); 10049 10050 return result; 10051}; 10052 10053/** 10054 * Update params in URI 10055 * @param {Object} data Data to update 10056 * @param {Boolean} override Override current data in URI or not 10057 * @return {String} resulting URI 10058 */ 10059String.prototype.updateURIParams = function(data, override) { 10060 // split given URI into parts [url, getParams, hash] 10061 var parts = this.split('#'); 10062 parts[2] = parts[1] ? '#' + parts[1] : ''; 10063 parts[0] = parts[0].split('?'); 10064 parts[1] = parts[0][1] || ''; 10065 parts[0] = parts[0][0]; 10066 parts[1] = '?' + $.param(override ? 10067 $.extend(parts[1].toQueryParams(), data) : 10068 $.extend($.extend({}, data), parts[1].toQueryParams()) 10069 ); 10070 return parts.join(''); 10071}; 10072 10073String.prototype.capitalize = function() { 10074 return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); 10075}; 10076 10077String.prototype.toHtml = function() { 10078 return this.replace(/&/g, '&') 10079 .replace(/\'/g, ''') 10080 .replace(/>/g, '>') 10081 .replace(/</g, '<') 10082 .replace(/"/g, '"'); 10083}; 10084 10085String.prototype.fromHtml = function() { 10086 return this 10087 .replace(/'/gi, "\'") 10088 .replace(/>/gi, '>') 10089 .replace(/</gi, '<') 10090 .replace(/"/g, '"') 10091 .replace(/&#(\d+);/g, function() { 10092 return decodeURIComponent('%' + parseInt(arguments[1], 10).toString(16)); 10093 }) 10094 .replace(/&/g, '&'); 10095}; 10096 10097/** 10098 * Return parsed date from string 10099 */ 10100String.prototype.toDate = function() { 10101 var match = this.match(/(\d{4})-(\d{1,2})-(\d{1,2})(\s(\d{1,2}):(\d{1,2}):(\d{1,2}))?/), 10102 date; 10103 if (match.length > 0) 10104 date = new Date(match[1], match[2] - 1, match[3], 10105 match[5] || 0, match[6] || 0, match[7] || 0); 10106 return date; 10107}; 10108 10109/** 10110 * Returns human readable time diff with specified or current date 10111 * Strings should have "2010-01-12 22:12:44" notation 10112 * @param {Number} limit Maximum number of returned items like "3 month and 3 days ago" for limit = 2. Default is 1 10113 * @param {String} date Date compare to, default is now. 10114 */ 10115String.prototype.toDateDiff = function(limit, date) { 10116 limit = limit || 1; 10117 date = date || new Date(); 10118 10119 var values = { 10120 year: 3600 * 24 * 365, 10121 month: 3600 * 24 * 30, 10122 day: 3600 * 24, 10123 hour: 3600, 10124 minute: 60, 10125 second: 1 10126 }, 10127 str = 'unknown', 10128 from = this.toDate(), 10129 to = (date != undefined && date instanceof Date) ? date : (date + '').toDate(); 10130 10131 if (from && to) { 10132 var sign = Math.round((to - from) / 1000), 10133 diff = Math.abs(sign), chunks = [], lvl = 0; 10134 10135 for (var name in values) { 10136 var value = Math.floor(diff / values[name]); 10137 if (value > 0) { 10138 diff -= values[name] * value; 10139 chunks.push(value + ' ' + name + (value != 1 ? 's' : '')); 10140 lvl++; 10141 } 10142 if (lvl >= limit) 10143 break; 10144 } 10145 10146 if (chunks.length != 0) { 10147 str = ''; 10148 if (sign < 0) 10149 str += 'in '; 10150 10151 for (var i = 0, ilen = chunks.length; i < ilen; i++) 10152 str += chunks[i] + (i < ilen - 2 ? ', ' : '') + (i == ilen - 2 ? ' and ' : ''); 10153 10154 if (sign > 0) 10155 str += ' ago'; 10156 } else 10157 str = 'now' 10158 } 10159 return str; 10160}; 10161 10162/** 10163 * Return string truncated with dots if current length more than specified 10164 * @param {Number} length Length truncate to, default is 30 10165 * @param {String} truncation Suffix added to string in case of truncate 10166 * @return {String} 10167 */ 10168String.prototype.truncate = function(length, truncation) { 10169 length = length || 30; 10170 truncation = truncation || '...'; 10171 return this.length > length ? 10172 this.slice(0, length - truncation.length) + truncation : String(this); 10173}; 10174 10175/** 10176 * Normalizes string, converts to lowercase, removes non-alpha characters, 10177 * and converts spaces to hyphens. 10178 * @return {String} Slugified string 10179 */ 10180String.prototype.slugify = function() { 10181 return this.toLowerCase().replace(/^\s+|[^\w\s-]|\s+$/g, '') 10182 .replace(/[\s-]+/g,'-'); 10183} 10184 10185/** 10186 * Format date as string by specified format (PHP-like format syntax) 10187 * @param {String} format 10188 * @return {String} 10189 */ 10190Date.prototype.format = function(format) {
10191 var month = 'January February March April May June July August September October November December'.split(' '), 10192 chunks = []; 10193 format = format || 'j F Y'; 10194 10195 // prepend values with zeroes to a specified length 10196 function zerofill(value, length) { 10197 // stringify value 10198 value = value + ''; 10199 while (value.length < length) 10200 value = '0' + value; 10201 return value; 10202 } 10203 10204 for (var i = 0, ilen = format.length; i < ilen; i++) { 10205 // IE doesn't want to return char as as format[i] 10206 var sym = format.substring(i, i + 1); 10207 switch (sym) { 10208 // day as 1..31 10209 case 'j': 10210 // day as 01..31 10211 case 'd': 10212 var day = this.getDate(); 10213 if (sym == 'd') 10214 day = zerofill(day, 2); 10215 chunks.push(day); 10216 break; 10217 // month as 1..12 10218 case 'n': 10219 // month as 01..12 10220 case 'm': 10221 var month = this.getMonth() + 1; 10222 if (sym == 'm') 10223 month = zerofill(month, 2); 10224 chunks.push(month); 10225 break; 10226 // month as January..December 10227 case 'F': 10228 chunks.push(month[this.getMonth()]); 10229 break; 10230 // year as 2001 10231 case 'Y': 10232 chunks.push(this.getFullYear()); 10233 break; 10234 default: 10235 chunks.push(sym); 10236 } 10237 } 10238 return chunks.join(''); 10239} 10240 10241// Prototype-style shorthand for stopping event. 10242$.Event.prototype.stop = function() { 10243 this.stopPropagation(); 10244 this.preventDefault(); 10245}; 10246 10247// Prototype-style shorthand for finding correct parent of event target. 10248$.Event.prototype.findElement = function(query) { 10249 return $(this.target).closest(query); 10250}; 10251 10252// indexOf implementation for IE browsers. 10253if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { 10254 i || (i = 0); 10255 var length = this.length; 10256 if (i < 0) i = length + i; 10257 for (; i < length; i++) 10258 if (this[i] === item) return i; 10259 return -1; 10260}; 10261 10262// function with the similar behaviour as prototype's identify() 10263// return ID if is set, or generate, set and return new otherwise 10264// works for the fist element in set, as attr() 10265$.fn.identify = function() { 10266 var self = $(this), 10267 id = self.attr('id'); 10268 if(id) return id; 10269 do { id = 'anonymous_element_' + jQuery.identify_counter++ } while ($(id).size() > 0); 10270 self.attr('id', id); 10271 return id; 10272} 10273 10274jQuery.identify_counter = 0; 10275 10276Array.prototype.reduce = function() { 10277 return this.length > 1 ? this : this[0]; 10278}; 10279 10280// remove duplicated items from array and return new one 10281Array.prototype.unique = function() { 10282 var result = [], x, xlen = this.length; 10283 10284 for (x = 0; x < xlen; x++) 10285 if (result.indexOf(this[x]) == -1) 10286 result.push(this[x]); 10287 10288 return result; 10289}; 10290 10291Array.prototype.get = function(index) { 10292 if (index < 0) { 10293 index = this.length + index; 10294 } 10295 return this[index]; 10296}; 10297 10298/** 10299 * jQuery monkey patch for events used for transparent proxying of mouse* to 10300 * touch* events in case of ipad devices 10301 */ 10302$(function() { 10303 if (myDeco.browser.isMobileSafari) { 10304 var oldFix = jQuery.event.fix, 10305 oldAdd = jQuery.event.add, 10306 oldTrigger = jQuery.event.trigger, 10307 oldRemove = jQuery.event.remove, 10308 map = { 10309 'mousemove': 'touchmove', 10310 'mousedown': 'touchstart', 10311 'mouseup': 'touchend' 10312 }, 10313 revMap = { 10314 'touchmove': 'mousemove', 10315 'touchstart': 'mousedown', 10316 'touchend': 'mouseup' 10317 }; 10318 10319 /** 10320 * Event fix monkey patch will restore coordiante events in jQuery event 10321 * instead of original event 10322 */ 10323 jQuery.event.fix = function(event) { 10324 var touches = event.touches, 10325 changedTouches = event.changedTouches, 10326 targetTouches = event.targetTouches, 10327 rotation = event.rotation, 10328 scale = event.scale; 10329 event = oldFix.call(jQuery.event, event); 10330 event.touches = touches; 10331 event.changedTouches = changedTouches; 10332 event.targetTouches = targetTouches; 10333 event.rotation = rotation; 10334 event.scale = scale; 10335 return event; 10336 }; 10337 10338 /** 10339 * Event trigger monkey patch 10340 * It will replace any mouse* events with corresponding touch* events 10341 */ 10342 jQuery.event.trigger = function(event, data, elem) { 10343 var parts = event.type ? [] : event.split('.'), 10344 type = event.type || parts.shift(), 10345 namespaces = event.namespace || parts.join('.'); 10346 10347 type = map[type] || type; 10348 10349 if (typeof event === 'string') { 10350 oldTrigger.call(this, type + (namespaces ? '.' + namespaces : ''), data, elem); 10351 } else { 10352 event.type = type; 10353 oldTrigger.call(this, event, data, elem); 10354 } 10355 return this; 10356 }; 10357
10358 /** 10359 * Event add monkeypatch function 10360 * Event handler can be an object or callable 10361 */ 10362 jQuery.event.add = function(elem, types, handler, data) { 10363 var etypes = types.split(' '), 10364 callee = handler.handler || handler, 10365 type, normalEvents = [], touchEvents = [], 10366 i = 0, namespaces, namespace, live, 10367 // wrapped handler used for intercept touch events and replace 10368 // type, add coordinate events 10369 wrappedHandler = function(e) { 10370 var touches = (e.type === 'touchend') 10371 ? e.changedTouches 10372 : e.touches, 10373 args = Array.prototype.slice.call(arguments); 10374 10375 if (touches && touches.length === 1) { 10376 args[0] = $.extend({}, args[0], { 10377 type: revMap[e.type], 10378 clientX: touches[0].clientX, 10379 clientY: touches[0].clientY, 10380 pageX: touches[0].pageX, 10381 pageY: touches[0].pageY 10382 }); 10383 } 10384 10385 return callee ? callee.apply(this, args) : false; 10386 }; 10387 10388 // iterate all event types and replace needed with custom callback 10389 while ((type = etypes[i++])) { 10390 // Namespaced event handlers 10391 if (type.indexOf(".") !== -1) { 10392 namespaces = type.split("."); 10393 // live events starting from «live» 10394 live = namespaces[0] === 'live' ? 10395 namespaces.shift() + '.' : ''; 10396 10397 // event type 10398 type = namespaces.shift(); 10399 10400 // rest is namespace 10401 namespace = namespaces.length !== 0 ? 10402 '.' + namespaces.join('.') : ''; 10403 10404 // ordinal event, no live and namespaces 10405 } else { 10406 live = namespace = ''; 10407 } 10408 10409 // check if we need to replace with touch event 10410 if (map[type]) { 10411 touchEvents.push(live + map[type] + namespace); 10412 } else { 10413 normalEvents.push(live + type + namespace); 10414 } 10415 } 10416 10417 // add replaced events using old jquery wrapper 10418 if (touchEvents.length !== 0) { 10419 callee._wrappedHandler = wrappedHandler; 10420 // handler can be object also with handler property 10421 if (jQuery.isPlainObject(handler)) { 10422 handler.handler = wrappedHandler; 10423 oldAdd.call(jQuery.event, elem, touchEvents.join(' '), handler, data); 10424 } else { 10425 oldAdd.call(jQuery.event, elem, touchEvents.join(' '), wrappedHandler, data); 10426 } 10427 } 10428 10429 // rest of events 10430 if (normalEvents.length !== 0) { 10431 oldAdd.call(jQuery.event, elem, normalEvents.join(' '), handler, data); 10432 } 10433 }; 10434 10435 /** 10436 * Remove monkeypatched method 10437 */ 10438 jQuery.event.remove = function(elem, types, handler, pos) { 10439 if (typeof types === 'object') { 10440 oldRemove.call(jQuery.event, elem, types, handler, pos); 10441 return; 10442 } 10443 10444 var callee = handler ? (handler.handler || handler) : handler, 10445 etypes = types.split(' '), 10446 type, i = 0, live, namespaces, 10447 normalEvents = [], touchEvents = []; 10448 10449 // walk through all requested types 10450 while ((type = etypes[i++])) { 10451 // Namespaced event handlers 10452 if (type.indexOf(".") !== -1) { 10453 namespaces = type.split("."); 10454 live = namespaces[0] === 'live' ? 10455 namespaces.shift() + '.' : ''; 10456 type = namespaces.shift(); 10457 namespace = namespaces.length === 0 ? '' : 10458 '.' + namespaces.join('.'); 10459 } else { 10460 live = namespace = ''; 10461 } 10462 10463 if (revMap[type]) { 10464 touchEvents.push(live + revMap[type] + namespace); 10465 } else { 10466 normalEvents.push(live + type + namespace); 10467 } 10468 10469 if (touchEvents.length !== 0) { 10470 oldRemove.call(jQuery.event, elem, touchEvents.join(' '), 10471 callee ? (callee._wrappedHandler || callee) : callee, 10472 pos); 10473 } 10474 10475 if (normalEvents.length !== 0) { 10476 oldRemove.call(jQuery.event, elem, normalEvents.join(' '), 10477 handler, pos); 10478 } 10479 } 10480 }; 10481 } 10482}); 10483 10484/*! 10485 * jQuery each2 - v0.2 - 8/02/2010 10486 * http://benalman.com/projects/jquery-misc-plugins/ 10487 * 10488 * Inspired by James Padolsey's quickEach 10489 * http://gist.github.com/500145 10490 * 10491 * Copyright (c) 2010 "Cowboy" Ben Alman 10492 * Dual licensed under the MIT and GPL licenses. 10493 * http://benalman.com/about/license/ 10494 */ 10495(function($) { 10496 var jq = $([1]); 10497 $.fn.each2 = function( fn ) { 10498 var i = -1; 10499 while ((jq.context = jq[0] = this[++i]) && fn.call(jq[0], i, jq) !== false) {} 10500 return this; 10501 }; 10502})(jQuery); 10503 10504/** 10505 * jQuery :focus filter 10506 */ 10507(function ($) { 10508 var filters = $.expr[":"]; 10509 if (!filters.focus) { 10510 filters.focus = function(elem) { 10511 return elem === document.activeElement && 10512 (elem.type || elem.href); 10513 }; 10514 } 10515})(jQuery); 10516/*global window, navigator, alert, $, encodeURIComponent, document */ 10517/*jslint forin: true, fragment: true, onevar: true, undef: true, nomen: false, eqeqeq: true, bitwise: true, newcap: true, immed: true, regexp: false, white: false, plusplus: false */ 10518/** 10519 * @fileOverview This file contains functions and objects that used sitewide with jQuery 10520 */ 10521 10522// no-op placeholder for django gettext functions 10523if (typeof window.gettext === 'undefined') { 10524 var gettext = function(msgid) { 10525 return msgid; 10526 }, 10527 ngettext = function(msgid) { 10528 return msgid; 10529 }, 10530 interpolate = function(fmt, obj, named) { 10531 if (named) { 10532 return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])}); 10533 } else { 10534 return fmt.replace(/%s/g, function(match){return String(obj.shift())}); 10535 } 10536 } 10537} 10538 10539/** 10540 * boolean XOR emulation 10541 * @param {Boolean} operand1 10542 * @param {Boolean} operand2 10543 * @return {Boolean} 10544 */ 10545function xor(operand1, operand2) { 10546 // to satisfy jslint 10547 operand1 = !operand1; 10548 operand2 = !operand2; 10549 return operand1 !== operand2; 10550} 10551
10552/** 10553 * @namespace Global mydeco namespace. 10554 */ 10555if (typeof window.myDeco === 'undefined') { 10556 var myDeco = {}; 10557} 10558 10559/* container for all vars based in window object */ 10560myDeco.vars = {}; 10561 10562if (typeof window.console === 'undefined') { 10563 // In some caching scenarios FB 1.2 console doesn't get initialized by that time. 10564 // This situation leads to errors further in code. That's why we need to load FB 10565 // manually if it exists. 10566 if (typeof window.loadFirebugConsole === 'function') { 10567 window.loadFirebugConsole(); 10568 } else { 10569 var console = { 10570 log: function(message) {}, 10571 info: function(message) {}, 10572 warn: function(message) {}, 10573 profile: function() {}, 10574 profileEnd: function() {}, 10575 error: function(message) {} 10576 }; 10577 } 10578} 10579 10580myDeco.browser = { 10581 isIE: navigator.userAgent.indexOf('MSIE ') >= 0, 10582 isIE6: navigator.userAgent.indexOf('MSIE 6.0') >= 0, 10583 isIE8: navigator.userAgent.indexOf('MSIE 8.0') >= 0, 10584 isMobileSafari: navigator.platform.slice(0, 2) === "iP", 10585 isChrome: navigator.userAgent.indexOf('Chrome') >= 0, 10586 isIOSWebView: /ip[ao][dn]e?.*webkit(?!.*safari)/i.test(navigator.userAgent) 10587}; 10588 10589/** 10590 * Add a random value parameter to the given link to force a cache miss. 10591 * If the link refers to an <a/> element, then the href property is updated. 10592 */ 10593myDeco.addCacheMissParam = function(link, paramName) { 10594 paramName = paramName || myDeco.settings.NO_CACHE_PARAM || '_nc'; 10595 var param = paramName + '=' + new Date().valueOf(), 10596 url = link.href || link, 10597 regexp = new RegExp('([&?])' + paramName + '=[0-9]+'); 10598 if(regexp.test(url)) { 10599 url = url.replace(regexp, '$1' + param); 10600 } else { 10601 url += (url.indexOf('?') >= 0 ? '&' : '?') + param; 10602 } 10603 if (link.href) { 10604 link.href = url; 10605 } 10606 return url; 10607}; 10608 10609/** 10610 * Returns true if no-cache cookie exists - which should be the case 10611 * if the user recently posted content. 10612 */ 10613myDeco.userHasCacheMissCookie = function() { 10614 return $.cookie(myDeco.settings.NO_CACHE_COOKIE_NAME); 10615}; 10616 10617/** 10618 * Function that fire event from child window on parent 10619 * @param {String} element_selector Element CSS selector 10620 * @param {String} event_name Event name 10621 * @param {String} event_namespace Event namespace, empty if not needed 10622 * @param {Mixed} event_params Event parameters 10623 */ 10624function fireEventFromChild(element_selector, event_name, event_namespace, event_params) { 10625 event_name = event_name + (event_namespace ? '.' + event_namespace : ''); 10626 if(typeof event_params === 'undefined') { 10627 $(element_selector).trigger(event_name); 10628 } else { 10629 $(element_selector).trigger(event_name, [event_params]); 10630 } 10631} 10632 10633/** 10634 * Format a number as a price including currency sumbol 10635 * @param {Number} value price value 10636 * @param {Number} locale the formatting locale 10637 * @param {Number} digits the number of digits after the decimal point 10638 */ 10639myDeco.formatPrice = function(value, locale, digits) { 10640 //if value couldn't be converted into number: e.g ["4"] or "on request" 10641 if (isNaN(+value)) { 10642 return gettext('Price on request'); 10643 } 10644 locale = locale || myDeco.settings.LOCALE; 10645 digits = digits || 2; 10646 10647 var decimal = Number(value).toFixed(digits).slice(-digits); 10648 10649 var language = locale.split('-x-')[0], 10650 conv = myDeco.settings.LANGUAGE_LOCALECONV_MAP[language], 10651 separator = conv.p_sep_by_space ? ' ' : ''; 10652 10653 var strnum = Math.floor(value).toString(); 10654 var number_parts = []; 10655 while (strnum) { 10656 number_parts.unshift(strnum.slice(-3)); 10657 strnum = strnum.slice(0,-3); 10658 }; 10659 10660 if ((typeof(number_parts.slice(-1)[0]) === "undefined") || (number_parts.slice(-1)[0] === "")) { 10661 number_parts.pop(); 10662 } 10663 10664 var s = number_parts.join(conv.mon_thousands_sep) + conv.decimal_point + decimal; 10665 10666 if(myDeco.settings.WL) { 10667 if(myDeco.settings.WL.WL_PARTNER_NAME.toLowerCase() === 'falabella') { 10668 conv.currency_symbol = '$'; 10669 s = number_parts.join('.'); 10670 } 10671 if(myDeco.settings.WL.WL_PARTNER_NAME.toLowerCase() === 'molenaar') { 10672 conv.currency_symbol = '€'; 10673 } 10674 } 10675 10676 return conv.p_cs_precedes ? 10677 conv.currency_symbol + separator + s : 10678 s + separator + conv.currency_symbol; 10679}; 10680 10681/** 10682 * Convert location to coordintes 10683 * @param {String} location Text notation 10684 * @param {Function} callback Function if response succeeded 10685 * @param {Function} errback Function if response failed 10686 */ 10687myDeco.location2coords = function(location, callback, errback) { 10688 $.ajax({ 10689 type: 'get', 10690 data: 'location=' + encodeURIComponent(location), 10691 dataType: 'json', 10692 url: myDeco.urls.ws.location_to_coords, 10693 success: function(data) { 10694 if(data.error && $.isFunction(errback)) { 10695 errback(data.error); 10696 } else if($.isFunction(callback)) { 10697 callback(data.shift().coord); 10698 } 10699 }, 10700 error: function(xhr, a, b) { 10701 var data = JSON.parse(xhr.responseText), 10702 error = data && data.error ? data.error : 'unexpected error'; 10703 if($.isFunction(errback)) { 10704 errback(error); 10705 } 10706 } 10707 }); 10708}; 10709 10710 10711//enable background images caching for IE 10712//needed to avoid reloading of carousel backgrounds 10713try {
10714 document.execCommand('BackgroundImageCache', false, true); 10715} catch(e) {} 10716 10717/** 10718 * Check of secure connection 10719 * @return {Boolean} true in case of secure connection 10720 */ 10721myDeco.isSecure = function() { 10722 return window.location.protocol === 'https:'; 10723}; 10724 10725/** 10726 * Joins base and relative url to form an absolute URL. 10727 * 10728 * @param {String} base Base of the URL 10729 * @param {String} url Relative URL 10730 * @return {String} Absolute URL 10731 */ 10732myDeco.urljoin = function(base, url) { 10733 if (!base) { 10734 return url; 10735 } 10736 if (!url) { 10737 return base; 10738 } 10739 10740 if (base.charAt(base.length - 1) === '/') { 10741 base = base.substr(0, base.length - 1); 10742 } 10743 if (url.charAt(0) === '/') { 10744 url = url.substr(1, url.length); 10745 } 10746 10747 return base + '/' + url; 10748}; 10749 10750/** 10751 * global vars for body, window and document 10752 */ 10753$(function(){ 10754 $.extend(myDeco, { 10755 body: $('body'), 10756 window: $(window), 10757 document: $(document) 10758 }); 10759}); 10760 10761/** 10762 * plugin for toggling element attribute 10763 * @param {string} name - name of attr 10764 * @param {string} value - value of attr 10765 * @param {boolean} state - state of toggling 10766 */ 10767$.fn.toggleAttr = function(name, value, state) { 10768 return this.each(function() { 10769 var element = $(this); 10770 if (typeof state === 'undefined') { 10771 state = !!element.attr(name); 10772 } 10773 10774 if (state) { 10775 element.attr(name, value); 10776 } else { 10777 element.removeAttr(name); 10778 } 10779 }); 10780 10781}; 10782 10783/** 10784 * Load js or css resources, insert them to DOM and fire callback if needed 10785 * @param {String} file, 10786 * @param {String} file, 10787 * @param {String} file, 10788 * ... 10789 * [@param {Function} callback] 10790 */ 10791myDeco.loadResources = function() { 10792 var args = Array.prototype.slice.call(arguments, 0), 10793 items = 0, 10794 head = $('head'), 10795 callback = $.isFunction(args[args.length - 1]) ? args.pop() : $.noop, 10796 itemLoaded = function() { 10797 if (args.length === ++items) { 10798 callback.call(); 10799 } 10800 }; 10801 10802 $.each(args, function(i, file) { 10803 if (file.indexOf('.css') !== -1) { 10804 head.append(document.createStyleSheet ? 10805 document.createStyleSheet(file) : 10806 $('<link rel="stylesheet" type="text/css" />').attr('href', file)); 10807 // Just assume css will load OK. Not all browsers have an onload for 10808 // css resources. We could create a timer check if we can access the 10809 // loaded rules, but that won't work if the css is loaded from another 10810 // domain. 10811 itemLoaded(); 10812 } else if (file.indexOf('.js') !== -1) { 10813 // use ajax() instead of getScript() so that browser cache is used 10814 $.ajax({url: file, dataType: "script", cache: true, success: itemLoaded}); 10815 10816 } 10817 }); 10818}; 10819 10820 10821/* enable HTML5 tags for IE */ 10822myDeco.vars.elements = ('article:aside:audio:canvas:figure:footer:header:hgroup:nav:section:video:output:detalis:keygen:meter:progress:figcaption:mark:summary:time:wbr').split(':'); 10823for (var c = 0; c < myDeco.vars.elements.length; c++) { 10824 document.createElement(myDeco.vars.elements[c]); 10825} 10826 10827/** 10828 * sort facebook friends to those who have mydeco account and the rest; 10829 * modifying main object 10830 * @param {object} object with all fb friends 10831 * using in austin save form and site register 10832 */ 10833myDeco.sortFacebookFriends = function(json) { 10834 var i, friend, md_friends=[], fb_friends=[], arr; 10835 for (i = json.friends.length-1; i > -1; i--) { 10836 friend = json.friends[i]; 10837 if (friend.mydeco_user) { 10838 friend.user_id = friend.mydeco_user.user_id; 10839 arr = md_friends; 10840 } else { 10841 arr = fb_friends; 10842 } 10843 arr.unshift(friend); 10844 } 10845 json.friends_not_on_mydeco = fb_friends; 10846 json.friends = md_friends; 10847}; 10848/*globals myDeco, Persist, window, Object */ 10849/*jslint forin: true, fragment: true, onevar: true, undef: true, eqeqeq: true, bitwise: true, newcap: true, regexp: false, white: false, plusplus: false */ 10850 10851/** 10852 * @fileOverview initialize local storage on load 10853 */ 10854 10855myDeco.storage = { 10856 /** 10857 * @var {Persist.Store} store storage holder 10858 */ 10859 store: null, 10860 ready: false, 10861 callbacks: [], 10862 init_counter: 0, 10863 10864 /** 10865 * Get value from storage 10866 * @param {String} name 10867 * @param {Function} callback Returning function 10868 */ 10869 get: function(name, callback) { 10870 var self = this; 10871 this.onready(function() { 10872 self.store.get(name, callback); 10873 }); 10874 }, 10875 10876 /** 10877 * Set value to storage 10878 * @param {String} name 10879 * @param {String} value 10880 */ 10881 set: function(name, value) { 10882 var self = this; 10883 this.onready(function() { 10884 self.store.set(name, value); 10885 }); 10886 }, 10887 10888 /** 10889 * Remove values from LS 10890 * @param {String|Array}
10890 name Single name or array of names to remove 10891 */ 10892 remove: function(name) { 10893 var self = this, i, ilen; 10894 10895 name = Object.prototype.toString.call(name) === '[object Array]' ? name : [name]; 10896 10897 this.onready(function() { 10898 for (i = 0, ilen = name.length; i < ilen; i++) { 10899 self.store.remove(name[i]); 10900 } 10901 }); 10902 }, 10903/** 10904 * Initializes store, and check if it's ready to operate, 10905 * runs previously stored callbacks. 10906 */ 10907 init: function() { 10908 var self = this, i, len; 10909 if(!this.store) { 10910 this.store = new Persist.Store('MYD_'+(window.location.hostname).replace(/(\.)/g, '_'), { 10911 swf_path: '/media/img/persist.swf' 10912 }); 10913 } 10914 try { 10915 this.store.set('storage_status','ready'); 10916 this.store.get('storage_status', function(ok, data) { 10917 if (ok && data === 'ready') { 10918 self.ready = true; 10919 } 10920 }); 10921 } catch (e) { // can raise errors when isnt ready yet 10922 } 10923 if (this.ready) { 10924 this.store.remove('storage_status'); 10925 for (i = 0, len = this.callbacks.length; i < len; i++) { 10926 this.callbacks[i](); 10927 } 10928 } else { 10929 // Limit to 200 loops, ~15 sec 10930 if (this.init_counter++ < 300) { 10931 window.setTimeout(this.init.bind(this), 50); 10932 } 10933 } 10934 }, 10935 10936 /** 10937 * Remember callback to run when storage will be ready 10938 * @param {Function} callback 10939 */ 10940 onready: function(callback) { 10941 if (typeof callback === 'function') { 10942 if (this.ready) { 10943 callback(); 10944 } else { 10945 this.callbacks.push(callback); 10946 } 10947 } 10948 } 10949}; 10950 10951window.addLoadEvent(myDeco.storage.init.bind(myDeco.storage)); 10952/*globals window, document, $, myDeco */ 10953/*jslint sloppy: true, white: true, eqeq: true, nomen: true */ 10954 10955myDeco.tracking = { 10956 tags: myDeco.settings.TRACKING_TAGS || [], 10957 cookiesEnabled: false, 10958 prefix: '', 10959 10960 init: function() { 10961 // set tracking cookie if tracking tag present in URL: 10962 var self = this, 10963 queryParams = document.location.href.toQueryParams(), 10964 cookieValue = (new Date()).valueOf(); 10965 10966 $.each(this.tags, function(i, tag) { 10967 var value = queryParams[tag.paramName]; 10968 if (value) { 10969 self.setTrackingCookie(tag.cookieName, value, tag.type == 'internal'); 10970 return false; 10971 } 10972 }); 10973 10974 // test user cookies support 10975 $.cookie('test', cookieValue); 10976 if ($.cookie('test') == cookieValue) { 10977 $.cookie('test', "", {expires: -1}); 10978 this.cookiesEnabled = true; 10979 } 10980 10981 // Track click events on tagged elements 10982 // example html: <a href="" data-tracker="<action>;<category>;<label>"></a> 10983 $('.custom-tracker').click(function(e) { 10984 // XXX: If the browser loads the link target quickly then there may not be 10985 // time for the event request to complete. Ideally we should stop the click 10986 // event and wait some time before explicitly setting a new location. 10987 // However such a delay isn't a great user experience and may cause unforseen 10988 // issues with browsers' popup blocker & click-jacking mitigation efforts. 10989 // See http://support.google.com/googleanalytics/bin/answer.py?hl=en&answer=55527 10990 var params = $(this).data('tracker'); 10991 if (params) { 10992 params = params.split(';'); 10993 myDeco.tracking.trackEvent(params[0], params[1] || 'none', params[2] || this.toString()); 10994 } 10995 }); 10996 10997 // Track clicks on site nav 10998 $('nav a').click(function(e) { 10999 // XXX: Stop event and redirect on timeout. See custom-tracker above. 11000 var menuId = $(this).closest('.popup-menu').attr('id'), 11001 category = menuId ? menuId.split('-')[1] : 'top', 11002 label = $(this).text().slugify(); 11003 myDeco.tracking.trackEvent('siteNavClick', category, label); 11004 }); 11005 11006 myDeco.body.bind('roomSaved', function(event, roomType, roomId) { 11007 self.trackEvent(event.type, roomType, String(roomId)); 11008 }); 11009 11010 myDeco.body.bind('mydeco.login', function(e, div) { 11011 if (div.view === 'register') { 11012 self.trackStruq("g/fddexTjKfkKRhVu74dmRyA/?v=2&qs=registration"); 11013 } 11014 }); 11015 }, 11016 11017 /** 11018 * Set a cookie for the specified tracking tag name & value. 11019 * Any existing cookies are removed unless retainExisting=True. 11020 */ 11021 setTrackingCookie: function(name, value, retainExisting) { 11022 $.each(this.tags, function(i, tag) { 11023 var cookieDomain = tag.cookieDomain || myDeco.settings.USER_DATA_COOKIE_DOMAIN; 11024 if (tag.paramName == name || tag.cookieName == name) { 11025 $.cookie(tag.cookieName, value, {expires: tag.cookieAge, domain: cookieDomain}); 11026 } else { 11027 if (!retainExisting) { 11028 $.cookie(tag.cookieName, "", {expires: -1, domain: cookieDomain}); 11029 } 11030 } 11031 }); 11032 }, 11033 11034 /** 11035 * Send a command to the tracking system. 11036 */ 11037 send: function() { 11038 if (!myDeco.settings.GOOGLE_ANALYTICS_SKIP_TRACKING) { 11039 var command = Array.prototype.slice.call(arguments); // convert arguments to native array 11040 window._gaq = window._gaq || [];
11041 window._gaq.push(command); 11042 } 11043 }, 11044 11045 /** 11046 * Track a page view. 11047 * @param {String} url Optional url. Use current document url if not specified. 11048 */ 11049 trackPageview: function(url) { 11050 return this.send(this.prefix + '_trackPageview', url); 11051 }, 11052 11053 /** 11054 * Track a user action. 11055 * 11056 * @param {String} action The action for the event. 11057 * @param {String} category The general event category. 11058 * @param {String} label An optional descriptor for the event. 11059 * @param {Int} value An optional value associated with the event. 11060 */ 11061 trackEvent: function(action, category, label, value) { 11062 return this.send(this.prefix + '_trackEvent', 11063 category, action, label, value); 11064 }, 11065 11066 /** 11067 * Set a tracking variable 11068 * 11069 * @param {String} name The tracking variable name. 11070 * @param {String} value The variable value. 11071 */ 11072 setVar: function(name, value) { 11073 return this.send(this.prefix + '_set' + name, value); 11074 }, 11075 11076 /** 11077 * Set a custom tracking variable by slot index. 11078 * 11079 * @param {Int} index The slot for this variable.. 11080 * @param {String} name The name for this custom variable. 11081 * @param {String} value The variable value. 11082 * @param {Int} scope Optional scope id. 11083 */ 11084 setCustomVar: function(index, name, value, scope) { 11085 return this.send(this.prefix + '_setCustomVar', index, name, value, scope); 11086 }, 11087 11088 /** 11089 * Add tracking information for a sale 11090 * 11091 * @param {Order} order The order information 11092 * 11093 * Where order is a struct containing: 11094 * 11095 * order_id The order ID 11096 * order_total Total transaction value of the sale 11097 * order_tax Taxable component of order_total 11098 * shipping_cost Shipping cost component of order_total 11099 * billing_city City from billing address for order 11100 * billing_state State from billiing address for order 11101 * billing_country Country from billing address for order 11102 * items An array of the items purchased, each array 11103 * being a structure of: 11104 * item_id The item ID 11105 * item_name The name of the item 11106 * category The category the item is in 11107 * item_price The price for a single item 11108 * item_quantity How many items we bought 11109 */ 11110 trackOrder: function(order) { 11111 var struq_vars = [], 11112 marin_vars = [], 11113 phg_vars = [], 11114 peerius_vars = {"products": [], "currency": "GBP"}; 11115 11116 // Order tracking with Google 11117 this.send(this.prefix + '_addTrans', 11118 order.order_id, 'MyDeco2 [' + myDeco.settings.LOCALE + ']', order.order_total, 11119 order.order_tax, order.shipping_cost, order.billing_city, 11120 order.billing_state, order.billing_country 11121 ); 11122 11123 // Collect order-level information 11124 $.extend(peerius_vars, { 11125 "orderNo": order.order_id, 11126 "shipping": order.shipping_cost, 11127 "total": order.order_total, 11128 "subtotal": order.order_subtotal 11129 }); 11130 11131 // Collect information about the various order items 11132 $.each(order.items, $.proxy(function (i, item) { 11133 this.send(this.prefix + '_addItem', 11134 order.order_id, item.item_id, item.item_name, 11135 '', item.item_price, item.item_qty 11136 ); 11137 11138 struq_vars.push('li=pid=' + encodeURIComponent(item.guid) + 11139 '|qty=' + encodeURIComponent(item.item_qty) + '|tv=' + 11140 encodeURIComponent(item.item_netprice)); 11141 11142 marin_vars.push('UTM:I|' + order.order_id +'|BASKET|||' + 11143 item.item_price + '|'); 11144 11145 phg_vars.push('[category:' + encodeURIComponent(item.cat) + 11146 '/sku:' + item.guid + '/value:' + item.item_netprice + 11147 '/quantity:' + item.item_qty + ']'); 11148 11149 peerius_vars.products.push({ 11150 "refcode": item.guid, 11151 "qty": item.item_qty, 11152 "price": item.item_netprice 11153 }); 11154 }, this)); 11155 11156 this.send(this.prefix + '_trackTrans'); 11157 11158 // Order tracking with Struq 11159 struq_vars.push('summary=tot=' + encodeURIComponent(order.order_total) + 11160 '|dis=' + encodeURIComponent(order.discount) + '|oid=' + 11161 encodeURIComponent(order.order_id)); 11162 11163 this.trackStruq('cd/Tj3m0Nmp1kqvtKCB_KPyOg/' + '?v=2&qs=' + 11164 encodeURIComponent(struq_vars.join('&'))); 11165 11166 // Google Adwords tracking. 11167 if (myDeco.settings.GOOGLE_ADWORDS_TRACKING_ID) { 11168 myDeco.tracking.trackingImage('https://www.googleadservices.com/pagead/conversion/' 11169 + myDeco.settings.GOOGLE_ADWORDS_TRACKING_ID +'/?value=' 11170 + order.order_total + '&label=' 11171 + myDeco.settings.GOOGLE_ADWORDS_TRACKING_LABEL + '&script=0'); 11172 } 11173 11174 // Marin tracking 11175 if (myDeco.settings.MARIN_TRANSACTION_SCRIPT) { 11176 myDeco.tracking.trackingImage(myDeco.settings.MARIN_TRANSACTION_SCRIPT + 11177 '&trans=UTM:T|' + [ 11178 order.order_id, '', order.order_total, order.order_tax, 11179 order.shipping_cost, order.billing_city, '', 11180 order.billing_country 11181 ].join('|') + '%0A' + marin_vars.join('%0A')); 11182 } 11183 11184 // PHG tracking 11185 if (myDeco.settings.PHG_TRACKING_URI) { 11186 myDeco.tracking.trackingImage(myDeco.settings.PHG_TRACKING_URI + 11187 'currency:GBP/conversionref:' + order.order_id +'/' + 11188 phg_vars.join('')); 11189 } 11190 11191 // Peerius tracking 11192 myDeco.externalUi.initPeerius("en-gb", "order", peerius_vars); 11193 }, 11194 11195 /** 11196 * Add tracking information for a checkout basket 11197 * 11198 * @param {Cart} cart The cart information 11199 * 11200 * Where cart is a struct containing the same information as order 11201 * from trackOrder() [see above] 11202 */ 11203 trackCheckoutBasket: function (data) { 11204 var peerius_vars = {"products": [], "currency": "GBP"}; 11205 11206 $.each(data.cart.items, function (i, item) { 11207 peerius_vars.products.push({ 11208 "refcode": item.guid,
11209 "qty": item.item_qty, 11210 "price": item.item_price 11211 }); 11212 }); 11213 11214 $.extend(peerius_vars, { 11215 "subtotal": data.cart.sub_totals, 11216 "total": data.cart.totals, 11217 "shipping": data.shippings[0].price 11218 }); 11219 11220 myDeco.externalUi.initPeerius("en-gb", "checkout", peerius_vars); 11221 }, 11222 11223 /** 11224 * Function for adding tracking custom images for misc objects 11225 * @param {String} src Tracking image source, use [timestamp] for replace 11226 * with current timestamp in url 11227 * @param {Function} callback Callback called when image is loaded 11228 */ 11229 trackingImage: function(src, callback) { 11230 src = src.replace('[timestamp]', (new Date()).valueOf()); 11231 11232 $('<img />') 11233 .load(function() { 11234 if ($.isFunction(callback)) { 11235 callback.call(this); 11236 } 11237 $(this).remove(); 11238 }) 11239 .error(function() { 11240 $(this).remove(); 11241 }) 11242 .attr({ 11243 'src': src, 11244 'class': 'seo-hidden' 11245 }) 11246 .appendTo('body'); 11247 }, 11248 11249 /** 11250 * Track something with Struq - remarketing product views, orders, etc. 11251 */ 11252 trackStruq: function (url) { 11253 var rnd = parseInt(Math.random() * 10000000000000000, 10), 11254 protocol = (document.location.protocol == "https:" ? "https:" : "http:"); 11255 11256 if (myDeco.settings.STRUQ_TRACKING) { 11257 myDeco.body.append('<ifr'+'ame src="' + protocol + 11258 myDeco.settings.STRUQ_TRACKING + url + "&rnd=" + rnd + 11259 '" style="width:0; height:0; border: 0;"></ifr'+'ame>'); 11260 } 11261 } 11262}; 11263 11264// track all ajax errors 11265$(document).ajaxError(function(e, xhr, options, message) { 11266 if (options.url && options.url.indexOf('/ws/render') > -1) { 11267 return; // ignore render errors 11268 } 11269 myDeco.tracking.trackEvent('ajaxError', message, options.url); 11270}); 11271 11272$(function(){myDeco.tracking.init();}); 11273/*globals myDeco, $, window, escape, s_gi, s_account, s_channel, s_pagename, Image */ 11274/*jslint forin: true, fragment: true, onevar: true, undef: true, eqeqeq: true, bitwise: true, newcap: true, regexp: false, white: false, plusplus: false */ 11275myDeco.buynow = { 11276 popup_id: 0, 11277 omniture: function(obj, guid, price) { 11278 try { 11279 var chan = 'GoToShop', 11280 gts = chan, 11281 s_gts = s_gi(s_account); 11282 11283 if (s_account.indexOf('wl-')) { 11284 chan = s_channel; 11285 } 11286 11287 s_gts.pageName = gts + ':' + s_pagename; 11288 s_gts.channel = chan; 11289 s_gts.prop1 = gts; 11290 s_gts.prop2 = gts; 11291 s_gts.prop3 = gts; 11292 11293 s_gts.events = 'purchase'; 11294 s_gts.products = ';'+guid+';1;'+price; 11295 s_gts.linkTrackVars = 'pageName,channel,prop1,prop2,prop3,events,products,eVar5'; 11296 s_gts.linkTrackEvents = 'purchase'; 11297 s_gts.tl(obj,'o',gts); 11298 } 11299 catch (e) {} 11300 }, 11301 11302 /** 11303 * Track a go-to-shop click with google as both an event and a page view. 11304 */ 11305 googleAnalytics: function(guid, price, category, src) { 11306 try { 11307 category = category || 'unknown'; 11308 src = src || 'unknown'; 11309 myDeco.tracking.trackEvent('GTS', category, src, parseInt(price, 10) || 0); 11310 myDeco.tracking.trackPageview('/goal/gts/' + category.slugify() + '/' + src + '/' + guid); 11311 } catch (e) {} 11312 }, 11313 11314 clickHandler : function(event, guid, price) { 11315 var button = event.findElement('A'); 11316 this.buynowClick(button.get(0), guid, price); 11317 11318 if (button.hasClass('external')) { 11319 event.stop(); 11320 this.openWindow(); 11321 } 11322 }, 11323 11324 buynowClick: function(button, guid, price, category, src, gwoSetting, e) { 11325 this.button_url = $(button).data('url') || button.href; 11326 this.googleAnalytics(guid, price, category, src); 11327 this.omniture(button, guid, price); 11328 //if it is emulation in IE, just open new page after all trackers; 11329 if (myDeco.browser.isIE && myDeco.buynow.eclick) { 11330 this.openWindow(true); 11331 } 11332 //else run experiment 11333 if (!myDeco.browser.isIE) { 11334 var gts_ex = ['gts_new', 'gts_same'], i, value; 11335 for (i = 0; i < gts_ex.length; i++) { 11336 value = gts_ex[i]; 11337 if (($.cookie('sv') && $.cookie('sv').indexOf(value) > 0) || myDeco.buynow.eclick) { 11338 if (e && e.stopPropagation) { 11339 e.stopPropagation(); 11340 e.preventDefault(); 11341 } 11342 this.openWindow(value === 'gts_same' || myDeco.buynow.eclick); 11343 myDeco.buynow.eclick = false; 11344 } 11345 } 11346 } 11347 }, 11348 11349 openWindow: function(sameTab) { 11350 var popup = window.open(myDeco.buynow.button_url, 'MD_GoToShop' + myDeco.buynow.popup_id, 11351 !sameTab ? 'width=900, height=600, scrollbars=no, toolbars=no, resizable=yes': '').focus(); 11352 myDeco.buynow.popup_id += (!sameTab); 11353 11354 }, 11355 11356/** 11357 * emulating go-to-shop behaviour for links to usuall product pages 11358 */ 11359 emulate: function() { 11360 $('.search-product-link').click(function(e) { 11361 var guid = this.href.split('/').reverse()[1], 11362 price = this.rel; 11363 11364 //we need to avoid emulation for buttons that already have href="/aff/?product=..." 11365 if (guid === 'aff') { 11366 return; 11367 } 11368 11369 e.stop(); 11370 myDeco.buynow.eclick = true; 11371 myDeco.buynow.buynowClick(this, guid, price, null, 'title', ''); 11372 }); 11373 } 11374 11375}; 11376/*globals myDeco, window, document, FB, addLoadEvent */ 11377/*jslint sloppy: true, nomen: true */ 11378 11379/** 11380 * @fileOverview This file contains some wrapper functions around the facebook 11381 * connect library that will load the scripts and iframe resources on demand. 11382 */ 11383 11384myDeco.facebook = { 11385 11386 /** 11387 * Expression to check for existence of facebook cookie. 11388 * @type {RegExp} 11389 */
11390 cookiePattern: new RegExp('\\bfbs_' + myDeco.settings.FACEBOOK_APP_ID + '='), 11391 11392 /** 11393 * Facebook permissions to request on login. 11394 * @type {String} 11395 */ 11396 permissions: 'email', 11397 11398 /** 11399 * Flag to record when API is initialised 11400 */ 11401 _initialized: false, 11402 11403 /** 11404 * Callbacks to fire on init 11405 */ 11406 _loadCallbacks: [], 11407 11408 /** 11409 * Initialise facebook config. 11410 */ 11411 init: function() { 11412 // If user has auto_share enabled but we don't have a facebook cookie 11413 // try and get one. 11414 if (myDeco.userData && myDeco.userData.auto_share && 11415 !this.cookiePattern.test(document.cookie)) { 11416 this.getLoginStatus(); 11417 } 11418 }, 11419 11420 /** 11421 * Load facebook connect script and initialise library. 11422 * @param {Function} callback Function to call when FB library is ready. 11423 */ 11424 loadConnect: function (callback) { 11425 if (this._initialized) { 11426 if (callback) { 11427 callback(); 11428 } 11429 return; 11430 } 11431 11432 var self = this, 11433 fbRoot = document.getElementById('fb-root'), 11434 script = document.createElement('script'), 11435 src = window.location.protocol + 11436 '//connect.facebook.net/' + myDeco.settings.POSIX_LOCALE + '/sdk.js'; 11437 11438 if (callback) { 11439 this._loadCallbacks.push(callback); 11440 } 11441 window.fbAsyncInit = function() { 11442 FB.init({ 11443 appId: myDeco.settings.FACEBOOK_APP_ID, 11444 channelUrl: myDeco.urls.facebookProxy, 11445 oauth: true, 11446 cookie: true, 11447 version: 'v19.0' 11448 }); 11449 self._initialized = true; 11450 fbRoot.removeChild(script); 11451 while (self._loadCallbacks.length) { 11452 self._loadCallbacks.pop()(); 11453 } 11454 }; 11455 script.src = src; 11456 // fbRoot.appendChild(script); 11457 }, 11458 11459 /** 11460 * Check if user is currently logged in to facebook. 11461 * Will call loadConnect if necessary. 11462 * @param {Function} callback Function to call when check is ready. 11463 */ 11464 getLoginStatus: function (callback) { 11465 this.loadConnect(function() { 11466 FB.getLoginStatus(callback); 11467 }); 11468 }, 11469 11470 /** 11471 * Check if a user is already logged in to facebook and if not then 11472 * display the facebook login popup window. 11473 * @param {Function} callback Function to call when login status is known. 11474 */ 11475 login: function (callback) { 11476 var options = {scope: this.permissions}; 11477 // If we need to wait for the connect script to load then the login 11478 // popup window will be blocked because the call will not be associated 11479 // with a click event. So if FB is not initialised we instead redirect 11480 // to the standalone login page. 11481 if (this._initialized) { 11482 if (FB.getAuthResponse()) { // can't use async getLoginStatus 11483 callback({authResponse: FB.getAuthResponse()}); 11484 } else { 11485 FB.login(callback, options); 11486 } 11487 } else { 11488 window.location = myDeco.urls.userLogin + '#form=facebook'; 11489 } 11490 } 11491}; 11492 11493addLoadEvent(myDeco.facebook.init.bind(myDeco.facebook)); 11494/*globals myDeco, window, $ */ 11495/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 11496 sloppy: true, white: true */ 11497/** 11498 * @fileOverview This file contains user object (reading user data from cookies, ajax login/logout, updating header) 11499 */ 11500 11501myDeco.user = { 11502 /** 11503 * Initialise page elements with user data. 11504 */ 11505 //need for pass form element through iframe-proxy callback 11506 parentForm: false, 11507 11508 /** 11509 * Login template cached data 11510 * @type {object} 11511 */ 11512 loginTemplate: { 11513 'login': false, 11514 'register': false 11515 }, 11516 11517 /** 11518 * Action performed after successful login 11519 * @type {Function} 11520 */ 11521 _delayedAction: $.noop, 11522 11523 /** 11524 * Default login form variant used for loginRequired 11525 * @type {String} 11526 */ 11527 defaultLoginVariant: 'login', 11528 11529 /** 11530 * User care module init 11531 */ 11532 init: function() { 11533 this.updateLoginArea(); 11534 this.updateCounters(); 11535 this.checkAuth(); 11536 this.newsLetter(); 11537 if (myDeco.showApplePopup) { 11538 this.applePopup(); 11539 } 11540 11541 if (myDeco.settings.LOCALE === 'en-gb-x-retail') { 11542 this.cookiesNotify(); 11543 } 11544 11545 // Running in Facebook canvas: 11546 if (myDeco.settings.FACEBOOK_CANVAS) { 11547 $('#guest_section').remove(); 11548 this.defaultLoginVariant = 'register'; 11549 if (!myDeco.username) { 11550 this.checkFacebookStatus(); 11551 } 11552 } 11553 11554 $('.ajax-login-link').click(this.ajaxLoginClickHandler.bind(this)); 11555 $('.ajax-logout-link').click(this.ajaxLogoutClickHandler.bind(this)); 11556 //tracking for search for from header 11557 $('#global-search').find('form').submit(function() { 11558 myDeco.tracking.trackEvent('searchbox', 'Header', $('#query').val(), 1); 11559 }); 11560 $('.toggle-link').click(this.toggleVisibleElements.bind(this)); 11561 }, 11562 11563 newsLetter: function(div) { 11564 div = div || myDeco.body; 11565 div.find('.newsletter-form').submit(function(e) { 11566 e.stop(); 11567 var form = $(this), 11568 errorFields = form.find('.hideme').hide(), 11569 unsubscribe = window.location.pathname.indexOf('unsubscribe') > -1; 11570 11571 $.ajax({ 11572 url: (unsubscribe ? myDeco.urls.newsletter_unsubscribe_ajax : 11573 myDeco.urls.newsletter_subscribe_ajax), 11574 data: form.serialize(), 11575 type: 'post', 11576 success: function() { 11577 myDeco.tracking.trackEvent(unsubscribe ? 'unsubscribe' : 'subscribe', 'newsletter'); 11578 form.find('.success-replace').toggle(); 11579 }, 11580 error: function(xhr) { 11581 var messages = JSON.parse(xhr.responseText).error.message; 11582 for (var key in messages) { 11583 errorFields.filter('.error-' + key).text(messages[key][0]).show(); 11584 } 11585 } 11586 }); 11587 }); 11588 }, 11589 11590 /** 11591 * Defines that current user is the owner of the current page 11592 */ 11593 isCurrentUser: function() { 11594 return typeof myDeco.username !== 'undefined' &&
11595 typeof myDeco.ownername !== 'undefined' && 11596 myDeco.username === myDeco.ownername; 11597 }, 11598 11599 /** 11600 * Update the content of the top-right profile/login area with relevant user data. 11601 */ 11602 updateLoginArea: function() { 11603 var guest_section = $('#guest_section'), 11604 user_section = $('#user_section'); 11605 11606 if (myDeco.username) { 11607 $('.replace-username-link').each(function() { 11608 var link = this, 11609 attr = (link.href || $(link).data('href')).replace('userid', myDeco.userid); 11610 if (link.href) { 11611 $(link).attr('href', attr); 11612 } else { 11613 $(link).data('href', attr); 11614 } 11615 }); 11616 11617 $('.top-avatar').css({'background-image': 'url(' + myDeco.avatar + ')'}); 11618 11619 $('.login-info').show(); 11620 11621 guest_section.hide(); 11622 user_section.show(); 11623 } else { 11624 guest_section.show(); 11625 user_section.hide(); 11626 $('.logout-info').show(); 11627 //no need to replace links in top nav as it broke ajax login 11628 $('.replace-username-link:not(.unchanged)').attr('href', myDeco.urls.userLogin + '?' + $.param({'next': window.location})); 11629 } 11630 }, 11631 11632 /** 11633 * Update alert & shortlist counter elements. 11634 */ 11635 updateCounters: function(reload) { 11636 if (reload) { 11637 myDeco.setUserData(); 11638 } 11639 11640 var link = $('#alerts-link')[0]; 11641 if (!myDeco.alert_count && link && !$('.active:first-child', link)) { 11642 link.addClass('grey-colored'); 11643 } 11644 }, 11645 11646 /** 11647 * Check if user can be silently logged in with facebook credentials 11648 */ 11649 checkFacebookStatus: function() { 11650 myDeco.facebook.getLoginStatus(function (response) { 11651 if (response.authResponse) { 11652 $.ajax({ 11653 url: myDeco.urls.userLoginFacebookAjax, 11654 type: 'post', 11655 dataType: 'json', 11656 success: function (data) { 11657 if (data.success) { 11658 myDeco.user.ajaxLoginSuccessHandler(data); 11659 } 11660 } 11661 }); 11662 } 11663 }); 11664 }, 11665 11666 ajaxLoginClickHandler: function(event, destination, variant) { 11667 if (event) { 11668 event.stop(); 11669 } 11670 11671 if (myDeco.username) { 11672 return; 11673 } 11674 11675 var self = this, 11676 isLogin, 11677 _variant = (variant || (event ? $(event.target).data('logintype') : 'login')).split('-'), 11678 form = _variant[1], 11679 context = myDeco.vars.reg_context, 11680 process = function(div) { 11681 myDeco.buttons.initSubmitButtons(div.find('input:submit')); 11682 div.find('a.popup-login-link') 11683 .click(self.ajaxLoginClickHandler.bind(self)).end() 11684 .find('form').submit(function (e) { 11685 var form = $(this); 11686 if (myDeco.buttons.isDisabled(form)) { 11687 e.stop(); 11688 } else { 11689 self.parentForm = myDeco.buttons.disable(form); 11690 } 11691 if ($('input[name="reg_context"]').size() > 0) { 11692 if (context) { 11693 $('input[name="reg_context"]').val(context); 11694 } 11695 } 11696 // looks like we have a bug in mobile safari 11697 if (myDeco.browser.isMobileSafari) { 11698 this.submit(); 11699 } 11700 }); 11701 myDeco.login.init(div); 11702 if (form && form === 'mydeco') { 11703 div.find('li.register-mydeco').click(); 11704 } 11705 }, 11706 success = function(data) { 11707 if (destination) { 11708 process(destination.text('').append(data)); 11709 } else { 11710 myDeco.popin.open({ 11711 template: data, 11712 tracking: 'login', 11713 closeText: '', 11714 height: 'auto', 11715 width: $('<div class="login-content-holder seo-hidden">').appendTo(myDeco.body).width(), 11716 className: 'login-popin', 11717 holderClassName: 'login-content-holder', 11718 backgroundClassName: 'mdtwo-alert-background', 11719 afterOpen: process 11720 }); 11721 } 11722 }, 11723 templateLoad = function() { 11724 $.ajax({ 11725 url: isLogin ? myDeco.urls.userLoginAjax : myDeco.urls.userRegisterAjax, 11726 type: 'get', 11727 success: function(data) { 11728 self.loginTemplate[variant] = data; 11729 success(data); 11730 } 11731 }); 11732 }; 11733 11734 variant = _variant[0]; 11735 isLogin = (variant === 'login'); 11736 11737 // login template is present only if we have loaded merges previously 11738 if (this.loginTemplate[variant]) { 11739 success(this.loginTemplate[variant]); 11740 } else { 11741 if (typeof myDeco.login === 'undefined') { 11742 myDeco.loadResources(myDeco.urls.merges.login_css, myDeco.urls.merges.login_js, templateLoad); 11743 } else { 11744 templateLoad.call(); 11745 } 11746 } 11747 }, 11748 11749 /** 11750 * Callback used from hidden login iframe proxy for login-register form 11751 */ 11752 ajaxLoginSuccessHandler: function(result) { 11753 var form = this.parentForm ? 11754 myDeco.buttons.enable(this.parentForm) : false, 11755 view = result && result.view_id || 'login', 11756 i, error; 11757 if (result && result.error && form ) { 11758 form.find('p.error').remove(); 11759 for (i = 0; i < result.error.message.length; i++) { 11760 error = result.error.message[i]; 11761 if (error.field == 'global') { 11762 form.prepend('<p class="error">' + error.error + '</p>'); 11763 } else { 11764 form.find(':input[name=' + error.field + ']') 11765 .addClass('error-border') 11766 .before('<p class="error">' + error.error + '</p>') 11767 .filter('input') 11768 .closest('p.name-field').addClass('not-available'); 11769 } 11770 // track first error only 11771 if (i == 0) { 11772 myDeco.tracking.trackEvent('login-error', view + '-' + error.field, error.error); 11773 } 11774 } 11775 myDeco.body.trigger('mydeco.login-error'); 11776 } else {
11777 myDeco.popin.close(); 11778 myDeco.setUserData(); 11779 this.updateLoginArea(); 11780 myDeco.body.trigger("mydeco.login", [{username: myDeco.username, view: view}]); 11781 myDeco.tracking.trackEvent('login', 11782 view.indexOf('register') >= 0 ? 'register' : 'login', 11783 view.indexOf('facebook') >= 0 ? 'facebook' : 'mydeco'); 11784 this._delayedAction.call(); 11785 this.checkAuth(); 11786 } 11787 }, 11788 11789 /** 11790 * Remove user cookies and update user data & DOM elements. 11791 */ 11792 ajaxLogoutClickHandler: function(event) { 11793 if (event) { 11794 event.stop(); 11795 } 11796 var self = this, 11797 updateUserData = function() { 11798 myDeco.username = null; 11799 11800 myDeco.setUserData(); 11801 self.updateLoginArea(); 11802 if (event && $(event.target).closest('a').hasClass('redirect-to-root')) { 11803 myDeco.document.one('mydeco.login', function() { 11804 window.location = '/'; 11805 }); 11806 } 11807 myDeco.body.trigger("mydeco.login", [{}]); 11808 myDeco.tracking.trackEvent('logout', 'logout'); 11809 }; 11810 11811 $.ajax({ 11812 url: myDeco.urls.ws.logout, 11813 type: 'post', 11814 success: updateUserData, 11815 error: function() { 11816 // remove cookies only if backend fails 11817 $.cookie(myDeco.settings.AUTH_COOKIE_NAME, null, {path: '/', domain: myDeco.settings.AUTH_COOKIE_DOMAIN}); 11818 $.cookie(myDeco.settings.USER_DATA_COOKIE_NAME, null, {path: '/', domain: myDeco.settings.USER_DATA_COOKIE_DOMAIN}); 11819 updateUserData(); 11820 } 11821 }); 11822 11823 }, 11824 11825 /** 11826 * Close iframe callback 11827 */ 11828 ajaxLoginCloseHandler: function() { 11829 this.iframe.remove(); 11830 }, 11831 11832 /** 11833 * Initializes elements that depend on user's auth state. 11834 */ 11835 checkAuth: function() { 11836 $('.authorized').toggle(this.isCurrentUser()); 11837 $('.unauthorized').toggle(!this.isCurrentUser()); 11838 11839 var forms = $('.user-rating-form'), socialLink; 11840 11841 // If user is logged in use logged in text and 11842 // attach handlers to remove it. 11843 if (myDeco.username) { 11844 $('.login-info').show(); 11845 $('.logout-info').hide(); 11846 11847 forms.find('fieldset:first').each(function() { 11848 var fieldset = $(this), 11849 textarea = fieldset.find('textarea:first'), 11850 info = fieldset.find('.login-placeholder'); 11851 fieldset.click(function() { 11852 info.hide(); 11853 textarea.focus(); 11854 }); 11855 // if textarea not empty in case of ordinal form POST with errors 11856 // hide info and set cursor to textarea 11857 if(textarea.val() !== '') { 11858 fieldset.click(); 11859 } 11860 }); 11861 } else { 11862 // If user is not logged in use redirect it to login form, 11863 //remove all observers from button to avoid validation and error bubble 11864 if (!forms.hasClass('ajax-login-form')) { 11865 forms.find('a.review-button').unbind().attr('href', myDeco.urls.userLogin + '?next=' + window.location.href); 11866 } 11867 $('.logout-info').show(); 11868 $('.login-info').hide(); 11869 } 11870 }, 11871 11872 /** 11873 * Wrapper for actions which require login state 11874 * @param {Function} successHandler Callback which will be executed after successful login. Very usable for actions which require login for it 11875 * @param {String} variant - variant of login form 11876 */ 11877 loginRequired: function(successHandler, variant) { 11878 this._delayedAction = $.isFunction(successHandler) ? successHandler : $.noop; 11879 11880 if (!myDeco.username) { 11881 if (myDeco.settings.WL && myDeco.settings.WL.WL_CUSTOM_LOGIN) { 11882 var msg = {name: "login required"}; 11883 parent.postMessage(msg.name, "*"); // backwards compatibility 11884 parent.postMessage(msg, "*"); 11885 parent.postMessage(JSON.stringify(msg), "*"); // IE9 11886 } else { 11887 this.ajaxLoginClickHandler(null, null, variant || this.defaultLoginVariant); 11888 } 11889 } else { 11890 this._delayedAction.call(); 11891 } 11892 }, 11893 11894 /** 11895 * toggling elements visibility, used on PP and on /c/ pages 11896 * @param {Event} e - click on element 11897 */ 11898 toggleVisibleElements: function(e) { 11899 e.stop(); 11900 var menu = $(e.target).closest('.toggle-element-container').toggleClass('opened'), 11901 popup = menu.find('.toggle-element:not(.full-desr)').toggle(); 11902 11903 myDeco.formDispatcher.setPosition(popup, { 11904 'control': menu, 11905 'position': 'bottom', 11906 'relative': true, 11907 //10 and 20 is random negative margin, need to overflow triangle in bg 11908 'vrow': menu.outerHeight() - 20, 11909 'rparent': popup.outerWidth() - 10 11910 }); 11911 //click on any place except Add-to-folder popup should collapce right-hand menu 11912 //why except Add-to-folder: see SITE-7351 11913 myDeco.body.click(function(e) { 11914 if (menu.hasClass('opened') && !$(e.target).closest('.object-action-popup').size()) { 11915 menu.removeClass('opened').find('.toggle-element:not(.no-autoclose)').toggle(); 11916 } 11917 }); 11918 }, 11919 11920 /** 11921 * showing AppStore popup on apple devices 11922 */ 11923 applePopup: function() { 11924 // Define Apple device 11925 var deviceAgent = navigator.userAgent.toLowerCase(); 11926 var devices = [ 11927 { 'name': 'iPhone', 'matches': deviceAgent.match(/iphone|ipod/) }, 11928 { 'name': 'iPad', 'matches': deviceAgent.match(/ipad/) } 11929 ]; 11930 11931 if ($.cookie("app") == null){ 11932 for (i = 0; i < devices.length;i++ ){ 11933 if(devices[i].matches) { 11934 if(confirm('Did you know that you can find inspiration on the go with the mydeco.com ' + devices[i].name +' app? Browse 3D rooms and create your own inspiration gallery on your phone. Download it from the app store b
11934y searching `mydeco`?')) { 11935 window.location.href = 'http://itunes.apple.com/gb/app/mydeco.com/id478304722'; 11936 } 11937 $.cookie("app", "seen", { expires: 30 }); 11938 } 11939 } 11940 } 11941 }, 11942 11943 /** 11944 * Notify our users that we use cookies 11945 */ 11946 cookiesNotify: function() { 11947 if (!myDeco.storage) { 11948 return; 11949 } 11950 11951 myDeco.storage.get('notified', function(check, saved) { 11952 if (!saved) { 11953 myDeco.body.addClass('notifier-on'); 11954 11955 $('.close-noty').click(function(e) { 11956 e.stop(); 11957 $(this).parent().hide(); 11958 }); 11959 11960 // save into storage 11961 $('.save-noty').click(function() { 11962 myDeco.storage.set('notified', true); 11963 myDeco.body.removeClass('notifier-on'); 11964 }); 11965 } 11966 }); 11967 } 11968 11969}; 11970 11971 11972// Update DOM elements: 11973$(function(){myDeco.user.init();}); 11974/*globals myDeco, window, $ */ 11975/*jslint forin: true, fragment: true, onevar: true, undef: true, nomen: false, eqeqeq: true, plusplus: true, bitwise: true, newcap: true, immed: true, regexp: false, white: false, plusplus: false */ 11976 11977if (typeof myDeco.sidebarMenu === 'undefined') { 11978 myDeco.sidebarMenu = { 11979 /** 11980 * Active class name 11981 * @var {String} 11982 */ 11983 activeClass: 'active', 11984 paneHiddenClass: 'hidden', 11985 11986 /** 11987 * Init function 11988 */ 11989 init: function(root) { 11990 (typeof root !== 'undefined' ? root.find('.sidebar-menu li') : $('.sidebar-menu li')) 11991 .click(this.menuTab.bind(this)); 11992 }, 11993 11994 /** 11995 * switcher for active/inactive tabs 11996 * @param {Object} e - event if this function was binded; but it can be a jquery object; 11997 *(it is jq object for cases we need to extract menu variant from url(see /account/login/) 11998 */ 11999 menuTab: function(e) { 12000 12001 //no need stop event if it is checkbox; 12002 if(e.target && e.target.tagName !== 'INPUT') { 12003 e.stop(); 12004 } 12005 12006 var self = this, 12007 newtab = e.target ? $(e.target).closest('li') : e, 12008 newinput = newtab.find('input'), 12009 menu = newtab.closest('.sidebar-menu'), 12010 oldtab = menu.find('li.' + this.activeClass), 12011 oldinput = oldtab.find('input'), 12012 root = newtab.closest('.sidebar-menu-root'); 12013 12014 if(newtab.get(0) === oldtab.get(0)) { 12015 return; 12016 } 12017 12018 oldtab.removeClass(this.activeClass); 12019 oldinput.removeAttr('checked'); 12020 root.find('.' + oldtab.data('name')).addClass(this.paneHiddenClass); 12021 12022 newtab.addClass(this.activeClass); 12023 newinput.attr('checked', 'checked'); 12024 12025 root.find('.' + newtab.data('name')).removeClass(this.paneHiddenClass); 12026 // resize popin if it already open 12027 if(myDeco.popin && myDeco.popin.isOpen()) { 12028 myDeco.popin.resize(); 12029 } 12030 12031 if(root.hasClass('has-history')) { 12032 //set variant url in a history for non-popin elements, e.g login page 12033 $.history.add('form=' + newtab.attr('id').split('-')[1]); 12034 } 12035 12036 root.trigger('switched'); 12037 } 12038 }; 12039 12040 // No need to init at loading yet 12041 //addLoadEvent(myDeco.sidebarMenu.init.bind(myDeco.sidebarMenu)); 12042}; 12043/*globals $, myDeco, window, tmpl, gettext */ 12044/*jslint sloppy: true, plusplus: true */ 12045/** 12046 * @fileOverview login observers 12047 */ 12048myDeco.login = { 12049 callback: null, 12050 12051 staticLogin: false, 12052 12053 staticLoginInvite: false, 12054 12055 alreadyBound: false, 12056 12057 customCallback: 'followStep', 12058 12059 //init login elements 12060 init: function (root) { 12061 root = root || $('div.sidebar-menu-root'); 12062 var self = this, fbConnect = $('#fb-connect'); 12063 if (myDeco.login.staticLogin) { 12064 $('a.ajax-login-link').unbind(); 12065 } 12066 12067 if ($.isFunction(myDeco.login.callback)) { 12068 myDeco.login.callback(root); 12069 } 12070 12071 if (!myDeco.settings.AUSTIN_DETECT) { 12072 root.find('.default-value-js').defaultValue(''); 12073 } 12074 12075 myDeco.sidebarMenu.init(root); 12076 12077 this.showPassword(root); 12078 12079 //prevent double-init events bind 12080 if (!self.alreadyBound) { 12081 myDeco.body 12082 .bind('mydeco.login', function (e, data) { 12083 self[self.customCallback](data.view); 12084 }) 12085 .bind('mydeco.fbusername-generated', function (e, data) { 12086 self.callback = function (div) { 12087 self.buildUsernameLayout(div, data.data); 12088 self.killDaddy(div.find('div.removeme:first'), $('#fb-call')); 12089 }; 12090 12091 myDeco.user.ajaxLoginClickHandler(null, null, 'register'); 12092 }); 12093 self.alreadyBound = true; 12094 } 12095 12096 if (myDeco.settings.FACEBOOK_CANVAS) { 12097 // It would be nice to call facebookConnect() here but since this 12098 // is probably a callback from loading login form the oauth popup 12099 // would be blocked by the browser. 12100 root.find('#register-area').addClass('fb-only'); 12101 } 12102 12103 if (fbConnect.length) { 12104 // Load facebook connect code early so that we don't run into 12105 // browser popup blocker in myDeco.facebook.login callbacks,
12106 myDeco.facebook.getLoginStatus(); 12107 fbConnect.click(this.facebookConnect.bind(this)); 12108 } 12109 12110 // reset password form init 12111 root.find('.reset-password-form').submit(this.initResetPassword.bind(this)); 12112 12113 //switch facebook new/returning forms 12114 root.find('a.form-switcher').click(function (e) { 12115 e.stop(); 12116 $(e.target).closest('div.form-container') 12117 .find('form').toggleClass('hidden').end() 12118 .find('form:not(.hidden) input:first') 12119 .focus(); 12120 }); 12121 12122 // put it to a delayed action because it flicker 12123 // screen on appearing 12124 window.setTimeout(function() { 12125 root.find('.returning-user input[name=username]').focus(); 12126 }, 100); 12127 12128 myDeco.body.trigger('mydeco.login-shown', [root]); 12129 12130 // Show register form after registrations error 12131 if (myDeco.vars.regError) { 12132 $('li.register-mydeco').trigger('click'); 12133 } 12134 }, 12135 12136 /** 12137 * Generates username based on user's Facebook account data. 12138 * Sends an Ajax request and forms an options list from returned values. 12139 */ 12140 facebookGenerateUsername: function (root) { 12141 var self = this; 12142 $.ajax({ 12143 url: myDeco.urls.generateFacebookUsername, 12144 dataType: 'json', 12145 success: function (data) { 12146 if (root.length) { 12147 self.buildUsernameLayout(root, data); 12148 } else { 12149 myDeco.body.trigger('mydeco.fbusername-generated', {data: data}); 12150 } 12151 } 12152 }); 12153 }, 12154 12155 buildUsernameLayout: function (root, data) { 12156 var select = root.find('select'), p = select.closest('div.username'), i, self = this; 12157 //if we have username variants, put them into select as <options> 12158 if (data.length > 0) { 12159 for (i = 0; i < data.length; i = i + 1) { 12160 select.find('option:last').before($('<option value="' + data[i] + '">' + data[i] + '</option>')); 12161 } 12162 select.find('option:first').attr('selected', 'selected') 12163 .end() 12164 .change(function (e) { 12165 //replace select with input if user want to choose his own username 12166 if ($(e.target).val() === 'own') { 12167 self.usernameInput(p); 12168 } 12169 }); 12170 } else { 12171 //the same replacing if there is no username variants 12172 self.usernameInput(p); 12173 } 12174 }, 12175 12176 /** 12177 * build list of user's friends from json 12178 * @param {object} container for template 12179 * @param {object} json string with params for template 12180 */ 12181 fillUsersList: function (container, params) { 12182 container.html(tmpl('flist-tmpl', params)).find('div.face-friends-on').slideDown(300); 12183 }, 12184 12185 /** 12186 * The "Facebook invitation" step 12187 */ 12188 facebookInvitationStep: function (form) { 12189 var self = this; 12190 form = self.killDaddy(form, $('#facebook-invitation-step')); 12191 $.ajax({ 12192 url: myDeco.urls.facebookInvitationStep, 12193 dataType: 'json', 12194 success: function (json) { 12195 myDeco.sortFacebookFriends(json); 12196 self.fillUsersList($('#fbshare'), { 12197 peoples: json.friends_not_on_mydeco, 12198 mode: 'share' 12199 }); 12200 $('#afterinvite').click(function (e) { 12201 e.stop(); 12202 $.ajax({ 12203 url: myDeco.urls.facebookInvitationStepPost, 12204 type: 'post', 12205 dataType: 'json', 12206 data: $(e.target).closest('form').serialize(), 12207 success: function() { 12208 if (myDeco.settings.WL_PROFILE) { 12209 myDeco.popin.close(); 12210 } else { 12211 self.killDaddy(form, $('#invite-form')); 12212 } 12213 }, 12214 error: function() { 12215 myDeco.popin.close(); 12216 } 12217 }); 12218 }); 12219 12220 $('#fbfriends-autocomplete').defaultValue('') 12221 .autocomplete({ 12222 param: 'q', 12223 ajax: myDeco.urls.facebookFriendsAutocomplete, 12224 list: $.map(json.friends_names, function (name) {return name; }), 12225 insertText: function (obj) {return obj.name; }, 12226 template: function (obj) { 12227 return '<li><span class="image" style="background-image: url(http://graph.facebook.com/' 12228 + obj.id + '/picture/)"></span>' 12229 + '<span class="face-name">' + obj.name + '</span></li>'; 12230 }, 12231 wrapper: '<div class="autocomplete super-top"><ul class="share-list"></ul></div>' 12232 }); 12233 } 12234 }); 12235 }, 12236 12237 /** 12238 * function that toggle password characters 12239 * @param {Object} root container 12240 */ 12241 showPassword: function (div) { 12242 if (div.hasClass('inited')) { 12243 return; 12244 } 12245 var pswrd = div.addClass('inited').find('input.password').change(function (e) {pswrd.val($(this).val()); }); 12246 12247 //show password toggle 12248 div.find('input.show_password') 12249 .toggleAttr('checked', 'checked', false) 12250 .change(function (e) { 12251 pswrd.toggleClass('hidden'); 12252 }); 12253 }, 12254 12255 /** 12256 * remove previous form 12257 * @param {Object} previous form 12258 */ 12259 killDaddy: function (daddy, decent) { 12260 daddy.closest('.removeme').remove(); 12261 if (decent) { 12262 return decent.slideDown(300); 12263 } 12264 }, 12265 12266 /** 12267 * Init the "follow" step of the registration 12268 */ 12269 followStep: function (view) { 12270 /* Disable invite and follow steps for 3d-tool login */ 12271 if (!((view === 'register' || view === 'facebook_register') && myDeco.user.loginTemplate.register) || myDeco.vars.reg_context === 'community') { 12272 return; 12273 } 12274 12275 var popin = myDeco.popin.open({ 12276 template: myDeco.user.loginTemplate.register, 12277 width: 568, 12278 className: 'login-popin steps', 12279 holderClassName: 'login-content-holder', 12280 backgroundClassName: 'mdtwo-alert-background' 12281 }), 12282 isFacebook = (view === 'facebook_register'),
12283 content = $('#follow-step').find('div.fbfriends').toggle(isFacebook).end(), 12284 form = this.killDaddy(content.prevAll(), content), 12285 list = form.find('div.updateme:visible'), 12286 self = this, 12287 i; 12288 12289 $.ajax({ 12290 url: isFacebook ? myDeco.urls.userFollowStepFacebook : myDeco.urls.userFollowStep, 12291 dataType: 'json', 12292 success: function (json) { 12293 for (i = 0; i < list.length; i++) { 12294 var mode = $(list[i]).data('mode'); 12295 if (json.friends) { 12296 myDeco.sortFacebookFriends(json); 12297 } 12298 self.fillUsersList($(list[i]), { 12299 peoples: json[mode], 12300 mode: mode 12301 }); 12302 } 12303 $('#afterfollow').click(function (e) { 12304 e.stop(); 12305 $.ajax({ 12306 type: 'post', 12307 data: $(e.target).closest('form').serialize(), 12308 dataType: 'json', 12309 url: myDeco.urls.userFollowStepPost, 12310 success: function() { 12311 if (isFacebook) { 12312 self.facebookInvitationStep(form); 12313 } else { 12314 myDeco.popin.close(); 12315 } 12316 } 12317 }); 12318 }); 12319 } 12320 }); 12321 }, 12322 12323 /** 12324 * replace select with username variants with input for custom username 12325 * @param {Object} p - <p> that contain our select 12326 */ 12327 usernameInput: function (p) { 12328 p.before($("<p class='username reg-form'>" + 12329 "<label>" + gettext('Choose a mydeco username') + "</label>" + 12330 "<label class='tip username_error'>" + gettext("This is how you'll be known on the site") + "</label>" + 12331 "<label class='username-icon'></label>" + 12332 "<input type='text' title='" + gettext('This is how you will be known on mydeco') + "' name='username' maxlength='35' id='id_username'></p>")) 12333 .closest('form') 12334 .find('.username input') 12335 .keyup(this.checkAvailability.bind(this)); 12336 p.remove(); 12337 }, 12338 12339 /** 12340 * reset password observer 12341 * @param {event} e - click on reset password link 12342 */ 12343 initResetPassword: function (e) { 12344 e.stop(); 12345 var form = $(e.target), 12346 parent = form.closest('div.form-container'); 12347 12348 $.ajax({ 12349 url: myDeco.urls.requestPasswordReset, 12350 dataType: 'json', 12351 type: 'post', 12352 data: form.serialize(), 12353 success: function (data) { 12354 if (data.error) { 12355 form.find('.error').text(data.message); 12356 } else { 12357 form.addClass('hidden'); 12358 parent 12359 .find('.error').empty() 12360 .end() 12361 .find('.reset-password-success') 12362 .show() 12363 .prepend('<p>' + data.message + '</p>'); 12364 12365 $('.reset-password-success').delegate('.close', 'click', function() { 12366 $('.reset-password-success').hide().parent().find('form:eq(0)').removeClass('hidden'); 12367 }); 12368 12369 } 12370 } 12371 }); 12372 }, 12373 12374 /** 12375 * observer for facebook connect forms 12376 * @param {event} e - click on facebook connect button 12377 */ 12378 facebookConnect: function (e) { 12379 e.stop(); 12380 var self = this, 12381 form = $(e.target).closest('div.form-fb').addClass('processed'); 12382 12383 function handleError(reason) { 12384 form.removeClass('processed'); 12385 $('#fb-connect').prev('.error').text( 12386 'There was an error checking your Facebook details.' + 12387 'Please ensure that you are logged in to Facebook and try again.' 12388 ); 12389 if (reason.responseText) { 12390 try { 12391 reason = JSON.parse(reason.responseText); 12392 reason = reason.error.message; 12393 } catch (e) { 12394 } 12395 } 12396 myDeco.tracking.trackEvent('login-error', 'facebook-connect', reason.toString()); 12397 } 12398 12399 myDeco.facebook.login(function (response) { 12400 if (!response.authResponse) { 12401 handleError('unauthorized'); 12402 return; 12403 } 12404 $.ajax({ 12405 url: myDeco.urls.userLoginFacebookAjax, 12406 dataType: 'json', 12407 type: 'post', 12408 success: function (data) { 12409 // if user already has account... 12410 if (data.redirect_url) { 12411 if (self.staticLogin && !self.staticLoginInvite) { 12412 window.location.href = data.redirect_url; 12413 } else { 12414 myDeco.user.ajaxLoginSuccessHandler(data); 12415 } 12416 // ...else user needs to register 12417 } else { 12418 if (data.mydeco_user) { 12419 $.ajax({ 12420 url: myDeco.urls.facebookAssociateAccount, 12421 type: 'post', 12422 success: function (data) { 12423 if (data.success) { 12424 myDeco.user.ajaxLoginSuccessHandler(data); 12425 } 12426 }, 12427 error: handleError 12428 }); 12429 } else { 12430 self.facebookGenerateUsername(self.killDaddy(form, $('#fb-call'))); 12431 } 12432 } 12433 }, 12434 error: handleError 12435 }); 12436 }); 12437 } 12438}; 12439 12440$(function() { 12441 if (myDeco.login.staticLogin) { 12442 myDeco.login.init(); 12443 } 12444}); 12445/** 12446 * @fileOverview Get userdata info from cookies 12447 */ 12448 12449myDeco.userData = {}; 12450 12451/** 12452 * Take data from user_data cookie and add it to myDeco object. 12453 */ 12454myDeco.setUserData = function() { 12455 var cookie = myDeco.readCookie(myDeco.settings.USER_DATA_COOKIE_NAME); 12456 if (cookie) { 12457 var userData = myDeco.userData; 12458 try { 12459 try { 12460 userData = eval('(' + cookie + ')'); 12461 } catch (e) { 12462 // trick to get rid of extra " symbols 12463 cookie = cookie.slice(1, -1); 12464 userData = eval('(' + cookie + ')'); 12465 } 12466 12467 if (typeof userData != 'object') 12468 throw "Not an object"; 12469 12470 } catch (e) { 12471 console.log('Invalid user_data cookie value: ' + e); 12472 } 12473 12474 for (var prop in userData) 12475 myDeco[prop] = userData[prop]; 12476 12477 myDeco.userData = userData; 12478 } else { 12479 //zero-fill mydeco properties getting from cookies (need for ajax-logout) 12480 for (var prop in myDeco.userData) 12481 myDeco[prop] = '';
12482 myDeco.userData = {}; 12483 } 12484 12485 myDeco.urls.updateUserUrls(myDeco.username); 12486} 12487// Dont need to wait for DOM ready event 12488myDeco.setUserData(); 12489/** 12490 * @fileOverview Class inheritance prototype 12491 */ 12492 12493(function(){ 12494 var initializing = false, 12495 fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 12496 12497 // The base Class implementation (does nothing) 12498 this.Class = function(){}; 12499 12500 // Create a new Class that inherits from this class 12501 Class.extend = function(prop) { 12502 var _super = this.prototype; 12503 12504 // Instantiate a base class (but only create the instance, 12505 // don't run the init constructor) 12506 initializing = true; 12507 var prototype = new this(); 12508 initializing = false; 12509 12510 // The dummy class constructor 12511 function Class() { 12512 // All construction is actually done in the init method 12513 if ( !initializing && this.init ) 12514 this.init.apply(this, arguments); 12515 } 12516 12517 // Copy the properties over onto the new prototype 12518 for (var name in prop) { 12519 // Check if we're overwriting an existing function 12520 prototype[name] = typeof prop[name] == "function" && 12521 typeof _super[name] == "function" && fnTest.test(prop[name]) 12522 ? (function(name, fn){ 12523 return function() { 12524 var tmp = this._super; 12525 12526 // Add a new ._super() method that is the same method 12527 // but on the super-class 12528 this._super = _super[name]; 12529 12530 // The method only need to be bound temporarily, so we 12531 // remove it when we're done executing 12532 var ret = fn.apply(this, arguments); 12533 this._super = tmp; 12534 12535 return ret; 12536 }; 12537 })(name, prop[name]) 12538 : prop[name]; 12539 12540 if (typeof prop[name] != 'function' && name.substring(0, 1) == '$') 12541 Class[name] = prototype[name]; 12542 } 12543 12544 for (var name in _super) 12545 if (prop[name] == undefined && _super[name] != undefined && typeof _super[name] == 'object') { 12546 switch (Object.prototype.toString.call(_super[name])) { 12547 case '[object Array]': 12548 prototype[name] = jQuery.extend(true, [], _super[name]); 12549 break; 12550 case '[object Object]': 12551 prototype[name] = jQuery.extend(true, {}, _super[name]); 12552 break; 12553 } 12554 } 12555 12556 // Populate our constructed prototype object 12557 Class.prototype = prototype; 12558 12559 // Enforce the constructor to be what we expect 12560 Class.constructor = Class; 12561 12562 // And make this class extendable 12563 Class.extend = arguments.callee; 12564 12565 return Class; 12566 }; 12567})(); 12568myDeco.tabSwitcher = { 12569 initTabSwitcher: function(switcher, options) { 12570 var options = $.extend(true, { 12571 // use to add possibility to hide all tabs 12572 always: !switcher.hasClass('not-always'), 12573 useVisibility: switcher.hasClass('use-visibility') 12574 }, options || {}), 12575 tabs = switcher.find('.tab'), 12576 activeTab = tabs.filter('.active'), 12577 index = tabs.index(activeTab) || (options.always ? 0 : -1), 12578 navButtons = switcher.find('.tabs-navigation .next-link, .tabs-navigation .prev-link').click(function(e) { 12579 e.stop(); 12580 var element = $(this); 12581 element.get(0).blur(); 12582 12583 if (element.hasClass('disabled')) 12584 return; 12585 12586 if (element.hasClass('next-link')) 12587 $(tabs.get(index + 1)).click(); 12588 else 12589 $(tabs.get(index - 1)).click(); 12590 }), 12591 12592 dummyCallback = function(tab, callback) { callback(tab); }; 12593 12594 updateNavLinks(); 12595 12596 options.onShow = options.onShow || dummyCallback; 12597 options.onHide = options.onHide || dummyCallback; 12598 12599 function updateNavLinks() { 12600 navButtons.removeClass('disabled prev-link-disabled next-link-disabled'); 12601 if (index == 0) 12602 navButtons.filter('.prev-link').addClass('disabled prev-link-disabled'); 12603 12604 if (index == tabs.length - 1) 12605 navButtons.filter('.next-link').addClass('disabled next-link-disabled'); 12606 } 12607 12608 function getContainer(tab) { 12609 var container = tab.data('container'); 12610 if (!container) { 12611 container = $(tab.data('hash') + '-container'); 12612 tab.data('container', container); 12613 }; 12614 12615 return container; 12616 } 12617 12618 function hideTab(t) { 12619 var container = getContainer(t.removeClass('active')); 12620 if (options.useVisibility) { 12621 container.css({visibility: 'hidden'}); 12622 } else { 12623 container.hide(); 12624 } 12625 activeTab = $(); 12626 } 12627 12628 function showTab(t) { 12629 activeTab = t.addClass('active'); 12630 12631 var container = getContainer(activeTab), 12632 profile = myDeco.settings.WL_PROFILE; 12633 12634 if (options.useVisibility) { 12635 container.css({visibility: 'visible'}); 12636 } else { 12637 container.show(); 12638 } 12639 12640 if (profile) { 12641 showDefaultCategory(); 12642 } 12643 } 12644 12645 function showDefaultCategory() { 12646 var cat = myDeco.settings.WL.WL_DEFAULT_TAB_ID; 12647 12648 // We don't have a reference to the SearchController here so instead a search is forced by simulating a click on the appropriate category facet item. 12649 if (cat) { 12650 $('#' + cat).click(); 12651 } 12652 } 12653 12654 tabs.click(function(e, nohide) { 12655 var tab = $(this), 12656 tabHash = (tab.data('hash') || '').substring(1), 12657 selectedItem; 12658 e.stop(); 12659 12660 // don't want to hide pane if always and tab is active 12661 if (tab[0] == activeTab[0] || (!options.always && tab.hasClass('tab-hide'))) { 12662 if (options.always || nohide) 12663 return; 12664 else 12665 options.onHide(activeTab, hideTab); 12666 } else { 12667 if (activeTab.size() > 0) { 12668 hideTab(activeTab); 12669 showTab(tab); 12670 } else 12671 options.onShow(tab, showTab) 12672 } 12673 12674 // update global tab index; 12675 index = tabs.index(this); 12676 12677 switcher.trigger('tab', [tab]); 12678 12679 updateNavLinks(); 12680 }); 12681 12682 switcher 12683 .bind('show', function(e, tabClass) { 12684 tabs.filter('.' + tabClass).trigger('click', [true]); 12685 }) 12686 .bind('hide', function(e) { 12687 options.onHide(activeTab, hideTab); 12688 }); 12689 }, 12690 12691 initTabSwitchers: function(switchers) { 12692 var self = this; 12693 return switchers.each(function() { 12694 self.initTabSwitcher($(this)); 12695 }); 12696 } 12697}; 12698/** 12699 * @fileOverview List of keycodes 12700 */ 12701var KEY = { 12702 Esc: 27, 12703 Delete: 46, 12704 Space: 32, 12705 BackSpace: 8, 12706 12707 Left: 37, 12708 Up: 38, 12709 Right: 39, 12710 Down: 40, 12711
12712 // keypad buttons 12713 Plus: 107, 12714 Minus: 109, 12715 // plus + equal button 12716 PlusEq: 61, 12717 12718 A: 65, 12719 B: 66, 12720 C: 67, 12721 D: 68, 12722 E: 69, 12723 F: 70, 12724 G: 71, 12725 H: 72, 12726 I: 73, 12727 J: 74, 12728 K: 75, 12729 L: 76, 12730 M: 77, 12731 N: 78, 12732 O: 79, 12733 P: 80, 12734 Q: 81, 12735 R: 82, 12736 S: 83, 12737 T: 84, 12738 U: 85, 12739 V: 86, 12740 W: 87, 12741 X: 88, 12742 Y: 89, 12743 Z: 90 12744}; 12745 12746//same but in backward compability 12747var _KEY = {"65":"A","66":"B","67":"C","68":"D","69":"E","70":"F","71":"G","72":"H","73":"I","74":"J","75":"K","76":"L","77":"M","78":"N","79":"O","80":"P","81":"Q","82":"R","83":"S","84":"T","85":"U","86":"V", "87": "W", "88":"X","89":"Y","90":"Z"}; 12748/*globals myDeco, $, Class, Matrix, T */ 12749/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 12750 sloppy: true, white: true */ 12751/** 12752 * @fileOverview Abstract point class for XYZ transformations 12753 */ 12754var Point = Class.extend({ 12755 /** 12756 * Init function 12757 * @param {Number|Array} x X coordinate or Point instance 12758 * @param {Number} y Y coordinate 12759 * @param {Number} z Z coordinate 12760 */ 12761 init: function(x) { 12762 var coords = typeof x === 'object' ? x : arguments; 12763 12764 this.cache = {}; 12765 this.x = coords[0] || 0; 12766 this.y = coords[1] || 0; 12767 this.z = coords[2] || 0; 12768 }, 12769 12770 /** 12771 * Drop all or particular cached value 12772 * @param {String} id Cache id 12773 */ 12774 dropCache: function(id) { 12775 if (id) { 12776 delete this.cache[id]; 12777 } else { 12778 this.cache = {}; 12779 } 12780 return this; 12781 }, 12782 12783 /** 12784 * Get point from the cache 12785 * @param {String} id Cache id 12786 * @returns {Point} Point from cache. 12787 */ 12788 getPointFromCache: function(id) { 12789 if (typeof id !== 'undefined' && this.cache[id]) { 12790 return new Point(this.cache[id]); 12791 } else { 12792 return false; 12793 } 12794 }, 12795 12796 /** 12797 * Apply transformations from the matrix to this point 12798 * @param {Transform} t Transform to apply. 12799 * @param {String} cacheId Apply to point and store into cache with id = cacheId. 12800 */ 12801 applyTransformation: function(t, cacheId, dropCache) { 12802 if (typeof cacheId === 'undefined') { 12803 this.fromVector(t.matrix.multiply(this.toVector())); 12804 this.dropCache(); 12805 12806 return this; 12807 } else { 12808 if (!dropCache && this.cache[cacheId]) { 12809 return this.getPointFromCache(cacheId); 12810 } 12811 12812 var vector = t.matrix.multiply(this.toVector()); 12813 this.cache[cacheId] = [vector[0][0], vector[1][0], vector[2][0]]; 12814 12815 return this.getPointFromCache(cacheId); 12816 } 12817 }, 12818 12819 /** 12820 * Convert to 4x1 vector, usable for matrix transformations. 12821 * @returns {Matrix} 12822 */ 12823 toVector: function() { 12824 var vector = new Matrix(4, 1); 12825 vector[0][0] = this.x; 12826 vector[1][0] = this.y; 12827 vector[2][0] = this.z; 12828 vector[3][0] = 1; 12829 12830 return vector; 12831 }, 12832 12833 /** 12834 * Convert from 4x1 vector 12835 * @param {Matrix} vector Vector to convert from 12836 */ 12837 fromVector: function(vector) { 12838 this.x = vector[0][0]; 12839 this.y = vector[1][0]; 12840 this.z = vector[2][0]; 12841 12842 return this; 12843 }, 12844 12845 /** 12846 * Clone current point 12847 * @returns {Point} cloned point 12848 */ 12849 clone: function() { 12850 return new Point([this.x, this.y, this.z]); 12851 }, 12852 12853 /** 12854 * Stringify point 12855 * @returns {String} 12856 */ 12857 toString: function() { 12858 return ['Point(', this.x.toFixed(2), ', ', this.y.toFixed(2), ', ', this.z.toFixed(2), ')'].join(''); 12859 }, 12860 12861 /** 12862 * Return point as array of XYZ values 12863 * @param {Boolean} TwoD return only x & y coord 12864 * @returns {Array} 12865 */ 12866 toArray: function(TwoD) { 12867 if (TwoD) { 12868 return [this.x, this.y]; 12869 } else { 12870 return [this.x, this.y, this.z]; 12871 } 12872 }, 12873 12874 /** 12875 * Rotate point CW using basePoint as rotation center 12876 * xoz is for x0y plane 12877 * yoz is for y0z plane 12878 * xoz is for x0z plane 12879 * 12880 * @param {Point} basePoint Rotating center 12881 * @param {Number} xoy x0y rotating angle 12882 * @param {Number} yoz y0z rotating angle 12883 * @param {Number} xoz x0z rotating angle 12884 * @returns {Point} this 12885 */ 12886 rotate: function(basePoint, xoy, yoz, xoz) { 12887 basePoint = basePoint.clone(); 12888 // translate point to 0,0,0 12889 this.translate(-basePoint.x, -basePoint.y, -basePoint.z); 12890 12891 if (typeof yoz !== 'undefined' && yoz !== 0) { 12892 this.rotateAroundAxis('x', yoz); 12893 } 12894 12895 if (typeof xoz !== 'undefined' && xoz !== 0) {
12896 this.rotateAroundAxis('y', xoz); 12897 } 12898 12899 if (typeof xoy !== 'undefined' && xoy !== 0) { 12900 this.rotateAroundAxis('z', xoy); 12901 } 12902 12903 // translate point back to source coordinates 12904 this.translate(basePoint); 12905 return this; 12906 }, 12907 12908 12909 /** 12910 * Rotating point around given axis (using 0,0,0 as rotation center) 12911 * @param {String} axis Axis to rotate around (x, y, z); 12912 * @param {Number} angle Angle in degrees to rotate on. 12913 * @returns {Point} this 12914 */ 12915 rotateAroundAxis: function(axis, angle) { 12916 this.dropCache(); 12917 var _cos = T.cos(angle), 12918 _sin = T.sin(angle), 12919 temp, 12920 _t = { 12921 'z': {c1: 'x', c2: 'y'}, 12922 'x': {c1: 'y', c2: 'z'}, 12923 'y': {c1: 'x', c2: 'z'} 12924 }[axis]; 12925 12926 temp = this[_t.c1] * _cos - this[_t.c2] * _sin; 12927 this[_t.c2] = this[_t.c1] * _sin + this[_t.c2] * _cos; 12928 this[_t.c1] = temp; 12929 12930 return this; 12931 }, 12932 12933 /** 12934 * Scale point using basePoint as scale center 12935 * @param {Point} basePoint Scaling center 12936 * @param {Number} scaleX X Scaling factor 12937 * @param {Number} scaleY Y Scaling factor 12938 * @param {Number} scaleZ Z Scaling factor 12939 * @returns {Point} this 12940 */ 12941 scale: function(basePoint, scaleX, scaleY, scaleZ) { 12942 basePoint = basePoint.clone(); 12943 this.translate(-basePoint.x, -basePoint.y, -basePoint.z); 12944 this.scaleC(scaleX, scaleY, scaleZ); 12945 this.translate(basePoint); 12946 return this; 12947 }, 12948 12949 /** 12950 * Scale point using (0, 0, 0) as scale center 12951 * @param {Number} scaleX X Scaling factor 12952 * @param {Number} scaleY Y Scaling factor 12953 * @param {Number} scaleZ Z Scaling factor 12954 * @returns {Point} this 12955 */ 12956 scaleC: function(scaleX, scaleY, scaleZ) { 12957 this.dropCache(); 12958 12959 this.x *= scaleX; 12960 this.y *= (typeof scaleY === 'undefined' ? scaleX : scaleY); 12961 this.z *= (typeof scaleZ === 'undefined' ? scaleX : scaleZ); 12962 12963 return this; 12964 }, 12965 12966 /** 12967 * Translate point using delta coords from another point or 3 delta numbers 12968 * @param {Number|Point} delta X delta or delta object 12969 * @param {Number} y Y delta 12970 * @param {Number} z Z delta 12971 * @returns {Point} this 12972 */ 12973 translate: function(delta, y, z) { 12974 this.dropCache(); 12975 12976 if (typeof delta === 'number' || typeof delta === 'undefined') { 12977 this.x += delta || 0; 12978 this.y += y || 0; 12979 this.z += z || 0; 12980 } else { 12981 this.x += delta.x; 12982 this.y += delta.y; 12983 this.z += delta.z; 12984 } 12985 12986 return this; 12987 }, 12988 12989 /** 12990 * Get isometric projection of point (http://www.gamedev.net/reference/articles/article1269.asp) 12991 * @param {Number} angle Angle of projection 12992 * @returns {Point} this. 12993 */ 12994 isometric: function(isoAngle, rotAngle) { 12995 this.dropCache(); 12996 12997 this.scaleC(1, -1, T.cos(isoAngle)).rotateAroundAxis('z', rotAngle - 45).rotateAroundAxis('x', 90 - isoAngle); 12998 12999 return this; 13000 }, 13001 13002 /** 13003 * Calculate distance between point to a point or line (2D, not absolute value) 13004 * @param {Point|Array} point Point or array (for line) distance calculate to 13005 * @param {String} coord Coordinate for measuring distance, will calculate distance 13006 * in 3D if omitted. Can by any combination of x, y or z. 13007 * @returns {Number} distance 13008 */ 13009 distance: function(point, coord) { 13010 var result, dx, dy; 13011 if (point instanceof Point) { 13012 coord = coord || 'xyz'; 13013 result = Math.sqrt( 13014 (this.x - point.x) * (this.x - point.x) * +(coord.indexOf('x') !== -1) + 13015 (this.y - point.y) * (this.y - point.y) * +(coord.indexOf('y') !== -1) + 13016 (this.z - point.z) * (this.z - point.z) * +(coord.indexOf('z') !== -1) 13017 ); 13018 } else { 13019 dx = point[1].x - point[0].x; 13020 dy = point[0].y - point[1].y; 13021 result = (dy * this.x + dx * this.y + point[0].x * point[1].y - point[1].x * point[0].y) / Math.sqrt(dx * dx + dy * dy); 13022 } 13023 return result; 13024 }, 13025 13026 /** 13027 * Distance to segment in a x-y plane 13028 * @param {Array} p Array of points for segment 13029 * @returns {Number} distance 13030 */ 13031 distance2segment: function(p) { 13032 var t, 13033 dx = p[1].x - p[0].x, 13034 dy = p[1].y - p[0].y, 13035 dpx1 = this.x - p[0].x, 13036 dpx2 = this.x - p[1].x, 13037 dpy1 = this.y - p[0].y, 13038 dpy2 = this.y - p[1].y; 13039 13040 // if point isn't between given calculate minimal distance to the points 13041 if ( (dpx1 * dx + dpy1 * dy) * (dpx2 * dx + dpy2 * dy) > 0) { 13042 t = Math.min(Math.pow(dpx1, 2) + Math.pow(dpy1, 2), Math.pow(dpx2, 2) + Math.pow(dpy2, 2)); 13043 // else calculate distance to the line 13044 } else { 13045 t = Math.pow(dpx1 * dy - dpy1 * dx, 2) / (Math.pow(dx, 2) + Math.pow(dy, 2)); 13046 } 13047 return Math.sqrt(t); 13048 }, 13049 13050 /** 13051 * Find projection of a point to a line 13052 * @param {Array} p Array of points representing line 13053 * @returns {Point} 13054 */ 13055 projection2line: function (p) {
13056 var angle = p[0].angleTo(p[1]); 13057 return new Point(this.clone().rotate(p[0], -angle).x, 13058 p[0].y).rotate(p[0], angle); 13059 }, 13060 13061 /** 13062 * Check if the point (with applied transform) is in the poly (with applied transform) 13063 * @param {Array of Point} poly Polygon to check. 13064 * @param {Transform} transform Transform to apply to both (optional). 13065 * @param {String} tName Transform name (optional). 13066 * @returns {Boolean} presence 13067 */ 13068 inPoly: function(poly, transform, tName) { 13069 var p = this, polygon = poly, i, j, c = false, len = polygon.length; 13070 13071 if (typeof transform !== 'undefined') { 13072 polygon = []; 13073 13074 for (i = 0; i < len; i++) { 13075 polygon.push(poly[i].applyTransformation(transform, tName)); 13076 } 13077 } 13078 13079 for (i = 0, j = len - 1; i < len; j = i++) { 13080 if (((polygon[i].y > p.y) !== (polygon[j].y > p.y)) && 13081 (p.x < (polygon[j].x - polygon[i].x) * (p.y - polygon[i].y) / 13082 (polygon[j].y - polygon[i].y) + polygon[i].x)) { 13083 c = !c; 13084 } 13085 } 13086 return c; 13087 }, 13088 13089 /** 13090 * Calculate and return normalized angle to given point 13091 * @param {Point} point 13092 * @param {Boolean} rough Return rough value without normalizing 13093 * to 0...360 scope 13094 */ 13095 angleTo: function(point, rough) { 13096 var angle = T.atan2(this.y - point.y, this.x - point.x); 13097 return rough ? angle : (angle + 360) % 360; 13098 }, 13099 13100 /** 13101 * Replace coordinates with coordinates of given point 13102 * @param {Point} point 13103 */ 13104 copy: function(point) { 13105 this.x = point.x; 13106 this.y = point.y; 13107 this.z = point.z; 13108 }, 13109 13110 /** 13111 * Update point coordinates from given 13112 * @param {Number} x 13113 * @param {Number} y 13114 * @param {Number} z 13115 * @returns {Point} 13116 */ 13117 update: function(x, y, z) { 13118 this.x = x === undefined ? this.x : x; 13119 this.y = y === undefined ? this.y : y; 13120 this.z = z === undefined ? this.z : z; 13121 return this; 13122 } 13123}); 13124/*globals myDeco, $, Point */ 13125/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 13126 sloppy: true, white: true */ 13127/** 13128 * @fileOverview Common drawing objects library 13129 */ 13130var ObjectsLibrary = { 13131 13132 /** 13133 * Drawing canvas 13134 * @type {Object} 13135 */ 13136 _canvas: undefined, 13137 13138 /** 13139 * Default object settings 13140 * @type {Object} 13141 */ 13142 _settings: { 13143 closeButton: { 13144 size: 20, 13145 img: '/delete-button.png' 13146 }, 13147 infoButton: { 13148 size: 20, 13149 img: '/default.png' 13150 }, 13151 duplicateButton: { 13152 size: 20, 13153 img: '/duplicate-button.png' 13154 }, 13155 directionButton: { 13156 size: 20, 13157 img: '/direction-button.png' 13158 }, 13159 hingeButton: { 13160 size: 20, 13161 img: '/hinge-button.png' 13162 }, 13163 splitButton: { 13164 size: 20, 13165 img: '/split-button.png' 13166 }, 13167 drawButton: { 13168 size: 20, 13169 img: '/draw-button.png' 13170 }, 13171 objectRotate: { 13172 distance: 65 13173 }, 13174 camera: { 13175 fov: 180 / 3.0, 13176 scaleFactor: 0.6, 13177 distance: 350 13178 }, 13179 dragNotice: { 13180 'font-family': 'Georgia, Times New Roman', 13181 'fill': '#ffffff' 13182 } 13183 }, 13184 13185 /** 13186 * Already initiated objects 13187 * @type {Object} 13188 */ 13189 _objects: {}, 13190 13191 /** 13192 * Init function 13193 * @param {Object} canvas Drawing canvas 13194 * @param {Object} settings Objects settings 13195 * @returns {Object} this 13196 */ 13197 init: function(canvas, settings) { 13198 this._settings.camera.fov = myDeco.App.settings.model.cameraFov; 13199 this._canvas = canvas; 13200 this._settings = $.extend(true, this._settings, settings || {}); 13201 return this; 13202 }, 13203 13204 /** 13205 * Get object by name 13206 * @param {String} name Object name 13207 * @param {Boolean} clone Will object copy insetead of referencing 13208 * @returns {Object} 13209 */ 13210 get: function(name, clone) { 13211 clone = clone || false; 13212 name = '__' + name; 13213 13214 if (typeof this._objects[name] !== 'undefined' && !clone) { 13215 return this._objects[name]; 13216 } 13217 13218 var object = this[name](); 13219 13220 if (typeof this._objects[name] === 'undefined') { 13221 this._objects[name] = object; 13222 } 13223 13224 return object; 13225 }, 13226 13227 13228 // objects drawing functions 13229 13230 _renderButton: function(settings) { 13231 // added 1 as the part of the image url as easiest way to cache them 13232 // on the CDN 13233 return this._canvas.image(myDeco.settings.STATIC_URL + '..' + 13234 settings.img, 0, 0, settings.size, settings.size).hide(); 13235 }, 13236 13237 __infoButton: function() { 13238 return this._renderButton(this._settings.infoButton); 13239 }, 13240 13241 __closeButton: function() { 13242 return this._renderButton(this._settings.closeButton); 13243 }, 13244 13245 __duplicateButton: function() { 13246 return this._renderButton(this._settings.duplicateButton); 13247 }, 13248 13249 __hingeButton: function() { 13250 return this._renderButton(this._settings.hingeButton); 13251 }, 13252 13253 __directionButton: function() { 13254 return this._renderButton(this._settings.directionButton); 13255 }, 13256 13257 __splitButton: function() { 13258 return this._renderButton(this._settings.splitButton); 13259 }, 13260 13261 __drawButton: function() { 13262 return this._renderButton(this._settings.drawButton); 13263 }, 13264 13265 __objectRotate: function() { 13266 var settings = this._settings.objectRotate, 13267 circleRadius = 30, 13268 arrowsRadius = 90, 13269 arrowStartX = 35, 13270 arrowStartY = 6, 13271 arrowEndX = 78, 13272 arrowEndY = 38, 13273 triangle = 45, 13274 tsin = Math.sin(Math.PI/3), 13275 tcos = Math.cos(Math.PI/3), 13276 triangleDY = triangle * tsin, 13277 baseDistance = settings.distance, 13278 baseArrow = 18, 13279 dist1 = 12, 13280 dist2 = 30, 13281 dist3 = 60, 13282 dist4 = 40; 13283 13284 return this._canvas.set().push( 13285 // 0 13286 this._canvas.path({'stroke': 'white', 'stroke-width': 4}).hold() 13287 .moveTo(-arrowStartX, -arrowStartY) 13288 .arcTo(arrowsRadius, arrowsRadius, 0, 1, -arrowEndX, -arrowEndY).render().hide(), 13289 // 1 13290 this._canvas.path({'stroke': '#6b6b6b', 'stroke-width': 2}).hold() 13291 .moveTo(-arrowStartX, -arrowStartY) 13292 .arcTo(arrowsRadius, arrowsRadius, 0, 1, -arrowEndX, -arrowEndY).render().hide(), 13293 // 2 13294 this._canvas.circle(0, 0, circleRadius).attr({'stroke': '#ffffff', 'stroke-width': 4}).hide(), 13295 // 3 13296 this._canvas.circle(0, 0, circleRadius).attr({'stroke': '#6b6b6b', 'fill': '#dfe1e1', 'stroke-width': 2}).hide(), 13297 // 4 13298 this._canvas.path({'stroke': 'white', 'stroke-width': 4}).hold() 13299 .moveTo(arrowStartX, -arrowStartY) 13300 .arcTo(arrowsRadius, arrowsRadius, 0, 0, arrowEndX, -arrowEndY).render().hide(), 13301 // 5 13302 this._canvas.path({'stroke': '#6b6b6b', 'stroke-width': 2}).hold() 13303 .moveTo(arrowStartX, -arrowStartY) 13304 .arcTo(arrowsRadius, arrowsRadius, 0, 0, arrowEndX, -arrowEndY).render().hide(), 13305 // 6 13306 this._canvas.path({'stroke': 'white', 'stroke-width': 1, 'fill': '#6b6b6b'}).hold() 13307 .moveTo(arrowEndX - triangle / 2 * tsin, -arrowEndY - triangle / 2 * tcos) 13308 .lineTo(arrowEndX + triangle / 2 * tsin, -arrowEndY + triangle / 2 * tcos) 13309 .lineTo(arrowEndX + triangleDY * tcos, -arrowEndY - triangleDY * tsin) 13310 .andClose() 13311 .render().hide(), 13312 // 7 13313 this._canvas.path({'stroke': 'white', 'stroke-width': 1, 'fill': '#6b6b6b'}).hold() 13314 .moveTo(-arrowEndX + triangle / 2 * tsin, -arrowEndY - triangle / 2 * tcos) 13315 .lineTo(-arrowEndX - triangle / 2 * tsin, -arrowEndY + triangle / 2 * tcos) 13316 .lineTo(-arrowEndX - triangleDY * tcos, -arrowEndY - triangleDY * tsin) 13317 .andClose() 13318 .render().hide(), 13319 // 8 13320 this._canvas.path({'stroke': 'white', 'stroke-width': 4}).hold() 13321 .moveTo(- baseArrow, - baseDistance - baseArrow) 13322 .lineTo(0, - baseDistance) 13323 .lineTo(baseArrow, - baseDistance - baseArrow) 13324 .render().hide(), 13325 // 9 13326 this._canvas.path({'stroke': 'white', 'stroke-width': 4}).hold() 13327 .moveTo(0, -baseDistance - dist1) 13328 .lineTo(0, -baseDistance - dist1 - dist2) 13329 .render().hide(), 13330 // 10 13331 this._canvas.path({'stroke': 'white', 'stroke-width': 4}).hold() 13332 .moveTo(-dist4 / 2, -baseDistance - dist3) 13333 .lineTo( dist4 / 2, -baseDistance - dist3) 13334 .render().hide(), 13335 // 11 13336 this._canvas.path({'stroke': '#6b6b6b', 'stroke-width': 2}).hold() 13337 .moveTo(- baseArrow, - baseDistance - baseArrow) 13338 .lineTo(0, - baseDistance) 13339 .lineTo(baseArrow, - baseDistance - baseArrow) 13340 .render().hide(), 13341 // 12 13342 this._canvas.path({'stroke': '#6b6b6b', 'stroke-width': 2}).hold() 13343 .moveTo(0, -baseDistance - dist1) 13344 .lineTo(0, -baseDistance - dist1 - dist2) 13345 .render().hide(), 13346 // 13 13347 this._canvas.path({'stroke': '#6b6b6b', 'stroke-width': 2}).hold() 13348 .moveTo(-dist4 / 2, -baseDistance - dist3) 13349 .lineTo( dist4 / 2, -baseDistance - dist3) 13350 .render().hide() 13351 ); 13352 }, 13353 13354 camera_cone: function(vfov) { 13355 var baseP = new Point(), 13356 distance = this._settings.camera.distance, 13357 aspect = 16/9, 13358 hfov = 2 * Math.atan(aspect * Math.tan(vfov * T.d2r/2)) * T.r2d, 13359 fov1 = new Point(0, distance, 0).rotate(baseP, - hfov / 2), 13360 fov2 = fov1.clone().rotate(baseP , hfov); 13361 13362 return this._canvas.path({stroke: 'white', fill: 'white', opacity: 0.5}) 13363 .hold() 13364 .lineTo(fov1.x, fov1.y) 13365 .lineTo(fov2.x, fov2.y) 13366 .andClose().render(); 13367 }, 13368 13369 __camera: function() { 13370 // Camera dimensions (base, lens, lens hood, shutter button) 13371 var settings = this._settings.camera, 13372 factor = settings.scaleFactor, 13373 baseW = 175 * factor, 13374 baseH = 60 * factor, 13375 lensW = 57 * factor, 13376 lensH = 53 * factor, 13377 hoodW = 95 * factor, 13378 hoodH = 82 * factor, 13379 shutterD = 35 * factor, 13380 shutterO = 30 * factor, 13381 speckO = 3, 13382 13383 hover = this.camera_cone(settings.fov), 13384 pseudo_base = this._canvas.rect(-25 * factor, -25 * factor, 50 * factor, 50 * factor)
13385 .attr({stroke: 'none', fill: 'none'}), 13386 base = this._canvas.set().push( 13387 // white outline for lens and lenshood 13388 this._canvas.path({stroke: 'white', 'stroke-width': 4}).hold() 13389 .moveTo(0, 0) 13390 .lineTo(-lensW / 2.0, 0) 13391 .lineTo(-lensW / 2.0, baseH / 2.0 + lensH) 13392 .lineTo(-hoodW / 2.0, baseH / 2.0 + lensH) 13393 13394 .lineTo(-hoodW / 2.0, baseH / 2.0 + lensH + hoodH) 13395 .lineTo( hoodW / 2.0, baseH / 2.0 + lensH + hoodH) 13396 13397 .lineTo( hoodW / 2.0, baseH / 2.0 + lensH) 13398 .lineTo( lensW / 2.0, baseH / 2.0 + lensH) 13399 .lineTo( lensW / 2.0, 0) 13400 .andClose() 13401 .render(), 13402 13403 // Camera lens + hood 13404 this._canvas.path({stroke: '#5bb9e8', 'fill': '#5bb9e8'}).hold() 13405 .moveTo(0, 0) 13406 .lineTo(-lensW / 2.0, 0) 13407 .lineTo(-lensW / 2.0, baseH / 2.0 + lensH) 13408 .lineTo(-hoodW / 2.0, baseH / 2.0 + lensH) 13409 13410 .lineTo(-hoodW / 2.0, baseH / 2.0 + lensH + hoodH) 13411 .lineTo( hoodW / 2.0, baseH / 2.0 + lensH + hoodH) 13412 13413 .lineTo( hoodW / 2.0, baseH / 2.0 + lensH) 13414 .lineTo( lensW / 2.0, baseH / 2.0 + lensH) 13415 .lineTo( lensW / 2.0, 0) 13416 .andClose() 13417 .render(), 13418 13419 // Speck on lens & hood 13420 this._canvas.path({'stroke': 'none', 'fill': '#b8e1ec'}).hold() 13421 .moveTo(-speckO, 0) 13422 .lineTo(-lensW / 2.0, 0) 13423 .lineTo(-lensW / 2.0, baseH / 2.0 + lensH) 13424 .lineTo(-hoodW / 2.0, baseH / 2.0 + lensH) 13425 .lineTo(-hoodW / 2.0, baseH / 2.0 + lensH + hoodH) 13426 .lineTo(-speckO, baseH / 2.0 + lensH + hoodH) 13427 .andClose() 13428 .render(), 13429 13430 // Black outline around lens & hood 13431 this._canvas.path({stroke: '#010101', 'fill': 'none'}).hold() 13432 .moveTo(0, 0) 13433 .lineTo(-lensW / 2.0, 0) 13434 .lineTo(-lensW / 2.0, baseH / 2.0 + lensH) 13435 .lineTo(-hoodW / 2.0, baseH / 2.0 + lensH) 13436 13437 .lineTo(-hoodW / 2.0, baseH / 2.0 + lensH + hoodH) 13438 .lineTo( hoodW / 2.0, baseH / 2.0 + lensH + hoodH) 13439 13440 .lineTo( hoodW / 2.0, baseH / 2.0 + lensH) 13441 .lineTo( lensW / 2.0, baseH / 2.0 + lensH) 13442 .lineTo( lensW / 2.0, 0) 13443 .andClose() 13444 .render(), 13445 13446 // white stroke around body 13447 this._canvas.rect(-baseW / 2.0, -baseH / 2.0, baseW, baseH) 13448 .attr({stroke:"#FFFFFF", 'stroke-width': "3", fill: 'none'}), 13449 13450 // Body of the camera 13451 this._canvas.rect(-baseW / 2.0, -baseH / 2.0, baseW, baseH) 13452 .attr({fill: "#9bddf2", stroke:"#0A0B0A", 'stroke-width': "1"}), 13453 13454 // shutter button 13455 this._canvas.circle(-baseW / 2.0 + shutterO, 0, shutterD / 2.0).attr({fill: "#ffffff", 'stroke': "none"}) 13456 ); 13457 return this._canvas.set().push(hover, pseudo_base, base.translate(0,-30)); 13458 }, 13459 13460 __dragNotice: function() { 13461 return this._canvas.text(0, 0, gettext("Drag\nand drop\nfurniture\nhere")) 13462 .attr(this._settings.dragNotice) 13463 .attr('font-size', Math.round(18 / (this._canvas.raphael.type === 'SVG' ? this._canvas.zoom : 1)) + 'px') 13464 .hide(); 13465 } 13466}; 13467/*globals myDeco, $ */ 13468/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 13469 sloppy: true, white: true */ 13470var Stack = (function() { 13471 /** 13472 * Subclassed JavaScript 1.5 Array for every browser. 13473 * 13474 * @author Andrea Giammarchi 13475 * @site devpro.it 13476 * @blog webreflection.blogspot.com 13477 * @date 2009/05/07 13478 * @requires IE 5.5+ , FF 1+ , Opera 8+ , Safari 2+ 13479 * @license Mit Style 13480 */ 13481 function Stack() { 13482 this.push.apply(this, Array.apply(null, arguments)); 13483 } 13484 13485 Stack.prototype = []; 13486 Stack.prototype.length = 0; 13487 13488 var split, length, 13489 toString = Object.prototype.toString, 13490 slice = Array.prototype.slice, 13491 concat = Array.prototype.concat; 13492 13493 13494 if (!new Stack(1).length) { 13495 Stack.prototype = { 13496 length : 0 13497 }; 13498 for (split = ['join', 'pop', 'push', 'reverse', 'shift', 'slice', 13499 'sort', 'splice', 'unshift'], length = split.length; length !== 0; length--) { 13500 Stack.prototype[split[length - 1]] = Array.prototype[split[length - 1]]; 13501 } 13502 } 13503 13504 Stack.prototype.concat = function() { 13505 var args = Array.prototype.slice(arguments, 0), 13506 array = this.slice(0), 13507 length = arguments.length, i; 13508 for (i = 0; i < length; ++i) { 13509 if (toString.call(args[i]) !== "[object Array]") { 13510 args[i] = typeof args[i] === "object" ? slice.call(args[i]) : [ args[i] ]; 13511 } 13512 } 13513 array.push.apply(array, concat.apply([], args)); 13514 return array; 13515 }; 13516 13517 Stack.prototype.toString = Stack.prototype.join; 13518 Stack.prototype.constructor = Stack; 13519 return Stack; 13520 13521}()); 13522/*globals myDeco, $, Stack */ 13523/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 13524 sloppy: true, white: true */ 13525/** 13526 * Cloud type 13527 * 13528 * @param {FpeView} view 13529 * @return {Cloud} 13530 */ 13531var Cloud = function(view) { 13532 this._view = view; 13533}; 13534 13535Cloud.prototype = new Stack(); 13536 13537/** 13538 * Empty callback used when no callback passed to append method 13539 * @param {Object} value 13540 * @returns {Object} 13541 */ 13542Cloud.prototype.emptyCallback = function(value) { 13543 return value; 13544}; 13545 13546/** 13547 * Append a new instance to a cloud 13548 * @param {Array} source Array of source item arguments 13549 * @param {String} ClassName Item constructor class name 13550 * @param {Function} callback Callback for post-processing of item 13551 * @param {String} filterBy name of the field to avoid adding duplicates 13552 * 13553 * @return {Cloud} 13554 */ 13555Cloud.prototype.append = function(source, ClassName, callback, filterBy) { 13556 var i, ilen; 13557 13558 callback = callback || this.emptyCallback; 13559 13560 for (i = 0, ilen = source.length; i < ilen; i++) { 13561 if (!filterBy || this.findByField(filterBy, [source[i]]).length === 0) { 13562 Array.prototype.push.apply(this, 13563 [callback(new ClassName(this._view, source[i]))]); 13564 } 13565 } 13566 13567 return this; 13568}; 13569 13570/** 13571 * Grep cloud using custom function 13572 * @param {Function} func 13573 * 13574 * @return {Cloud} 13575 */ 13576Cloud.prototype.grep = function(func) { 13577 var result = new Cloud(this._view); 13578 13579 Array.prototype.push.apply(result, $.grep(this, func)); 13580 return result; 13581}; 13582 13583/** 13584 * Find cloud items by particular value of the field 13585 * @param {String} field Name of the field 13586 * @param {Array} values List of values to compare with 13587 * 13588 * @return {Cloud} 13589*/ 13590 13591Cloud.prototype.findByField = function(field, values) { 13592 return this.grep(function(element) { 13593 return values.indexOf(element[field]) !== -1; 13594 }); 13595}; 13596 13597/** 13598 * Remove item from cloud 13599 * @param {Object} item Cloud item 13600 * 13601 * @return {Array} List of removed items 13602 */ 13603Cloud.prototype.remove = function(item) { 13604 var idx = Array.prototype.indexOf.call(this, item); 13605 return (idx !== -1) ? 13606 Array.prototype.splice.call(this, idx, 1) : 13607 []; 13608}; 13609 13610/** 13611 * Batch processing on cloud. Pass parameters to method as a next to method n
13611ame arguments. 13612 * 13613 * @param {Any} fname Function reference or cloud item method name 13614 * 13615 * @return {Cloud} 13616 */ 13617Cloud.prototype.batch = function(fname) { 13618 var params = Array.prototype.splice.call(arguments, 1, arguments.length - 1), 13619 i; 13620 13621 for (i = this.length - 1; i > -1; i--) { 13622 // execute callback 13623 if ($.isFunction(fname)) { 13624 // insert item as a first element 13625 fname.apply(this[i], $.merge([this[i]], params)); 13626 } else if ($.isFunction(this[i][fname])) { 13627 this[i][fname].apply(this[i], params); 13628 } 13629 } 13630 13631 return this; 13632}; 13633/*globals myDeco, $, jQuery, window */ 13634/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 13635 sloppy: true, white: true */ 13636/** 13637 * @fileOverview Misc functions 13638 */ 13639 13640/** 13641 * Clone object properties except functions 13642 * @param {Object} obj Object to clone 13643 * @returns {Object} cloned object 13644 */ 13645jQuery.cloneObject = function(obj) { 13646 var destination = {}, property, val; 13647 for (property in obj) { 13648 val = obj[property]; 13649 if (!$.isFunction(val)) { 13650 destination[property] = $.isArray(val) ? 13651 val.slice(0, val.length) : val; 13652 } 13653 } 13654 13655 return destination; 13656}; 13657 13658/** 13659 * Build thumbnail url based on image and settings 13660 * @param {String} image Source image 13661 * @param {Object} settings Converting settings 13662 * @returns {String} thumbnail url 13663 */ 13664function buildThumbnailLink(image, settings) { 13665 if (!settings || !image) { 13666 return image; 13667 } 13668 13669 var postfix = '_', 13670 decodingTable = { 13671 'width': 'w', 13672 'height': 'h', 13673 'percent': 'c', 13674 'border': 'b', 13675 'shadow': 's', 13676 'offset': 'o', 13677 'mirror': 'm', 13678 'rotate': 'r', 13679 'shear': 'e', 13680 'y_wrapped_shear': 'f', 13681 'filled_rotation': 'g', 13682 'y_scale': 'y' 13683 }, 13684 name; 13685 13686 // remove unnesessary options 13687 if (settings.filled_rotation === 0) { 13688 delete settings.filled_rotation; 13689 } 13690 13691 for (name in decodingTable) { 13692 if (typeof settings[name] !== 'undefined') { 13693 if (name == 'width' || name == 'height') { 13694 postfix += decodingTable[name] + "270"; 13695 } else { 13696 postfix += decodingTable[name] + settings[name]; 13697 } 13698 } 13699 } 13700 13701 return image.replace(/cdb\/(renders|textures)/, 'cdb/scaled/$1') + 13702 postfix + '.png'; 13703} 13704 13705/** 13706 * Convert color hex representation to a array of rgb values 13707 * @param {String} hex Hex string like '#ff0044' 13708 * @return {Array} 13709 */ 13710function hex2RGB(hex) { 13711 var rgb = [], offset = hex[0] === '#' ? 1 : 0; 13712 while (offset < hex.length) { 13713 rgb.push(parseInt(hex.substring(offset, offset += 2), 16)); 13714 } 13715 return rgb; 13716} 13717 13718/** 13719 * Convert color hex representation to a array of intensivity values 13720 * @param {String} hex Hex string like '#ff0044' 13721 * @return {Array} 13722 */ 13723function hex2Diffuse(hex) { 13724 if (!hex) { 13725 return false; 13726 } 13727 return $.map(hex2RGB(hex), function(val) { return val / 255 }); 13728} 13729 13730/** 13731 * Convert decimal color representation to diffuse values 13732 * @param {Number} dec 13733 * @return {Array} 13734 */ 13735function dec2Diffuse(dec) { 13736 return hex2Diffuse(parseInt(dec, 10).toString(16)); 13737} 13738 13739/** 13740 * Convert array of intensivity values (0...1) to an array of rgb-values (0...255,0...255,0...255) 13741 * @param {Array} arr 13742 * @return {Array} 13743 */ 13744function diffuse2RGBa(arr) { 13745 return $.map(arr, function(item) { 13746 return Math.floor(item * 255); 13747 }); 13748} 13749 13750/** 13751 * Convert array of intensivity values (0...1) to a string of rgb-values (0...255,0...255,0...255) 13752 * @param {Array} arr 13753 * @return {String} 13754 */ 13755function diffuse2RGB(arr) { 13756 return diffuse2RGBa(arr).join(','); 13757} 13758 13759/** 13760 * Convert array of intensivity values (0...1) to a string of hex color (00...FF,00...FF,00...FF) 13761 * @param {Array} arr 13762 * @return {String} 13763 */ 13764function diffuse2Hex(arr) { 13765 return $.map(arr, function(item) { 13766 var v = Math.floor(item * 255).toString(16); 13767 if (v.length < 2) { 13768 v = '0' + v; 13769 } 13770 return v; 13771 }).join(''); 13772} 13773 13774/** 13775 * Convert cm to feet and inches 13776 * @param {Boolean} flat Return flat representation in inches without units 13777 * @returns {String} 13778 */ 13779Number.prototype.toFeet = function(flat) { 13780 var inches = Math.round(this / 2.54), 13781 feet = Math.floor(inches / 12); 13782 inches -= feet * 12; 13783 return flat ? feet * 12 + inches + '"': feet + "'" + inches + '"'; 13784}; 13785 13786/** 13787 * Converts cm² to square feet 13788 * @returns string 13789 */ 13790Number.prototype.toSquareFeet = function() { 13791 var inches = Math.round(this / 6.4516), 13792 feet = Math.floor(inches / 144); 13793 inches -= feet * 144; 13794 return feet + "." + inches + ' sq.ft.'; 13795}; 13796 13797/** 13798 * Converts feet string representation to cm 13799 * @returns {Number} 13800 */ 13801String.prototype.fromFeet = function() { 13802 var value = this 13803 .replace(/[^0-9'"]/g, '') 13804 .match(/(?:(\d+)')?(?:(\d+)")?/) || []; 13805 return (parseInt(value[1] || '0', 10) * 12 + 13806 parseInt(value[2] || '0', 10)) * 2.54; 13807}; 13808 13809/** 13810 * Convert cm to meter notation 13811 * @param {Boolean} flat Return flat representation in meters without units 13812 * @returns {String} 13813 */ 13814Number.prototype.toMeters = function(flat) { 13815 return (this / 100).toFixed(2) + (flat ? 0 : ' m'); 13816}; 13817 13818/** 13819 * Converts cm² to square feet 13820 * @returns {String} 13821 */ 13822Number.prototype.toSquareMeters = function() { 13823 return (this / 10000).toFixed(2) + ' m²'; 13824}; 13825 13826/** 13827 * Convert meters back to cm 13828 * @returns {String} 13829 */ 13830String.prototype.fromMeters = function() { 13831 return parseFloat(this.replace(/[^0-9.,]/g, '').replace(/,/, '.')) * 100; 13832}; 13833 13834/** 13835 * Convert value to a currently selected unit 13836 * @param {Boolean} flat Return flat representation without units 13837 * @returns {String} 13838 */ 13839Number.prototype.toViewUnits = function(flat) { 13840 return this['to' + myDeco.settings.UNITS_LIST[myDeco.settings.UNITS][1]] 13841 .call(this, flat); 13842}; 13843 13844/** 13845 * Parse string using currently selected unit 13846 * @returns {Number} 13847 */ 13848String.prototype.fromViewUnits = function() { 13849 return this['from' + myDeco.settings.UNITS_LIST[myDeco.settings.UNITS][1]] 13850 .call(this); 13851}; 13852 13853/** 13854 * Convert square cm to square units 13855 * @returns {String} 13856 */ 13857Number.prototype.toSquareUnits = function() { 13858 return this['toSquare' + myDeco.settings.UNITS_LIST[myDeco.settings.UNITS][1]] 13859 .call(this); 13860}; 13861 13862/** 13863 * Parse units string according selected units to a list of values in cm 13864 * @returns {Array} 13865 */ 13866String.prototype.fromUnits = function() { 13867 var func = 'from' + 13868 myDeco.settings.UNITS_LIST[myDeco.settings.UNITS][1]; 13869 return $.map(this.split(/\s*x\s*/), function(value) { 13870 return value[func].call(value); 13871 }); 13872 13873}; 13874 13875/** 13876 * Convert array of values in cm to a string in current units 13877 * @param {Array} prefixes Prefixes before each value in string 13878 * @param {Boolean} flat Return flat representation in inches without units 13879 * @returns {String} 13880 */ 13881Array.prototype.toUnits = function(prefixes, flat) { 13882 prefixes = prefixes || []; 13883 13884 return $.map(this, function(value, index) { 13885 return (typeof prefixes[index] !== 'undefined' ? prefixes[index] : '') + 13886 Number(value)['to' + myDeco.settings.UNITS_LIST[myDeco.settings.UNITS][1]] 13887 .call(value, flat); 13888 }).join(' x '); 13889 13890}; 13891 13892/**
13893 * Function returning string representation of component dimensions 13894 * @param {Object} component 13895 * @returns {String} 13896 */ 13897function toDimensionsString(component) { 13898 return [component.componentData.height, component.componentData.width, 13899 component.componentData.depth].toUnits(['H', 'W', 'D'], myDeco.settings.UNITS == 'ft'); 13900} 13901 13902/** 13903 * Remove arrays in the response from WS (inplace) 13904 * @param {Object} data 13905 */ 13906function unArray(data) { 13907 var name; 13908 for (name in data) { 13909 if (name !== 'bounding_box' && $.isArray(data[name])) { 13910 data[name] = data[name].shift(); 13911 } 13912 } 13913 return data; 13914} 13915 13916/** 13917 * Get thumbnail URL from a image url 13918 * @param {String} image Image URL 13919 * @param {Number} width Desired thumbnail width, default is 70 13920 * @param {Number} height Desired thumbnail height, default is 70 13921 * @param {Number} quality Desired quality of jpeg image, default is 85 13922 * @returns {String} 13923 */ 13924function appImage2Thumbnail(image, width, height, quality) { 13925 width = width || 70; 13926 height = height || 70; 13927 quality = quality || 85; 13928 13929 return myDeco.settings.MEDIA_URL + image; 13930 //var matches = image.match(/^.*?(scrapbook_images\/|rooms\/images\/)(.*)$/); 13931 //return matches ? 13932 // myDeco.settings.MEDIA_URL + matches[1] + '_thumbs/' + matches[2] + '_' + 13933 // width + 'x' + height + '_q' + quality + '.jpg' : 13934 // image; 13935} 13936 13937/** 13938 * Generate object for jQ css method from the material entity 13939 * @param material 13940 * @param {Boolean} plain Return plain string if true 13941 * @returns {Object} 13942 */ 13943function material2Style(material, plain) { 13944 var thumbnail = material.thumbnail || 'none', 13945 value; 13946 13947 if (thumbnail !== 'none') { 13948 if (!thumbnail.match(/^http/i)) { 13949 thumbnail = myDeco.settings.MATERIAL_MEDIA_URL + thumbnail; 13950 } 13951 thumbnail = 'url(' + thumbnail + ')'; 13952 } 13953 13954 value = { 13955 'background-color': material.diffuse ? 13956 'rgb(' + diffuse2RGB(material.diffuse) + ')' : 'transparent', 13957 'background-image': thumbnail 13958 }; 13959 13960 if (plain) { 13961 $.each(value, function(name, v) { 13962 value[name] = name + ': ' + v; 13963 }); 13964 value = $.values(value).join('; '); 13965 } 13966 13967 return value; 13968} 13969 13970/** 13971 * Returns X coordinate of lines projection to OX coordinate 13972 * @param {Wall} a 13973 * @param {Wall} b 13974 * @returns {Number} 13975 */ 13976function getIntersection(a, b) { 13977 var ax0 = Math.min(a[0].x, a[1].x), 13978 ax1 = Math.max(a[0].x, a[1].x), 13979 bx0 = Math.min(b[0].x, b[1].x), 13980 bx1 = Math.max(b[0].x, b[1].x); 13981 13982 if (ax1 > bx0 && ax0 < bx1) { 13983 return (Math.min(ax1, bx1) + Math.max(ax0, bx0)) / 2; 13984 } 13985} 13986 13987/** 13988 * Return Y coordinate for specified line and X coordinate 13989 * @param {Array} points Points for line 13990 * @param {Numher} 13991 */ 13992function getY(points, x) { 13993 return points[0].y + (x - points[0].x) * (points[1].y - points[0].y) / 13994 (points[1].x - points[0].x); 13995} 13996 13997/** 13998 * Construct texture fill depending on given options 13999 * @param {Object} material Used material 14000 * @param {Object} options 14001 * Options are: 14002 * scale: 0...100 14003 * vskew: decimal 14004 * hskew: decimal 14005 * rotate: 0...359 14006 * defaultColor: [0.5, 0.5, 0.5] 14007 * noTexture: fill only with diffuse 14008 * 14009 * @return {String|Boolean} url if possible or false 14010 */ 14011function textureFill(material, options) { 14012 options = options || {}; 14013 14014 return (!options.noTexture && material.diffuse_texture) ? 14015 'url(' + buildThumbnailLink(myDeco.settings.MATERIAL_MEDIA_URL + 14016 material.diffuse_texture, options) + ')' : 14017 '#' + diffuse2Hex(material.diffuse || options.defaultColor || 14018 [0.5, 0.5, 0.5]); 14019} 14020 14021/** 14022 * Extract root from absolute url 14023 * @param {String} url 14024 * @returns {String} 14025 */ 14026function extractRootUrl(url) { 14027 return url.substring(0, url.indexOf('/', 8) + 1); 14028} 14029 14030/** 14031 * Function for updating material info in html blocks 14032 * @param {Object} material 14033 * @param {jQuery} holder Holder where material data is 14034 * @returns {jQuery} 14035 */ 14036function updateMaterialInfo(material, holder) { 14037 var hasSupplier = !!material.supplier && material.supplier !== 'mydeco', 14038 supplier = holder.toggleClass('branded', !!material.branded).toggleClass('color', !!material.isColor) 14039 .find('.name').text(material.name || '').end() 14040 .find('.texture div').css(material2Style(material)).end() 14041 .find('.gts').attr('href', material.url || '').toggle(!!material.url).end() 14042 .find('.delete, .change').hide().filter(material.isColor ? '.change' : '.delete').show().end().end() 14043 .find('.supplier').toggle(hasSupplier); 14044 14045 if (hasSupplier) { 14046 supplier.find('a, span').text(material.supplier || '') 14047 .filter('a').toggle(!!material.supplier_url).attr('href', material.supplier_url || '').end() 14048 .filter('span').toggle(!material.supplier_url); 14049 } 14050 14051 return holder; 14052} 14053 14054/**
14055 * Function for check input values on input time 14056 * for numberInput plugin 14057 * @param {Event} e 14058 */ 14059function numberInputKeyPress(e) { 14060 var input = $(this), 14061 value = input.val(); 14062 14063 switch (e.which) { 14064 // special keys 14065 case 0: 14066 break; 14067 14068 // minus 14069 case 45: 14070 e.preventDefault(); 14071 break; 14072 14073 // dot 14074 case 46: 14075 if (myDeco.settings.UNITS !== 'm' || 14076 (e.which === 46 && value.indexOf('.') !== -1)) { 14077 e.preventDefault(); 14078 } 14079 break; 14080 14081 // ' and " 14082 case 39: 14083 case 34: 14084 if (myDeco.settings.UNITS !== 'ft' || ( 14085 (e.which === 39 && value.indexOf("'") !== -1) || 14086 (e.which === 34 && value.indexOf('"') !== -1))) { 14087 e.preventDefault(); 14088 } 14089 break; 14090 14091 // digits 14092 case 48: case 49: case 50: case 51: case 52: case 53: 14093 case 54: case 55: case 56: case 57: 14094 14095 // backspace 14096 case 8: 14097 break; 14098 14099 // deny any changes by default 14100 default: 14101 e.preventDefault(); 14102 } 14103} 14104 14105/** 14106 * from http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c 14107 * Converts an RGB color value to HSL. Conversion formula 14108 * adapted from http://en.wikipedia.org/wiki/HSL_color_space. 14109 * Assumes r, g, and b are contained in the set [0, 255] and 14110 * returns h, s, and l in the set [0, 1]. 14111 * 14112 * @param color hex representation of an RGB color (RRGGBB) 14113 * @return Array The HSL representation 14114 */ 14115function rgb2hsl(hex_color) { 14116 var diffuse = hex2Diffuse(hex_color); 14117 var r = diffuse[0], g = diffuse[1], b = diffuse[2]; 14118 var max = Math.max(r, g, b), min = Math.min(r, g, b); 14119 var h, s, l = (max + min) / 2; 14120 14121 if(max == min){ 14122 h = s = 0; // achromatic 14123 }else{ 14124 var d = max - min; 14125 s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 14126 switch(max){ 14127 case r: h = (g - b) / d + (g < b ? 6 : 0); break; 14128 case g: h = (b - r) / d + 2; break; 14129 case b: h = (r - g) / d + 4; break; 14130 } 14131 h /= 6; 14132 } 14133 14134 return [h, s, l]; 14135} 14136 14137/** 14138 * Converts an HSL color value to RGB. Conversion formula 14139 * adapted from http://en.wikipedia.org/wiki/HSL_color_space. 14140 * Assumes h, s, and l are contained in the set [0, 1] and 14141 * returns r, g, and b in the set [0, 1]. 14142 * 14143 * @param Number h The hue 14144 * @param Number s The saturation 14145 * @param Number l The lightness 14146 * @return Array The RGB representation 14147 */ 14148function hsl2rgb(h, s, l){ 14149 var r, g, b; 14150 14151 if(s == 0){ 14152 r = g = b = l; // achromatic 14153 }else{ 14154 function hue2rgb(p, q, t){ 14155 if(t < 0) t += 1; 14156 if(t > 1) t -= 1; 14157 if(t < 1/6) return p + (q - p) * 6 * t; 14158 if(t < 1/2) return q; 14159 if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; 14160 return p; 14161 } 14162 14163 var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 14164 var p = 2 * l - q; 14165 r = hue2rgb(p, q, h + 1/3); 14166 g = hue2rgb(p, q, h); 14167 b = hue2rgb(p, q, h - 1/3); 14168 } 14169 14170 return [r, g, b]; 14171} 14172 14173/* palette index number (in hex) to hsl [0..1]. rows, cols - number of colors in the whole palette */ 14174function pal2hsl(number, rows, cols) { 14175 var h,s,l,p=parseInt(number, 16); 14176 var rows = 9, cols = 12; 14177 var hstep = 1/cols, vstep = 1/rows; 14178 14179 if (p < rows * cols) { 14180 h = (p % cols) * hstep + hstep / 2; 14181 s = Math.floor(p / cols) * vstep + vstep / 2; 14182 l = 0.5; 14183 } else { // the last row is grayscale 14184 h = s = 0; 14185 l = (p % cols) * hstep + hstep / 2; 14186 } 14187 14188 return [h,s,l]; 14189}; 14190/*globals myDeco, $ */ 14191/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 14192 sloppy: true, white: true */ 14193/** 14194 * @fileOverview Trigonometry lib for dealing with degrees not radians 14195 */ 14196 14197var T = { 14198 /** 14199 * Degrees to radoans converting factor 14200 * @type {Number} 14201 */ 14202 d2r: Math.PI / 180.0, 14203 14204 /** 14205 * Radians to degrees converting factor 14206 * @type {Number} 14207 */ 14208 r2d: 180.0 / Math.PI, 14209 14210 /** 14211 * Angle cosinus 14212 * @param {Number} a Angle 14213 * @returns {Number} Value 14214 */ 14215 cos: function(a) { 14216 return Math.cos(a * this.d2r); 14217 }, 14218 14219 /** 14220 * Angle sinus 14221 * @param {Number} a Angle 14222 * @returns {Number} Value 14223 */ 14224 sin: function(a) { 14225 return Math.sin(a * this.d2r); 14226 }, 14227 14228 /** 14229 * Angle tangens 14230 * @param {Number} a Angle 14231 * @returns {Number} Value 14232 */ 14233 tan: function(a) { 14234 return Math.tan(a * this.d2r); 14235 }, 14236 14237 /** 14238 * Angle arcsinus 14239 * @param {Number} a Angle 14240 * @returns {Number} Value 14241 */ 14242 asin: function(a) { 14243 return Math.asin(a) * this.r2d; 14244 }, 14245 14246 /** 14247 * Angle arccosinus 14248 * @param {Number} a Value 14249 * @returns {Number} Angle 14250 */ 14251 acos: function(a) { 14252 return Math.acos(a) * this.r2d; 14253 }, 14254 14255 /** 14256 * Angle arctangens 14257 * @param {Number} a Value 14258 * @returns {Number} Angle 14259 */ 14260 atan: function(a) { 14261 return Math.atan(a) * this.r2d; 14262 }, 14263 14264 /** 14265 * Angle arctangens with sector support 14266 * @param {Number} a Value 14267 * @returns {Number} Angle 14268 */ 14269 atan2: function(a, b) { 14270 return Math.atan2(a, b) * this.r2d; 14271 } 14272}; 14273/* 14274 * Raphael 0.8.6 - JavaScript Vector Library 14275 * 14276 * Copyright (c) 2008 - 2009 Dmitry Baranovskiy (http://raphaeljs.com) 14277 * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. 14278 */ 14279 14280 14281window.Raphael = (function() { 14282 // indexOf implementation for IE browsers. 14283 if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { 14284 i || (i = 0); 14285 var length = this.length; 14286 if (i < 0) i = length + i; 14287 for (; i < length; i++) 14288 if (this[i] === item) return i; 14289 return -1; 14290 }; 14291 14292 var separator = /[, ]+/, 14293 doc = document, 14294 win = window, 14295 oldRaphael = { 14296 was: "Raphael" in window, 14297 is: window.Raphael 14298 }, 14299 R = function() { 14300 return create.apply(R, arguments); 14301 }, 14302 paper = {}, 14303 availableAttrs = {cx: 0, cy: 0, fill: "#fff", "fill-opacity": 1, font: '10px "Arial"', "font-family": '"Arial"', "font-size": "10", "font-style": "normal", "font-weight": 400, gradient: 0, height: 0, href: "http://raphaeljs.com/", opacity: 1, path: "M0,0", r: 0, rotation: 0, rx: 0, ry: 0, scale: "1 1", src: "", stroke: "#000", "stroke-dasharray": "", "stroke-linecap": "butt", "stroke-linejoin": "butt", "stroke-miterlimit": 0, "stroke-opacity": 1, "stroke-width": 1, target: "_blank", "text-anchor": "middle", title: "Raphael", translation: "0 0", width: 0, x: 0, y: 0}, 14304 availableAnimAttrs = {cx: "number", cy: "number", fill: "colour", "fill-opacity": "number", "font-size": "number", height: "number", opacity: "number", path: "path", r: "number", rotation: "csv", rx: "number", ry: "number", scale: "csv", stroke: "colour", "stroke-opacity": "number", "stroke-width": "number", translation: "csv", width: "number", x: "number", y: "number"},
14305 events = ["click", "dblclick", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup", "touchstart", "touchmove", "touchend", "orientationchange", "touchcancel", "gesturestart", "gesturechange", "gestureend"], 14306 supportsTouch = "createTouch" in doc, 14307 isGecko = navigator.userAgent.indexOf('Gecko') >= 0, 14308 isChrome = navigator.userAgent.indexOf('Chrome') !== -1; 14309 14310 14311 R.version = "0.8.6"; 14312 R.type = (window.SVGAngle || document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") ? "SVG" : "VML"); 14313 R.svg = !(R.vml = R.type == "VML"); 14314 R.idGenerator = 0; 14315 R.fn = {}; 14316 R.isArray = function (arr) { 14317 return Object.prototype.toString.call(arr) == "[object Array]"; 14318 }; 14319 R.setWindow = function (newwin) { 14320 win = newwin; 14321 doc = win.document; 14322 }; 14323 R.extend = function(destination, source) { 14324 for (var property in source) 14325 destination[property] = source[property]; 14326 return destination; 14327 }; 14328 R.formatCoord =function(num) { 14329 return R.svg 14330 ? num.toFixed(3) 14331 : Math.round(num) - 1; 14332 }; 14333 14334 var getRGBcache = {}, 14335 getRGBcount = []; 14336 R.getRGB = function (colour) { 14337 if (colour in getRGBcache) { 14338 return getRGBcache[colour]; 14339 } 14340 var htmlcolors = {aliceblue: "#f0f8ff", amethyst: "#96c", antiquewhite: "#faebd7", aqua: "#0ff", aquamarine: "#7fffd4", azure: "#f0ffff", beige: "#f5f5dc", bisque: "#ffe4c4", black: "#000", blanchedalmond: "#ffebcd", blue: "#00f", blueviolet: "#8a2be2", brown: "#a52a2a", burlywood: "#deb887", cadetblue: "#5f9ea0", chartreuse: "#7fff00", chocolate: "#d2691e", coral: "#ff7f50", cornflowerblue: "#6495ed", cornsilk: "#fff8dc", crimson: "#dc143c", cyan: "#0ff", darkblue: "#00008b", darkcyan: "#008b8b", darkgoldenrod: "#b8860b", darkgray: "#a9a9a9", darkgreen: "#006400", darkkhaki: "#bdb76b", darkmagenta: "#8b008b", darkolivegreen: "#556b2f", darkorange: "#ff8c00", darkorchid: "#9932cc", darkred: "#8b0000", darksalmon: "#e9967a", darkseagreen: "#8fbc8f", darkslateblue: "#483d8b", darkslategray: "#2f4f4f", darkturquoise: "#00ced1", darkviolet: "#9400d3", deeppink: "#ff1493", deepskyblue: "#00bfff", dimgray: "#696969", dodgerblue: "#1e90ff", firebrick: "#b22222", floralwhite: "#fffaf0", forestgreen: "#228b22", fuchsia: "#f0f", gainsboro: "#dcdcdc", ghostwhite: "#f8f8ff", gold: "#ffd700", goldenrod: "#daa520", gray: "#808080", green: "#008000", greenyellow: "#adff2f", honeydew: "#f0fff0", hotpink: "#ff69b4", indianred: "#cd5c5c", indigo: "#4b0082", ivory: "#fffff0", khaki: "#f0e68c", lavender: "#e6e6fa", lavenderblush: "#fff0f5", lawngreen: "#7cfc00", lemonchiffon: "#fffacd", lightblue: "#add8e6", lightcoral: "#f08080", lightcyan: "#e0ffff", lightgoldenrodyellow: "#fafad2", lightgreen: "#90ee90", lightgrey: "#d3d3d3", lightpink: "#ffb6c1", lightsalmon: "#ffa07a", lightsalmon: "#ffa07a", lightseagreen: "#20b2aa", lightskyblue: "#87cefa", lightslategray: "#789", lightsteelblue: "#b0c4de", lightyellow: "#ffffe0", lime: "#0f0", limegreen: "#32cd32", linen: "#faf0e6", magenta: "#f0f", maroon: "#800000", mediumaquamarine: "#66cdaa", mediumblue: "#0000cd", mediumorchid: "#ba55d3", mediumpurple: "#9370db", mediumseagreen: "#3cb371", mediumslateblue: "#7b68ee", mediumslateblue: "#7b68ee", mediumspringgreen: "#00fa9a", mediumturquoise: "#48d1cc", mediumvioletred: "#c71585", midnightblue: "#191970", mintcream: "#f5fffa", mistyrose: "#ffe4e1", moccasin: "#ffe4b5", navajowhite: "#ffdead", navy: "#000080", oldlace: "#fdf5e6", olive: "#808000", olivedrab: "#6b8e23", orange: "#ffa500", orangered: "#ff4500", orchid: "#da70d6", palegoldenrod: "#eee8aa", palegreen: "#98fb98", paleturquoise: "#afeeee", palevioletred: "#db7093", papayawhip: "#ffefd5", peachpuff: "#ffdab9", peru: "#cd853f", pink: "#ffc0cb", plum: "#dda0dd", powderblue: "#b0e0e6", purple: "#800080", red: "#f00", rosybrown: "#bc8f8f", royalblue: "#4169e1", saddlebrown: "#8b4513", salmon: "#fa8072", sandybrown: "#f4a460", seagreen: "#2e8b57", seashell: "#fff5ee", sienna: "#a0522d", silver: "#c0c0c0", skyblue: "#87ceeb", slateblue: "#6a5acd", slategray: "#708090", snow: "#fffafa", springgreen: "#00ff7f", steelblue: "#4682b4", tan: "#d2b48c", teal: "#008080", thistle: "#d8bfd8", tomato: "#ff6347", turquoise: "#40e0d0", violet: "#ee82ee", wheat: "#f5deb3", white: "#fff", whitesmoke: "#f5f5f5", yellow: "#ff0", yellowgreen: "#9acd32"}, 14341 res; 14342 if ((colour + "").toLowerCase() in htmlcolors) { 14343 colour = htmlcolors[colour.toString().toLowerCase()]; 14344 } 14345 if (!colour) { 14346 return {r: 0, g: 0, b: 0, hex: "#000"}; 14347 } 14348 if (colour == "none") { 14349 return {r: -1, g: -1, b: -1, hex: "none"}; 14350 } 14351 var red, 14352 green, 14353 blue, 14354 rgb = (colour + "").match(/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgb\(\s*([\d\.]+\s*,\s*[\d\.]+\s*,\s*[\d\.]+)\s*\)|rgb\(\s*([\d\.]+%\s*,\s*[\d\.]+%\s*,\s*[\d\.]+%)\s*\)|hsb\(\s*([\d\.]+\s*,\s*[\d\.]+\s*,\s*[\d\.]+)\s*\)|hsb\(\s*([\d\.]+%\s*,\s*[\d\.]+%\s*,\s*[\d\.]+%)\s*\))\s*$/i); 14355 if (rgb) { 14356 if (rgb[2]) { 14357 blue = parseInt(rgb[2].substring(5), 16); 14358 green = parseInt(rgb[2].substring(3, 5), 16); 14359 red = parseInt(rgb[2].substring(1, 3), 16); 14360 } 14361 if (rgb[3]) { 14362 blue = parseInt(rgb[3].substring(3) + rgb[3].substring(3), 16); 14363 green = parseInt(rgb[3].substring(2, 3) + rgb[3].substring(2, 3), 16); 14364 red = parseInt(rgb[3].substring(1, 2) + rgb[3].substring(1, 2), 16); 14365 } 14366 if (rgb[4]) { 14367 rgb = rgb[4].split(/\s*,\s*/); 14368 red = parseFloat(rgb[0]); 14369 green = parseFloat(rgb[1]); 14370 blue = parseFloat(rgb[2]); 14371 } 14372 if (rgb[5]) { 14373 rgb = rgb[5].split(/\s*,\s*/); 14374 red = parseFloat(rgb[0]) * 2.55; 14375 green = parseFloat(rgb[1]) * 2.55; 14376 blue = parseFloat(rgb[2]) * 2.55; 14377 } 14378 var rgb = {r: red, g: green, b: blue}, 14379 r = Math.round(red).toString(16), 14380 g = Math.round(green).toString(16), 14381 b = Math.round(blue).toString(16); 14382 (r.length == 1) && (r = "0" + r); 14383 (g.length == 1) && (g = "0" + g); 14384 (b.length == 1) && (b = "0" + b); 14385 rgb.hex = "#" + r + g + b; 14386 res = rgb; 14387 } else { 14388 res = {r: -1, g: -1, b: -1, hex: "none"}; 14389 } 14390 if (getRGBcount.length > 20) { 14391 delete getRGBcache[getRGBcount.unshift()]; 14392 } 14393 getRGBcount.push(colour); 14394 getRGBcache[colour] = res; 14395 return res; 14396 }; 14397 14398 // path utilities 14399
14400 var paramsMap = { 14401 'm': ['x', 'y'], 14402 'l': ['x', 'y'], 14403 'z': [], 14404 'a': ['rx', 'ry', 'large_arc_flag', 'sweep_flag', 'x', 'y'], 14405 'cp': ['x', 'y', 'w'], 14406 'c': ['x1', 'y1', 'x2', 'y2', 'x', 'y'], 14407 's': ['x2', 'y2', 'x', 'y'], 14408 'q': ['x1', 'y1', 'x', 'y'], 14409 't': ['x', 'y'] 14410 }; 14411 var vmlToSvgMap = { 14412 'M': 'm', 14413 'm': 't', 14414 'L': 'l', 14415 'l': 'r', 14416 'z': 'x', 14417 'CP': 'c', 14418 'cp': 'v', 14419 'C': 'c', 14420 'c': 'v', 14421 'S': 'c', 14422 's': 'v', 14423 'Q': 'c', 14424 'q': 'v', 14425 'T': 'c', 14426 't': 'v' 14427 }; 14428 14429 var PathPoint = function(path, type) { 14430 this.type = path.isAbsolute && type !='z' ? type.toUpperCase() : type; 14431 this.path = path; 14432 this._cached = false; 14433 14434 this.dropCache = function() { 14435 this._cached = false; 14436 return this; 14437 }; 14438 14439 var map = paramsMap[type.toLowerCase()], 14440 data = Array.prototype.splice.call(arguments, 2, arguments.length-2), 14441 len = data.length, 14442 i; 14443 14444 for (i = 0; i < len; i++) 14445 this[map[i]] = parseFloat(data[i]); 14446 14447 this.toString = function() { 14448 var type = R.svg ? this.type : vmlToSvgMap[this.type]; 14449 14450 if (!this._cached) 14451 switch(this.type.toLowerCase()) { 14452 case 'm': 14453 case 'l': 14454 this._cached = type + [R.formatCoord(this.x), R.formatCoord(this.y)].join(' '); 14455 break; 14456 // Workaround for cpline shorthand. 14457 case 'cp': 14458 if (!this.prev) 14459 path.pathPoints._getAbsoluteCoords(this, true); 14460 14461 var type = R.svg ? type[0] : type; 14462 14463 this._cached = type + [ R.formatCoord(this.prev.x + this.w), R.formatCoord(this.prev.y), 14464 R.formatCoord(this.x - this.w), R.formatCoord(this.y), 14465 R.formatCoord(this.x), R.formatCoord(this.y)].join(' '); 14466 break; 14467 case 'c': 14468 this._cached = type + [ R.formatCoord(this.x1), R.formatCoord(this.y1), 14469 R.formatCoord(this.x2), R.formatCoord(this.y2), 14470 R.formatCoord(this.x), R.formatCoord(this.y)].join(' '); 14471 break; 14472 case 's': 14473 if (!this.prev) 14474 path.pathPoints._getAbsoluteCoords(this, true); 14475 14476 this._cached = type + (R.vml 14477 ? R.formatCoord(this.prev.x * (path.isAbsolute + 1) - this.prev.bx) + " " + 14478 R.formatCoord(this.prev.y * (path.isAbsolute + 1) - this.prev.by) + " " 14479 : '') + 14480 [ R.formatCoord(this.x2), R.formatCoord(this.y2), 14481 R.formatCoord(this.x), R.formatCoord(this.y)].join(' '); 14482 break; 14483 case 'q': 14484 if (!this.prev) 14485 path.pathPoints._getAbsoluteCoords(this, true); 14486 14487 this._cached = type + (R.svg 14488 ? [R.formatCoord(this.x1), R.formatCoord(this.y1), 14489 R.formatCoord(this.x), R.formatCoord(this.y)].join(' ') 14490 : [R.formatCoord(2 / 3 * this.x1 + 1 / 3 * this.prev.x), 14491 R.formatCoord(2 / 3 * this.y1 + 1 / 3 * this.prev.y), 14492 R.formatCoord(2 / 3 * this.x1 + 1 / 3 * this.x), 14493 R.formatCoord(2 / 3 * this.y1 + 1 / 3 * this.y), 14494 R.formatCoord(this.x), 14495 R.formatCoord(this.y)].join(' ')); 14496 break; 14497 case 't': 14498 if (!this.prev) 14499 path.pathPoints._getAbsoluteCoords(this, true); 14500 // TBD: correct implementation for IE 14501 this._cached = type + (R.svg 14502 ? [R.formatCoord(this.x), R.formatCoord(this.y)].join(' ') 14503 : [ R.formatCoord(2 / 3 * (this.prev.x - this.prev.qx) + 1 / 3 * this.prev.x), 14504 R.formatCoord(2 / 3 * (this.prev.y - this.prev.qy) + 1 / 3 * this.prev.y), 14505 R.formatCoord(2 / 3 * this.prev.qx + 1 / 3 * this.x), 14506 R.formatCoord(2 / 3 * this.prev.qy + 1 / 3 * this.y), 14507 R.formatCoord(this.x), 14508 R.formatCoord(this.y)].join(' ')); 14509 break; 14510 14511 case 'z': 14512 this._cached = type; 14513 break; 14514 case 'a': 14515 if (R.svg) 14516 this._cached = type + [ R.formatCoord(this.rx), R.formatCoord(this.ry), 14517 0, this.large_arc_flag, this.sweep_flag, 14518 R.formatCoord(this.x), R.formatCoord(this.y)].join(' '); 14519 else { 14520 if (!this.prev) 14521 path.pathPoints._getAbsoluteCoords(this, true); 14522 14523 type = this.sweep_flag ? (path.isAbsolute ? "wa" : "wa") : (path.isAbsolute ? "at" : "at"); 14524 14525 var x22 = (path.isAbsolute ? 0 : this.prev.x) + this.x, 14526 y22 = (path.isAbsolute ? 0 : this.prev.y) + this.y, 14527 x1 = this.prev.x, 14528 y1 = this.prev.y, 14529 x = (x1 - x22) / 2, 14530 y = (y1 - y22) / 2, 14531 rxS = this.rx * this.rx, 14532 ryS = this.ry * this.ry, 14533 xS = x * x, 14534 yS = y * y, 14535 k = (this.large_arc_flag == this.sweep_flag ? -1 : 1) * 14536 Math.sqrt(Math.abs(rxS * ryS - rxS * yS - ryS * xS) / (rxS * yS + ryS * xS)), 14537 cx = k * this.rx * y / this.ry + (x1 + x22) / 2, 14538 cy = k * -this.ry * x / this.rx + (y1 + y22) / 2, 14539 left = cx - this.rx, 14540 top = cy - this.ry; 14541 14542 this._cached = type + [ R.formatCoord(left), R.formatCoord(top), 14543 R.formatCoord(left + this.rx * 2), R.formatCoord(top + this.ry * 2), 14544 R.formatCoord(x1), R.formatCoord(y1), 14545 R.formatCoord(x22), R.formatCoord(y22)].join(" "); 14546 } 14547 break; 14548 } 14549 14550 return this._cached; 14551 } 14552 }; 14553 14554 14555 var pointsMethods = { 14556 getPathString: function() { 14557 return this.join(' '); 14558 }, 14559 14560 _checkFirstPoint: function() { 14561 if (!this.bb) 14562 this.bb = {x1: Number.MAX_VALUE, y1: Number.MAX_VALUE, x2: -Number.MAX_VALUE, y2: -Number.MAX_VALUE}; 14563 14564 if (this.length == 0)
14565 this.push(new PathPoint(this.path, 'M', 0, 0)); 14566 else if (this[0].type.toUpperCase() != 'M') 14567 this.unshift(new PathPoint(this.path, 'M', 0, 0)); 14568 }, 14569 14570 _indexInBounds: function(index) { 14571 return (index >= 0 && index < this.length || 14572 index < 0 && -index <= this.length); 14573 }, 14574 14575 find: function(point) { 14576 if (typeof Array.prototype.indexOf != 'undefined') 14577 return this.indexOf(point); 14578 else { 14579 var i, len = this.length; 14580 for (i = 0; i < len; i++) 14581 if (this[i] === point) 14582 return i; 14583 } 14584 return -1; 14585 }, 14586 14587 deletePoint: function(point) { 14588 if (typeof point == 'object') { 14589 point = this.find(point); 14590 14591 if (point == -1) 14592 return false; 14593 } 14594 14595 if (this._indexInBounds(point)) { 14596 var deleted = this.splice(point, 1).shift(), 14597 path = deleted.path; 14598 14599 deleted.path = ''; // preventing some memleaks (I hope). 14600 this._checkFirstPoint(); 14601 this._updateCoords(); 14602 path.render(); 14603 return deleted; 14604 } 14605 14606 return false; 14607 }, 14608 14609 insertBefore: function(point, newPoint, after) { 14610 if (typeof point == 'object') { 14611 point = this.find(point); 14612 14613 if (point == -1) 14614 return false; 14615 } 14616 14617 if (this._indexInBounds(point)) { 14618 this.splice(point + (typeof after == 'undefined' ? 0 : 1), 0, newPoint); 14619 this._checkFirstPoint(); 14620 this._updateCoords(); 14621 path.render(); 14622 } 14623 }, 14624 14625 insertAfter: function(point, newPoint) { 14626 this.insertBefore(point, newPoint, true); 14627 }, 14628 14629 add: function(newPoint) { 14630 var num = this.push(newPoint); 14631 this._checkFirstPoint(); 14632 this._getAbsoluteCoords(num - 1, true); 14633 }, 14634 14635 _getAbsoluteCoords: function(point, recalculate) { 14636 if (typeof point == 'object') { 14637 point = this.find(point); 14638 14639 if (point == -1) 14640 return false; 14641 } 14642 // Dummy for cases when first point aren't M type. 14643 if (point == -1) 14644 return {'x': 0, 'y': 0, 'bx': 0, 'by': 0, 'qx': 0, 'qy': 0}; 14645 14646 if (this._indexInBounds(point)) { 14647 if (typeof this[point].abs !='undefined' && !recalculate) 14648 return this[point].abs; 14649 else { 14650 var curr = this[point]; 14651 var prev = 1; 14652 if (this[point - prev] && this[point - prev].type.toLowerCase() === 'z') { 14653 prev++; 14654 } 14655 14656 curr.prev = this._getAbsoluteCoords(point - prev); 14657 14658 // if we have relative point 14659 var relative = (curr.type != curr.type.toUpperCase() && curr.type != 'z'); 14660 14661 switch (curr.type.toLowerCase()) { 14662 case 's': 14663 case 'c': 14664 curr.abs = {'x': curr.prev.x * relative + curr.x, 'y': curr.prev.y * relative + curr.y, 14665 'bx': curr.prev.x * R.vml * relative + curr.x2, 'by': curr.prev.y * R.vml * relative + curr.y2, 14666 'qx': 0, 'qy': 0}; 14667 break; 14668 14669 case 'q': 14670 curr.abs = {'x': curr.prev.x * relative + curr.x, 'y': curr.prev.y * relative + curr.y, 14671 'bx': 0, 'by': 0, 14672 'qx': curr.prev.x * R.vml * relative + curr.x1, 'qy': curr.prev.y * R.vml * relative + curr.y1}; 14673 break; 14674 14675 case 'a': 14676 case 't': 14677 case 'm': 14678 case 'l': 14679 curr.abs = {'x': curr.prev.x * relative + curr.x, 'y': curr.prev.y * relative + curr.y, 14680 'bx': 0, 'by': 0, 'qx': 0, 'qy': 0}; 14681 break; 14682 case 'z': 14683 return; 14684 break 14685 default: 14686 // dummy 14687 curr.abs = {'x': 0, 'y': 0, 'bx': 0, 'by': 0, 'qx': 0, 'qy': 0}; 14688 break; 14689 } 14690 14691 // Updating bounding box 14692 this.bb.x1 = Math.min(this.bb.x1, curr.abs.x); 14693 this.bb.y1 = Math.min(this.bb.y1, curr.abs.y); 14694 this.bb.x2 = Math.max(this.bb.x2, curr.abs.x); 14695 this.bb.y2 = Math.max(this.bb.y2, curr.abs.y); 14696 return curr.abs; 14697 } 14698 } 14699 }, 14700 14701 _updateCoords: function() { 14702 var i, len = this.length; 14703 this.bb = {x1: Number.MAX_VALUE, y1: Number.MAX_VALUE, x2: -Number.MAX_VALUE, y2: -Number.MAX_VALUE}; 14704 for (i = 0; i < len; i++) 14705 this._getAbsoluteCoords(i, true); 14706 } 14707 }; 14708 14709 var pathMethods = { 14710 absolutely: function() { 14711 this.isAbsolute = true; 14712 return this; 14713 }, 14714 14715 relatively: function() { 14716 this.isAbsolute = false;
14717 return this; 14718 }, 14719 14720 render: function() { 14721 this.freeze = false; 14722 this.attrs.path = this.pathPoints.getPathString() || 14723 (R.svg ? 'M 0 0' : ''); 14724 this.node.setAttribute((R.svg ? 'd' : 'path'), this.attrs.path); 14725 14726 return this; 14727 }, 14728 14729 getBBox: function() { 14730 var yshift = 0, height = 0; 14731 14732 this.pathPoints._updateCoords(); 14733 if (typeof this.pathPoints.bb == 'undefined' || 14734 (this.pathPoints.bb.x1 == Number.MAX_VALUE && this.pathPoints.bb.y1 == Number.MAX_VALUE)) 14735 return {x: 0, y: 0, width: 0, height: 0}; 14736 else { 14737 if (this.isCircle) { 14738 height = this.pathPoints.bb.x2 - this.pathPoints.bb.x1; 14739 yshift = height / 2; 14740 } 14741 return {x: this.pathPoints.bb.x1, 14742 y: this.pathPoints.bb.y1 - yshift, 14743 width: this.pathPoints.bb.x2 - this.pathPoints.bb.x1, 14744 height: height || (this.pathPoints.bb.y2 - this.pathPoints.bb.y1)}; 14745 } 14746 }, 14747 14748 hold: function() { 14749 this.freeze = true; 14750 return this; 14751 }, 14752 14753 moveTo: function (x, y) { 14754 this.pathPoints.add(new PathPoint(this, 'm', x, y)); 14755 return this.freeze ? this : this.render(); 14756 }, 14757 14758 lineTo: function (x, y) { 14759 this.pathPoints.add(new PathPoint(this, 'l', x, y)); 14760 return this.freeze ? this : this.render(); 14761 }, 14762 14763 arcTo: function (rx, ry, large_arc_flag, sweep_flag, x, y) { 14764 this.pathPoints.add(new PathPoint(this, 'a', rx, ry, large_arc_flag, sweep_flag, x, y)); 14765 return this.freeze ? this : this.render(); 14766 }, 14767 14768 transform: function(callback) { 14769 var i, len = this.pathPoints.length; 14770 14771 for (i = 0; i < len; i++) 14772 this.pathPoints[i] = callback(this.pathPoints[i].dropCache()); 14773 14774 this.pathPoints._updateCoords(); 14775 this.render(); 14776 }, 14777 14778 cplineTo: function (x1, y1, w1) { 14779 if (!w1) { 14780 return this.lineTo(x1, y1); 14781 } else { 14782 this.pathPoints.add(new PathPoint(this, 'cp', x1, y1, w1)); 14783 return this.freeze ? this : this.render(); 14784 } 14785 }, 14786 14787 curveTo: function() { 14788 if (arguments.length == 6) 14789 this.pathPoints.add(new PathPoint(this, 'c', arguments[0], arguments[1] 14790 , arguments[2], arguments[3] 14791 , arguments[4], arguments[5])); 14792 else 14793 this.pathPoints.add(new PathPoint(this, 's', arguments[0], arguments[1] 14794 , arguments[2], arguments[3])); 14795 return this.freeze ? this : this.render(); 14796 }, 14797 14798 qcurveTo: function() { 14799 if (arguments.length == 4) 14800 this.pathPoints.add(new PathPoint(this, 'q', arguments[0], arguments[1] 14801 , arguments[2], arguments[3])); 14802 else 14803 this.pathPoints.add(new PathPoint(this, 't', arguments[0], arguments[1])); 14804 return this.freeze ? this : this.render(); 14805 }, 14806 14807 andClose: function() { 14808 this.pathPoints.add(new PathPoint(this, 'z')); 14809 return this.freeze ? this : this.render(); 14810 }, 14811 14812 clear: function() { 14813 this.pathPoints.splice(0, this.pathPoints.length); 14814 this.pathPoints._updateCoords(); 14815 return this.freeze ? this : this.render(); 14816 }, 14817 14818 // Austin-specific functions, works with Point objects 14819 // with negative Y coordinate 14820 update: function(points, positive) { 14821 var p = 0, plen = points.length; 14822 for (; p < plen; p++) { 14823 this.pathPoints[p].dropCache(); 14824 this.pathPoints[p].x = points[p].x; 14825 this.pathPoints[p].y = (positive ? 1 : -1) * points[p].y; 14826 } 14827 return this.freeze ? this : this.render(); 14828 }, 14829 14830 poly: function(points, positive) { 14831 var i = 1, ilen = points.length; 14832 if (ilen == 0) 14833 return this; 14834 this.clear().moveTo(points[0].x, (positive ? 1 : -1) * points[0].y); 14835 for (; i < ilen; i++) { 14836 this.lineTo(points[i].x, (positive ? 1 : -1) * points[i].y); 14837 } 14838 this.andClose(); 14839 return this.freeze ? this : this.render(); 14840 } 14841 }; 14842 14843 var getContainer = function() { 14844 var container, 14845 x, 14846 y, 14847 width, 14848 height; 14849 if (typeof arguments[0] == "string" || typeof arguments[0] == "object") { 14850 if (typeof arguments[0] == "string") { 14851 container = doc.getElementById(arguments[0]); 14852 } else { 14853 container = arguments[0]; 14854 } 14855 if (container.tagName) { 14856 if (arguments[1] == null) { 14857 return { 14858 container: container, 14859 width: container.style.pixelWidth || container.offsetWidth, 14860 height: container.style.pixelHeight || container.offsetHeight 14861 }; 14862 } else { 14863 return {container: container, width: arguments[1], height: arguments[2]}; 14864 } 14865 } 14866 } 14867 }; 14868 14869 // SVG 14870 if (R.svg) { 14871 R.toString = function() { 14872 return "Your browser supports SVG.\nYou are running Rapha\u00ebl " + this.version; 14873 }; 14874 14875 var thePath = function (SVG, params, pathString) { 14876 params = R.extend({}, params || {}); 14877 var el = doc.createElementNS(SVG.svgns, "path"); 14878 if (SVG.canvas) { 14879 SVG.canvas.appendChild(el); 14880 } 14881 var p = new Element(el, SVG); 14882 p.isAbsolute = true; 14883 14884 R.extend(p, pathMethods); 14885 14886 p.type = "path"; 14887 p.pathPoints = new Array; 14888 R.extend(p.pathPoints, pointsMethods); 14889 p.pathPoints.path = p; 14890 14891 if (params) { 14892 !params.gradient && (params.fill = params.fill || "none"); 14893 params.stroke = params.stroke || "#000"; 14894 } else { 14895 params = {fill: "none", stroke: "#000"}; 14896 } 14897 setFillAndStroke(p, params); 14898 return p; 14899 }; 14900 var updatePosition = function (o) { 14901 if (o.pattern) { 14902 var bbox = o.getBBox(); 14903 o.pattern.setAttribute("patternTransform", "translate(".concat(bbox.x, ",", bbox.y, ")")); 14904 } 14905 }; 14906 var setFillAndStroke = function (o, params) { 14907 var dasharray = { 14908 "": [0], 14909 "none": [0], 14910 "-": [3, 1], 14911 ".": [1, 1], 14912 "-.": [3, 1, 1, 1], 14913 "-..": [3, 1, 1, 1, 1, 1], 14914 ". ": [1, 3], 14915 "- ": [4, 3], 14916 "--": [8, 3], 14917 "- .": [4, 3, 1, 3], 14918 "--.": [8, 3, 1, 3], 14919 "--..": [8, 3, 1, 3, 1, 3] 14920 }, 14921 node = o.node, 14922 attrs = o.attrs, 14923 rot = attrs.rotation, 14924 rcx = attrs.rcx, 14925 rcy = attrs.rcy, 14926 addDashes = function (o, value) { 14927 value = dasharray[value.toString().toLowerCase()]; 14928 if (value) { 14929 var width = o.attrs["stroke-width"] || "1", 14930 butt = {round: width, square: width, butt: 0}[o.attrs["stroke-linecap"] || params["stroke-linecap"]] || 0, 14931 dashes = []; 14932 for (var i = 0, ii = value.length; i < ii; i++) { 14933 dashes.push(value[i] * width + ((i % 2) ? 1 : -1) * butt); 14934 } 14935 value = dashes.join(","); 14936 node.setAttribute("stroke-dasharray", value); 14937 } 14938 }; 14939 // o.rotate(0, rcx, rcy); 14940 for (var att in params) { 14941 if (!(att in availableAttrs)) { 14942 continue; 14943 } 14944 var value = params[att]; 14945 attrs[att] = value; 14946 switch (att) { 14947 // Hyperlink 14948 case "href": 14949 case "title": 14950 case "target": 14951 var pn = node.parentNode; 14952 if (pn.tagName.toLowerCase() != "a") { 14953 var hl = doc.createElementNS(o.paper.svgns, "a"); 14954 pn.insertBefore(hl, node); 14955 hl.appendChild(node); 14956 pn = hl; 14957 } 14958 pn.setAttributeNS(o.paper.xlink, att, value); 14959 break; 14960 case "path": 14961 if (o.type == "path") { 14962 node.setAttribute("d", "M0,0"); 14963 paper.pathfinder(o, value); 14964 } 14965 case "width": 14966 node.setAttribute(att, value); 14967 if (attrs.fx) { 14968 att = "x"; 14969 value = attrs.x; 14970 } else { 14971 break; 14972 } 14973 case "x": 14974 if (attrs.fx) { 14975 value = -attrs.x - (attrs.width || 0); 14976 } 14977 case "rx": 14978 case "cx": 14979 node.setAttribute(att, value); 14980 updatePosition(o); 14981 break; 14982 case "height": 14983 node.setAttribute(att, value); 14984 if (attrs.fy) { 14985 att = "y"; 14986 value = attrs.y; 14987 } else { 14988 break; 14989 } 14990 case "y": 14991 if (attrs.fy) { 14992 value = -attrs.y - (attrs.height || 0
14992); 14993 } 14994 case "ry": 14995 case "cy": 14996 node.setAttribute(att, value); 14997 updatePosition(o); 14998 break; 14999 case "r": 15000 if (o.type == "rect") { 15001 node.setAttribute("rx", value); 15002 node.setAttribute("ry", value); 15003 } else { 15004 node.setAttribute(att, value); 15005 } 15006 break; 15007 case "src": 15008 if (o.type == "image") { 15009 node.setAttributeNS(o.paper.xlink, "href", value); 15010 } 15011 break; 15012 case "stroke-width": 15013 node.style.strokeWidth = value; 15014 // Need following line for Firefox 15015 node.setAttribute(att, value); 15016 if (attrs["stroke-dasharray"]) { 15017 addDashes(o, attrs["stroke-dasharray"]); 15018 } 15019 break; 15020 case "stroke-dasharray": 15021 addDashes(o, value); 15022 break; 15023 case "rotation": 15024 rot = value; 15025 break; 15026 case "translation": 15027 var xy = (value + "").split(separator); 15028 o.translate((+xy[0] + 1 || 2) - 1, (+xy[1] + 1 || 2) - 1); 15029 break; 15030 case "scale": 15031 var xy = (value + "").split(separator); 15032 o.scale(+xy[0] || 1, +xy[1] || +xy[0] || 1, +xy[2] || null, +xy[3] || null); 15033 break; 15034 case "fill": 15035 var isURL = (value + "").match(/^url\(([^\)]+)\)$/i); 15036 if (isURL) { 15037 var el = doc.createElementNS(o.paper.svgns, "pattern"), 15038 ig = doc.createElementNS(o.paper.svgns, "image"), 15039 img = doc.createElement("img"), 15040 updImage = function() { 15041 // Special workaround for FF browsers. 15042 if (isGecko && false) { 15043 el.setAttribute("width", img.offsetWidth - 2); 15044 el.setAttribute("height", img.offsetHeight - 2); 15045 } else { 15046 el.setAttribute("width", img.offsetWidth); 15047 el.setAttribute("height", img.offsetHeight); 15048 } 15049 ig.setAttribute("width", img.offsetWidth); 15050 ig.setAttribute("height", img.offsetHeight); 15051 doc.body.removeChild(img); 15052 paper.safari(); 15053 }; 15054 el.id = "raphael-pattern-" + R.idGenerator++; 15055 el.setAttribute("x", 0); 15056 el.setAttribute("y", 0); 15057 el.setAttribute("patternUnits", "userSpaceOnUse"); 15058 ig.setAttribute("x", 0); 15059 ig.setAttribute("y", 0); 15060 ig.setAttributeNS(o.paper.xlink, "href", isURL[1]); 15061 el.appendChild(ig); 15062 15063 img.style.cssText = "position:absolute;left:-9999em;top:-9999em;"; 15064 if (isChrome) { 15065 img.onload = function() { 15066 window.setTimeout(updImage, 1); 15067 }; 15068 } else { 15069 img.onload = updImage; 15070 } 15071 img.onerror = function() { 15072 doc.body.removeChild(img); 15073 } 15074 doc.body.appendChild(img); 15075 img.src = isURL[1]; 15076 o.paper.defs.appendChild(el); 15077 node.style.fill = "url(#" + el.id + ")"; 15078 node.setAttribute("fill", "url(#" + el.id + ")"); 15079 o.pattern = el; 15080 updatePosition(o); 15081 break; 15082 } 15083 delete params.gradient; 15084 delete attrs.gradient; 15085 if (typeof attrs.opacity != "undefined" && typeof params.opacity == "undefined" ) { 15086 node.style.opacity = attrs.opacity; 15087 // Need following line for Firefox
15088 node.setAttribute("opacity", attrs.opacity); 15089 } 15090 if (typeof attrs["fill-opacity"] != "undefined" && typeof params["fill-opacity"] == "undefined" ) { 15091 node.style.fillOpacity = o.attrs["fill-opacity"]; 15092 // Need following line for Firefox 15093 node.setAttribute("fill-opacity", attrs["fill-opacity"]); 15094 } 15095 case "stroke": 15096 node.style[att] = R.getRGB(value).hex; 15097 // Need following line for Firefox 15098 node.setAttribute(att, R.getRGB(value).hex); 15099 break; 15100 case "opacity": 15101 case "fill-opacity": 15102 if (attrs.gradient) { 15103 var gradient = doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, "")); 15104 if (gradient) { 15105 var stops = gradient.getElementsByTagName("stop"); 15106 stops[stops.length - 1].setAttribute("stop-opacity", value); 15107 } 15108 break; 15109 } 15110 default : 15111 var cssrule = att.replace(/(\-.)/g, function (w) { 15112 return w.substring(1).toUpperCase(); 15113 }); 15114 node.style[cssrule] = value; 15115 // Need following line for Firefox 15116 node.setAttribute(att, value); 15117 break; 15118 } 15119 } 15120 15121 tuneText(o, params); 15122 // o.rotate(rot, rcx, rcy); 15123 }; 15124 var leading = 1.2; 15125 var tuneText = function (el, params) { 15126 if (el.type != "text" || !("text" in params || "font" in params || "font-size" in params || "x" in params || "y" in params)) { 15127 return; 15128 } 15129 var a = el.attrs, 15130 node = el.node, 15131 fontSize = node.firstChild ? parseInt(doc.defaultView.getComputedStyle(node.firstChild, "").getPropertyValue("font-size"), 10) : 10; 15132 15133 if ("text" in params) { 15134 while (node.firstChild) { 15135 node.removeChild(node.firstChild); 15136 } 15137 var texts = (params.text + "").split("\n"); 15138 for (var i = 0, ii = texts.length; i < ii; i++) { 15139 var tspan = doc.createElementNS(el.paper.svgns, "tspan"); 15140 i && tspan.setAttribute("dy", fontSize * leading); 15141 i && tspan.setAttribute("x", a.x); 15142 tspan.appendChild(doc.createTextNode(texts[i])); 15143 node.appendChild(tspan); 15144 } 15145 } else { 15146 var texts = node.getElementsByTagName("tspan"); 15147 for (var i = 0, ii = texts.length; i < ii; i++) { 15148 i && texts[i].setAttribute("dy", fontSize * leading); 15149 i && texts[i].setAttribute("x", a.x); 15150 } 15151 } 15152 node.setAttribute("y", a.y); 15153 var bb = el.getBBox(), 15154 dif = a.y - (bb.y + bb.height / 2); 15155 dif && node.setAttribute("y", a.y + dif); 15156 }; 15157 var Element = function (node, svg) { 15158 var X = 0, 15159 Y = 0; 15160 this[0] = node; 15161 this.node = node; 15162 this.paper = svg; 15163 this.attrs = this.attrs || {}; 15164 this.transformations = []; // rotate, translate, scale 15165 this._ = { 15166 tx: 0, 15167 ty: 0, 15168 rt: {deg: 0, x: 0, y: 0}, 15169 sx: 1, 15170 sy: 1 15171 }; 15172 }; 15173 15174 var combinedTrans = function(trans) { 15175 return trans.filter(function(t) { return t != '';}).join(" "); 15176 }; 15177 15178 Element.prototype.rotate = function (deg, cx, cy) { 15179 if (deg == null) { 15180 return this._.rt.deg; 15181 } 15182 deg = deg.toString().split(separator); 15183 if (deg.length - 1) { 15184 cx = parseFloat(deg[1]); 15185 cy = parseFloat(deg[2]); 15186 } 15187 deg = parseFloat(deg[0]); 15188 if (cx != null) { 15189 this._.rt.deg = deg; 15190 } else { 15191 this._.rt.deg += deg; 15192 } 15193 if (cy == null) { 15194 cx = null; 15195 var bbox = this.getBBox(); 15196 } 15197 15198 cx = cx == null ? bbox.x + bbox.width / 2 : cx; 15199 cy = cy == null ? bbox.y + bbox.height / 2 : cy; 15200 15201 if (this._.rt.deg) { 15202 this.transformations[0] = ("rotate(" + this._.rt.deg + " " + cx + " " + cy + ")"); 15203 } else { 15204 this.transformations[0] = ""; 15205 } 15206 this.attrs.rotation = deg; 15207 this.attrs.rcx = cx; 15208 this.attrs.rcy = cy; 15209 this.node.setAttribute("transform", combinedTrans(this.transformations)); 15210 return this; 15211 }; 15212 Element.prototype.hide = function() { 15213 this.node.style.display = "none"; 15214 return this; 15215 }; 15216 Element.prototype.show = function() { 15217 this.node.style.display = "block"; 15218 return this; 15219 }; 15220 Element.prototype.remove = function() { 15221 this.node.parentNode.removeChild(this.node); 15222 }; 15223 Element.prototype.getBBox = function() { 15224 if (this.node.style.display == "none") { 15225 this.show(); 15226 var hide = true; 15227 } 15228 var bbox = {}; 15229 try { 15230 bbox = this.node.getBBox(); 15231 } catch(e) { 15232 // Firefox 3.0.x plays badly here 15233 } finally { 15234 bbox = bbox || {}; 15235 } 15236 /*
15237 if (this.type == "text") { 15238 bbox = {x: bbox.x, y: Infinity, width: bbox.width, height: 0}; 15239 for (var i = 0, ii = this.node.getNumberOfChars(); i < ii; i++) { 15240 var bb = this.node.getExtentOfChar(i); 15241 (bb.y < bbox.y) && (bbox.y = bb.y); 15242 (bb.y + bb.height - bbox.y > bbox.height) && (bbox.height = bb.y + bb.height - bbox.y); 15243 } 15244 } 15245 */ 15246 hide && this.hide(); 15247 return bbox; 15248 }; 15249 Element.prototype.attr = function() { 15250 if (arguments.length == 1 && typeof arguments[0] == "string") { 15251 if (arguments[0] == "translation") { 15252 return this.translate(); 15253 } 15254 return this.attrs[arguments[0]]; 15255 } 15256 if (arguments.length == 1 && R.isArray(arguments[0])) { 15257 var values = {}; 15258 for (var j in arguments[0]) { 15259 values[arguments[0][j]] = this.attrs[arguments[0][j]]; 15260 } 15261 return values; 15262 } 15263 if (arguments.length == 2) { 15264 var params = {}; 15265 params[arguments[0]] = arguments[1]; 15266 setFillAndStroke(this, params); 15267 } else if (arguments.length == 1 && typeof arguments[0] == "object") { 15268 setFillAndStroke(this, arguments[0]); 15269 } 15270 return this; 15271 }; 15272 Element.prototype.toFront = function() { 15273 this.node.parentNode.appendChild(this.node); 15274 return this; 15275 }; 15276 Element.prototype.toBack = function() { 15277 if (this.node.parentNode.firstChild != this.node) { 15278 this.node.parentNode.insertBefore(this.node, this.node.parentNode.firstChild); 15279 } 15280 return this; 15281 }; 15282 15283 Element.prototype.insertAfter = function (element) { 15284 if (element instanceof Set) 15285 element = element.getLast(); 15286 15287 if (!element) 15288 return this; 15289 15290 element = element.node || element; 15291 15292 if (element.nextSibling) 15293 element.parentNode.insertBefore(this.node, element.nextSibling); 15294 else 15295 element.parentNode.appendChild(this.node); 15296 15297 return this; 15298 }; 15299 15300 Element.prototype.insertBefore = function (element) { 15301 if (element instanceof Set) 15302 element = element.getFirst(); 15303 15304 if (!element) 15305 return this; 15306 15307 element = element.node || element; 15308 element.parentNode.insertBefore(this.node, element); 15309 return this; 15310 }; 15311 var theCircle = function (svg, x, y, r) { 15312 var el = doc.createElementNS(svg.svgns, "circle"); 15313 el.setAttribute("cx", x); 15314 el.setAttribute("cy", y); 15315 el.setAttribute("r", r); 15316 el.setAttribute("fill", "none"); 15317 el.setAttribute("stroke", "#000"); 15318 if (svg.canvas) { 15319 svg.canvas.appendChild(el); 15320 } 15321 var res = new Element(el, svg); 15322 res.attrs = res.attrs || {}; 15323 res.attrs.cx = x; 15324 res.attrs.cy = y; 15325 res.attrs.r = r; 15326 res.attrs.stroke = "#000"; 15327 res.type = "circle"; 15328 return res; 15329 }; 15330 var theRect = function (svg, x, y, w, h) { 15331 var el = doc.createElementNS(svg.svgns, "rect"); 15332 el.setAttribute("x", x); 15333 el.setAttribute("y", y); 15334 el.setAttribute("width", w); 15335 el.setAttribute("height", h); 15336 15337 el.setAttribute("fill", "none"); 15338 el.setAttribute("stroke", "#000"); 15339 if (svg.canvas) 15340 svg.canvas.appendChild(el); 15341 15342 var res = new Element(el, svg); 15343 res.attrs = res.attrs || {}; 15344 res.attrs.x = x; 15345 res.attrs.y = y; 15346 res.attrs.width = w; 15347 res.attrs.height = h; 15348 res.attrs.stroke = "#000"; 15349 15350 res.type = "rect"; 15351 return res; 15352 }; 15353 var theImage = function (svg, src, x, y, w, h) { 15354 var el = doc.createElementNS(svg.svgns, "image"); 15355 el.setAttribute("x", x); 15356 el.setAttribute("y", y); 15357 el.setAttribute("width", w); 15358 el.setAttribute("height", h); 15359 el.setAttribute("preserveAspectRatio", "none"); 15360 el.setAttributeNS(svg.xlink, "href", src); 15361 if (svg.canvas) { 15362 svg.canvas.appendChild(el); 15363 } 15364 var res = new Element(el, svg); 15365 res.attrs = res.attrs || {}; 15366 res.attrs.x = x; 15367 res.attrs.y = y; 15368 res.attrs.width = w; 15369 res.attrs.height = h; 15370 res.type = "image"; 15371 return res; 15372 }; 15373 var theText = function (svg, x, y, text) { 15374 var el = doc.createElementNS(svg.svgns, "text"); 15375 el.setAttribute("x", x); 15376 el.setAttribute("y", y); 15377 el.setAttribute("text-anchor", "middle"); 15378 if (svg.canvas) { 15379 svg.canvas.appendChild(el); 15380 } 15381 var res = new Element(el, svg); 15382 res.attrs = res.attrs || {}; 15383 res.attrs.x = x; 15384 res.attrs.y = y; 15385 res.type = "text"; 15386 setFillAndStroke(res, {font: availableAttrs.font, stroke: "none", fill: "#000", text: text}); 15387 return res; 15388 }; 15389 var setSize = function (width, height) { 15390 this.width = width || this.width; 15391 this.height = height || this.height; 15392 this.canvas.setAttribute("width", this.width); 15393 this.canvas.setAttribute("height", this.height); 15394 15395 if (this.webkitPan) { 15396 this.zoom = this._zFactor = 10; 15397 this._adjX = width / 2; 15398 this._adjY = height / 2; 15399 this.canvas.setAttribute('viewBox', [-width * this.zoom / 2, -height * this.zoom / 2, 15400 width * this.zoom, height * this.zoom].join(' ')); 15401 } else { 15402 this.zoom = paper._zFactor = 1; 15403 this._adjX = 0; 15404 this._adjY = 0; 15405 } 15406 15407 return this; 15408 }; 15409 var create = function() { 15410 var con = getContainer.apply(null, arguments), 15411 container = con.container, 15412 x = con.x, 15413 y = con.y, 15414 width = con.width, 15415 height = con.height; 15416 if (!container) { 15417 throw new Error("SVG container not found."); 15418 } 15419 15420 paper.canvas = doc.createElementNS(paper.svgns, "svg"); 15421 paper.webkitPan = arguments[3] === true; 15422 15423 paper.setSize(width, height); 15424 paper.x = 0; 15425 paper.y = 0; 15426 15427 paper._updateViewBox(); 15428 15429 if (container == 1) { 15430 doc.body.appendChild(paper.canvas); 15431 paper.canvas.style.position = "absolute"; 15432 paper.canvas.style.left = x + "px"; 15433 paper.canvas.style.top = y + "px"; 15434 } else { 15435 if (container.firstChild) { 15436 container.insertBefore(paper.canvas, container.firstChild); 15437 } else { 15438 container.appendChild(paper.canvas); 15439 } 15440 } 15441 container = { 15442 canvas: paper.canvas, 15443 clear: function() { 15444 while (this.canvas.firstChild) { 15445 this.canvas.removeChild(this.canvas.firstChild); 15446 } 15447 this.defs = doc.createElementNS(paper.svgns, "defs"); 15448 this.canvas.appendChild(this.defs); 15449 } 15450 }; 15451 for (var prop in paper) { 15452 if (prop != "create") { 15453 container[prop] = paper[prop]; 15454 } 15455 } 15456 container.clear(); 15457 container.raphael = R; 15458 return container; 15459 }; 15460 paper.remove = function() { 15461 this.canvas.parentNode.removeChild(this.canvas); 15462 }; 15463 15464 paper._updateViewBox = function() { 15465 if (this.webkitPan) { 15466 this.canvas.style.webkitTransform = 'translate(' + (-this.x) + 'px, ' + (-this.y) + 'px' + ') scale(' + this.zoom + ')'; 15467 } else { 15468 this.canvas.setAttribute('viewBox', [this.x / this.zoom, this.y / this.zoom, 15469 this.width / this.zoom, this.height / this.zoom ].join(' ')); 15470 } 15471 } 15472 15473 paper.pan = function(dx, dy, absolute) { 15474 if (absolute) { 15475 this.x = -dx + this._adjX; 15476 this.y = -dy + this._adjY; 15477 } else { 15478 this.x -= dx; 15479 this.y -= dy; 15480 } 15481 15482 this._updateViewBox(); 15483 }; 15484 15485 paper.scale = function(zoom) { 15486 this.zoom = this._zFactor * zoom; 15487 this._updateViewBox(); 15488 }; 15489 15490 paper.svgns = "http://www.w3.org/2000/svg"; 15491 paper.xlink = "http://www.w3.org/1999/xlink"; 15492 15493 // rest 15494 // WebKit rendering bug workaround method 15495 if ((navigator.vendor == "Apple Computer, Inc.") && (win.navigator.platform.slice(0, 2) == "iP")) { 15496 paper.safari = function() { 15497 var rect = this.rect(-this.width, -this.height, this.width * 3, this.height * 3).attr({stroke: "none"}); 15498 win.setTimeout(function() {rect.remove();}); 15499 }; 15500 } else { 15501 paper.safari = function() {}; 15502 } 15503 } 15504 15505 // VML 15506 if (R.vml) { 15507 R.toString = function() { 15508 return "Your browser doesn\u2019t support SVG. Assuming it is Internet Explorer and falling down to VML.\nYou are running Rapha\u00ebl " + this.version; 15509 }; 15510 15511 var thePath = function (vml, params, pathString) { 15512 if (params) { 15513 params = R.extend({}, params || {}); 15514 params.fill = params.fill || "none"; 15515 params.stroke = params.stroke || "none"; 15516 } else { 15517 params = {fill: "none", stroke: "#000"}; 15518 } 15519 15520 var node = createNode("shape"), 15521 style = node.style, 15522 element; 15523 15524 node.path = ""; 15525 15526 if (params["class"]) 15527 node.className = "rvml " + params["class"]; 15528 15529 // node.coordsize = vml.coordsize; 15530 // node.coordorigin = vml.coordorigin; 15531 15532 element = new Element(node, vml);
15533 element.isAbsolute = true; 15534 element.type = "path"; 15535 element.path = []; 15536 R.extend(element, pathMethods); 15537 15538 element.pathPoints = []; 15539 R.extend(element.pathPoints, pointsMethods); 15540 element.pathPoints.path = element; 15541 15542 style.position = "absolute"; 15543 style.width = vml.width + "px"; 15544 style.height = vml.height + "px"; 15545 style.left = 0; 15546 style.top = 0; 15547 15548 setFillAndStroke(element, params); 15549 15550 element.attrs.x = 0; 15551 element.attrs.y = 0; 15552 element.attrs.w = vml.width; 15553 element.attrs.h = vml.height; 15554 15555 return element; 15556 }; 15557 15558 var setFillAndStroke = function (o, params) { 15559 var node = o.node, 15560 s = node.style, 15561 xy, left, top, bbox = false, 15562 res = o; 15563 o.attrs = o.attrs || {}; 15564 15565 if (params.x != undefined) { 15566 if (o.attrs.rcx == undefined) { 15567 bbox = bbox || o.getBBox(); 15568 15569 o.attrs.rcx = params.x + (o.type != 'text' ? bbox.width / 2: 0); 15570 } 15571 15572 left = o.attrs.rcx - paper.width / 2; 15573 o.holder.style.left = left + 'px'; 15574 s.left = params.x - left + 'px'; 15575 } 15576 15577 if (params.y != undefined) { 15578 if (o.attrs.rcy == undefined) { 15579 bbox = bbox || o.getBBox(); 15580 15581 o.attrs.rcy = params.y + (o.type != 'text' ? bbox.height / 2: 0); 15582 } 15583 15584 top = o.attrs.rcy - paper.height / 2; 15585 15586 o.holder.style.top = top + 'px'; 15587 s.top = params.y - top + 'px'; 15588 } 15589 15590 for (var par in params) 15591 o.attrs[par] = params[par]; 15592 15593 params.width != undefined && (node.style.width = params.width); 15594 params.height != undefined && (node.style.height = params.height); 15595 15596 params.href && (node.href = params.href); 15597 params.title && (node.title = params.title); 15598 params.target && (node.target = params.target); 15599 if (params.path && o.type == "path") { 15600 o.Path = ""; 15601 o.path = []; 15602 o.attrs.path = ""; 15603 paper.pathfinder(o, params.path); 15604 } 15605 if (params.rotation != null) { 15606 o.rotate(params.rotation, true); 15607 } 15608 if (params.translation) { 15609 xy = (params.translation + "").split(separator); 15610 o.translate(xy[0], xy[1]); 15611 } 15612 if (params.scale) { 15613 xy = (params.scale + "").split(separator); 15614 o.scale(+xy[0] || 1, +xy[1] || +xy[0] || 1, +xy[2] || null, +xy[3] || null); 15615 } 15616 if (o.type == "image" && params.src) { 15617 node.src = params.src; 15618 } 15619 if (o.type == "image" && params.opacity) { 15620 node.filterOpacity = " progid:DXImageTransform.Microsoft.Alpha(opacity=" + (params.opacity * 100) + ")"; 15621 node.style.filter = (node.filterMatrix || "") + (node.filterOpacity || ""); 15622 } 15623 15624 if (typeof params.opacity != "undefined" || typeof params["stroke-width"] != "undefined" || typeof params.fill != "undefined" || typeof params.stroke != "undefined" || params["stroke-width"] || params["stroke-opacity"] || params["fill-opacity"] || params["stroke-dasharray"] || params["stroke-miterlimit"] || params["stroke-linejoin"] || params["stroke-linecap"]) { 15625 o = o.shape || node; 15626 var fill = (o.getElementsByTagName("fill") && o.getElementsByTagName("fill")[0]) || createNode("fill"); 15627 if ("fill-opacity" in params || "opacity" in params) { 15628 var opacity = ((+params["fill-opacity"] + 1 || 2) - 1) * ((+params.opacity + 1 || 2) - 1); 15629 opacity < 0 && (opacity = 0); 15630 opacity > 1 && (opacity = 1); 15631 fill.opacity = opacity; 15632 } 15633 params.fill && (fill.on = true); 15634 if (typeof fill.on == "undefined" || params.fill == "none") { 15635 fill.on = false; 15636 } 15637 if (fill.on && params.fill) { 15638 var isURL = params.fill.match(/^url\(([^\)]+)\)$/i); 15639 if (isURL) { 15640 fill.src = isURL[1]; 15641 fill.type = "tile"; 15642 } else { 15643 fill.color = R.getRGB(params.fill).hex; 15644 fill.src = ""; 15645 fill.type = "solid"; 15646 } 15647 } 15648 o.appendChild(fill); 15649 var stroke = (o.getElementsByTagName("stroke") && o.getElementsByTagName("stroke")[0]) || createNode("stroke"); 15650 if ((params.stroke && params.stroke != "none") || params["stroke-width"] || typeof params["stroke-opacity"] != "undefined" || params["stroke-dasharray"] || params["stroke-miterlimit"] || params["stroke-linejoin"] || params["stroke-linecap"]) { 15651 stroke.on = true; 15652 } 15653 if (params.stroke == "none" || typeof stroke.on == "undefined" || params.stroke == 0) { 15654 stroke.on = false;
15655 } 15656 if (stroke.on && params.stroke) { 15657 stroke.color = R.getRGB(params.stroke).hex; 15658 } 15659 var opacity = ((+params["stroke-opacity"] + 1 || 2) - 1) * ((+params.opacity + 1 || 2) - 1); 15660 opacity < 0 && (opacity = 0); 15661 opacity > 1 && (opacity = 1); 15662 stroke.opacity = opacity; 15663 params["stroke-linejoin"] && (stroke.joinstyle = params["stroke-linejoin"] || "miter"); 15664 stroke.miterlimit = params["stroke-miterlimit"] || 8; 15665 params["stroke-linecap"] && (stroke.endcap = {butt: "flat", square: "square", round: "round"}[params["stroke-linecap"]] || "miter"); 15666 params["stroke-width"] && (stroke.weight = (parseFloat(params["stroke-width"]) || 1) * 12 / 16); 15667 if (params["stroke-dasharray"]) { 15668 var dasharray = { 15669 "-": "shortdash", 15670 ".": "shortdot", 15671 "-.": "shortdashdot", 15672 "-..": "shortdashdotdot", 15673 ". ": "dot", 15674 "- ": "dash", 15675 "--": "longdash", 15676 "- .": "dashdot", 15677 "--.": "longdashdot", 15678 "--..": "longdashdotdot" 15679 }; 15680 stroke.dashstyle = dasharray[params["stroke-dasharray"]] || ""; 15681 } 15682 o.appendChild(stroke); 15683 } 15684 if (res.type == "text") { 15685 var s = res.textpath.style, 15686 a = res.attrs; 15687 15688 params.font && (s.font = params.font); 15689 params["font-family"] && (s.fontFamily = '"' + params["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g, "") + '"'); 15690 params["font-size"] && (s.fontSize = parseInt(params["font-size"]) * paper.zoom + 'px'); 15691 params["font-weight"] && (s.fontWeight = params["font-weight"]); 15692 params["font-style"] && (s.fontStyle = params["font-style"]); 15693 15694 s = res.paper._span.style, 15695 a.font && (s.font = a.font); 15696 a["font-family"] && (s.fontFamily = a["font-family"]); 15697 a["font-size"] && (s.fontSize = a["font-size"]); 15698 a["font-weight"] && (s.fontWeight = a["font-weight"]); 15699 a["font-style"] && (s.fontStyle = a["font-style"]); 15700 res.paper._span.innerText = res.node.string; 15701 15702 a.width = res.paper._span.offsetWidth; 15703 a.height = res.paper._span.offsetHeight; 15704 15705 // text-anchor emulation 15706 switch (a["text-anchor"]) { 15707 case "start": 15708 res.node.style["v-text-align"] = "left"; 15709 res.bbx = Math.round(res.W / 2); 15710 break; 15711 case "end": 15712 res.node.style["v-text-align"] = "right"; 15713 res.bbx = -Math.round(res.W / 2); 15714 break; 15715 default: 15716 res.node.style["v-text-align"] = "center"; 15717 break; 15718 } 15719 } 15720 }; 15721 15722 var Element = function (node, vml) { 15723 this.node = node; 15724 this.holder = createNode("group"); 15725 15726 this.attrs = {}; 15727 this.paper = vml; 15728 15729 var style = this.holder.style; 15730 style.position = "absolute"; 15731 style.left = 0; 15732 style.top = 0; 15733 style.width = vml.width + "px"; 15734 style.height = vml.height + "px"; 15735 this.holder.coordsize = vml.coordsize; 15736 this.holder.coordorigin = vml.coordorigin; 15737 15738 this.holder.appendChild(node); 15739 this.paper.group.appendChild(this.holder); 15740 15741 this._ = { 15742 tx: 0, 15743 ty: 0, 15744 sx: 1, 15745 sy: 1 15746 }; 15747 }; 15748 15749 Element.prototype.rotate = function (deg, cx, cy) { 15750 if (deg == null) 15751 return this.attrs.rotation; 15752 15753 deg = parseFloat(deg); 15754 15755 if (cx != null) 15756 this.attrs.rotation = deg; 15757 else 15758 this.attrs.rotation += deg; 15759 15760 if (cy == null) { 15761 var bbox = this.getBBox(); 15762 15763 if (this.type != 'text') { 15764 cx = bbox.x + bbox.width / 2; 15765 cy = bbox.y + bbox.height / 2; 15766 } else { 15767 cx = bbox.x; 15768 cy = bbox.y; 15769 } 15770 } 15771 15772 this.attrs.rcx = cx; 15773 this.attrs.rcy = cy; 15774 var left = cx - this.paper.width / 2, 15775 top = cy - this.paper.height / 2; 15776 15777 this.holder.style.left = left + 'px'; 15778 this.holder.style.top = top + 'px'; 15779 15780 this.node.style.left = (this.attrs.x || 0) - left + 'px'; 15781 this.node.style.top = (this.attrs.y || 0) - top + 'px'; 15782 15783 this.holder.style.rotation = this.attrs.rotation; 15784 return this; 15785 }; 15786 15787 Element.prototype.hide = function() { 15788 this.holder.style.display = "none"; 15789 return this; 15790 }; 15791 15792 Element.prototype.show = function() { 15793 this.holder.style.display = "block"; 15794 return this; 15795 }; 15796 15797 Element.prototype.getBBox = function() { 15798 return { 15799 x: this.attrs.x + (this.bbx || 0), 15800 y: this.attrs.y, 15801 width: this.attrs.width, 15802 height: this.attrs.height 15803 }; 15804 }; 15805 15806 Element.prototype.remove = function() { 15807 this.node.parentNode.removeChild(this.node); 15808 this.holder.parentNode.removeChild(this.holder); 15809 this.shape && this.shape.parentNode.removeChild(this.shape); 15810 }; 15811 15812 Element.prototype.attr = function() { 15813 if (arguments.length == 1 && typeof arguments[0] == "string") { 15814 if (arguments[0] == "translation") { 15815 return this.translate(); 15816 } 15817 return this.attrs[arguments[0]]; 15818 } 15819 if (this.attrs && arguments.length == 1 && R.isArray(arguments[0])) { 15820 var values = {}; 15821 for (var i = 0, ii = arguments[0].length; i < ii; i++) { 15822 values[arguments[0][i]] = this.attrs[arguments[0][i]]; 15823 }; 15824 return values; 15825 } 15826 15827 var params; 15828 if (arguments.length == 2) { 15829 params = {}; 15830 params[arguments[0]] = arguments[1]; 15831 } 15832 if (arguments.length == 1 && typeof arguments[0] == "object") { 15833 params = arguments[0]; 15834 } 15835 if (params) { 15836 if (params.text && this.type == "text") 15837 this.textpath.string = params.text; 15838 setFillAndStroke(this, params); 15839 } 15840 return this; 15841 }; 15842 15843 Element.prototype.toFront = function() { 15844 this.holder.parentNode.appendChild(this.holder); 15845 return this; 15846 }; 15847 Element.prototype.toBack = function() { 15848 if (this.holder.parentNode.firstChild != this.holder) { 15849 this.holder.parentNode.insertBefore(this.holder, this.holder.parentNode.firstChild); 15850 } 15851 return this; 15852 }; 15853 Element.prototype.insertAfter = function (element) { 15854 if (element instanceof Set) 15855 element = element.getLast(); 15856 15857 if (!element) 15858 return this; 15859 15860 element = element.holder || element; 15861 15862 if (element.nextSibling) 15863 element.parentNode.insertBefore(this.holder, element.nextSibling); 15864 else 15865 element.parentNode.appendChild(this.holder); 15866 15867 return this; 15868 }; 15869 15870 Element.prototype.insertBefore = function (element) { 15871 if (element instanceof Set) 15872 element = element.getFirst(); 15873 15874 if (!element) 15875 return this; 15876 15877 // getFirst will return DOM node, not raphael object 15878 element = element.holder || element; 15879 15880 element.parentNode.insertBefore(this.holder, element); 15881 return this; 15882 }; 15883 15884 var theCircle = function (vml, x, y, r) { 15885 var element = thePath(vml) 15886 .hold() 15887 .moveTo(x - r, y) 15888 .arcTo(r, r, 0, 1, x + r, y) 15889 .arcTo(r, r, 0, 1, x - r, y) 15890 .render(); 15891 element.isCircle = true; 15892 return element; 15893 }; 15894 15895 var theRect = function (vml, x, y, w, h) { 15896 var element = thePath(vml) 15897 .hold() 15898 .moveTo(x, y) 15899 .lineTo(x + w, y) 15900 .lineTo(x + w, y + h) 15901 .lineTo(x, y + h) 15902 .andClose() 15903 .render(); 15904 15905 return element; 15906 }; 15907 15908 var theImage = function (vml, src, x, y, w, h) { 15909 var node = createNode("image"), 15910 style = node.style; 15911 15912 node.src = src; 15913 var element = new Element(node, vml); 15914 element.type = "image"; 15915 15916 element.attrs.x = x; 15917 element.attrs.y = y; 15918 element.attrs.width = w; 15919 element.attrs.height = h; 15920 15921 style.left = x + 'px'; 15922 style.top = y + 'px'; 15923 style.width = w; 15924 style.height = h; 15925 15926 return element; 15927 }; 15928 var theText = function (vml, x, y, text) { 15929 var node = createNode("shape"), 15930 style = node.style, 15931 path = createNode("path"), 15932 ps = path.style, 15933 o = createNode("textpath"); 15934 15935 path.v = ["m0,0 l1,0"].join(""); 15936 path.textpathok = true; 15937 15938 o.string = text; 15939 o.on = true; 15940 node.appendChild(o); 15941 node.appendChild(path); 15942 var element = new Element(node, vml); 15943 element.shape = node; 15944 element.textpath = o; 15945 element.type = "text"; 15946 15947 element.attrs.x = x; 15948 element.attrs.y = y; 15949 15950 setFillAndStroke(element, {font: availableAttrs.font, stroke: "none", fill: "#000"}); 15951 15952 style.left = x + 'px'; 15953 style.top = y + 'px'; 15954 style.width = element.attrs.width; 15955 style.height = element.attrs.height; 15956 15957 return element; 15958 }; 15959 15960 var setSize = function (width, height) { 15961 width = width || this.width; 15962 height = height || this.height; 15963 15964 this.zoom = 1; 15965 15966 if (width / height < this.ratio) 15967 width = parseInt(height * this.ratio); 15968 else 15969 height = parseInt(width / this.ratio); 15970 15971 var cs = this.canvas.style; 15972 cs.width = width + "px"; 15973 cs.height = height + "px"; 15974 cs.clip = "rect(0 " + width + "px " + height + "px 0)" 15975 return this; 15976 }; 15977 doc.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)"); 15978 try { 15979 if (!doc.namespaces.rvml) { 15980 doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml"); 15981 } 15982 var createNode = function (tagName) { 15983 return doc.createElement('<rvml:' + tagName + ' class="rvml">'); 15984 }; 15985 } catch (e) { 15986 var createNode = function (tagName) { 15987 return doc.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">'); 15988 }; 15989 } 15990 var create = function() { 15991 var con = getContainer.apply(null, arguments), 15992 container = con.container, 15993 x = con.x, 15994 y = con.y, 15995 width = con.width, 15996 s, 15997 height = con.height; 15998 if (!container) { 15999 throw new Error("VML container not found."); 16000 } 16001 var c = paper.canvas = doc.createElement("div"), 16002 cs = c.style, 16003 group = paper.group = createNode('group'), 16004 style = group.style; 16005 16006 width = parseFloat(width) || 512; 16007 height = parseFloat(height) || 342; 16008 16009 paper.ratio = width / height; 16010 16011 paper.width = width; 16012 paper.height = height; 16013 paper.coordsize = width + " " + height; 16014 16015 paper.x = 0; 16016 paper.y = 0; 16017 16018 paper.coordorigin = "0 0"; 16019 group.coordorigin = "0 0"; 16020 16021 paper.scale(1); 16022 16023 c.appendChild(paper.group); 16024 16025 cs.width = width + "px"; 16026 cs.height = height + "px"; 16027 cs.position = "absolute"; 16028 cs.clip = "rect(0 " + width + "px " + height + "px 0)"; 16029 16030 style.width = width + "px"; 16031 style.height = height + "px"; 16032 style.position = "absolute"; 16033 16034 if (container == 1) { 16035 doc.body.appendChild(c); 16036 cs.left = x + "px"; 16037 cs.top = y + "px"; 16038 container = { 16039 style: { 16040 width: width, 16041 height: height 16042 } 16043 }; 16044 } else { 16045 container.style.width = width; 16046 container.style.height = height; 16047 if (container.firstChild) { 16048 container.insertBefore(c, container.firstChild); 16049 } else { 16050 container.appendChild(c); 16051 } 16052 } 16053 for (var prop in paper) { 16054 container[prop] = paper[prop]; 16055 } 16056 container.clear = function() { 16057 while (c.firstChild) { 16058 c.removeChild(c.firstChild); 16059 } 16060 }; 16061 container.raphael = R; 16062 container._span = doc.createElement("span"); 16063 s = container._span.style; 16064 s.position = "absolute"; 16065 s.left = "-99999px"; 16066 s.top = "-99999px"; 16067 s.padding = 0; 16068 s.margin = 0; 16069 s.lineHeight = 1; 16070 s.display = "inline"; 16071 16072 c.appendChild(container._span); 16073 return container; 16074 }; 16075 16076 paper.pan = function(dx, dy, absolute) { 16077 if (absolute) { 16078 this.x = -dx; 16079 this.y = -dy; 16080 } else { 16081 this.x -= dx; 16082 this.y -= dy; 16083 } 16084 16085 this.group.coordorigin = this.x / this.zoom + ' ' + this.y / this.zoom; 16086 }; 16087 16088 paper.scale = function(zoom) { 16089 this.zoom = zoom; 16090 this.group.coordorigin = this.x / this.zoom + ' ' + this.y / this.zoom; 16091 this.group.coordsize = this.width / this.zoom + " " + this.height / this.zoom; 16092 }; 16093 16094 paper.remove = function() { 16095 this.group.parentNode.removeChild(this.group); 16096 }; 16097 paper.safari = function() {}; 16098 } 16099 16100 // rest 16101 16102 // Events 16103 var addEvent = (function() { 16104 var map = { 16105 'mousemove': 'touchmove', 16106 'mousedown': 'touchstart', 16107 'mouseup': 'touchend' 16108 }, 16109 revMap = { 16110 'touchmove': 'mousemove', 16111 'touchstart': 'mousedown', 16112 'touchend': 'mouseup' 16113 }; 16114 16115 if (doc.addEventListener) { 16116 return function (obj, type, fn, element) { 16117 var realName = supportsTouch && map[type] ? map[type] : type; 16118 var f = function (e) { 16119 if (typeof jQuery != undefined) 16120 e = jQuery.event.fix(e); 16121 16122 if (supportsTouch && typeof map[type] != 'undefined') { 16123 if (e.type == 'touchend') { 16124 if (e.changedTouches.length == 1) { 16125 R.extend(e, { 16126 type: revMap[e.type], 16127 clientX: e.changedTouches[0].clientX, 16128 clientY: e.changedTouches[0].clientY, 16129 pageX: e.changedTouches[0].pageX, 16130 pageY: e.changedTouches[0].pageY 16131 }); 16132 } 16133 } else if (e.touches.length == 1) { 16134 R.extend(e, { 16135 type: revMap[e.type], 16136 clientX: e.touches[0].clientX, 16137 clientY: e.touches[0].clientY, 16138 pageX: e.touches[0].pageX, 16139 pageY: e.touches[0].pageY 16140 }); 16141 } 16142 } 16143
16144 return fn.call(element, e); 16145 }; 16146 16147 obj.addEventListener(realName, f, false); 16148 return function() { 16149 obj.removeEventListener(realName, f, false); 16150 return true; 16151 }; 16152 }; 16153 } else if (doc.attachEvent) { 16154 return function (obj, type, fn, element) { 16155 var f = function (e) { 16156 if (typeof jQuery != undefined) 16157 e = jQuery.event.fix(e); 16158 return fn.call(element, e || win.event); 16159 }; 16160 var detacher = function() { 16161 obj.detachEvent("on" + type, f); 16162 return true; 16163 }; 16164 if (type == "mouseover") { 16165 obj.attachEvent("onmouseenter", f); 16166 return function() { 16167 obj.detachEvent("onmouseenter", f); 16168 return detacher; 16169 }; 16170 } else if (type == "mouseout") { 16171 obj.attachEvent("onmouseleave", f); 16172 return function() { 16173 obj.detachEvent("onmouseleave", f); 16174 return detacher; 16175 }; 16176 } else 16177 obj.attachEvent("on" + type, f); 16178 return detacher; 16179 }; 16180 } 16181 })(); 16182 for (var i = events.length; i--;) { 16183 (function (eventName) { 16184 Element.prototype[eventName] = function (fn, trackDefault) { 16185 if (typeof fn == "function") { 16186 this.events = this.events || {}; 16187 this.events[eventName] = this.events[eventName] || {}; 16188 this.events[eventName][fn] = this.events[eventName][fn] || []; 16189 this.events[eventName][fn].push(addEvent(this.shape || this.node, eventName, trackDefault && jQuery && jQuery._transparentBindListener ? jQuery._transparentBindListener(eventName, fn) : fn, this)); 16190 } 16191 return this; 16192 }; 16193 Element.prototype["un" + eventName] = function (fn) { 16194 this.events && 16195 this.events[eventName] && 16196 this.events[eventName][fn] && 16197 this.events[eventName][fn].length && 16198 this.events[eventName][fn].shift()() && 16199 !this.events[eventName][fn].length && 16200 delete this.events[eventName][fn]; 16201 }; 16202 16203 })(events[i]); 16204 } 16205 paper.circle = function (x, y, r) { 16206 return theCircle(this, x, y, r); 16207 }; 16208 paper.rect = function (x, y, w, h) { 16209 return theRect(this, x, y, w, h); 16210 }; 16211 paper.path = function (params, pathString) { 16212 return thePath(this, params, pathString); 16213 }; 16214 paper.image = function (src, x, y, w, h) { 16215 return theImage(this, src, x, y, w, h); 16216 }; 16217 paper.text = function (x, y, text) { 16218 return theText(this, x, y, text); 16219 }; 16220 16221 paper.set = function (itemsArray) { 16222 return new Set(itemsArray); 16223 }; 16224 paper.setSize = setSize; 16225 16226 Element.prototype.scale = function (x, y, cx, cy) { 16227 y = y || x; 16228 16229 var dx, dy, dcx, dcy, 16230 a = this.attrs; 16231 16232 if (x != 0) { 16233 var rot = this.attrs.rotation || 0; 16234 this.attrs.rcx = this.attrs.rcx || 0; 16235 this.attrs.rcy = this.attrs.rcy || 0; 16236 16237 if (rot != 0) 16238 this.rotate(0, this.attrs.rcx, this.attrs.rcy); 16239 16240 var bb = this.getBBox(), 16241 rcx = bb.x + bb.width / 2, 16242 rcy = bb.y + bb.height / 2, 16243 kx = x / this._.sx, 16244 ky = y / this._.sy; 16245 16246 cx = (+cx || cx == 0) ? cx : rcx; 16247 cy = (+cy || cy == 0) ? cy : rcy; 16248 16249 var dirx = Math.round(x / Math.abs(x)), 16250 diry = Math.round(y / Math.abs(y)), 16251 s = this.node.style, 16252 ncx = cx + (rcx - cx) * dirx * kx, 16253 ncy = cy + (rcy - cy) * diry * ky; 16254 16255 16256 switch (this.type) { 16257 case "rect": 16258 case "image": 16259 var neww = a.width * dirx * kx, 16260 newh = a.height * diry * ky; 16261 16262 this.attr({ 16263 width: neww, 16264 height: newh, 16265 x: ncx - neww / 2, 16266 y: ncy - newh / 2 16267 }); 16268 break; 16269 case "circle": 16270 case "ellipse": 16271 this.attr({ 16272 rx: a.rx * kx, 16273 ry: a.ry * ky, 16274 r: a.r * kx, 16275 cx: ncx, 16276 cy: ncy 16277 }); 16278 break; 16279 case "path": 16280 this.transform(function(point) { 16281 var u = point.type.toUpperCase(); 16282 if (point.type == u) { 16283 point.x = cx + (point.x - cx) * kx; 16284 point.y = cy + (point.y - cy) * ky; 16285 } else { 16286 point.x *= kx; 16287 point.y *= ky; 16288 } 16289 16290 if (u == "A") { 16291 point.rx *= kx; 16292 point.ry *= ky; 16293 } 16294 return point; 16295 }); 16296 break; 16297 }
16298 if (this.type in {text: 1, image:1} && (dirx != 1 || diry != 1)) { 16299 if (this.transformations) { 16300 this.transformations[2] = "scale(".concat(dirx, ",", diry, ")"); 16301 this.node.setAttribute("transform", combinedTrans(this.transformations)); 16302 dx = (dirx == -1) ? -a.x - (neww || 0) : a.x; 16303 dy = (diry == -1) ? -a.y - (newh || 0) : a.y; 16304 this.attr({x: dx, y: dy}); 16305 a.fx = dirx - 1; 16306 a.fy = diry - 1; 16307 } else { 16308 this.node.filterMatrix = " progid:DXImageTransform.Microsoft.Matrix(M11=".concat(dirx, 16309 ", M12=0, M21=0, M22=", diry, 16310 ", Dx=0, Dy=0, sizingmethod='auto expand', filtertype='bilinear')"); 16311 16312 s.filter = (this.node.filterMatrix || "") + (this.node.filterOpacity || ""); 16313 } 16314 } else { 16315 if (this.transformations) { 16316 this.transformations[2] = ""; 16317 this.node.setAttribute("transform", combinedTrans(this.transformations)); 16318 a.fx = 0; 16319 a.fy = 0; 16320 } else { 16321 this.node.filterMatrix = ""; 16322 s.filter = (this.node.filterMatrix || "") + (this.node.filterOpacity || ""); 16323 } 16324 } 16325 a.scale = [x, y, cx, cy].join(" "); 16326 this._.sx = x; 16327 this._.sy = y; 16328 16329 this.attrs.rcx = cx + (this.attrs.rcx - cx) * kx; 16330 this.attrs.rcy = cy + (this.attrs.rcy - cy) * ky; 16331 16332 if (rot != 0) 16333 this.rotate(rot, this.attrs.rcx, this.attrs.rcy); 16334 } 16335 return this; 16336 }; 16337 16338 Element.prototype.translate = function (x, y) { 16339 if (x == null) { 16340 return {x: this._.tx, y: this._.ty}; 16341 } 16342 this._.tx += +x; 16343 this._.ty += +y; 16344 16345 var rot = this.attrs.rotation || 0; 16346 this.attrs.rcx = this.attrs.rcx || 0; 16347 this.attrs.rcy = this.attrs.rcy || 0; 16348 16349 if (rot != 0 || R.vml) 16350 this.rotate(0, this.attrs.rcx, this.attrs.rcy); 16351 16352 switch (this.type) { 16353 case "circle": 16354 case "ellipse": 16355 this.attr({cx: +x + this.attrs.cx, cy: +y + this.attrs.cy}); 16356 break; 16357 case "rect": 16358 case "image": 16359 case "text": 16360 this.attr({x: +x + this.attrs.x, y: +y + this.attrs.y}); 16361 break; 16362 case "path": 16363 this.transform(function(point) { 16364 if (point.type == point.type.toUpperCase()) { 16365 point.x += +x; 16366 point.y += +y; 16367 } 16368 return point; 16369 }); 16370 break; 16371 } 16372 16373 this.attrs.rcx = (this.attrs.rcx || 0) + x; 16374 this.attrs.rcy = (this.attrs.rcy || 0) + y; 16375 16376 if (rot != 0 || R.vml) 16377 this.rotate(rot, this.attrs.rcx, this.attrs.rcy); 16378 16379 return this; 16380 }; 16381 16382 // Set 16383 var Set = function (items) { 16384 this.items = []; 16385 this.length = 0; 16386 if (items) { 16387 for (var i = 0, ii = items.length; i < ii; i++) { 16388 if (items[i] && (items[i].constructor == Element || items[i].constructor == Set)) { 16389 this[this.items.length] = this.items[this.items.length] = items[i]; 16390 this.length++; 16391 } 16392 } 16393 } 16394 }; 16395 Set.prototype.push = function() { 16396 var item, 16397 len; 16398 for (var i = 0, ii = arguments.length; i < ii; i++) { 16399 item = arguments[i]; 16400 if (item && (item.constructor == Element || item.constructor == Set && 16401 this.items.indexOf(item) === -1)) { 16402 len = this.items.length; 16403 this[len] = this.items[len] = item; 16404 this.length++; 16405 } 16406 } 16407 return this; 16408 }; 16409 Set.prototype.pop = function (id) { 16410 if (typeof id == 'object') { 16411 var i = 0; 16412 while (i < this.items.length && this.items[i] != id) 16413 i++; 16414 id = i; 16415 } 16416 var res = this.items.splice(id, 1)[0]; 16417 for (var j = id, jj = this.items.length; j < jj; j++) 16418 this[j] = this[j + 1]; 16419 16420 delete this[jj + 1]; 16421 this.length--; 16422 return res; 16423 }; 16424 for (var method in Element.prototype) { 16425 Set.prototype[method] = (function (methodname) { 16426 return function() { 16427 for (var i = 0, ii = this.items.length; i < ii; i++) { 16428 this.items[i][methodname].apply(this.items[i], arguments); 16429 } 16430 return this; 16431 }; 16432 }
16432)(method); 16433 } 16434 Set.prototype.attr = function (name, value) { 16435 if (name && R.isArray(name) && typeof name[0] == "object") { 16436 for (var j = 0, jj = name.length; j < jj; j++) { 16437 this.items[j].attr(name[j]); 16438 } 16439 } else { 16440 for (var i = 0, ii = this.items.length; i < ii; i++) { 16441 this.items[i].attr.apply(this.items[i], arguments); 16442 } 16443 } 16444 return this; 16445 }; 16446 16447 Set.prototype.insertAfter = function(element, before) { 16448 if (this.items.length == 0) 16449 return this; 16450 16451 var i, length = this.items.length, 16452 sorted = this.items.slice(0, length), 16453 nodesArray = this.getAllSiblings(); 16454 16455 for (i = 0; i < length; i++) 16456 sorted[i]._position = nodesArray.indexOf(this.items[i].node || this.items[i].holder); 16457 16458 sorted.sort(function(a, b) { 16459 return a._position - b._position; 16460 }); 16461 16462 if (before) 16463 for (i = length - 1; i >= 0 ; i--) { 16464 sorted[i].insertBefore(element); 16465 delete sorted._position; 16466 element = sorted[i]; 16467 } 16468 else 16469 for (i = 0; i < length; i++) { 16470 sorted[i].insertAfter(element); 16471 delete sorted._position; 16472 element = sorted[i]; 16473 } 16474 16475 return this; 16476 }; 16477 16478 Set.prototype.insertBefore = function(element) { 16479 this.insertAfter(element, true); 16480 }; 16481 16482 Set.prototype.getAllSiblings = function() { 16483 var nodesCollection = (this.items[0].node || this.items[0].holder).parentNode.childNodes, 16484 nodesArray = [], i, length = nodesCollection.length; 16485 16486 for (i = 0; i < length; i++) 16487 nodesArray.push(nodesCollection[i]); 16488 16489 return nodesArray; 16490 }; 16491 16492 Set.prototype.getFirst = function(last) { 16493 if (this.items.length == 0) 16494 return false; 16495 16496 var pos = last ? -1 : Number.MAX_VALUE, 16497 i, length = this.items.length, 16498 nodesArray = this.getAllSiblings(); 16499 compare = last ? Math.max : Math.min; 16500 16501 for (i = 0; i < length; i++) 16502 pos = compare(nodesArray.indexOf(this.items[i].node || this.items[i].holder)); 16503 16504 return nodesArray[pos]; 16505 }; 16506 16507 Set.prototype.getLast = function() { 16508 return this.getFirst(true); 16509 }; 16510 16511 Set.prototype.getBBox = function() { 16512 var x = [], 16513 y = [], 16514 w = [], 16515 h = []; 16516 for (var i = this.items.length; i--;) { 16517 var box = this.items[i].getBBox(); 16518 x.push(box.x); 16519 y.push(box.y); 16520 w.push(box.x + box.width); 16521 h.push(box.y + box.height); 16522 } 16523 x = Math.min.apply(Math, x); 16524 y = Math.min.apply(Math, y); 16525 return { 16526 x: x, 16527 y: y, 16528 width: Math.max.apply(Math, w) - x, 16529 height: Math.max.apply(Math, h) - y 16530 }; 16531 }; 16532 16533 R.ninja = function() { 16534 var r = window.Raphael; 16535 if (oldRaphael.was) { 16536 window.Raphael = oldRaphael.is; 16537 } else { 16538 try { 16539 delete window.Raphael; 16540 } catch (e) { 16541 window.Raphael = void(0); 16542 } 16543 } 16544 return r; 16545 }; 16546 R.el = Element.prototype; 16547 return R; 16548})(); 16549/*globals myDeco, $, Stack */ 16550/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 16551 sloppy: true, white: true */ 16552/** 16553 * @fileOverview Lib for matrix operations 16554 */ 16555 16556/** 16557 * Constructor to create rows x cols matrix and fill it with zeros 16558 * @param {Number} rows Number of rows 16559 * @param {Number} cols Number of cols 16560 * @param {Number} identity Initialize diagonal (identity when true or 1) matrix 16561 */ 16562function Matrix(rows, cols, identity) { 16563 var i, j, 16564 row = []; 16565 16566 for (j = 0; j < cols; j++) { 16567 row[j] = 0; 16568 } 16569 16570 for (i = 0; i < rows; i++) { 16571 this.push(row.slice(0, cols)); 16572 } 16573 16574 if (identity) { 16575 // Converting identity value to number. 16576 identity *= 1; 16577 for (i = 0; i < Math.min(rows, cols); i++) { 16578 this[i][i] = identity; 16579 } 16580 } 16581 16582 this.rows = rows; 16583 this.cols = cols; 16584 16585 $.extend(this, { 16586 /** 16587 * Multiply this matrix on given one. 16588 * @param {Matrix} m Second matrix to multiply on 16589 * @returns {Matrix} New matrix with result of multiplication or false, if it's impossible to multiply 16590 */ 16591 multiply: function(m) { 16592 if (this.cols !== m.rows) { 16593 return false;
16594 } 16595 16596 var result = new Matrix(this.rows, m.cols), i, j, k; 16597 16598 if (this.rows === 4 && m.cols === 4) { 16599 result[0][0] = this[0][0] * m[0][0] + this[0][1] * m[1][0] + this[0][2] * m[2][0] + this[0][3] * m[3][0]; 16600 result[0][1] = this[0][0] * m[0][1] + this[0][1] * m[1][1] + this[0][2] * m[2][1] + this[0][3] * m[3][1]; 16601 result[0][2] = this[0][0] * m[0][2] + this[0][1] * m[1][2] + this[0][2] * m[2][2] + this[0][3] * m[3][2]; 16602 result[0][3] = this[0][0] * m[0][3] + this[0][1] * m[1][3] + this[0][2] * m[2][3] + this[0][3] * m[3][3]; 16603 result[1][0] = this[1][0] * m[0][0] + this[1][1] * m[1][0] + this[1][2] * m[2][0] + this[1][3] * m[3][0]; 16604 result[1][1] = this[1][0] * m[0][1] + this[1][1] * m[1][1] + this[1][2] * m[2][1] + this[1][3] * m[3][1]; 16605 result[1][2] = this[1][0] * m[0][2] + this[1][1] * m[1][2] + this[1][2] * m[2][2] + this[1][3] * m[3][2]; 16606 result[1][3] = this[1][0] * m[0][3] + this[1][1] * m[1][3] + this[1][2] * m[2][3] + this[1][3] * m[3][3]; 16607 result[2][0] = this[2][0] * m[0][0] + this[2][1] * m[1][0] + this[2][2] * m[2][0] + this[2][3] * m[3][0]; 16608 result[2][1] = this[2][0] * m[0][1] + this[2][1] * m[1][1] + this[2][2] * m[2][1] + this[2][3] * m[3][1]; 16609 result[2][2] = this[2][0] * m[0][2] + this[2][1] * m[1][2] + this[2][2] * m[2][2] + this[2][3] * m[3][2]; 16610 result[2][3] = this[2][0] * m[0][3] + this[2][1] * m[1][3] + this[2][2] * m[2][3] + this[2][3] * m[3][3]; 16611 result[3][0] = this[3][0] * m[0][0] + this[3][1] * m[1][0] + this[3][2] * m[2][0] + this[3][3] * m[3][0]; 16612 result[3][1] = this[3][0] * m[0][1] + this[3][1] * m[1][1] + this[3][2] * m[2][1] + this[3][3] * m[3][1]; 16613 result[3][2] = this[3][0] * m[0][2] + this[3][1] * m[1][2] + this[3][2] * m[2][2] + this[3][3] * m[3][2]; 16614 result[3][3] = this[3][0] * m[0][3] + this[3][1] * m[1][3] + this[3][2] * m[2][3] + this[3][3] * m[3][3]; 16615 } else if (this.rows === 4 && m.cols === 1) { 16616 result[0][0] = this[0][0] * m[0][0] + this[0][1] * m[1][0] + this[0][2] * m[2][0] + this[0][3] * m[3][0]; 16617 result[1][0] = this[1][0] * m[0][0] + this[1][1] * m[1][0] + this[1][2] * m[2][0] + this[1][3] * m[3][0]; 16618 result[2][0] = this[2][0] * m[0][0] + this[2][1] * m[1][0] + this[2][2] * m[2][0] + this[2][3] * m[3][0]; 16619 result[3][0] = this[3][0] * m[0][0] + this[3][1] * m[1][0] + this[3][2] * m[2][0] + this[3][3] * m[3][0]; 16620 } else { 16621 for (i = 0; i < result.rows; i++) { 16622 for (j = 0; j < result.cols; j++) { 16623 for (k = 0; k < this.cols; k++) { 16624 result[i][j] += this[i][k] * m[k][j]; 16625 } 16626 } 16627 } 16628 } 16629 16630 return result; 16631 }, 16632 16633 /** 16634 * Get particular row of the this matrix 16635 * @param {Integer} n Number of row to extract 16636 * @returns {Matrix} New 1 x cols matrix with data from n'th row of this one 16637 */ 16638 row: function(n) { 16639 var row = new Matrix(1, this.cols), i; 16640 16641 for (i = 0; i < this.cols; i++) { 16642 row[0][i] = this[n][i]; 16643 } 16644 16645 return row; 16646 }, 16647 16648 /** 16649 * Get particular column of the this matrix 16650 * @param {Integer} n Number of col to extract 16651 * @returns {Matrix} New rows x 1 matrix with data from n'th col of this one 16652 */ 16653 col: function(n) { 16654 var col = new Matrix(this.rows, 1), i; 16655 16656 for (i = 0; i < this.rows; i++) { 16657 col[i][0] = this[i][n]; 16658 } 16659 16660 return col; 16661 }, 16662 16663 /** 16664 * Get string representation of the matrix 16665 * @returns {String} 16666 */ 16667 toString: function() { 16668 var i, result = ''; 16669 16670 for (i = 0; i < this.rows; i++) { 16671 result += this[i].join(",\t") + "\n"; 16672 } 16673 16674 return result; 16675 } 16676 }); 16677} 16678 16679Matrix.prototype = new Stack(); 16680/*globals myDeco, $, T, Matrix */ 16681/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 16682 sloppy: true, white: true */ 16683/** 16684 * @fileOverview Lib for matrix transformations 16685 */ 16686 16687/** 16688 * Constructor to create transform object 16689 */ 16690function Transform() { 16691 this.reset(); 16692} 16693 16694/** 16695 * Add rotate transformation (using 0,0,0 as rotation center) 16696 * @param {String} axis Axis to rotate around 16697 * @param {Number} angle Angle in degrees to rotate on. 16698 * @returns {Transform} this 16699 */ 16700Transform.prototype.rotate = function(axis, angle) { 16701 var _cos = T.cos(angle), 16702 _sin = T.sin(angle), 16703 _t = { 16704 'z': {m00: [0, 0], m01: [0, 1], m10: [1, 0], m11: [1, 1]}, 16705 'x': {m00: [1, 1], m01: [1, 2], m10: [2, 1], m11: [2, 2]}, 16706 'y': {m00: [0, 0], m01: [2, 0], m10: [0, 1], m11: [2, 2]} 16707 }[axis], 16708 rMatrix = new Matrix(4, 4, true); 16709 16710 rMatrix[_t.m00[0]][_t.m00[1]] = _cos;
16711 rMatrix[_t.m01[0]][_t.m01[1]] = - _sin; 16712 rMatrix[_t.m10[0]][_t.m10[1]] = _sin; 16713 rMatrix[_t.m11[0]][_t.m11[1]] = _cos; 16714 16715 this.matrix = rMatrix.multiply(this.matrix); 16716 16717 return this; 16718}; 16719 16720/** 16721 * Add isometric transformation (http://www.gamedev.net/reference/articles/article1269.asp) 16722 * @param {Number} angle Angle of projection 16723 * @returns {Transform} this. 16724 */ 16725Transform.prototype.isometric = function(isoAngle, rotAngle) { 16726 this.scale(1, -1, T.cos(isoAngle)).rotate('z', rotAngle - 45).rotate('x', 90 - isoAngle); 16727 return this; 16728}; 16729 16730/** 16731 * Apply translate transformation 16732 * @param {Number|Point} delta X delta or delta object 16733 * @param {Number} y Y delta 16734 * @param {Number} z Z delta 16735 * @returns {Transform} this 16736 */ 16737Transform.prototype.translate = function(delta, y, z) { 16738 var dx, dy, dz, tMatrix; 16739 16740 if (typeof delta === 'number') { 16741 dx = delta || 0; 16742 dy = y || 0; 16743 dz = z || 0; 16744 } else { 16745 dx = delta.x; 16746 dy = delta.y; 16747 dz = delta.z; 16748 } 16749 16750 tMatrix = new Matrix(4, 4, true); 16751 tMatrix[0][3] = dx; 16752 tMatrix[1][3] = dy; 16753 tMatrix[2][3] = dz; 16754 16755 this.matrix = tMatrix.multiply(this.matrix); 16756 16757 return this; 16758}; 16759 16760/** 16761 * Apply scale transformation 16762 * @param {Number} scaleX X Scaling factor 16763 * @param {Number} scaleY Y Scaling factor 16764 * @param {Number} scaleZ Z Scaling factor 16765 * @returns {Transform} this 16766 */ 16767Transform.prototype.scale = function(scaleX, scaleY, scaleZ) { 16768 scaleY = (typeof scaleY === 'undefined' ? scaleX : scaleY); 16769 scaleZ = (typeof scaleZ === 'undefined' ? scaleX : scaleZ); 16770 16771 var sMatrix = new Matrix(4, 4, true); 16772 sMatrix[0][0] = scaleX; 16773 sMatrix[1][1] = scaleY; 16774 sMatrix[2][2] = scaleZ; 16775 16776 this.matrix = sMatrix.multiply(this.matrix); 16777 return this; 16778}; 16779 16780 16781/** 16782 * Reset transformation matrix 16783 * @returns {Transform} this 16784 */ 16785Transform.prototype.reset = function() { 16786 this.matrix = new Matrix(4, 4, true); 16787 return this; 16788}; 16789/*globals myDeco, $, Point */ 16790/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 16791 sloppy: true, white: true */ 16792/** 16793 * Utility function to find intersection point of two sections (walls). 16794 * @param {Array of Point} wall1 First wall (array of two Points) 16795 * @param {Array of Point} wall2 Second wall 16796 * @param {Boolean} real check if intersection point is in bound of sections. 16797 * @param {Boolean} relative Return relative coords on both lines or real Point. 16798 * @return {Point} 16799 */ 16800function findWallIntersection (wall1, wall2, real, relative) { 16801 // Math from http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/ 16802 var dx1 = wall1[1].x - wall1[0].x, 16803 dy1 = wall1[1].y - wall1[0].y, 16804 dx2 = wall2[1].x - wall2[0].x, 16805 dy2 = wall2[1].y - wall2[0].y, 16806 d = dx2 * dy1 - dx1 * dy2, 16807 point, di, dx, dy, s, t; 16808 16809 if (Math.abs(d) < 0.001) { 16810 return false; 16811 } 16812 16813 di = 1.0 / d; 16814 dx = wall2[0].x - wall1[0].x; 16815 dy = wall2[0].y - wall1[0].y; 16816 s = (dx2 * dy - dy2 * dx) * di; 16817 16818 if (real && (s > 1 || s < 0)) { 16819 return false; 16820 } 16821 16822 t = (dx1 * dy - dy1 * dx) * di; 16823 16824 if (real && (t > 1 || t < 0)) { 16825 return false; 16826 } 16827 16828 point = new Point(wall1[0].x + dx1 * s, 16829 wall1[0].y + dy1 * s, 16830 0); 16831 16832 if (relative) { 16833 return { 16834 s: s, 16835 t: t, 16836 point: point 16837 }; 16838 } else { 16839 return point; 16840 } 16841} 16842 16843function weilerAtherton(poly1, poly2, operation) { 16844 // Algo from http://cs1.bradley.edu/public/jcm/weileratherton.html 16845 // http://www.anirudh.net/practical_training/main/node10.html 16846 16847 // convert array to doubly circular linked list 16848 function arrayToLinkList(array) { 16849 var i, length = array.length, item; 16850 16851 for (i = 0; i < length; i++) { 16852 item = array[i]; 16853 16854 item.prev = i === 0 ? array[length - 1] : array[i - 1]; 16855 item.next = i === length - 1 ? array[0] : array[i + 1]; 16856 } 16857 16858 return array[0]; 16859 } 16860 16861 // convert doubly circular linked list to array 16862 function linkListToArray(item) { 16863 var result = [], 16864 curr = item, first = true; 16865 16866 while (curr !== item || first) { 16867 first = false; 16868 result.push(curr); 16869 curr = curr.next; 16870 } 16871 16872 return result; 16873 } 16874 16875 // Break linked list apart 16876 function breakLinkList(item) { 16877 var temp, curr = item; 16878 16879 while (curr !== null) { 16880 curr.prev = null; 16881 curr.link = null; 16882 temp = curr.next; 16883 curr.next = null; 16884 curr = temp; 16885 } 16886 } 16887 16888 // Sort intersection points added between points of original poly 16889 // by their position on edge to keep original order of points in poly 16890 function sortAndInject(orig, inter) { 16891 var i, len1 = orig.length, 16892 j, len2, result = [], 16893 sortFunc = function (a, b) { 16894 return b.pos - a.pos; 16895 }; 16896 16897 for (i = 0; i < len1; i++) { 16898 result.push(orig[i]); 16899 16900 if (inter[i]) { 16901 inter[i].sort(sortFunc); 16902 16903 for (j = 0, len2 = inter[i].length; j < len2; j++) { 16904 result.push(inter[i][j]); 16905 } 16906 } 16907 } 16908 16909 return result; 16910 } 16911 16912 var poly1Intersections = [], poly2Intersections = [], enterings = [], 16913 i1, i2, j1, j2, 16914 len1 = poly1.length, len2 = poly2.length, 16915 intersection, pointInPoly1, pointinPoly2, enterTest, result, 16916 currPoint, startPoint, clippedPolys = [], poly, first; 16917 16918 16919 // Find intersections between any edge of poly1ect poly and any edge of poly2 poly. 16920 for (i1 = 0, i2 = len1 - 1; i1 < len1; i2 = i1++) { 16921 for (j1 = 0, j2 = len2 - 1; j1 < len2; j2 = j1++) {
16922 intersection = findWallIntersection([poly1[i1], poly1[i2]], [poly2[j1], poly2[j2]], true, true); 16923 if (intersection) { 16924 16925 if (!poly1Intersections[i2]) { 16926 poly1Intersections[i2] = []; 16927 16928 poly1Intersections[i2].from = poly1[i2]; 16929 poly1Intersections[i2].to = poly1[i1]; 16930 } 16931 16932 if (!poly2Intersections[j2]) { 16933 poly2Intersections[j2] = []; 16934 16935 poly2Intersections[j2].from = poly2[j2]; 16936 poly2Intersections[j2].to = poly2[j1]; 16937 } 16938 16939 // Check, if edge of poly1 poly is entering to poly2 poly. 16940 enterTest = poly1[i2].clone().rotate(poly2[j1], -poly2[j2].angleTo(poly2[j1])); 16941 16942 pointInPoly1 = intersection.point.clone(); 16943 pointinPoly2 = intersection.point.clone(); 16944 16945 pointinPoly2.entering = (enterTest.y >= poly2[j1].y); 16946 16947 if (pointinPoly2.entering) { 16948 enterings.push(pointinPoly2); 16949 } 16950 16951 // create links between same intersection points inserted into poly1 and poly2 polys 16952 pointInPoly1.link = pointinPoly2; 16953 pointInPoly1.pos = intersection.s; 16954 pointinPoly2.link = pointInPoly1; 16955 pointinPoly2.pos = intersection.t; 16956 pointinPoly2.inPoly2 = true; 16957 16958 // add found intersection points into poly1 and poly2 polygons for further sorting and expanding 16959 poly1Intersections[i2].push(pointInPoly1); 16960 poly2Intersections[j2].push(pointinPoly2); 16961 } 16962 } 16963 } 16964 16965 // Escape if no intersections found. 16966 if (enterings.length === 0) { 16967 return false; 16968 } 16969 16970 // Expand stored intersection points in poly1 and poly2 polygons and convert polygons to linked list 16971 poly1 = sortAndInject(poly1, poly1Intersections); 16972 16973 if (operation === 'clip') { 16974 poly1 = poly1.reverse(); 16975 } 16976 16977 poly1 = arrayToLinkList(poly1); 16978 poly2 = arrayToLinkList(sortAndInject(poly2, poly2Intersections)); 16979 16980 // Find shapes which was cut from poly1 polygon by poly2 polygon 16981 for (i1 = 0, len1 = enterings.length; i1 < len1; i1++) { 16982 if (enterings[i1]) { 16983 poly = []; 16984 clippedPolys.push(poly); 16985 16986 startPoint = currPoint = enterings[i1].link; 16987 first = true; 16988 delete(enterings[i1]); 16989 16990 while (currPoint !== startPoint || first) { 16991 first = false; 16992 16993 // Collecting only points from poly2 polygon in our resulting polygons, not 16994 // their twines from poly1 16995 poly.push((!currPoint.link || currPoint.inPoly2) ? currPoint : currPoint.link); 16996 16997 if (currPoint.link) { 16998 for (i2 = 0; i2 < len1; i2++) { 16999 if (enterings[i2] === currPoint || 17000 enterings[i2] === currPoint.link) { 17001 delete enterings[i2]; 17002 break; 17003 } 17004 } 17005 currPoint = currPoint.link.next; 17006 } else { 17007 currPoint = currPoint.next; 17008 } 17009 } 17010 poly.reverse(); 17011 } 17012 } 17013 17014 if (operation === 'clip') { 17015 result = { 17016 // new area with corners added 17017 clip: linkListToArray(poly2), 17018 // new areas clipped from old one 17019 clipped: clippedPolys, 17020 // points were added 17021 added: { 17022 subj: poly1Intersections, 17023 clip: poly2Intersections 17024 } 17025 }; 17026 } else { 17027 result = clippedPolys; 17028 } 17029 17030 // Prevent possible memleaks. 17031 breakLinkList(poly1); 17032 breakLinkList(poly2); 17033 17034 return result; 17035} 17036 17037/** 17038 * Function for determining of closed shape area 17039 * @param {Array} points 17040 * @returns {Number} 17041 */ 17042function findShapeArea(points) { 17043 var i, 17044 n = points.length, 17045 s = 0; 17046 17047 if (n == 0) 17048 return 0; 17049 17050 // Classical math from http://en.wikipedia.org/wiki/Polygon#Area_and_centroid 17051 for (i = 0; i < n - 1; i++) { 17052 s += points[i].x * points[i + 1].y - points[i + 1].x * points[i].y; 17053 } 17054 17055 s += points[n - 1].x * points[0].y - points[0].x * points[n - 1].y; 17056 17057 return s / 2.0; 17058} 17059 17060/** 17061 * Function to determine shape center 17062 * @param {Array} points 17063 * @returns {Point} 17064 */ 17065function findPointInShape(points) { 17066 var i, j, ilen = points.length, jlen, x = 0, y = 0, jn, point; 17067 17068 // calculate center point for triangle as middle point 17069 if (points.length < 4) { 17070 for (i = 0, ilen = points.length; i < ilen; i++) { 17071 x += points[i].x; 17072 y += points[i].y; 17073 } 17074 17075 return new Point(x / ilen, y / ilen); 17076 } else { 17077 // in other case try to find diagonal 17078 for (i = 0; i < ilen; i++) { 17079 for (j = i + 2, jlen = i + ilen - 1; j < jlen; j++) { 17080 jn = j % ilen; 17081 point = new Point( 17082 (points[i].x + points[jn].x) / 2, 17083 (points[i].y + points[jn].y) / 2 17084 ); 17085 if (point.inPoly(points)) { 17086 return point; 17087 } 17088 } 17089 } 17090 } 17091}; 17092/*globals myDeco, $, FpeModel */ 17093/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 17094 sloppy: true, white: true */ 17095/** 17096 * @fileOverview FPE dispatcher 17097 */ 17098myDeco.App = { 17099 /** 17100 * views/plugins settings 17101 */ 17102 settings: { 17103 views: {}, 17104 plugins: {} 17105 }, 17106 /** 17107 * List of registered controllers 17108 * @type {Array} 17109 */ 17110 controllers: [], 17111 17112 /** 17113 * List of registered views 17114 * @type {Array} 17115 */ 17116 views: [], 17117 17118 /** 17119 * List of registered plugins 17120 * @type {Array} 17121 */ 17122 plugins: [], 17123 17124 /** 17125 * Dispatcher object 17126 */ 17127 dispatcher: { 17128 /** 17129 * Started flag 17130 * @type {Boolean} 17131 * @private 17132 */ 17133 _started: false, 17134 17135 /** 17136 * List of callbacks that will executed after app initialization 17137 * @type {Array} 17138 * @private 17139 */ 17140 _callOnInit: [], 17141 17142 /** 17143 * Init function 17144 * @param {Object} options Dispatcher startup options 17145 */ 17146 init: function(options) { 17147 // default layout and app holder 17148 options.app = options.app || $('#app'); 17149 17150 // check for layout 17151 options.layout = options.layout || options.app; 17152 17153 // instantiate model 17154 switch (options.model.type) { 17155 case '3d': 17156 myDeco.App.model = new FpeModel(options.model, myDeco.App.settings.model); 17157 break; 17158 case 'montage': 17159 myDeco.App.model = new MontageModel(options.model, {}); 17160 break; 17161 default: 17162 myDeco.App.model = new AppModel(options.model, myDeco.App.settings.model); 17163 } 17164 17165 // fill options with empty arrays if needed 17166 options = $.extend({ 17167 controllers: [], 17168 views: [], 17169 plugins: [] 17170 }, options); 17171 17172 var i, ilen, type, typeOptions, ControllerClass, controller, 17173 viewPlugins, canvas, viewOptions, view, 17174 controllers = $.grep(myDeco.App.controllers, function(c) { 17175 return $.inArray(c, options.controllers.ignore || []) < 0; 17176 }), 17177 plugins = $.grep(myDeco.App.plugins, function(c) { 17178 return $.inArray(c, options.plugins.ignore || []) < 0; 17179 }), 17180 views = $.grep(myDeco.App.views, function(c) { 17181 return $.inArray(c, options.views.ignore || []) < 0; 17182 }), 17183 typeFilter = function(item) { 17184 return item.$type === type; 17185 }, 17186 convertPlugins = function(plugin) { 17187 return { 17188 'class': plugin, 17189 'settings': myDeco.App.settings.plugins[plugin.$name] 17190 }; 17191 }; 17192 17193 // iterate views list and init found items 17194 for (i = 0, ilen = views.length; i < ilen; i++) { 17195 // view type name 17196 type = views[i].$type; 17197 17198 // view default options 17199 typeOptions = options.views && options.views[type] ? options.views[type] : {}; 17200 17201 // default controller constructor for this view 17202 ControllerClass = $.grep(controllers, typeFilter).shift(); 17203 17204 // controller if exists 17205 controller = ControllerClass ? new ControllerClass(typeOptions.model || myDeco.App.model, $.extend(true, {}, 17206 myDeco.App.settings.controllers.common || {}
17206, myDeco.App.settings.controllers[type] || {})) : undefined; 17207 17208 // list of plugin classes for current view 17209 viewPlugins = $.grep(plugins, typeFilter); 17210 canvas = options.layout.find(typeOptions.canvasSelector || '#view-' + type); 17211 17212 // finally view options 17213 viewOptions = $.extend({ 17214 model: myDeco.App.model, 17215 controller: controller, 17216 17217 // merge plugin classes with its options 17218 plugins: $.map(viewPlugins, convertPlugins), 17219 17220 // default canvas should have ID like "view-viewType" 17221 canvas: canvas.size() === 0 ? options.app : canvas, 17222 app: options.app, 17223 settings: myDeco.App.settings.views[type] 17224 }, typeOptions); 17225 17226 // view itself 17227 view = new views[i](viewOptions); 17228 // register view in controller if exists 17229 if (controller) { 17230 controller.registerView(view); 17231 } 17232 } 17233 17234 // check for stored callbacks 17235 this._started = true; 17236 $.each(this._callOnInit, function() { 17237 this.call(null); 17238 }); 17239 }, 17240 17241 /** 17242 * Lazy loaded callback execution 17243 * @param {Function} callback 17244 */ 17245 callOnInit: function(callback) { 17246 if (!$.isFunction(callback)) { 17247 return; 17248 } 17249 17250 if (this._started) { 17251 callback(); 17252 } else { 17253 this._callOnInit.push(callback); 17254 } 17255 } 17256 } 17257}; 17258(function() { 17259 myDeco.new_search = { 17260 apply_wl_settings: function(search) { 17261 var wl_filter, wl_sort; 17262 if (wl_filter = myDeco.settings.WL.WL_NEW_SEARCH_FILTER) { 17263 search.must.push(JSON.parse(wl_filter)); 17264 } 17265 if (wl_sort = myDeco.settings.WL.WL_NEW_SEARCH_SORT) { 17266 return search.sort.unshift(JSON.parse(wl_sort)); 17267 } 17268 }, 17269 apply_color_filter: function(search, color) { 17270 var cols, functions, h, hstep, l, ref, rows, s, vstep; 17271 rows = myDeco.settings.PALETTE_ROWS; 17272 cols = myDeco.settings.PALETTE_COLS; 17273 hstep = 0.5 / cols; 17274 vstep = 0.5 / rows; 17275 ref = pal2hsl(color, rows, cols), h = ref[0], s = ref[1], l = ref[2]; 17276 functions = []; 17277 if (h === 0 && s === 0) { 17278 functions.push({ 17279 exp: { 17280 color_l: { 17281 origin: l, 17282 scale: 1 / 72 17283 } 17284 } 17285 }); 17286 search.must.push({ 17287 range: { 17288 color_l: { 17289 gte: l - hstep, 17290 lte: l + hstep 17291 } 17292 } 17293 }, { 17294 term: { 17295 color_h: 0 17296 } 17297 }, { 17298 term: { 17299 color_s: 0 17300 } 17301 }); 17302 } else { 17303 functions.push({ 17304 exp: { 17305 color_h: { 17306 origin: h, 17307 scale: 5 17308 } 17309 } 17310 }, { 17311 exp: { 17312 color_s: { 17313 origin: s, 17314 scale: 1 / 60 17315 } 17316 } 17317 }, { 17318 exp: { 17319 color_l: { 17320 origin: l, 17321 scale: 1 / 9 17322 } 17323 } 17324 }); 17325 search.must.push({ 17326 range: { 17327 color_h: { 17328 gte: h - hstep, 17329 lte: h + hstep 17330 } 17331 } 17332 }, { 17333 range: { 17334 color_s: { 17335 gte: s - vstep, 17336 lte: s + vstep 17337 } 17338 } 17339 }); 17340 } 17341 return search.queries.push({ 17342 function_score: { 17343 score_mode: "multiply", 17344 functions: functions 17345 } 17346 }); 17347 }, 17348 apply_dimensions_filter: function(search, dims) { 17349 var functions; 17350 search.must.push({ 17351 range: { 17352 width: { 17353 lte: dims.w 17354 } 17355 } 17356 }, { 17357 range: { 17358 depth: { 17359 lte: dims.d 17360 } 17361 } 17362 }); 17363 search.must_not.push({ 17364 term: { 17365 level: -1 17366 } 17367 }); 17368 functions = [ 17369 { 17370 exp: { 17371 width: { 17372 origin: dims.w, 17373 scale: dims.w / 2 17374 } 17375 } 17376 }, { 17377 exp: { 17378 depth: { 17379 origin: dims.d, 17380 scale: dims.d / 2 17381 } 17382 } 17383 } 17384 ]; 17385 return search.queries.push({ 17386 function_score: { 17387 score_mode: "multiply", 17388 functions: functions, 17389 boost: 10 17390 } 17391 }); 17392 }, 17393 build_fetch_query: function(ids, kind) { 17394 var fields, filter, id, size, source; 17395 fields = '*'; 17396 source = false;
17397 size = ids.length; 17398 filter = { 17399 ids: { 17400 values: ids 17401 } 17402 }; 17403 if (kind === 'product') { 17404 size = size * 50; 17405 filter = { 17406 terms: { 17407 parent: ids 17408 } 17409 }; 17410 } 17411 if (kind === 'material') { 17412 filter = { 17413 ids: { 17414 values: (function() { 17415 var i, len, results; 17416 results = []; 17417 for (i = 0, len = ids.length; i < len; i++) { 17418 id = ids[i]; 17419 results.push(id.toString()); 17420 } 17421 return results; 17422 })() 17423 } 17424 }; 17425 fields = false; 17426 source = true; 17427 } 17428 return JSON.stringify({ 17429 filter: filter, 17430 fields: fields, 17431 _source: source, 17432 size: size 17433 }); 17434 }, 17435 build_materials_query: function(params) { 17436 var expr, must, ref, wl_filter; 17437 must = []; 17438 expr = { 17439 _source: true, 17440 from: params.from, 17441 size: params.size, 17442 filter: { 17443 bool: { 17444 must: must 17445 } 17446 }, 17447 sort: { 17448 created_at: "desc" 17449 } 17450 }; 17451 if (wl_filter = (ref = myDeco.settings.WL) != null ? ref.MATERIALS_SEARCH_FILTER : void 0) { 17452 must.push(JSON.parse(wl_filter)); 17453 } 17454 if (params.material_class) { 17455 must.push({ 17456 term: { 17457 class_name: params.material_class 17458 } 17459 }); 17460 } 17461 if (params.name) { 17462 expr.query = { 17463 match: { 17464 name: params.name 17465 } 17466 }; 17467 } 17468 return JSON.stringify(expr); 17469 }, 17470 build_query: function(params) { 17471 var expr, filter, i, len, ref, search; 17472 search = { 17473 sort: params.sort || [ 17474 { 17475 completed_at: 'desc' 17476 } 17477 ], 17478 must: [], 17479 must_not: [], 17480 queries: [] 17481 }; 17482 if (myDeco.settings.WL && !params.skip_wl_filters) { 17483 this.apply_wl_settings(search); 17484 } 17485 if (params.name) { 17486 search.queries.push({ 17487 multi_match: { 17488 query: params.name, 17489 fields: ["name", "brand^3", "supplier_name^3"] 17490 } 17491 }); 17492 } 17493 if (params.category_id) { 17494 search.must.push({ 17495 term: { 17496 tags: parseInt(params.category_id) 17497 } 17498 }); 17499 } 17500 if (params.filters) { 17501 ref = params.filters; 17502 for (i = 0, len = ref.length; i < len; i++) { 17503 filter = ref[i]; 17504 search.must.push(filter); 17505 } 17506 } 17507 if (params.color) { 17508 this.apply_color_filter(search, params.color); 17509 } 17510 if (params.dimensions) { 17511 this.apply_dimensions_filter(search, params.dimensions); 17512 } 17513 expr = { 17514 filter: { 17515 bool: { 17516 must: search.must, 17517 must_not: search.must_not 17518 } 17519 }, 17520 fields: "*", 17521 from: params.from, 17522 size: params.size, 17523 sort: search.sort 17524 }; 17525 if (search.queries.length > 0) { 17526 expr.sort.unshift({ 17527 _score: "desc" 17528 }); 17529 expr.query = { 17530 bool: { 17531 must: search.queries 17532 } 17533 }; 17534 } 17535 return JSON.stringify(expr); 17536 }, 17537 mydeco_path: function(model) { 17538 return (model.substr(0, 2)) + "/" + model; 17539 }, 17540 render_path: function(model, kind) { 17541 return "https://fp-media-img-cdn.floorplanner.com/cdb/renders/" + (this.mydeco_path(model)) + "." + kind + ".png"; 17542 }, 17543 scaled_render_path: function(model, kind, side) { 17544 return "https://fp-media-img-cdn.floorplanner.com/cdb/scaled/renders/" + (this.mydeco_path(model)) + "." + kind + ".png_w" + side + "h" + side + ".png"; 17545 }, 17546 product_image_thumb: function(base, kind) { 17547 if (kind === '110') { 17548 return "https://dnkstn0pmvirh.cloudfront.net/product_images/thumbs/110/" + base + ".jpg"; 17549 } else { 17550 return "https://dnkstn0pmvirh.cloudfront.net/product_images/nobkg/270/" + base + ".png"; 17551 } 17552 }, 17553 texture_thumb_url: function(filename) { 17554 return "https://fp-media-img-cdn.floorplanner.com/cdb/textures/floor_and_wall/thumbn
17554ails/" + filename; 17555 }, 17556 texture_path: function(filename) { 17557 return "floor_and_wall/original/" + filename; 17558 }, 17559 es_to_legacy_item: function(item) { 17560 var attributes, i, len, model, ref, view; 17561 model = item.fields.model[0]; 17562 attributes = item.fields; 17563 attributes.model = ["models/" + (this.mydeco_path(model)) + ".mi"]; 17564 attributes.bounding_box = [attributes.bbox_min.slice(0), attributes.bbox_max.slice(0)]; 17565 attributes.renders = {}; 17566 ref = ['top', 'front', 'vir270w1', 'perspective']; 17567 for (i = 0, len = ref.length; i < len; i++) { 17568 view = ref[i]; 17569 attributes.renders[view] = this.render_path(model, view); 17570 } 17571 attributes.render_path = [attributes.renders.perspective]; 17572 return attributes; 17573 }, 17574 es_to_legacy: function(data, kind) { 17575 var i, item, j, k, len, len1, len2, ref, ref1, ref2, result; 17576 switch (kind) { 17577 case 'component_search': 17578 return { 17579 results: { 17580 component: { 17581 estmatches: data.hits.total, 17582 items: (function() { 17583 var i, len, ref, results; 17584 ref = data.hits.hits; 17585 results = []; 17586 for (i = 0, len = ref.length; i < len; i++) { 17587 item = ref[i]; 17588 results.push([item._id, this.es_to_legacy_item(item)]); 17589 } 17590 return results; 17591 }).call(this) 17592 } 17593 } 17594 }; 17595 case 'fetch': 17596 result = {}; 17597 ref = data.hits.hits; 17598 for (i = 0, len = ref.length; i < len; i++) { 17599 item = ref[i]; 17600 result[item._id] = this.es_to_legacy_item(item); 17601 } 17602 return { 17603 products: result 17604 }; 17605 case 'product_search': 17606 return { 17607 results: { 17608 product: { 17609 estmatches: data.hits.total, 17610 items: (function() { 17611 var j, len1, ref1, results; 17612 ref1 = data.hits.hits; 17613 results = []; 17614 for (j = 0, len1 = ref1.length; j < len1; j++) { 17615 item = ref1[j]; 17616 results.push([item.fields.parent[0], this.es_to_product(item)]); 17617 } 17618 return results; 17619 }).call(this) 17620 } 17621 } 17622 }; 17623 case 'product_fetch': 17624 result = {}; 17625 ref1 = data.hits.hits; 17626 for (j = 0, len1 = ref1.length; j < len1; j++) { 17627 item = ref1[j]; 17628 result[item.fields.parent[0]] = this.es_to_product(item); 17629 } 17630 return { 17631 products: result 17632 }; 17633 case 'material_fetch': 17634 result = {}; 17635 ref2 = data.hits.hits; 17636 for (k = 0, len2 = ref2.length; k < len2; k++) { 17637 item = ref2[k]; 17638 result[item._id] = this.es_to_material(item._source); 17639 } 17640 return { 17641 docs: result 17642 }; 17643 case 'material_search': 17644 return { 17645 results: { 17646 materials: { 17647 estmatches: data.hits.total, 17648 items: (function() { 17649 var len3, m, ref3, results; 17650 ref3 = data.hits.hits; 17651 results = []; 17652 for (m = 0, len3 = ref3.length; m < len3; m++) { 17653 item = ref3[m]; 17654 results.push([item._id, this.es_to_material(item._source)]); 17655 } 17656 return results; 17657 }).call(this) 17658 } 17659 } 17660 }; 17661 } 17662 }, 17663 es_to_material: function(source) { 17664 var shader_settings; 17665 shader_settings = { 17666 texture_width: source.width, 17667 texture_height: source.height, 17668 diffuse_texture: this.texture_path(source.texture), 17669 diffuse: hex2Diffuse(source.color) 17670 }; 17671 $.extend(shader_settings, source.material_properties); 17672 return { 17673 description: source.description, 17674 supplier: source.supplier_name,
17675 "class": source.class_name, 17676 texture_thumbnail: this.texture_thumb_url(source.texture), 17677 shader_settings: shader_settings, 17678 name: source.name 17679 }; 17680 }, 17681 es_to_product: function(item) { 17682 var attributes, model, product_image; 17683 attributes = item.fields; 17684 attributes.available = [true]; 17685 attributes.description || (attributes.description = ['']); 17686 product_image = attributes.product_image; 17687 if (product_image) { 17688 attributes.thumbnail_nobkg_270 = [this.product_image_thumb(product_image[0], 'nobkg_270')]; 17689 attributes.thumbnail_110 = [this.product_image_thumb(product_image[0], '110')]; 17690 } else { 17691 model = item.fields.model[0]; 17692 attributes.thumbnail_nobkg_270 = [this.render_path(model, 'perspective')]; 17693 attributes.thumbnail_110 = [this.scaled_render_path(model, 'perspective', 110)]; 17694 } 17695 return attributes; 17696 }, 17697 get_used_categories: function(callback) { 17698 var expr, wl_filter; 17699 expr = { 17700 aggs: { 17701 filtered: { 17702 filter: { 17703 match_all: {} 17704 }, 17705 aggs: { 17706 categories: { 17707 terms: { 17708 field: "tags", 17709 size: 0 17710 } 17711 } 17712 } 17713 } 17714 } 17715 }; 17716 if (myDeco.settings.WL && (wl_filter = myDeco.settings.WL.WL_NEW_SEARCH_FILTER)) { 17717 expr.aggs.filtered.filter = JSON.parse(wl_filter); 17718 } 17719 return $.ajax({ 17720 type: 'post', 17721 url: myDeco.urls.ws.new_search + '?search_type=count', 17722 dataType: 'json', 17723 data: JSON.stringify(expr), 17724 success: function(data) { 17725 var bucket, keys; 17726 keys = (function() { 17727 var i, len, ref, results; 17728 ref = data.aggregations.filtered.categories.buckets; 17729 results = []; 17730 for (i = 0, len = ref.length; i < len; i++) { 17731 bucket = ref[i]; 17732 results.push(bucket.key); 17733 } 17734 return results; 17735 })(); 17736 return callback(keys); 17737 } 17738 }); 17739 } 17740 }; 17741 17742}).call(this); 17743/*globals $, hex2Diffuse, unArray, myDeco, getArchitecturalPath */ 17744/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 17745 sloppy: true, white: true */ 17746 17747if (!myDeco.App.ws) { 17748 myDeco.App.ws = {}; 17749} 17750 17751/** 17752 * Proxy module for locaStorage or flash storage. 17753 * Will work as session local storage if locaStorage or flash storage isn't 17754 * available 17755 */ 17756myDeco.App.ws.storage = { 17757 17758 /** 17759 * Data cache until storage isn't ready 17760 * @type {Object} 17761 */ 17762 _cache: {}, 17763 17764 /** 17765 * Init function, will copy local content to storage when it is ready 17766 */ 17767 init: function() { 17768 var self = this; 17769 17770 myDeco.storage.onready(function() { 17771 var key; 17772 17773 for (key in self._cache) { 17774 myDeco.storage.set(key, self._cache[key]); 17775 } 17776 17777 self.get = $.proxy(myDeco.storage.get, myDeco.storage); 17778 self.set = $.proxy(myDeco.storage.set, myDeco.storage); 17779 }); 17780 }, 17781 17782 /** 17783 * Get value routine, has the same signature as storage 17784 * @param {String} key 17785 * @param {Function} callback 17786 */ 17787 get: function(key, callback) { 17788 callback.call(null, false, this._cache[key]); 17789 }, 17790 17791 /** 17792 * Set value routine, has the same signature as storage 17793 * @param {String} key 17794 * @param {String} value 17795 */ 17796 set: function(key, value) { 17797 this._cache[key] = value; 17798 } 17799}; 17800 17801myDeco.App.ws.storage.init(); 17802 17803myDeco.App.ws.proxy = { 17804 /** 17805 * Memory components cache 17806 * @type {Object} 17807 */ 17808 _cache: {}, 17809 17810 /** 17811 * List of failed components from ws 17812 * @type {Object} 17813 */ 17814 _failed: {}, 17815 17816 /** 17817 * LS cache expire timeout in days, default is to 2 weeks 17818 * @type {Number} 17819 */ 17820 _expire: 14, 17821 17822 /** 17823 * List of available backends 17824 * @type {Object} 17825 */ 17826 _backends: { 17827 component: { 17828 // urls for fetch by id and search 17829 urls: { 17830 fetch: myDeco.urls.ws.new_search, 17831 search: myDeco.urls.ws.new_search 17832 }, 17833 // list of fields to fetch from backend 17834 display: ['bounding_box', 'level', 'surface_height', 'renders', 'name', 'price', 17835 '3ddepth', '3dheight', '3dwidth', 'labels', 'price_before_discount', 17836 'parent', 'model', 'supplier', 'supplier_id', 'render_path', 'locale', 17837 'url', 'product_available', 'description', 'category', 'category_id'], 17838 17839 fetchReqKey: 'id', 17840 fetchResKey: 'products', 17841 17842 validateKey: /^[a-z0-9]{40}$/, 17843 17844 // normalize component got from backend 17845 normalize: function(id, data) { 17846 var renders, level, bbox, result = {}, i, conf, 17847 // originally width: 0X, height: 0Y, depth: 0Z 17848 map = {width: 0, height: 1, depth: 2}; 17849 17850 if (data._normalized) { 17851 return data; 17852 } 17853 17854 // set some default values for components with configuration 17855 // and preprocess them 17856 if (data.configuration) { 17857 conf = data.configuration; 17858 result.configurations = {}; 17859 result.configuration = {}; 17860 data.bounding_box = [[0, 0, 0], [0, 0, 0]]; 17861 for (i = conf.length - 1; i >= 0; i--) { 17862 result.configurations[conf[i].name] = conf[i]; 17863 switch (conf[i].name) { 17864 case 'width': 17865 case 'height': 17866 case 'depth': 17867 data['3d' + conf[i].name] = conf[i]['default']; 17868 data.bounding_box[0][map[conf[i].name]] = 17869 - conf[i]['default'] / 2; 17870 data.bounding_box[1][map[conf[i].name]] = 17871 - data.bounding_box[0][map[conf[i].name]]; 17872 break; 17873 default: 17874 result[conf[i].name] = conf[i]['default']; 17875 } 17876 } 17877 } 17878 17879 renders = data.renders || {}; 17880 bbox = data.bounding_box; 17881 level = +data.level || 0; 17882 result = $.extend(true, result, { 17883 _normalized: true, 17884 component_id: id || '', 17885 componentData: { 17886 name: data.name || '', 17887 price: +data.price || 0, 17888 price_before_discount: +data.price_before_discount || 0, 17889 has_special_price: data.has_special_price || false, 17890 out_of_stock: data.out_of_stock || false, 17891 stock: parseInt(data.stock || "0", 10), 17892 supplier: data.supplier || '', 17893 supplier_id: data.supplier_id || '', 17894 supplier_url: (data.supplier_id && data.supplier) ? '/suppliers/' + data.supplier.slugify() + '/' + data.supplier_id + '/' : '', 17895 locale: data.locale || myDeco.settings.BASE_LOCALE, 17896 //FIXME - mdm replacement only for mydeco products, wl products urls remain the same 17897 url: data.source_url || data.url || '', 17898 available: data.product_available, 17899 width: Math.abs(bbox[0][0] - bbox[1][0]), 17900 depth: Math.abs(bbox[0][1] - bbox[1][1]), 17901 height: Math.abs(bbox[0][2] - bbox[1][2]), 17902 description: data.description || '', 17903 category: data.category || '', 17904 category_id: data.category_id || 0, 17905 '3d': { 17906 level: level, 17907 render_back: renders.back, 17908 render_bottom: renders.bottom, 17909 render_front: renders.front, 17910 render_left: renders.left, 17911 render_perspective: renders.perspective, 17912 render_right: renders.right, 17913 render_top: renders.top 17914 }, 17915 labels: data.labels ? [data.labels] : [] 17916 }, 17917 type: data.type ? "ArchitecturalComponent" : undefined, 17918 category: data.type, 17919 aperture: data.aperture, 17920 level: level, 17921 model: data.model, 17922 position: [0, 0, 0], 17923 rotation: [0, 0, 0], 17924 render_path: data.render_path, 17925 bounding_box: bbox, 17926 width: Math.abs(bbox[0][0] - bbox[1][0]), 17927 depth: Math.abs(bbox[0][1] - bbox[1][1]), 17928 height: Math.abs(bbox[0][2] - bbox[1][2]), 17929 product_id: data.parent || id || '', 17930 surface_height: +data.surface_height || 0 17931 }); 17932 17933 if (data["light.wattage"]) { 17934 result.light = { 17935 on: true, 17936 color: hex2RGB(data["light.color"]), 17937 wattage: data["light.wattage"], 17938 color_fixed: data["light.color_fixed"], 17939 wattage_fixed: data["light.wattage_fixed"]} 17940 } 17941 17942/* Turned off on Oct 23th to make Falabella WL work 17943 17944 // change url for affiliates 17945 if (result.product_id && result.componentData.locale !== 'en-gb-x-retail') { 17946 result.componentData.url = '/aff/?product=' + result.product_id; 17947 } 17948*/ 17949 if (typeof result.revision === 'undefined' && 17950 typeof result.model === 'string') { 17951 result.revision = 17952 (result.model.match(/_(\d*)\.mi/) || ['00']).pop(); 17953 } 17954 return result; 17955 }, 17956 17957 get: function(key) { 17958 if (myDeco.App.ws.components && myDeco.App.ws.components[key]) { 17959 return this.normalize('component', key, $.extend(true, {}, myDeco.App.ws.components[key])); 17960 17961 } 17962 } 17963 }, 17964 17965 material: { 17966 urls: { 17967 fetch: myDeco.urls.ws.new_materials, 17968 search: myDeco.urls.ws.new_materials 17969 }, 17970 17971 display: ['class', 'name', 'colour_value', 'texture_thumbnail', 'shader_settings', 'product_id', 17972 'texture_width', 'product_price', 'supplier', 'product_supplier_id', 'description', 'product_available'], 17973 17974 fetchReqKey: 'docid', 17975 fetchResKey: 'docs', 17976 17977 validateKey: /^[0-9]+$/, 17978 17979 normalize: function(id, data) { 17980 if (typeof data.shader_settings === 'string') { 17981 data.shader_settings = $.parseJSON(data.shader_settings); 17982 } 17983 17984 var supplier = data.supplier || '', 17985 urlReg = /http.*?cdb\/.*?\//, 17986 result = { 17987 component_id: data.component_id || id, 17988 ID: 'decor-' + (data.component_id || id), 17989 name: data.name, 17990 diffuse_texture: data.shader_settings.diffuse_texture || data.texture_thumbnail, 17991 // FIXME: needs backend support 17992 average_colour: data.shader_settings.diffuse || hex2Diffuse(data.colour_value) || [0.5, 0.5, 0.5], 17993 guid: data.product_id, 17994 product_id: data.product_id, 17995 thumbnail: data.texture_thumbnail || data.shader_settings.diffuse_texture, 17996 shader: "combi_material", 17997 texture_width: data.shader_settings.texture_width, 17998 texture_height: data.shader_settings.texture_height, 17999 ambient: data.shader_settings.ambient, 18000 use_diffusemap: data.shader_settings.use_diffusemap, 18001 shiny: data.shader_settings.shiny, 18002 diffuse: data.shader_settings.diffuse, 18003 url: data.product_url, 18004 locale: myDeco.settings.LOCALE, 18005 supplier: supplier, 18006 supplier_url: data.product_supplier_id ? '/suppliers/' + supplier.slugify() + '/' + data.product_supplier_id + '/' : '', 18007 price: data.product_price || 0,
18008 // TODO: fix this 18009 available: data.product_available, 18010 description: data.description, 18011 'class': $.inArray(data['class'], ['flooring', 'tile', 'tiles', 'wood', 'carpet']) >= 0 ? 'flooring' : 'general' 18012 }; 18013 18014 // remove prefix for textures, will generate automatically later 18015 if (typeof result.diffuse_texture === 'string' && result.diffuse_texture.indexOf('http') === 0) { 18016 result.diffuse_texture = result.diffuse_texture.replace(urlReg, ''); 18017 } 18018 18019 return result; 18020 }, 18021 18022 get: function(key) { 18023 if (typeof key === 'string' && key.substring(0, 1) === '#') { 18024 return myDeco.App.palette.getMaterial(key); 18025 } 18026 } 18027 }, 18028 // moodboard products 18029 product: { 18030 // urls for fetch by id and search 18031 urls: { 18032 fetch: myDeco.urls.ws.new_search, 18033 search: myDeco.urls.ws.new_search 18034 }, 18035 // list of fields to fetch from backend 18036 display: ['name', 'url', 'product_page_url', 'thumbnail_110', 'thumbnail_nobkg_270', 'description', 'available'], 18037 18038 fetchReqKey: 'id', 18039 fetchResKey: 'products', 18040 18041 validateKey: /^[A-Z0-9]{40}$/, 18042 18043 // normalize data from backend 18044 normalize: function(id, data) { 18045 var result = {}, i, conf; 18046 18047 if (data._normalized) { 18048 return data; 18049 } 18050 18051 result = $.extend(true, result, { 18052 _normalized: true, 18053 productData: { 18054 id: id, 18055 name: data.name || '', 18056 thumbnail: data.thumbnail_110, 18057 url: data.url || data.product_page_url, 18058 description: data.description, 18059 selectable: true, 18060 available: data.available 18061 }, 18062 thumbnail: data.thumbnail_110, 18063 thumbnailWidth: 110, 18064 thumbnailHeight: 110, 18065 image: data.thumbnail_nobkg_270 || data.thumbnail_110, 18066 // no thumbnail_nobkg_270 for marketplace products 18067 imageWidth: 270, 18068 imageHeight: 270,
18069 component_id: id // needed for compatibility with ItemsListView 18070 }); 18071 18072 return result; 18073 } 18074 } 18075 }, 18076 18077 /** 18078 * Returns is this backend type supported or not 18079 * @param {String} type Backend type 18080 * @return {Boolean} 18081 */ 18082 has: function(type) { 18083 return typeof this._backends[type] !== 'undefined'; 18084 }, 18085 18086 /** 18087 * Fetch options 18088 * @param {String} type Database type 18089 * @return {Object} 18090 */ 18091 _type: function(type) { 18092 if (!this.has(type)) { 18093 throw "Unknown backend type " + type; 18094 } 18095 18096 if (!this._cache[type]) { 18097 this._cache[type] = {}; 18098 } 18099 18100 if (!this._failed[type]) { 18101 this._failed[type] = {}; 18102 } 18103 18104 return this._backends[type]; 18105 }, 18106 18107 /** 18108 * Get entity structure by type and key 18109 * @param {String} type Database type 18110 * @param {String} key 18111 * @param {Function} callback to fire when data available 18112 */ 18113 get: function(type, key, callback) { 18114 callback = callback || $.noop; 18115 // check for backend 18116 var self = this, 18117 options = this._type(type), 18118 result, 18119 response = function(result) { 18120 result = result || false; 18121 if (options.fix && result) { 18122 result = options.fix(result); 18123 } 18124 callback.call(self, result); 18125 }; 18126 18127 if ($.isFunction(options.get)) { 18128 result = options.get.call(this, key); 18129 } 18130 18131 if (!result) { 18132 if (this._cache[type][key]) { 18133 response(this._cache[type][key]); 18134 } else { 18135 myDeco.App.ws.storage.get(type + key, function(dummy, value) { 18136 if (value) { 18137 value = JSON.parse(value); 18138 if (value[0] + self._expire * 60 * 60 * 24 * 1000 > $.now()) { 18139 value = value[1]; 18140 } else { 18141 value = false; 18142 } 18143 } 18144 response(value || false); 18145 }); 18146 } 18147 } else { 18148 response(result); 18149 } 18150 }, 18151 18152 /** 18153 * Store key/value pair for specified type 18154 * @param {String} type Database type 18155 * @param {String} key Data key 18156 * @param {Object} value Data structure 18157 * @param {Boolean} ls Store value in LS cache (true) or memory 18158 */ 18159 set: function(type, key, value, ls) { 18160 // check for backend 18161 this._type(type); 18162 if (ls) { 18163 try { 18164 // there is no working mechanism to obtain list of all elements 18165 // in LS, so in case of exception throw just put them to memory 18166 // cache. Later it will have much smalles size since we'll have 18167 // a resizables and will be able to stop all thses caches) 18168 myDeco.App.ws.storage.set(type + key, JSON.stringify([$.now(), value])); 18169 } catch(e) { 18170 this._cache[type][key] = value; 18171 } 18172 } else { 18173 this._cache[type][key] = value; 18174 } 18175 }, 18176 18177 /** 18178 * Fetch entity structure by id(s) 18179 * @param {String} type Database type 18180 * @param {Array} list List of ID to fetch 18181 * @param {Function} callback Response callback, passed param is id=>value dictionary 18182 * @param {Boolean} ls set local (true)/memory (falce) storage, default is 18183 * false 18184 */ 18185 fetch: function(type, list, callback, ls) { 18186 list = typeof list === 'string' ? [list] : list; 18187 callback = callback || $.noop; 18188 18189 var self = this, 18190 options = this._type(type), 18191 requests = 0, 18192 data = { 18193 db: type === 'material' ? 'materials' : type, 18194 display: options.display 18195 }, 18196 result = {}, 18197 dataPreProcessed = false, 18198 processRequest = function() { 18199 if (data[options.fetchReqKey].length === 0) { 18200 return callback.call(null, result); 18201 } 18202 18203 var request_url = options.urls.fetch; 18204 var request_data = myDeco.new_search.build_fetch_query(data[options.fetchReqKey], type); 18205 18206 $.ajax({ 18207 type: 'post', 18208 url: request_url, 18209 dataType: 'json', 18210 data: request_data, 18211 success: function(data) { 18212 switch (type) { 18213 case 'component': 18214 data = myDeco.new_search.es_to_legacy(data, 'fetch'); 18215 break; 18216 case 'product': 18217 data = myDeco.new_search.es_to_legacy(data, 'product_fetch'); 18218 break; 18219 case 'material': 18220 data = myDeco.new_search.es_to_legacy(data, 'material_fetch'); 18221 } 18222 var id; 18223 for (id in data[options.fetchResKey]) { 18224 if (data[options.fetchResKey][id]) { 18225 unArray(data[options.fetchResKey][id]); 18226 result[id] = self.normalize(type, id, data[options.fetchResKey][id]); 18227 self.set(type, id, result[id], ls); 18228 } else { 18229 self._failed[type][id] = true; 18230 } 18231 } 18232 callback.call(null, result); 18233 } 18234 }); 18235 }; 18236 18237 data[options.fetchReqKey] = []; 18238 18239 if (myDeco.settings.SEARCH_SOURCE) { 18240 data.sv = myDeco.settings.SEARCH_SOURCE; 18241 } 18242 18243 $.each(list.unique(), function(i, id) { 18244 if (!self._failed[type][id]) { 18245 requests++; 18246 18247 self.get(type, id, function(struct) { 18248 if (struct) { 18249 result[id] = struct; 18250 } else if (options.validateKey.test(id)) { 18251 data[options.fetchReqKey].push(id); 18252 } 18253 requests--; 18254 18255 if (dataPreProcessed && requests === 0) { 18256 processRequest(); 18257 } 18258 }); 18259 } 18260 }); 18261 18262 dataPreProcessed = true; 18263 18264 if (requests === 0) { 18265 processRequest(); 18266 } 18267 }, 18268 18269 /** 18270 * Clear memory storage. If type isn't specified it will clear all caches 18271 * @param {String} type Cache to clear, defaults to undefined 18272 */ 18273 clear: function(type) { 18274 var i, ilen; 18275 18276 type = type === 'materials' ? 'material' : type; 18277 if (typeof type !== 'undefined') { 18278 this._type(type); 18279 type = [type]; 18280 } else { 18281 type = $.keys(this._cache); 18282 } 18283 18284 for (i = 0, ilen = type.length; i < ilen; i++) { 18285 this._cache[type[i]] = {}; 18286 } 18287 }, 18288 18289 /** 18290 * Make a search request to a backend 18291 * @param {Object}
18291 params URL params 18292 * @param {Function} callback Response callback, passed params are: 18293 * - response object (excluding result items) 18294 * - items dictionary as key=>value 18295 * - list of id of fetched entities 18296 */ 18297 search: function(params, callback) { 18298 var self = this, 18299 type = params.db === 'materials' ? 'material' : params.db, 18300 options = this._type(type); 18301 18302 if (myDeco.settings.SEARCH_SOURCE) { 18303 params.sv = myDeco.settings.SEARCH_SOURCE; 18304 } 18305 18306 params.display = options.display; 18307 18308 var facets = params.facet; 18309 var ranges = params.ranges; 18310 var request_url = options.urls.search; 18311 var request_data = params; 18312 var category_id = null; 18313 var dimensions = null; 18314 var query = params.query; 18315 var color = null; 18316 var material_class = null; 18317 18318 if (type == 'material') { 18319 // extract material class from facets 18320 if (facets && facets.length > 0) { 18321 material_class = facets[0].split(",")[1]; 18322 } 18323 18324 request_data = myDeco.new_search.build_materials_query({from: params.startrank, 18325 size: params.count, 18326 material_class: material_class, 18327 name: params.query}); 18328 } else { 18329 // extract category_id when 'new' facets are used 18330 if (facets && facets.length > 0) { 18331 for(var i = 0; i < params.facet.length; i++) { 18332 var new_facet = facets[i].match(/^new,([0-9]+)$/); 18333 if (new_facet) { 18334 category_id = new_facet[1]; 18335 break; 18336 } 18337 } 18338 } 18339 18340 // extract color from the color facet, if used 18341 if (facets && facets.length > 0) { 18342 for(var i = 0; i < params.facet.length; i++) { 18343 var color_facet = facets[i].match(/^manual_colour,([0-9a-fA-F]+)$/); 18344 if (color_facet) { 18345 color = color_facet[1]; 18346 break; 18347 } 18348 } 18349 } 18350 18351 // extract dimensions if there is a filter by size 18352 if (ranges && (ranges.length == 2) && ranges[0].match(/^3dwidth/)) { 18353 dimensions = {"w": ranges[0].split(',')[2], 18354 "d": ranges[1].split(',')[2] }; 18355 } 18356 18357 if (type == 'product') { 18358 params.skip_wl_filters = true; 18359 params.filters = params.filters || []; 18360 params.filters.push({exists: {field: "parent"}}); 18361 }; 18362 18363 request_data = myDeco.new_search.build_query({from: params.startrank, 18364 size: params.count, 18365 name: query, 18366 category_id: category_id, 18367 dimensions: dimensions, 18368 filters: params.filters, 18369 sort: params.sort, 18370 color: color, 18371 skip_wl_filters: params.skip_wl_filters}); 18372 } 18373 18374 $.ajax({ 18375 type: 'post', 18376 url: request_url, 18377 dataType: 'json', 18378 data: request_data, 18379 success: function(data) { 18380 var dictionary = {}, list = []; 18381 18382 var legacy_type = null; 18383 switch (type) { 18384 case 'product': 18385 legacy_type = 'product_search'; 18386 break; 18387 case 'component': 18388 legacy_type = 'component_search'; 18389 break; 18390 case 'material': 18391 legacy_type = 'material_search'; 18392 } 18393 data = myDeco.new_search.es_to_legacy(data, legacy_type); 18394 18395 $.each(data.results[params.db].items, function() { 18396 var id = this.shift(), 18397 model = this.shift(); 18398 18399 unArray(model); 18400 model = self.normalize(type, id, model); 18401 dictionary[id] = model; 18402 list.push(id); 18403 }); 18404 18405 delete data.results[params.db].items; 18406 18407 if ($.isFunction(callback)) { 18408 callback.call(null, data, dictionary, list); 18409 } 18410 } 18411 }); 18412 }, 18413 18414 /** 18415 * Normalize passed entity according to database type 18416 * @param {String} type Database type 18417 * @param {String} id Entity ID 18418 * @param {Object} data Entity structure 18419 */ 18420 normalize: function(type, id, data) { 18421 var options = this._type(type), 18422 struct = options.normalize.call(this, id, data); 18423 18424 return struct; 18425 }, 18426 18427 /** 18428 * Main init function 18429 */ 18430 init: function() { 18431 if (myDeco.settings.WL) { 18432 this._backends.component.display.push('source_url'); 18433 } 18434 } 18435}; 18436 18437$(function() { 18438 myDeco.App.ws.proxy.init(); 18439}); 18440/*globals Class, $, myDeco, emptyRoom */ 18441/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 18442 sloppy: true, white: true */ 18443/* 18444 * Abstract room model class for apps (FPE, montage, DMP) 18445 */ 18446var AppModel = Class.extend({ 18447 /** 18448 * Room loaded flag 18449 * @type {Boolean} 18450 */ 18451 loaded: false, 18452 18453 /** 18454 * Model settings 18455 * @type {Object} 18456 * @public 18457 */
18458 settings: { 18459 // max depth of undo 18460 historyLimit: 100 18461 }, 18462 18463 /** 18464 * Editing history object 18465 */ 18466 history: { 18467 changes: [], 18468 position: -1, 18469 counter: 0 18470 }, 18471 18472 /** 18473 * Room metadata 18474 * @type {Object} 18475 */ 18476 _metadata: {}, 18477 18478 /** 18479 * Is current room already saved or a brand new one 18480 * @type {Boolean} 18481 */ 18482 isNew: true, 18483 18484 /** 18485 * URL to a web page of this model 18486 * @type {String} 18487 */ 18488 weburl: '', 18489 18490 /** 18491 * Room WS url 18492 * @type {String} 18493 */ 18494 _ws_url: '', 18495 18496 /** 18497 * Constructor 18498 * @param {Object} rawModel Room model 18499 */ 18500 init: function(rawModel, settings) { 18501 var self = this; 18502 18503 this.settings = $.extend(this.settings, settings || {}); 18504 18505 if (rawModel) { 18506 this.load(rawModel); 18507 } else { 18508 this.reset(); 18509 } 18510 18511 myDeco.body 18512 .bind('loadRoom', function(e, urlOrModel) { 18513 myDeco.body.trigger('clearRoom'); 18514 self.loadRoom(urlOrModel); 18515 }) 18516 .bind('roomMetadata', this.metadata.bind(this)) 18517 .bind('saveRoom', function(e, options) { 18518 self.saveRoom(options); 18519 }); 18520 }, 18521 18522 metadata: function(e, callbackOrData) { 18523 if ($.isFunction(callbackOrData)) { 18524 callbackOrData(this._metadata, this._room_id); 18525 } else { 18526 this._metadata = $.extend(true, this._metadata, callbackOrData); 18527 } 18528 }, 18529 18530 /** 18531 * Reset the model to initial state 18532 */ 18533 reset: function() { 18534 this._backup = false; 18535 this._room_id = 0; 18536 this.loaded = false; 18537 this.weburl = ''; 18538 }, 18539 18540 /** 18541 * Stup for method for making web url 18542 */ 18543 _makeWebUrl: function() { 18544 this.weburl = this.isNew ? '' : myDeco.urls.roomDetails.replace(/test-room-0/, this._metadata.name.slugify() + '-' + this._room_id); 18545 }, 18546 18547 /** 18548 * Undo the last action 18549 */ 18550 undo: function() { 18551 this.history.counter = Math.max(0, this.history.counter - 1); 18552 }, 18553 18554 /** 18555 * Redo the last action 18556 */ 18557 redo: function() { 18558 this.history.counter++; 18559 }, 18560 18561 /** 18562 * Base method for room loading 18563 * @oparam {Object} rawModel 18564 */ 18565 load: function(rawModel) { 18566 var name, value; 18567 this.isNew = !rawModel.id || !myDeco.username || myDeco.username !== rawModel.author; 18568 18569 this._ws_url = rawModel.parent || this._ws_url; 18570 this._room_id = (this.isNew ? '' : rawModel.id) || ''; 18571 18572 // metadata 18573 for (name in rawModel) { 18574 if ($.inArray(name, ['model', 'products']) < 0) { 18575 value = rawModel[name]; 18576 if ($.inArray(name, ['name', 'category', 'style']) !== -1) { 18577 value = value.fromHtml(); 18578 } 18579 this._metadata[name] = value; 18580 } 18581 } 18582 this._makeWebUrl(); 18583 }, 18584 18585 /** 18586 * Load room from given url 18587 * @param {String|Model} url Url to room ws or it's model. 18588 */ 18589 loadRoom: function(urlOrModel) { 18590 var self = this; 18591 18592 myDeco.body.trigger('roomLoading', [urlOrModel]); 18593 if (typeof(urlOrModel) === 'string') { 18594 $.ajax({ 18595 type: 'get', 18596 dataType: 'json', 18597 url: urlOrModel, 18598 success: function(data) { 18599 self._ws_url = self._fixUrl(urlOrModel); 18600 self.load(data); 18601 }, 18602 error: function() { 18603 myDeco.body.trigger('roomLoadError'); 18604 } 18605 }); 18606 } else { 18607 self._ws_url = ''; 18608 self.load(urlOrModel); 18609 } 18610 18611 this.history = { 18612 changes: [], 18613 position: -1, 18614 counter: 0 18615 }; 18616 }, 18617 18618 /** 18619 * Helper to fix room url if needed 18620 * @param {String} url Incoming url 18621 * @returns {String} 18622 */ 18623 _fixUrl: function(url) { 18624 return url; 18625 }, 18626 18627 /** 18628 * Save current model to backend 18629 * 18630 * @param {Object} options Save options, expected keys/values are: 18631 * {Boolean} forceNew Create new room or replace old one 18632 * {Object} settings Top-level settings of model 18633 * {Object} data Already wrapped room data 18634 * {Function} successCallback Callback which will be executed after success of ajax request 18635 * {String} urlParams Params which will be added to the save url 18636 */ 18637 saveRoom: function(options) { 18638 // TODO: implement login 18639 if (!myDeco.username) { 18640 return false; 18641 } 18642 options = options || {}; 18643 18644 options.forceNew = options.forceNew || this.isNew || 18645 this._ws_url === '' || this.restoreOwner; 18646 18647 options.isNew = options.forceNew || !this._room_id; 18648 18649 var self = this, 18650 url = options.forceNew ? myDeco.urls.ws.userRooms : this._ws_url, 18651 roomData = options.data || this._wrapRoom(options.settings); 18652 18653 if (options.restoreOwner) {
18654 options.urlParams = (options.urlParams ? options.urlParams + '&' : '') + 'restore_owner=1'; 18655 } 18656 18657 if (options.urlParams) { 18658 url += (url.indexOf('?') === -1 ? '?' : '&') + options.urlParams; 18659 } 18660 18661 // let plugins add data to the room before saving it 18662 for (var i=0; i < myDeco.settings.SAVE_CALLBACKS.length; i++) { 18663 myDeco.settings.SAVE_CALLBACKS[i](roomData); 18664 } 18665 18666 $.ajax({ 18667 type: options.isNew ? 'POST' : 'PUT', 18668 url: url, 18669 contentType: "application/json", 18670 dataType: "json", 18671 data: JSON.stringify(roomData), 18672 beforeSend: function() { 18673 myDeco.body.trigger('roomSaving', 18674 [roomData.type, self._room_id, options]); 18675 }, 18676 success: function(data, textStatus) { 18677 if (data !== null) { 18678 self._ws_url = self._fixUrl(data.location); 18679 self._room_id = data.id; 18680 self.isNew = false; 18681 self._makeWebUrl(); 18682 18683 if ($.isFunction(options.successCallback)) { 18684 options.successCallback(data, textStatus, roomData); 18685 } 18686 18687 self.history.counter = 0; 18688 18689 myDeco.body.trigger('roomSaved', 18690 [roomData.type, data.id, options]); 18691 18692 if (parent) { 18693 var msg = {name: "saved", id: data.id}; 18694 parent.postMessage(msg, "*"); 18695 parent.postMessage(JSON.stringify(msg), "*"); // IE9 18696 } 18697 } else { 18698 myDeco.body.trigger('roomSavingError', 18699 [504, gettext('Gateway Timeout'), gettext('Connection is broken')]); 18700 } 18701 }, 18702 error: function(xhr) { 18703 var response, 18704 message; 18705 try { 18706 response = JSON.parse(xhr.responseText); 18707 } catch(ex) { 18708 response = {}; 18709 } 18710 if (response.error) { 18711 message = response.error.message; 18712 if (message.__all__) { 18713 message = message.__all__; 18714 } else { 18715 message = JSON.stringify(message); 18716 } 18717 } else { 18718 message = xhr.status + ': ' + xhr.statusText; 18719 } 18720 myDeco.body.trigger('roomSavingError', [ 18721 xhr.status, 18722 xhr.statusText, 18723 message 18724 ]); 18725 } 18726 }); 18727 return true; 18728 }, 18729 18730 /** 18731 * Delete active room 18732 * @param {Object} options 18733 */ 18734 deleteRoom: function(options) { 18735 var self = this, 18736 params = ''; 18737 18738 options = options || {}; 18739 18740 if (this._ws_url) { 18741 if (options.urlParams) { 18742 params = (this._ws_url.indexOf('?') === -1 ? '?' : '&') + 18743 options.urlParams; 18744 } 18745 18746 $.ajax({ 18747 // DELETE method don't accept params, so add them directly to URL 18748 url: this._ws_url + params, 18749 type: 'DELETE', 18750 success: function() { 18751 myDeco.body.trigger('roomDeleted'); 18752 self.loadRoom(emptyRoom); 18753 }, 18754 error: function() { 18755 myDeco.body.trigger('roomDeletionError'); 18756 } 18757 }); 18758 } 18759 }, 18760 18761 /** 18762 * Discard all changes made by user and start again with this room 18763 */ 18764 revert: function() { 18765 this.load($.extend(true, {}, this._backup)); 18766 }, 18767 18768 // Stub method for get the room ready to save. 18769 /** 18770 * Wrap room model with some settings from template. 18771 * @param {Object} template Default room settings 18772 * @return {Object} Room ready for saving 18773 */ 18774 18775 _wrapRoom: function(template) { 18776 if (this.isNew && this._metadata.author && 18777 // keep original author 18778 !this._metadata.original_author) { 18779 this._metadata.original_author = this._metadata.author; 18780 } 18781 18782 if (this.isNew && this._metadata.image) { 18783 this._metadata.original_image = this._metadata.image; 18784 } 18785 18786 $.extend(true, this._metadata, template); 18787 this._metadata.author = myDeco.username; 18788 18789 // remove original parent 18790 delete this._metadata.parent; 18791 this._metadata.name = (this._metadata.name || '').substring(0, 64); 18792 18793 return this._metadata; 18794 }, 18795 18796 /** 18797 * Add room to one or more competitions. 18798 * @param {Array} competitions array of competitions id (or just one id) 18799 */ 18800 addToCompetitions: function(competitions) { 18801 if(!this._room_id) { 18802 return; 18803 } 18804 var success = true, 18805 data = {room_id: this._room_id}; 18806 if(!$.isArray(competitions)) { 18807 competitions = [competitions]; 18808 } 18809 $.each(competitions, function() { 18810 $.ajax({ 18811 type: 'POST', 18812 url: myDeco.urls.ws.room_competitions + this.id + '/', 18813 data: data, 18814 async: false, 18815 error: function() { 18816 success = false;
18817 } 18818 }); 18819 }); 18820 return success; 18821 }, 18822 18823 /** 18824 * Store some changes over the model to history 18825 * @param {Any} Data to store 18826 */ 18827 operationEnd: function(data) { 18828 // populate change to history 18829 if (this.history.changes.length - 1 > this.history.position) { 18830 this.history.changes = this.history.changes.slice(0, this.history.position); 18831 } 18832 18833 this.history.changes.push(data); 18834 18835 if (this.history.changes.length > this.settings.historyLimit) { 18836 this.history.changes.shift(); 18837 } 18838 18839 this.history.position = this.history.changes.length - 1; 18840 18841 this.history.counter++; 18842 }, 18843 18844 /** 18845 * Return string representation for latest changes. 18846 * @param {Number} latest Count of latest changes, leave empty for all 18847 */ 18848 changeLog: function(latest) { 18849 return $.map(typeof latest === 'undefined' ? 18850 this.history.changes : 18851 this.history.changes.slice(-latest), 18852 function(item) { 18853 return item[0]; 18854 }); 18855 } 18856 18857}); 18858/*globals Class, $, myDeco */ 18859/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 18860 sloppy: true, white: true */ 18861/** 18862 * @fileOverview App base controller 18863 */ 18864var AppController = Class.extend({ 18865 /** 18866 * Controller type 18867 * @type {String} 18868 * @static 18869 */ 18870 $type: 'app', 18871 18872 /** 18873 * Model instance 18874 * @type {Object} 18875 */ 18876 _model: false, 18877 18878 /** 18879 * View instance 18880 * @type {FpeView} 18881 */ 18882 _view: false, 18883 18884 /** 18885 * Controller settings 18886 */ 18887 settings: {}, 18888 18889 /** 18890 * Operation info 18891 * @type: {Object} 18892 */ 18893 _operation: {}, 18894 18895 /** 18896 * Current controller state 18897 * @type {Boolean} 18898 */ 18899 state: true, 18900 18901 /** 18902 * Init function 18903 * @param {Object} model FPE model 18904 */ 18905 init: function(model, settings) { 18906 this._model = model; 18907 this.settings = $.extend(true, this.settings, settings || {}); 18908 }, 18909 18910 /** 18911 * Register view 18912 * @param {Object} view FPE view 18913 */ 18914 registerView: function(view) { 18915 this._view = view; 18916 18917 myDeco.body 18918 .bind('operationEnd', this.operationEnd.bind(this)) 18919 .bind('operationStart', this.operationStart.bind(this)) 18920 .bind('operationReset', this.operationReset.bind(this)) 18921 .bind('viewToggle', this.toggle.bind(this)); 18922 }, 18923 18924 /** 18925 * End operation handler, will fire 'endOperation' event with custom parameters 18926 * @param {Array} eventData Custom event data 18927 */ 18928 operationEnd: function(e, data) { 18929 return (data || []); 18930 }, 18931 18932 /** 18933 * Callback for resetting current operation state 18934 */ 18935 operationReset: function() { 18936 }, 18937 18938 /** 18939 * Event operation start event listener 18940 * Hide info and prepare data passed to event 18941 * @param {Event} e 18942 * @param {Array} data Passed data 18943 */ 18944 operationStart: function(e, data) { 18945 data = $.isArray(data) ? data : []; 18946 18947 return data; 18948 }, 18949 18950 /** 18951 * Toggle view to a visible/nonvsible state 18952 * @param {Event} e 18953 * @param {String} type Destination type to enable 18954 */ 18955 toggle: function(e, type) { 18956 var state = this.state; 18957 18958 if (type === this.$type && this.state !== true) { 18959 this.state = true; 18960 } else if (this.state === true && $.inArray(type, (this.settings.linked || [])) !== -1) { 18961 this.state = false; 18962 } 18963 18964 if (state !== this.state) { 18965 this._view.toggle(this.state); 18966 return true; 18967 } 18968 18969 return false; 18970 } 18971}); 18972/*globals Class, $, myDeco */ 18973/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 18974 sloppy: true, white: true */ 18975/** 18976 * @fileOverview base class for FPE view 18977 */ 18978var AppView = Class.extend({ 18979 /** 18980 * View type, should be static 18981 * @type {String} 18982 * @static 18983 */ 18984 $type: 'base', 18985 18986 /** 18987 * Real view holder, will find closest with #view-$type 18988 * @type {jQset} 18989 */ 18990 viewHolder: false, 18991 18992 /** 18993 * Canvas DOM element 18994 * @type {Object} 18995 */ 18996 canvasElement: false, 18997 18998 /** 18999 * Canvas width 19000 * @type {Number} 19001 */ 19002 canvasHeight: 0, 19003 19004 /** 19005 * Canvas height 19006 * @type {Number} 19007 */ 19008 canvasWidth: 0, 19009 19010 /** 19011 * Canvas offset 19012 * @type {Object} 19013 */ 19014 _canvasOffset: false, 19015 19016 /** 19017 * Application layout 19018 */ 19019 app: false, 19020 19021 /** 19022 * View state 19023 */ 19024 state: false, 19025 19026 /** 19027 * App width and height 19028 * @type {Object} 19029 */ 19030 _appDimensions: false, 19031 19032 /** 19033 * App offset 19034 * @type {Object} 19035 */ 19036 _appOffset: false, 19037 19038 /** 19039 * View settings 19040 * @type {Object} 19041 */
19042 settings: {}, 19043 19044 /** 19045 * FPE model 19046 * @type {Object} 19047 */ 19048 _model: undefined, 19049 19050 /** 19051 * FPE controller 19052 * @type {Object} 19053 */ 19054 _controller: undefined, 19055 19056 /** 19057 * Should view fire 'viewStarted' event after init 19058 * Usable for plugins 19059 * @type true 19060 */ 19061 autostart: true, 19062 19063 /** 19064 * Flag for loaded state 19065 * @type {Boolean} 19066 */ 19067 loaded: false, 19068 19069 /** 19070 * Init function 19071 * Required options are: 19072 * 19073 * @param {Object} options Init options 19074 */ 19075 init: function(options) { 19076 var i, ilen, instance; 19077 19078 options = options || {}; 19079 options.settings = options.settings || {}; 19080 this.settings = $.extend(true, this.settings, options.settings || {}); 19081 options.canvas = options.canvas || $('#view-' + this.$type); 19082 19083 if (!options.canvas || options.canvas.size() === 0) { 19084 throw "No canvas found in the document"; 19085 } 19086 19087 // canvas 19088 this.canvasElement = options.canvas; 19089 this.canvasWidth = this.canvasElement.innerWidth(); 19090 this.canvasHeight = this.canvasElement.innerHeight(); 19091 19092 // view holder 19093 this.viewHolder = this.canvasElement.closest('#view-' + this.$type); 19094 19095 // app 19096 this.app = options.app; 19097 19098 this._model = options.model; 19099 this._controller = options.controller; 19100 19101 // init plugins 19102 for (i = 0, ilen = (options.plugins || []).length; i < ilen; i++) { 19103 // to satisfy jsLint 19104 instance = new options.plugins[i]['class'](this, options.plugins[i].settings); 19105 } 19106 19107 myDeco.body.trigger('runPlugins.' + this.$type); 19108 19109 // tell to plugins that we started 19110 if (this.autostart) { 19111 myDeco.body.trigger('viewStarted.' + this.$type); 19112 } 19113 19114 // load different sprite image for different locales 19115 if (myDeco.settings.I18N_LOCALE === 'de') { 19116 $('.decorate-target').css('background-image', 'url(/assets/austin/sprite-de.png)') 19117 } else if (myDeco.settings.I18N_LOCALE === 'fr') { 19118 $('.decorate-target').css('background-image', 'url(/assets/austin/sprite-fr.png)') 19119 } else if (myDeco.settings.I18N_LOCALE === 'es') { 19120 $('.decorate-target').css('background-image', 'url(/assets/austin/sprite-es.png)') 19121 } 19122 }, 19123 19124 /** 19125 * Get canvas offset 19126 * 19127 * @return {Object} 19128 */ 19129 getCanvasOffset: function() { 19130 if (!this._canvasOffset) { 19131 this._canvasOffset = this.canvasElement.offset(); 19132 } 19133 19134 return this._canvasOffset; 19135 }, 19136 19137 /** 19138 * Get App offset 19139 * 19140 * @return {Object} 19141 */ 19142 getAppOffset: function(){ 19143 if (!this._appOffset) { 19144 this._appOffset = this.app.offset();
19145 this._appOffset.left = Math.ceil(this._appOffset.left); 19146 this._appOffset.top = Math.ceil(this._appOffset.top); 19147 } 19148 19149 return this._appOffset; 19150 }, 19151 19152 /** 19153 * Get app width and height as object 19154 * 19155 * @return {Object} 19156 */ 19157 getAppDimensions: function() { 19158 if (!this._appDimensions || 19159 (!this._appDimensions.width && !this._appDimensions.height)) { 19160 this._appDimensions = { 19161 width: this.app.innerWidth(), 19162 height: this.app.innerHeight() 19163 }; 19164 } 19165 return this._appDimensions; 19166 }, 19167 19168 /** 19169 * Set cursor style depend on current element hover 19170 * 19171 * @param {String} cur Body className 19172 */ 19173 cursor: function(cur) { 19174 var body = myDeco.body.get(0); 19175 if (typeof cur === 'undefined') { 19176 return body.cursor || ''; 19177 } else if (!this.disableCursors) { 19178 myDeco.body 19179 .removeClass(body.cursor || '') 19180 .addClass(cur); 19181 19182 body.cursor = cur; 19183 } 19184 }, 19185 19186 /** 19187 * Toggle view 19188 * @param {Boolean} state 19189 */ 19190 toggle: function(state) { 19191 this.viewHolder 19192 .toggle(state) 19193 .trigger(state === true ? 'maximized' : 'minimized'); 19194 myDeco.body.trigger('view-toggle', 19195 [this, state]); 19196 } 19197}); 19198/*jslint white: false, forin: true, fragment: true, undef: true, nomen: true, bitwise: false, regexp: false, devel: true, sloppy: true, plusplus: true, bitwise: true */ 19199 19200/** 19201 * banners delay loading 19202 */ 19203myDeco.advertiseBanners = { 19204 19205 /** 19206 * Init banner 19207 */ 19208 init: function (banner) { 19209 if (!banner.size()) { 19210 return; 19211 } 19212 19213 var prop = banner.data(), 19214 cachebuster = Math.floor(Math.random() * 10000000000), 19215 dcopt = null, 19216 tile = 0; 19217 19218 if (document.body.clientWidth > prop.cwidth) { 19219 dcopt = (dcopt !== null) ? "dcopt=ist" : ''; 19220 tile++; 19221 19222 banner.append('<iframe src="http://ad.doubleclick.net/adi/' 19223 + prop.site + '/' + prop.zone 19224 + ';pos=' + prop.pos + ';' + dcopt + ';tile=' + tile 19225 + ';sz=' + prop.sz + ';ord=' + cachebuster + '?" marginwidth="0" marginheight="0"' 19226 + 'hspace="0" vspace="0" frameborder="0" scrolling="no" allowtransparency="true"' 19227 + prop.attrs + '></iframe>'); 19228 } 19229 }, 19230 19231 /** 19232 * Init all base banners 19233 */ 19234 initAll: function() { 19235 var banners = $('#mpu-banner, #sky-banner, #leaderboard-search-banner, #leaderboard-banner.ad2one'), 19236 i, 19237 len = banners.length, 19238 banner; 19239 19240 if (myDeco.settings.ad2one) { 19241 for (i = len; i > -1; i--) { 19242 this.init($(banners[i])); 19243 } 19244 } 19245 } 19246}; 19247 19248$(function() { 19249 myDeco.advertiseBanners.initAll(); 19250}); 19251/*globals $ */ 19252/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 19253 sloppy: true, white: true */ 19254/* 19255 * Common "ancestor" for model entities such Corner, Wall, Camera, etc 19256 */ 19257var commonMethods = { 19258 /** 19259 * type of the entity 19260 * @type {String} 19261 */ 19262 type: 'entity', 19263 19264 /** 19265 * pointer to the model 19266 * @type {FpeModel} 19267 */ 19268 _model: false, 19269 19270 /** 19271 * way to wrap all private data into the one field (which can be easily dropped) before model exporting 19272 * @type {Object} 19273 */ 19274 _internals: {}, 19275 19276 /** 19277 * Selected flag 19278 * @type {Boolean} 19279 */ 19280 selected: false, 19281 19282 /** 19283 * Emulation of the constructor. Needed to store pointer to the model 19284 */ 19285 init: function(model) { 19286 this._model = model; 19287 }, 19288 19289 /** 19290 * fire event and pass there params which was passed to this function 19291 */ 19292 fireEvent: function() { 19293 // first param - callback name 19294 var args = $.makeArray(arguments), 19295 eventName = args.shift(), 19296 i, ilen, group; 19297 19298 for (group in this._internals) { 19299 if (this._internals[group][eventName]) { 19300 for (i = 0, ilen = this._internals[group][eventName].length; i < ilen; i++) { 19301 this._internals[group][eventName][i].apply(this, args); 19302 } 19303 } 19304 } 19305 }, 19306 19307 /** 19308 * Add callback to the queue 19309 * @type {String} eventName (onMove or onRotate) 19310 * @type {Function} observer Callback function 19311 * @type {String} group Event group 19312 */ 19313 attachEvent: function(eventName, observer, group) { 19314 if (!this._internals[group]) { 19315 this._internals[group] = {}; 19316 } 19317 19318 if (!this._internals[group][eventName]) { 19319 this._internals[group][eventName] = []; 19320 } 19321 19322 this._internals[group][eventName].push(observer); 19323 19324 return this; 19325 }, 19326 19327 /** 19328 * Detach event listener from model 19329 * @type {String} eventName (onMove or onRotate) 19330 * @type {Function} observer Callback function 19331 * @type {String}
19331 group Event group 19332 */ 19333 detachEvent: function(eventName, observer, group) { 19334 var idx; 19335 19336 if (this._internals[group] && this._internals[group][eventName]) { 19337 idx = this._internals[group][eventName].indexOf(observer); 19338 if (idx !== -1) { 19339 this._internals[group][eventName].splice(idx, 1); 19340 } 19341 } 19342 }, 19343 19344 /** 19345 * Drop event observers group from list 19346 * @param {String} group Event observers group name 19347 */ 19348 dropEvents: function(group) { 19349 delete this._internals[group]; 19350 }, 19351 19352 /** 19353 * Abstract move to the new position method 19354 * @param {Number} dx 19355 * @param {Number} dy 19356 */ 19357 move: function(dx, dy, dz) { 19358 if (typeof this.position !== 'undefined') { 19359 this.position.translate(dx, dy, dz); 19360 } 19361 this.fireEvent('onMove', dx, dy, dz); 19362 }, 19363 19364 /** 19365 * Return cached angle of this object (if applicable) 19366 * @returns Number 19367 */ 19368 getAngle: function() { 19369 return this.angle || 0; 19370 }, 19371 19372 /** 19373 * Return cached x coord of entity center (if applicable) 19374 * @returns Number 19375 */ 19376 getCX: function() { 19377 return (typeof this.position !== 'undefined' ? this.position.x : 0); 19378 }, 19379 19380 /** 19381 * Return cached y coord of entity center (if applicable) 19382 * @returns Number 19383 */ 19384 getCY: function() { 19385 return (typeof this.position !== 'undefined' ? this.position.y : 0); 19386 }, 19387 19388 /** 19389 * Select entity 19390 * @returns {Object} 19391 */ 19392 _select: function() { 19393 if (!this.selected) { 19394 this.selected = true; 19395 this.fireEvent('onSelect', this._model._selected); 19396 return true; 19397 } 19398 return false; 19399 }, 19400 19401 /** 19402 * Unselect entity 19403 * @returns {Object} 19404 */ 19405 _unselect: function() { 19406 if (this.selected) { 19407 this.selected = false; 19408 this.fireEvent('onUnselect', this._model._selected); 19409 return true; 19410 } 19411 return false; 19412 }, 19413 19414 /** 19415 * Event method executed on remove 19416 */ 19417 remove: function() { 19418 this.fireEvent('onRemove', true); 19419 return this; 19420 }, 19421 19422 /** 19423 * Event method executed on highlighting 19424 */ 19425 highlightOn: function() { 19426 this.fireEvent('onHightlightOn'); 19427 return this; 19428 }, 19429 19430 /** 19431 * Event method executed on dishighlighting 19432 */ 19433 highlightOff: function() { 19434 this.fireEvent('onHightlightOff'); 19435 return this; 19436 }, 19437 19438 /** 19439 * Event method executed on operation end 19440 * @param {String} name Operation name 19441 */ 19442 operationEnd: function(name) { 19443 this.fireEvent('onOperationEnd', name); 19444 return this; 19445 }, 19446 19447 /** 19448 * Event method executed on operation start 19449 * @param {String} name Operation name 19450 */ 19451 operationStart: function(name) { 19452 this.fireEvent('onOperationStart', name); 19453 return this; 19454 }, 19455 19456 /** 19457 * Reset operation event 19458 */ 19459 operationReset: function() { 19460 this.fireEvent('onOperationReset'); 19461 return this; 19462 }, 19463 19464 /** 19465 * Default action for entity 19466 */ 19467 defaultAction: function() { 19468 return this; 19469 } 19470 19471}; 19472/*globals $, commonMethods, findShapeArea */ 19473/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 19474 sloppy: true, white: true */ 19475/* 19476 * Shape class for the model (derived from common ancestor from entity.js) 19477 */ 19478 19479var shapeMethods = $.extend({}, commonMethods, { 19480 /** 19481 * type of the entity 19482 * @type string 19483 */ 19484 type: 'shape', 19485 19486 /** 19487 * Materials 19488 * DO NOT DEFINE THIS PROPERTIES! 19489 */ 19490 // ceilingMaterial: false, 19491 // floorMaterial: false, 19492 19493 /** 19494 * Walls attached to shape 19495 * @type {Array} 19496 */ 19497 _walls: [], 19498 19499 /** 19500 * Flag for decora acceptance 19501 * @type {Boolean} 19502 */ 19503 acceptDecor: true, 19504 19505 /** 19506 * Emulation of the constructor. Needed to register corner point in snapping array. 19507 * @param {FpeModel} model 19508 * @param {Array} walls Walls to attach 19509 */ 19510 init: function(model, walls) { 19511 var i, ilen; 19512 19513 this._model = model; 19514 this.ID = 'area' + this._model._areaCounter++; 19515 19516 this._model.shapes.push(this); 19517 this._walls = []; 19518 walls = walls || []; 19519 19520 for (i = 0, ilen = this.length; i < ilen; i++) { 19521 this[i].attachShape(this); 19522 } 19523 19524 for (i = 0, ilen = walls.length; i < ilen; i++) { 19525 walls[i].attachShape(this); 19526 } 19527 19528 var defaultFloorMaterial = this._model.settings.defaultMaterials.floor; 19529 if(myDeco.settings.WL && myDeco.settings.WL.WL_DEFAULT_FLOOR_MATERIAL) 19530 defaultFloorMaterial = eval('('+ myDeco.settings.WL.WL_DEFAULT_FLOOR_MATERIAL +')'); 19531 19532 // materials 19533 this._model.assignMaterial(this, 19534 typeof this.floorMaterial === 'object' ? this.floorMaterial : 19535 defaultFloorMaterial, 'floor'); 19536 this._model.assignMaterial(this, 19537 typeof this.ceilingMaterial === 'object' ? this.ceilingMaterial : 19538 this._model.settings.defaultMaterials.ceiling, 'ceiling'); 19539 19540 this.restoreCorrectOrder(); 19541 return this; 19542 }, 19543 19544 /** 19545 * Calculate area of the given room shape. 19546 */ 19547 getArea: function() { 19548 var s = findShapeArea(this); 19549 this.area = Math.abs(s); 19550 return s; 19551 }, 19552 19553 /** 19554 * Restore correct order for shape points 19555 */ 19556 restoreCorrectOrder: function() { 19557 if (this.getArea() > 0) { 19558 this.reverse(); 19559 } 19560 }, 19561 19562 /** 19563 * Append corner to this shape 19564 * @param {Corner} corner 19565 * @param {Array} points Two points from shape for insertion between them 19566 * @returns {Shape} 19567 */ 19568 addCorner: function(corner, points) { 19569 var index = points ? (function(shape) { 19570 var i1 = shape.indexOf(points[0]), 19571 i2 = shape.indexOf(points[1]); 19572 19573 return i1 !== -1 && i2 !== -1 && Math.abs(i1 - i2) === 1 ? 19574 Math.max(i1, i2) : shape.length; 19575 }(this)) : this.length; 19576 19577 corner.attachShape(this); 19578 19579 this.splice(index, 0, corner); 19580 this.fireEvent('onCornerAdd', corner, index); 19581 return this; 19582 }, 19583 19584 /** 19585 * Get attached to this shape walls 19586 * @returns {Array} 19587 */ 19588 getWalls: function() { 19589 return this._walls; 19590 }, 19591 19592 /** 19593 * Attach wall to this shape 19594 * @param {Wall} wall 19595 * @returns {Shape} 19596 */ 19597 attachWall: function(wall) { 19598 if (this._walls.indexOf(wall) === -1) { 19599 this._walls.push(wall); 19600 wall.attachShape(this); 19601 } 19602 return this; 19603 }, 19604 19605 /** 19606 * Detach wall from this shape 19607 * @param {Wall} wall 19608 * @returns {Shape} 19609 */ 19610 detachWall: function(wall) { 19611 if ($.extract(wall, this._walls)) { 19612 wall.detachShape(this); 19613 } 19614 return this; 19615 }, 19616 19617 /** 19618 * Remove shape form model. Will remove single walls/corners which isn't 19619 * connected to any other walls/shapes 19620 * @param {Boolean} removeWalls remove walls when removing shape 19621 */ 19622 remove: function(keepWalls) { 19623 var i, wall; 19624 19625 this._model.unselect(this); 19626 19627 // unassign materials 19628 this._model.assignMaterial(this, undefined, 'ceiling'); 19629 this._model.assignMaterial(this, undefined, 'floor'); 19630 19631 // dereference self from walls and remove walls if they do not have 19632 // any shapes 19633 for (i = this._walls.length - 1; i >= 0; i--) { 19634 wall = this._walls[i].detachShape(this); 19635 if (!keepWalls && wall.getShapes().length === 0) { 19636 wall.remove(); 19637 } 19638 } 19639 19640 for (i = this.length - 1; i >= 0; i--) { 19641 this[i].detachShape(this); 19642 } 19643 19644 // deregister self from model 19645 $.extract(this, this._model.shapes); 19646 19647 this.fireEvent('onRemove', true); 19648 }, 19649 19650 /** 19651 * String representation of shape 19652 * @returns {String} 19653 */ 19654 toString: function() { 19655 var ret = 'ID: ' + this.ID, 19656 i, ilen = this.length; 19657 19658 for (i = 0; i < ilen; i++) { 19659 ret += ', ' + this[i].toString(); 19660 } 19661 19662 return ret; 19663 }, 19664 19665 /** 19666 * Return opposite corner from shape 19667 * @param {Point} point Center point 19668 * @param {Point} existing Point opposite to 19669 * @returns {Point} 19670 */ 19671 getOpposite: function(point, existing) { 19672 var neighbors = this.getNeighbors(point); 19673 return neighbors[0] === existing ? neighbors[1] : neighbors[0]; 19674 }, 19675 19676 /** 19677 * Get neighbors points for provided point in shape 19678 * @param {Corner} 19679 * @returns {Array} 19680 */ 19681 getNeighbors: function(point) { 19682 var idx = this.indexOf(point); 19683 19684 return [this[(idx + 1) % this.length], 19685 this[(idx - 1 + this.length) % this.length]]; 19686 }, 19687 19688 getBBox: function() { 19689 var xs = $.map(this, function(p) { 19690 return p.x; 19691 }), 19692 x = Math.min.apply(null, xs), 19693 ys = $.map(this, function(p) { 19694 return p.y; 19695 }), 19696 y = Math.min.apply(null, ys); 19697 19698 return { 19699 x: x, y: y, 19700 width: Math.max.apply(null, xs) - x, 19701 depth: Math.max.apply(null, ys) - y 19702 }; 19703 } 19704}); 19705/*globals $, myDeco, commonMethods, T */ 19706/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 19707 sloppy: true, white: true */ 19708/* 19709 * Camera class for the model (derived from common ancestor from entity.js) 19710 */ 19711 19712var cameraMethods = $.extend({}, commonMethods, { 19713 /** 19714 * type of the entity 19715 * @type {String} 19716 */ 19717 type: 'camera', 19718 19719 /** 19720 * Camera angle in horizontal plane 19721 * @type {Number} 19722 */ 19723 _angleH: undefined, 19724 19725 /** 19726 * Camera angle in vertical plane 19727 * @type {Number} 19728 */ 19729 _angleV: undefined, 19730 19731 /** 19732 * Focal length calculated value 19733 * @type {Number} 19734 */ 19735 // fieldofview: 180 / 3.0, 19736 19737 /** 19738 * Camera init 19739 * @param {Object} model Camera model 19740 */ 19741 init: function(model) { 19742 this._model = model; 19743 this.setFov(model.camera.fieldofview); 19744 }, 19745 19746 setFov: function(fov) { 19747 this.fieldofview = fov; 19748 this.fireEvent('onFov'); 19749 }, 19750 19751 /** 19752 * Rotate the camera 19753 * @param {Number} angle new camera angle in x0y plane 19754 * @param {Number} angleV new camera angle relative to z axis 19755 * @returns {Camera} 19756 */ 19757 rotate: function(angleH, angleV) {
19758 this._angleH = typeof angleH === 'undefined' ? this.getAngle() : angleH; 19759 this._angleV = typeof angleV === 'undefined' ? this.getVAngle() : angleV; 19760 19761 this.focal_point = this.position.clone() 19762 // Magical number involved 19763 .translate(318.826, 0, 0) 19764 .rotate(this.position, - this._angleH - 90, undefined, this._angleV); 19765 this.fireEvent('onRotate', this._angleH, this._angleV); 19766 return this; 19767 }, 19768 19769 /** 19770 * Move camera to the new position 19771 * @param {Number} dx 19772 * @param {Number} dy 19773 * @param {Number} dz 19774 * @returns {Camera} 19775 */ 19776 move: function(dx, dy, dz) { 19777 this.position.translate(dx, dy, dz); 19778 this.focal_point.translate(dx, dy, dz); 19779 this.fireEvent('onMove', dx, dy, dz); 19780 return this; 19781 }, 19782 19783 /** 19784 * Return angle between camera's base and focal point in x0y plane 19785 * @returns {Number} 19786 */ 19787 getAngle: function() { 19788 if (typeof this._angleH === 'undefined') { 19789 this._angleH = T.atan2(this.position.y - this.focal_point.y, this.focal_point.x - this.position.x) - 90.0; 19790 } 19791 19792 return this._angleH; 19793 }, 19794 19795 /** 19796 * Return angle between camera base and focal point relative to Z axis 19797 * @return {Number} 19798 */ 19799 getVAngle: function() { 19800 if (typeof this._angleV === 'undefined') { 19801 this._angleV = T.asin((this.focal_point.z - this.position.z) / Math.abs(this.position.distance(this.focal_point))); 19802 } 19803 return this._angleV; 19804 } 19805 19806}); 19807/*globals $, commonMethods, Point, myDeco, T, findWallIntersection */ 19808/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 19809 sloppy: true, white: true */ 19810/* 19811 * Wall class for the model (derived from common ancestor from entity.js) 19812 */ 19813 19814var wallMethods = $.extend({}, commonMethods, { 19815 /** 19816 * type of the entity 19817 * @type {String} 19818 */ 19819 type: 'wall', 19820 19821 /** 19822 * attached components (doors and windows) 19823 * @type {Array} of Object 19824 */ 19825 components: [], 19826 19827 /** 19828 * Point at the center of the wall. 19829 * @type {Point} 19830 */ 19831 middle: false, 19832 19833 /** 19834 * Shapes attached to wall 19835 * @type {Array} 19836 */ 19837 _shapes: [], 19838 19839 /** 19840 * Flag for decora acceptance 19841 * @type {Boolean} 19842 */ 19843 acceptDecor: true, 19844 19845 /** 19846 * Will this instance accept photo or not 19847 * @type {Boolean} 19848 */ 19849 acceptPicture: true, 19850 19851 /** 19852 * Emulation of the constructor. Needed to register wall's middle point in snapping array. 19853 * @param {FpeModel} model 19854 * @param {Array} shapes Shapes attached to wall 19855 */ 19856 init: function(model, shapes) { 19857 var i, ilen; 19858 this._model = model; 19859 if (typeof this.ID === 'undefined') { 19860 this.ID = 'wall' + this._model._wallCounter++; 19861 } else { 19862 i = +this.ID.replace('wall', ''); 19863 if (!isNaN(i)) { 19864 this._model._wallCounter = 19865 Math.max(this._model._wallCounter, i + 1); 19866 } 19867 } 19868 19869 // for old models without thickness 19870 if (typeof this.thickness === 'undefined') { 19871 this.thickness = this._model.settings 19872 .wallThickness[this._model.settings.wallThicknessDefault].value; 19873 } 19874 19875 this._model.walls.push(this); 19876 19877 // reference self to points 19878 for (i = 0; i < 2; i++) { 19879 this[i].attachWall(this); 19880 } 19881 19882 // avoid of "shared" shapes 19883 this._shapes = []; 19884 shapes = shapes || []; 19885 for (i = 0, ilen = shapes.length; i < ilen; i++) { 19886 shapes[i].attachWall(this); 19887 } 19888 19889 this._corners = {}; 19890 this.middle = new Point(); 19891 this._updateMiddle(); 19892 19893 // used as flag for snapping rules 19894 this.middle.type = 'wall'; 19895 this._model._snapPoints.push(this.middle); 19896 19897 // material 19898 this._model.assignMaterial(this, typeof this.material === 'object' ? 19899 this.material : this._model.settings.defaultMaterials.wall); 19900 19901 return this; 19902 }, 19903 19904 /** 19905 * Recalculate pre-cached value of the middle of the wall 19906 */ 19907 _updateMiddle: function() { 19908 this.middle.x = (this[1].x + this[0].x) / 2; 19909 this.middle.y = (this[1].y + this[0].y) / 2; 19910 this.middle.z = (this[1].z + this[0].z) / 2; 19911 }, 19912 19913 /** 19914 * Move Wall to the new position (if possible) 19915 * @param {Number} dx 19916 * @param {Number} dy 19917 */ 19918 move: function(dx, dy) { 19919 var self = this, 19920 pdx = [], 19921 pdy = [], 19922 // Finding an angle and distance between point to which we are 19923 // trying to move the wall sand current wall position. 19924 dx2 = this[1].x - this[0].x, 19925 dy2 = this[1].y - this[0].y, 19926 d1 = (dx * dy2 - dx2 * dy) / Math.sqrt(dx2 * dx2 + dy2 * dy2), 19927 angle = T.atan2(dy2, dx2) - 90, 19928 pretender, i, wall, neighbourAngle, point; 19929 19930 // Finding real position of the wall after movement 19931 pdx[0] = pdx[1] = dx = d1 * T.cos(angle); 19932 pdy[0] = pdy[1] = dy = d1 * T.sin(angle); 19933 19934 // New corners of the wall 19935 pretender = [self[0].clone().translate(dx, dy, 0), 19936 self[1].clone().translate(dx, dy, 0)]; 19937 19938 // Checking new corners for constraints. 19939 for (i = 0; i < 2; i++) { 19940 wall = this[i].getWalls(this).shift(); 19941 19942 if (wall) { 19943 neighbourAngle = wall.getAngle(); 19944 19945 // if new wall and it's neighbor has almost no angle between 19946 if (Math.abs((neighbourAngle - angle + 90) % 180) <= 19947 this._model.settings.minAngle) { 19948 // block wall movement when wal is almost parallel to 19949 // neighbor 19950 return; 19951 } else { 19952 // finding new position as intersection point between new 19953 // wall and it's neighbor 19954 point = findWallIntersection(pretender, wall); 19955 if (!point) { 19956 return; 19957 } 19958 pdx[i] = point.x - this[i].x; 19959 pdy[i] = point.y - this[i].y; 19960 } 19961 // Checking if wall move isn't blocked by it's components and 19962 // components of 19963 // walls-neighbours 19964 if (!this[i].checkConstraints(pdx[i], pdy[i])) { 19965 return; 19966 } 19967 } 19968 } 19969 19970 // forbid movement if walls create intersection 19971 if (findWallIntersection( 19972 [this[0], new Point(this[0].x + pdx[0], this[0].y + pdy[0])], 19973 [this[1], new Point(this[1].x + pdx[1], this[1].y + pdy[1])], 19974 true 19975 )) { 19976 return; 19977 } 19978 19979 // Walls components will be moved automatically by corner method 19980 this[0].move(pdx[0], pdy[0], true); 19981 this[1].move(pdx[1], pdy[1], true); 19982 19983 this._updateMiddle(); 19984 19985 this.fireEvent('onMove', dx, dy, 0); 19986 }, 19987 19988 /** 19989 * Get wall attribute 19990 * @param {String} name 19991 * @returns {Mixed} 19992 */ 19993 getAttribute: function(name) { 19994 var value; 19995 19996 switch(name) { 19997 case 'width': 19998 value = this[0].distance(this[1]); 19999 break; 20000 case 'height': 20001 value = this._model.ceilingHeight; 20002 break; 20003 } 20004 20005 return value; 20006 }, 20007 20008 /** 20009 * Set wall attribute 20010 * @param {String} name Attribute name 20011 * @param {Mixed} value Attribute value 20012 * @returns {Object} this 20013 */
20014 setAttribute: function(name, value) { 20015 var length, half, angle, dx, dy, corners, i, ilen, components; 20016 20017 switch(name) { 20018 case 'height': 20019 this._model.setCeilingHeight(value); 20020 this._model.operationEnd(['wallHeight', this, this.getAttribute('height')]); 20021 break; 20022 case 'width': 20023 length = Math.max(10, value); 20024 half = (length - this.getAttribute('width')) / 2; 20025 angle = this.getAngle(); 20026 dx = half * T.cos(angle + 180); 20027 dy = half * T.sin(angle); 20028 corners = this.getCorners(); 20029 corners[0].move(-dx, -dy); 20030 corners[1].move(dx, dy); 20031 this._model.operationEnd(['wallWidth', this, this.getAttribute('width')]); 20032 break; 20033 case 'thickness': 20034 this.thickness = value; 20035 for (i = 0; i < 2; i++) { 20036 this[i].jointPoints(); 20037 } 20038 20039 components = this.getComponents(); 20040 for (i = 0, ilen = components.length; i < ilen; i++) { 20041 if (components[i].type === 'component') { 20042 components[i].placeToWall(components[i].parent, components[i]._shape); 20043 } else { 20044 components[i].setAttribute('depth', value); 20045 } 20046 } 20047 this.fireEvent('onThickness'); 20048 this._model.operationEnd(['wallThickness', this, value]); 20049 break; 20050 } 20051 20052 return this; 20053 }, 20054 20055 /** 20056 * Return angle between wall and 0X axis 20057 * @param {Object} shape 20058 * @returns {Number} 20059 */ 20060 getAngle: function(shape) { 20061 var corners = this.getCorners(shape); 20062 return corners ? ((360 - corners[0].angleTo(corners[1])) % 360) : false; 20063 }, 20064 20065 /** 20066 * Return coeffs of line which represents wall 20067 * @returns {Object} 20068 */ 20069 getCoeffs: function() { 20070 var dy = (this[1].y - this[0].y), 20071 dx = (this[1].x - this[0].x); 20072 20073 return {a: -dy, 20074 b: dx, 20075 c: -dx * this[0].y + dy * this[0].x}; 20076 }, 20077 20078 /** 20079 * Return wall corners in a proper order relative to passed shape 20080 * First shape attached to wall is a default if no shape passed 20081 * @param {Shape} shape 20082 * @returns {Array} 20083 */ 20084 getCorners: function(shape) { 20085 if (shape && !this._corners[shape.ID]) { 20086 var i1, i2, len = shape.length; 20087 20088 for (i2 = 0, i1 = len - 1; i2 < len; i1 = i2++) { 20089 if (this.indexOf(shape[i1]) !== -1 && 20090 this.indexOf(shape[i2]) !== -1) { 20091 this._corners[shape.ID] = [shape[i1], shape[i2]]; 20092 break; 20093 } 20094 } 20095 } 20096 20097 return shape ? this._corners[shape.ID] : this; 20098 }, 20099 20100 /** 20101 * Return opposite point to given one 20102 * @param {Number|Point} point Point or point index 20103 */ 20104 getOpposite: function(point) { 20105 if (typeof point !== 'number') { 20106 return this[0] === point ? this[1] : this[0]; 20107 } else { 20108 return this[(point + 1) % 2]; 20109 } 20110 }, 20111 20112 /** 20113 * Return attached walls with the current one 20114 * @returns {Array} 20115 */ 20116 getWalls: function() { 20117 return $.merge($.merge([], this[0].getWalls(this)), this[1].getWalls()); 20118 }, 20119 20120 /** 20121 * Return array of points for current wall including base points 20122 * in format where p1 and p4 are base points 20123 * p0 p5 20124 * p1 p4 20125 * p2 p3 20126 * @returns {Array} 20127 */ 20128 getJoints: function() { 20129 var p0 = this[0].getJoints(this), 20130 p1 = this[1].getJoints(this); 20131 20132 return [ 20133 p0[0], this[0], p0[1], 20134 p1[0], this[1], p1[1] 20135 ]; 20136 }, 20137 20138 /** 20139 * Proxy method to redraw measurer 20140 * @param {Boolean} state Show/hide/update measurer; [true/hide/undefined] respectively 20141 * @returns {Wall} 20142 */ 20143 updateMeasurer: function(state) { 20144 this.fireEvent('onUpdateMeasurer', state); 20145 return this; 20146 }, 20147 20148 /** 20149 * Attach shape to current wall 20150 * @param {Shape} shape 20151 * @returns {Wall} 20152 */ 20153 attachShape: function(shape) { 20154 if (this._shapes.indexOf(shape) === -1) { 20155 this._shapes.push(shape); 20156 shape.attachWall(this); 20157 } 20158 return this; 20159 }, 20160 20161 /** 20162 * Detach shape from current wall 20163 * @param {Shape} shape 20164 * @returns {Wall} 20165 */ 20166 detachShape: function(shape) { 20167 if ($.extract(shape, this._shapes)) { 20168 shape.detachWall(this); 20169 } 20170 return this; 20171 }, 20172 20173 /** 20174 * Get list of currently attached shapes 20175 * @returns {Array} 20176 */ 20177 getShapes: function() { 20178 return this._shapes; 20179 }, 20180 20181 /** 20182 * Attach on-wall component to current wall 20183 * @param {Component} component 20184 * @returns {Wall} 20185 */ 20186 addComponent: function(component) { 20187 this.components.push(component); 20188 component.parent = this; 20189 return this; 20190 }, 20191 20192 /** 20193 * Deattach component from wall 20194 * @param {Component} component 20195 * @returns {Wall} 20196 */ 20197 removeComponent: function(component) { 20198 $.extract(component, this.components); 20199 delete component.parent; 20200 return this; 20201 }, 20202 20203 /** 20204 * Return list of attached components 20205 * @returns {Array} 20206 */ 20207 getComponents: function() { 20208 return this.components; 20209 }, 20210 20211 /** 20212 * Remove wall from model 20213 */ 20214 remove: function(keepPoints) { 20215 var i; 20216 20217 this._model.unselect(this); 20218 20219 // extract self from snap points 20220 $.extract(this.middle, this._model._snapPoints); 20221 20222 // dereference material 20223 this._model.assignMaterial(this, undefined, this.type); 20224 20225 // dereference self from points/shapes 20226 for (i = this._shapes.length - 1; i >= 0; i--) { 20227 this._shapes[i].detachWall(this); 20228 } 20229 20230 for (i = 0; i < 2; i++) { 20231 this[i].detachWall(this); 20232 if (!keepPoints && this[i].getShapes().length === 0 && 20233 this[i].getWalls().length === 0) { 20234 this[i].remove(); 20235 } 20236 } 20237 20238 // remove on-wall components attached to current wall 20239 for (i = this.components.length - 1; i >= 0; i--) { 20240 this.components[i].remove(); 20241 } 20242 20243 // extract self from model 20244 $.extract(this, this._model.walls); 20245 20246 this.fireEvent('onRemove', true); 20247 }, 20248 20249 /** 20250 * String representation of a wall
20251 * @returns {String} 20252 */ 20253 toString: function() { 20254 var ret, i; 20255 20256 ret = 'ID: ' + this.ID; 20257 20258 for (i = 0; i < 2; i++) { 20259 ret += ", " + this[i].toString(); 20260 } 20261 20262 return ret; 20263 } 20264}); 20265/*globals $, commonMethods, findWallIntersection, T */ 20266/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 20267 sloppy: true, white: true */ 20268/* 20269 * Corner class for the model (derived from common ancestor from entity.js) 20270 */ 20271var cornerMethods = $.extend({}, commonMethods, { 20272 /** 20273 * type of the entity 20274 * @type string 20275 */ 20276 type: 'corner', 20277 20278 /** 20279 * Walls attached to this corner 20280 * @type {Array} 20281 */ 20282 _walls: [], 20283 20284 /** 20285 * Shapes attached to this corner 20286 * @type {Array} 20287 */ 20288 _shapes: [], 20289 20290 /** 20291 * Walls joint points with neighbor walls 20292 * @type {Object} 20293 */ 20294 _joints: {}, 20295 20296 /** 20297 * Emulation of the constructor. Needed to register corner point in snapping array. 20298 * @param {FpeModel} 20299 */ 20300 init: function(model) { 20301 this._model = model; 20302 20303 this._model.corners.push(this); 20304 20305 // avoid of "shared" walls 20306 this._walls = []; 20307 this._shapes = []; 20308 this._joints = {}; 20309 20310 this._model._snapPoints.push(this); 20311 20312 this.jointPoints(); 20313 20314 return this; 20315 }, 20316 20317 /** 20318 * Check if we can move the current corner from it's current position 20319 * to x0 + dx, y0 + dy without breaking the room. 20320 * @param {Number} dx 20321 * @param {Number} dy 20322 * @returns {Boolean} can the corner be moved 20323 */ 20324 checkConstraints: function(dx, dy) { 20325 var self = this, 20326 pretender = this.clone().translate(dx, dy, 0), 20327 hasIntersection = false, 20328 willIntersect = false, 20329 i, wall, nextPoint, w, n1, n2, n3, testWall, s, newWallLength, 20330 component, angle, 20331 angles = $.map(this.getWalls(), function(wall) { 20332 return pretender.angleTo(wall.getOpposite(self)); 20333 }), 20334 checkAngle = function(angle1, angle2) { 20335 var diff = Math.abs(angle1 - angle2); 20336 return diff > self._model.settings.minAngle && diff < 20337 360 - self._model.settings.minAngle; 20338 }, 20339 invokeCheckAngle = function(w) { 20340 return !checkAngle(nextPoint.angleTo(w.getOpposite(nextPoint)), 20341 angle); 20342 }; 20343 20344 if (angles.length > 1) { 20345 // check for walls angles 20346 angles.sort(function(a, b) { 20347 return a < b ? -1 : (a > b ? 1 : 0); 20348 }); 20349 20350 for (n1 = 0, i = angles.length, n2 = i - 1; n1 !== i; n2 = n1++) { 20351 if (!checkAngle(angles[n1], angles[n2])) { 20352 return false; 20353 } 20354 } 20355 } 20356 20357 // for each wall, which creates corner. 20358 for (i = 0, n1 = this._walls.length; i < n1; i++) { 20359 wall = this._walls[i]; 20360 20361 // Finding neighbour corner to current one 20362 nextPoint = wall.getOpposite(this); 20363 20364 // check for minimal angle to connected walls 20365 angle = nextPoint.angleTo(pretender); 20366 if ($.any(nextPoint.getWalls(wall), invokeCheckAngle)) { 20367 return false; 20368 } 20369 20370 // And checking, if our new wall intersecting with other room walls. 20371 // but only for those walls which assigned to any shape 20372 for (w = 0, n2 = this._model.walls.length; w < n2 && !hasIntersection; w++) { 20373 testWall = this._model.walls[w]; 20374 20375 // Check only walls which assigned to the same shape 20376 if (testWall.indexOf(self) === -1 && testWall.indexOf(nextPoint) === -1) { 20377 for (s = 0, n3 = wall._shapes.length; s < n3; s++) { 20378 if (testWall._shapes.indexOf(wall._shapes[s]) !== -1) { 20379 if (findWallIntersection([this, nextPoint], testWall, true)) { 20380 // check if the walls linked to this corner already has some intersections. 20381 // (possible with old rooms) 20382 hasIntersection = true; 20383 break; 20384 } 20385 20386 if (findWallIntersection([nextPoint, pretender], testWall, true)) { 20387 willIntersect = true; 20388 } 20389 } 20390 } 20391 } 20392 } 20393 20394 newWallLength = nextPoint.distance(pretender); 20395 // minimal wall length 20396 if (newWallLength < this._model.settings.minDistance && 20397 wall.getAttribute('width') > newWallLength) { 20398 return false; 20399 } 20400 20401 // checking if new wall length is enough to keep all doors/windows 20402 for (s = 0, n2 = wall.components.length; s < n2; s++) { 20403 component = wall.components[s]; 20404 if (component.wallX + component.width / 2 > newWallLength) { 20405 return false;
20406 } 20407 } 20408 } 20409 20410 // disable intersection constraint if walls has intersections before the change. 20411 // (to make able to edit old rooms with intersections). 20412 if (willIntersect && !hasIntersection) { 20413 return false; 20414 } 20415 20416 return true; 20417 }, 20418 20419 /** 20420 * Move corner to the new position 20421 * @param {Number} dx 20422 * @param {Number} dy 20423 * @param {Boolean} dontCheck don't check new constraints for the new position 20424 */ 20425 move: function(dx, dy, dontCheck) { 20426 if (!dontCheck && !this.checkConstraints(dx, dy)) { 20427 return; 20428 } 20429 20430 var i, ilen, wall, length, j, jlen, component, corners, angle, coeff, x, y, 20431 self = this, 20432 c = this._model.corners; 20433 20434 this.translate(dx, dy, 0); 20435 // For each wall, which creating the corner 20436 for (i = 0, ilen = this._walls.length; i < ilen; i++) { 20437 wall = this._walls[i]; 20438 length = wall.getAttribute('width'); 20439 angle = wall.getAngle(); 20440 corners = wall.getCorners(); 20441 20442 // ...update position of components (windows/doors) which belongs to this wall 20443 for (j = 0, jlen = wall.components.length; j < jlen; j++) { 20444 component = wall.components[j]; 20445 coeff = component.wallX / length; 20446 x = (corners[1].x - corners[0].x) * coeff + corners[0].x; 20447 y = (corners[1].y - corners[0].y) * coeff + corners[0].y; 20448 component.rotate(angle); 20449 component.move( x - component.position.x, 20450 y - component.position.y, 20451 0, true); 20452 if (component.level === -1 && component.type === 'component') { 20453 component.rotation[2] = component.rotation[2] - 180; 20454 } 20455 20456 } 20457 wall._updateMiddle(); 20458 } 20459 20460 this.jointPoints(); 20461 20462 $.each(this._walls, function() { 20463 this.getOpposite(self).jointPoints(); 20464 }); 20465 20466 this.fireEvent('onMove', dx, dy, 0); 20467 }, 20468 20469 /** 20470 * Get bounding box for this corner 20471 * @param {Transform} transform Transform to apply 20472 * @param {String} transformName Transform name 20473 * @returns {Object} bounding box hash. 20474 */ 20475 getBBox: function(transform, transformName) { 20476 if (typeof transform === 'undefined') { 20477 return { 20478 x1: this.x, 20479 y1: this.y, 20480 z1: 0, 20481 x2: this.x, 20482 y2: this.y, 20483 z2: this._model.ceilingHeight 20484 }; 20485 } else { 20486 var p1 = this.applyTransformation(transform, transformName), 20487 p2 = this.clone().translate(0, 0, this._model.ceilingHeight).applyTransformation(transform); 20488 20489 return { 20490 x1: p1.x, 20491 y1: p1.y, 20492 z1: p1.z, 20493 x2: p2.x, 20494 y2: p2.y, 20495 z2: p2.z 20496 }; 20497 } 20498 }, 20499 20500 /** 20501 * Return walls connected to current point except given 20502 * @param {Wall} except Wall to discard from response 20503 */ 20504 getWalls: function(except) { 20505 return except ? 20506 $.grep(this._walls, function(wall) { return except !== wall; }) : 20507 this._walls.slice(0); 20508 }, 20509 20510 /** 20511 * Attach wall to corner 20512 * @param {Wall} wall 20513 * @returns {Corner} 20514 */ 20515 attachWall: function(wall) { 20516 if (this._walls.indexOf(wall) === -1) { 20517 this._walls.push(wall); 20518 this._sortAttachedWalls(); 20519 this.jointPoints(); 20520 this.fireEvent('neighborsUpdated'); 20521 } 20522 return this; 20523 }, 20524 20525 /** 20526 * Detach wall from corner 20527 * @param {Wall} wall 20528 * @returns {Corner} 20529 */ 20530 detachWall: function(wall) { 20531 if ($.extract(wall, this._walls)) { 20532 this.jointPoints(); 20533 this.fireEvent('neighborsUpdated'); 20534 } 20535 return this; 20536 }, 20537 20538 /** 20539 * Get shapes from corner 20540 * @returns {Array} 20541 */ 20542 getShapes: function() { 20543 return this._shapes; 20544 }, 20545 20546 /** 20547 * Attach shape to this corner 20548 * @param {Shape} shape 20549 * @returns {Corner} 20550 */ 20551 attachShape: function(shape) { 20552 if (this._shapes.indexOf(shape) === -1) { 20553 this._shapes.push(shape); 20554 } 20555 return this; 20556 }, 20557 20558 /** 20559 * Detach shape from this corner 20560 * @param {Shape} shape 20561 * @returns {Corner} 20562 */ 20563 detachShape: function(shape) { 20564 if ($.extract(shape, this._shapes)) { 20565 // remove orphaned corner 20566 if (this._walls.length === 0 && this._shapes.length === 0) { 20567 this.remove(); 20568 } 20569 } 20570 return this; 20571 }, 20572 20573 /** 20574 * Sort attached walls by angle 20575 * @returns {Corner} 20576 */ 20577 _sortAttachedWalls: function() { 20578 var self = this; 20579 this._walls.sort(function(a, b) { 20580 var aangle = self.angleTo(a.getOpposite(self), true), 20581 bangle = self.angleTo(b.getOpposite(self), true); 20582 if (aangle > bangle) { 20583 return -1; 20584 } else if (aangle < bangle) { 20585 return 1; 20586 } 20587 return 0; 20588 }); 20589 return this; 20590 }, 20591 20592 /** 20593 * Calculate joint points for all attached walls 20594 * @returns {Corner} 20595 */ 20596 jointPoints: function() { 20597 var i, ilen = this._walls.length, 20598 w, n, wT, nT, wA, nA, jA; 20599 20600 this._joints = {}; 20601 20602 for (i = 0; i < ilen; i++) { 20603 w = this._walls[i]; 20604 wT = w.thickness / 2; 20605 wA = this.angleTo(w.getOpposite(this)); 20606 20607 if (!this._joints[w.ID]) { 20608 this._joints[w.ID] = []; 20609 } 20610 20611 if (ilen === 1) { 20612 // special case when there is only one wall attached 20613 this._joints[w.ID] = [ 20614 this.clone().translate(wT, 0).rotate(this, wA + 90), 20615 this.clone().translate(wT, 0).rotate(this, wA + 270) 20616 ]; 20617 } else { 20618 n = this._walls[(i + 1) % ilen]; 20619 nT = n.thickness / 2; 20620 nA = this.angleTo(n.getOpposite(this)); 20621 jA = ((nA - wA + 360) % 360); 20622 20623 // common case for walls with the same thickness and walls with 20624 // different thickness and angle between them in range 160° — 200° 20625 if (wT === nT || (jA > 160 && jA < 200)) { 20626 jA /= 2; 20627 20628 if (!this._joints[n.ID]) { 20629 this._joints[n.ID] = []; 20630 } 20631 20632 this._joints[w.ID][0] = this.clone() 20633 .translate(Math.abs(wT / T.sin(jA)), 0) 20634 .rotate(this, jA + wA); 20635 20636 // if the wall has the same thickness just reference 20637 // original point 20638 if (nT === nA) { 20639 this._joints[n.ID][1] = this._joints[w.ID][0]; 20640 } else { 20641 this._joints[n.ID][1] = this.clone() 20642 .translate(Math.abs(nT / T.sin(jA)), 0) 20643 .rotate(this, jA + wA); 20644 } 20645 20646 // different thickness and angle not in range 160° — 200° 20647 } else { 20648 if (!this._joints[n.ID]) { 20649 this._joints[n.ID] = []; 20650 } 20651 20652 jA = T.atan2(wT * T.sin(jA), nT + wT * T.cos(jA)); 20653 20654 this._joints[w.ID][0] = this._joints[n.ID][1] = this.clone() 20655 .translate(wT / T.sin(jA), 0) 20656 .rotate(this, jA + wA); 20657 } 20658 20659 } 20660 } 20661 20662 return this; 20663 }, 20664 20665 /** 20666 * Return joints array for specified wall in order where specified wall is 20667 * first in the list 20668 * @param {Object} wall 20669 * @returns {Array} 20670 */ 20671 getJoints: function(wall) { 20672 return this._joints[wall.ID] || []; 20673 }, 20674 20675 /** 20676 * Returns list of connected walls in joint relative to given wall 20677 * @param {Object} wall 20678 * @returns {Array} 20679 */ 20680 getJointWalls: function(wall) { 20681 var i = this._walls.indexOf(wall), 20682 ilen = this._walls.length, 20683 ret = []; 20684 20685 if (i !== -1) { 20686 ret = [ 20687 this._walls[(i + 1) % ilen], 20688 this._walls[(i - 1 + ilen) % ilen] 20689 ]; 20690 } 20691 return ret; 20692 }, 20693 20694 /**
20695 * Remove corner from model 20696 */ 20697 remove: function() { 20698 // var i, ilen; 20699 20700 this._model.unselect(this); 20701 20702 // find shapes which have those point and extract it 20703 // for (i = 0, ilen = this.shapes.length; i < ilen; i++) { 20704 // if (this._model.shapes[i].indexOf(this) !== -1) { 20705 // this. 20706 // } 20707 // } 20708 // this._model.shapes. 20709 20710 // extract self from snap points 20711 $.extract(this, this._model._snapPoints); 20712 // deregister self from model 20713 $.extract(this, this._model.corners); 20714 20715 this.fireEvent('onRemove', true); 20716 }, 20717 20718 /** 20719 * Returns list of directly attached points detected by attached shapes 20720 * except given one 20721 * @param {Array} except Array of corners to exclude 20722 * @returns {Array} array of attached points 20723 */ 20724 getCorners: function(except, includeSelf, onlyWalls) { 20725 var i, points = []; 20726 20727 // from shapes 20728 if (!onlyWalls) { 20729 for (i = this._shapes.length - 1; i >= 0; i--) { 20730 points = points.concat(this._shapes[i].getNeighbors(this)); 20731 } 20732 } 20733 20734 // from walls 20735 for (i = this._walls.length - 1; i >= 0; i--) { 20736 points.push(this._walls[i].getOpposite(this)); 20737 } 20738 20739 if (includeSelf) { 20740 points.push(this); 20741 } 20742 20743 points = points.unique(); 20744 20745 if ($.isArray(except)) { 20746 points = $.grep(points, function(point) { 20747 return except.indexOf(point) === -1; 20748 }); 20749 } 20750 20751 return points; 20752 } 20753}); 20754/*globals $, myDeco, commonMethods, Point, Transform, T */ 20755/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 20756 sloppy: true, white: true */ 20757/* 20758 * Components class for the model (derived from common ancestor from entity.js) 20759 */ 20760 20761var componentMethods = $.extend({}, commonMethods, { 20762 /** 20763 * type of the entity 20764 * @type string 20765 */ 20766 type: 'component', 20767 20768 /** 20769 * Cached points of component's bounding box. 20770 * @type Array of Points. 20771 */ 20772 _bbox: [], 20773 20774 /** 20775 * Top surface of component. 20776 * @type Array of Points. 20777 */ 20778 _surface: [], 20779 20780 /** 20781 * Component type, values are: 20782 * -2 Ceiling fixtures such as lights 20783 * -1 Windows, doors, pictures, mirrors and other objects which would be attached to a wall 20784 * 0 Rugs, carpet and other floor coverings 20785 * 1 Chairs, tables, sofas, beds and most other furniture 20786 * 2 Desk lamps, cushions, ornaments and other objects which wouldn't normally rest on the floor 20787 * @type {Number} 20788 */ 20789 // DO NOT DEFINE THIS PROPERTY! 20790 // level: 0, 20791 20792 /** 20793 * Emulation of the constructor. Needed to store pointer to the model and precalculate Transform object. 20794 */ 20795 init: function(model) { 20796 var i, p, self = this; 20797 20798 this._model = model; 20799 20800 // default level 20801 this.level = this.level || 0; 20802 20803 if (this.category === 'Door' || this.category === 'Window') { 20804 this.type = this.category.toLowerCase(); 20805 this.acceptDecor = true; 20806 this.level = -1; 20807 } 20808 20809 if (this.type === 'door') { 20810 // put door to floor 20811 this.position.z = this.height / 2; 20812 } 20813 this._bbox = []; 20814 for (i = 0; i < 8; i++) { 20815 p = new Point(); 20816 this._bbox.push(p); 20817 } 20818
20819 // check for configuration given 20820 if (this.configuration) { 20821 this.setAttribute(this.configuration, undefined, true); 20822 } 20823 20824 // for on-wall components 20825 switch(this.level) { 20826 case -2: 20827 this.position.z = this._model.ceilingHeight; 20828 break; 20829 case -1: 20830 this.parent = this.parent || this._model.findNearestWall(this.position, false, this.width); 20831 if (this.parent) { 20832 this.placeToWall(this.parent, $.grep(this._model.shapes, function() { 20833 return this.ID === self.area; 20834 }).shift()); 20835 this.updateInWallCoords(); 20836 } 20837 break; 20838 case 0: 20839 this.position.z = 0; 20840 break; 20841 case 1: 20842 case 2: 20843 this.position.z = this._calculateZposition(this.position.x, this.position.y); 20844 break; 20845 } 20846 20847 if (this.type === 'component') { 20848 this.position.type = 'component'; 20849 } 20850 // SITE-8241 20851 // this._model._snapPoints.push(this.position); 20852 20853 this._calculateTransform(); 20854 20855 this._surface.push(this._bbox[1]); 20856 this._surface.push(this._bbox[3]); 20857 this._surface.push(this._bbox[7]); 20858 this._surface.push(this._bbox[5]); 20859 20860 if (!this.category && (this.level === 1 || this.level === 0)) { 20861 this._surface.surface_height = this.surface_height; 20862 this._surface.level = this.level; 20863 this._surface.ID = this.ID; 20864 this._surface.position = this.position; 20865 this._surface.zDiff = this.height - this.surface_height; 20866 this._model._surfaces.push(this._surface); 20867 } 20868 20869 this._model[this.type + 's'].push(this); 20870 20871 if (this.material && $.isPlainObject(this.material)) { 20872 this._model.assignMaterial(this, this.material); 20873 } 20874 20875 return this; 20876 }, 20877 20878 /** 20879 * Move to the new position method 20880 * @param {Number} dx 20881 * @param {Number} dy 20882 * @param {Number} dz 20883 * @param {Any} notUsed to skip parameter passed to doors and windows 20884 */ 20885 move: function(dx, dy, dz, disableRestrictions) { 20886 var halfH, halfW, corners, dx2, dy2, dl, angle, cosA, sinA, wallLength, 20887 wx, i; 20888 dx = +(dx || 0); 20889 dy = +(dy || 0); 20890 20891 if (!disableRestrictions && this.parent) { 20892 // Calculating projection of the new position of the component to it's wall 20893 corners = this.parent.getCorners(); 20894 dx2 = corners[1].x - corners[0].x; 20895 dy2 = corners[1].y - corners[0].y; 20896 dl = (dy * dy2 + dx2 * dx) / Math.sqrt(dx2 * dx2 + dy2 * dy2); 20897 angle = T.atan2(dy2, dx2); 20898 cosA = T.cos(angle); 20899 sinA = T.sin(angle); 20900 halfW = this.width / 2; 20901 20902 // Use this projection as new dx/dy to kept component in plane of the wall. 20903 dx = dl * cosA; 20904 dy = dl * sinA; 20905 20906 // Checking if our new position of the window is in bounds of the wall 20907 wallLength = this.parent.getAttribute('width'); 20908 wx = this._calculateWallX(dx, dy); 20909 // on-wall components outside the walls aren't allowed :) 20910 if (wx < halfW) { 20911 dl = halfW - wx; 20912 dx += dl * cosA; 20913 dy += dl * sinA; 20914 } else if (wx + halfW > wallLength) { 20915 20916 dl = wx - wallLength + halfW; 20917 dx -= dl * cosA; 20918 dy -= dl * sinA; 20919 } 20920 } 20921 20922 if (this.level >= 1 && dz === undefined) { 20923 dz = this._calculateZposition((this.position.x || 0) + dx, (this.position.y || 0) + dy) - this.position.z; 20924 20925 } else if (dz === undefined) { 20926 dz = 0; 20927 } 20928 20929 if (!disableRestrictions) { 20930 halfH = this.height / 2; 20931 20932 if (this.type === 'door (DISABLED)') { 20933 // door should be on ground 20934 dz = halfH - this.position.z; 20935 } else if (this.level === 0) { 20936 // ground components, should be always on floor 20937 dz = -this.position.z; 20938 } else if (this.level === -1) { 20939 // on-wall components, should be at least under ceiling 20940 dz = Math.max(Math.min(this.position.z + dz, 20941 this._model.ceilingHeight - halfH), halfH) - 20942 this.position.z; 20943 } else if (this.level === -2) { 20944 // ceiling components 20945 dz = this._model.ceilingHeight - this.position.z; 20946 } 20947 } 20948 20949 this.position.translate(dx, dy, dz); 20950 20951 for (i = 0; i < 8; i++) { 20952 this._bbox[i].translate(dx, dy, dz); 20953 } 20954 20955 // for on-wall components 20956 if (this.parent) { 20957 this.updateInWallCoords(); 20958 } 20959 20960 this.fireEvent('onMove', dx, dy, dz); 20961 }, 20962 20963 /** 20964 * Rotate the component 20965 * @param {Number} angle new component angle in x0y plane 20966 */ 20967 rotate: function(angle) { 20968 var oldAngle = this.rotation[2], i; 20969 this.rotation[2] = (angle + (this.x0yRotation || 0)) % 360; 20970 for (i = 0; i < 8; i++) { 20971 this._bbox[i].rotate(this.position, this.rotation[2] - oldAngle); 20972 } 20973 this._calculateTransform(); 20974 this.fireEvent('onRotate', this.rotation[2]); 20975 }, 20976 20977 /** 20978 * Get bounding box for the component 20979 * @param {Transform} transform Transform to apply 20980 * @param {String} transformName Transform name 20981 * @returns {Object} bounding box hash. 20982 */ 20983 getBBox: function(transform, transformName) { 20984 var x = [], y = [], z = [],
20985 q1, q2, q3, p, p1, p2; 20986 20987 for (q1 = 0; q1 <= 1; q1++) { 20988 for (q2 = 0; q2 <= 1; q2++) { 20989 for (q3 = 0; q3 <= 1; q3++) { 20990 p = new Point(this.bounding_box[q1].x, this.bounding_box[q2].y, this.bounding_box[q3].z) 20991 .translate(this.position.x, this.position.y, this.position.z) 20992 .rotate(this.position, this.rotation[2], this.rotation[0], this.rotation[1]); 20993 20994 if (transform) { 20995 p.applyTransformation(transform); 20996 } 20997 20998 x.push(p.x); 20999 y.push(p.y); 21000 z.push(p.z); 21001 } 21002 } 21003 } 21004 21005 // Main goal of this function - calculation of the bbox for doors, which opening outside and may affect bbox of the full room 21006 if (this.open_direction === 'o') { 21007 p1 = this.position 21008 .clone() 21009 .translate(this.width / 2, this.bounding_box[1].z, 0); 21010 p2 = p1.clone() 21011 .translate( - this.width * (this.open_side === 'b' ? 0.5 : 1), 0, 0) 21012 // Magic numbers 21013 .rotate(p1, -30 * (this.open_side === 'b' ? 0.5 : 1)) 21014 .rotate(this.position, this.rotation[2]); 21015 21016 if (transform) { 21017 p2.applyTransformation(transform); 21018 } 21019 21020 x.push(p2.x); 21021 y.push(p2.y); 21022 z.push(p2.z); 21023 } 21024 21025 return { 21026 x1: Math.min.apply(Math, x), 21027 y1: Math.min.apply(Math, y), 21028 z1: Math.min.apply(Math, z), 21029 x2: Math.max.apply(Math, x), 21030 y2: Math.max.apply(Math, y), 21031 z2: Math.max.apply(Math, z) 21032 }; 21033 }, 21034 21035 /** 21036 * Update cached points for bbox and transformation matrix 21037 */ 21038 _calculateTransform: function() { 21039 if (this._transform) { 21040 this._transform.reset(); 21041 } else { 21042 this._transform = new Transform(); 21043 } 21044 21045 var q1 = (this.bounding_box[1].x + this.bounding_box[0].x), 21046 q2 = (this.bounding_box[1].y + this.bounding_box[0].y), 21047 q3 = (this.bounding_box[1].z + this.bounding_box[0].z), 21048 i = 0; 21049 21050 this._transform 21051 .translate(new Point(-q1, -q2, -q3)) 21052 .rotate('x', this.rotation[0]) 21053 .rotate('y', this.rotation[1]) 21054 .rotate('z', -this.rotation[2]) 21055 .translate(new Point(q1, q2, q3)) 21056 .translate(this.position); 21057 21058 for (q1 = 0; q1 <= 1; q1++) { 21059 for (q2 = 0; q2 <= 1; q2++) { 21060 for (q3 = 0; q3 <= 1; q3++) { 21061 this._bbox[i].x = this.bounding_box[q1].x; 21062 this._bbox[i].y = this.bounding_box[q2].y; 21063 this._bbox[i].z = this.bounding_box[q3].z; 21064 21065 this._bbox[i].applyTransformation(this._transform); 21066 i++; 21067 } 21068 } 21069 } 21070 }, 21071 21072 /** 21073 * Return angle between of the component in x0y plane 21074 * @returns Number 21075 */ 21076 getAngle: function() { 21077 return this.rotation[2]; 21078 }, 21079 21080 /** 21081 * Return new in-wall x coord after moving the component 21082 * @param {Number} dx 21083 * @param {Number} dy 21084 * @returns {Boolean} new in-wall x coord 21085 */ 21086 _calculateWallX: function(dx, dy) { 21087 return this.position.clone() 21088 .translate(dx || 0, dy || 0) 21089 .distance(this.parent.getCorners()[0], 'xy'); 21090 }, 21091 21092 /** 21093 * Update in-wall coords from real world ones. 21094 */ 21095 updateInWallCoords: function() { 21096 this.wallX = this._calculateWallX(); 21097 this.wallY = this.position.z; 21098 }, 21099 21100 /** 21101 * Calculate Z position of element 21102 * @param {Number} x 21103 * @param {Number} y 21104 * @returns {Number} z 21105 */ 21106 _calculateZposition: function(x, y) { 21107 var z = 0, 21108 self = this, 21109 surfaces, p, i, len; 21110 21111 switch (this.level || 0) { 21112 case -2: 21113 z = this._model.ceilingHeight; 21114 break; 21115 case 0: 21116 z = this.position.z || 0; 21117 break; 21118 case -1: 21119 z = this.position.z || this._model.ceilingHeight / 2; 21120 break; 21121 case 1: 21122 case 2: 21123 p = new Point(x, y, 0); 21124 21125 // Trying to find intersection of the component position with other component's surfaces 21126 surfaces = $.grep(this._model._surfaces, function(surface) { 21127 return (self.level > surface.level) && p.inPoly(surface); 21128 }); 21129 21130 if (surfaces.length > 0) { 21131 z = Number.MIN_VALUE; 21132 for (i = 0, len = surfaces.length; i < len; i++) { 21133 z = Math.max(surfaces[i].surface_height + surfaces[i].position.z, z); 21134 } 21135 } 21136 break; 21137 } 21138 return z; 21139 }, 21140 21141 /** 21142 * Get component attribute 21143 * @param {String} name Attribute name 21144 * @returns {Mixed} 21145 */ 21146 getAttribute: function(name) { 21147 return this[name]; 21148 }, 21149 21150 /** 21151 * Set component attribute 21152 * @param {String} name Attribute name 21153 * @param {Mixed}
21153 name Attribute value 21154 * @param {Boolean} noevent Skip event propagation 21155 * @returns {Object} this 21156 */ 21157 setAttribute: function(name, value, noevent) { 21158 var self = this, 21159 map = {width: 'x', height: 'y', depth: 'z'}, 21160 limit; 21161 21162 if ($.isPlainObject(name)) { 21163 $.each(name, function(key, value) { 21164 self.setAttribute(key, value, noevent); 21165 }); 21166 return; 21167 } 21168 21169 if (!this.configurations || !this.configurations[name]) { 21170 return; 21171 } 21172 21173 switch(name) { 21174 case 'width': 21175 limit = this.parent ? this.parent.getAttribute('width') : value; 21176 break; 21177 case 'height': 21178 limit = this.parent ? this.parent.getAttribute('height') : value; 21179 break; 21180 case 'depth': 21181 limit = value; 21182 /*if (this.parent && (this.type === 'door' || this.type === 'window')) { 21183 limit = value = this.parent.thickness; 21184 }*/ 21185 break; 21186 case 'open_side': 21187 case 'open_direction': 21188 limit = $.map(this.configurations[name].values, function(v) { 21189 return v[0]; 21190 }); 21191 if (limit.indexOf(value) === -1) { 21192 // keep old value 21193 value = this[name]; 21194 } 21195 break; 21196 default: 21197 return; 21198 } 21199 21200 if (map[name]) { 21201 value = Math.max(this.configurations[name].minimum, 21202 Math.min(value, this.configurations[name].maximum, limit)); 21203 // correct bbox 21204 this.bounding_box[0][map[name]] = -value / 2; 21205 this.bounding_box[1][map[name]] = value / 2; 21206 this._calculateTransform(); 21207 } 21208 21209 this[name] = value; 21210 21211 // force model to align inside wall 21212 if (map[name] && this.parent) { 21213 this.move(); 21214 } 21215 21216 if (!noevent) { 21217 this.fireEvent('onAttributeChange', name, value); 21218 this._model.operationEnd(['attributeChanged', this]); 21219 } 21220 21221 return this; 21222 }, 21223 21224 /** 21225 * Move component to a specified wall and place it properly 21226 * @param {Object} wall Wall attach to 21227 * @param {Object} shape Shape which wall attached to 21228 */ 21229 placeToWall: function(wall, shape) { 21230 // find shape if it not given 21231 this._shape = shape = shape || (function() { 21232 var shapes = wall.getShapes(), 21233 i, ilen; 21234 for (i = 0, ilen = shapes.length; i < ilen; i++) { 21235 if (this.position.inPoly(shapes[i])) { 21236 return shapes[i]; 21237 } 21238 } 21239 // didn't find shape, use first one 21240 return shapes[0]; 21241 }.call(this)); 21242 21243 // place it to a correct position 21244 var d = this.position.distance(wall), 21245 angle = wall.getAngle(), 21246 dx = -d * T.cos(angle + 90), 21247 dy = d * T.sin(angle + 90), 21248 dz = 0, 21249 wallX = 0, 21250 wallL = wall.getAttribute('width'), 21251 halfW = this.width / 2, 21252 halfH = this.height / 2; 21253 21254 // remove component from old wall if exist 21255 if (this.parent) { 21256 this.parent.removeComponent(this); 21257 } 21258 21259 this.x0yRotation = wall.getAngle(shape) ? (wall.getAngle(shape) - angle) : 180; 21260 // add component to a new wall 21261 wall.addComponent(this); 21262 wallX = this._calculateWallX(dx, dy); 21263 21264 if (this.position.z - halfH < 0) { 21265 dz = halfH - this.position.z; 21266 } else if (this.position.z + halfH > this._model.ceilingHeight) { 21267 dz = this._model.ceilingHeight - this.position.z - halfH; 21268 } 21269 21270 this.rotate(angle, false); 21271 21272 // disable restrictions for doors and windows 21273 this.move(dx, dy, dz, true);
21274 if (this.parent && (this.type === 'door' || this.type === 'window')) { 21275 this.setAttribute('depth', 6, true); 21276 } 21277 21278 this.updateInWallCoords(); 21279 }, 21280 21281 /** 21282 * Event method executed on operation end 21283 * @param {String} name Operation name 21284 */ 21285 operationEnd: function(name) { 21286 if ((name === 'drag' || name === 'add') && 21287 this.level === -1 && 21288 this.type !== 'door' && 21289 this.type !== 'window') { 21290 if(name === 'drag') { 21291 // Degrading gratefuly if there are no wall of desired size for the component; 21292 var wall = this._model.findNearestWall(this.position, false, this.width) || 21293 this._model.findNearestWall(this.position); 21294 21295 this.placeToWall(wall); 21296 } 21297 this.rotation[2] -= 180; 21298 } 21299 21300 this.fireEvent('onOperationEnd', name); 21301 }, 21302 21303 /** 21304 * Get distance from floor to component base 21305 * @return {Number} 21306 */ 21307 getFloorDistance: function() { 21308 return this.position.z - this.height / 2; 21309 }, 21310 21311 /** 21312 * Set new distance from floor to component base 21313 * @param {Number} distance 21314 * @return {Number} 21315 */
21316 setFloorDistance: function(distance) { 21317 this.rotation[2] += 180; 21318 this.move(0, 0, distance + this.height / 2 - this.position.z); 21319 this._model.operationEnd(['componentFloorDistance', this]); 21320 this.rotation[2] -= 180; 21321 return this.getFloorDistance(); 21322 }, 21323 21324 /** 21325 * Remove entity from model 21326 */ 21327 remove: function() { 21328 this._model.unselect(this); 21329 21330 // remove material 21331 if (this.material) { 21332 this._model.assignMaterial(this); 21333 } 21334 21335 // remove self from model 21336 $.extract(this, this._model[this.type + 's']); 21337 21338 // remove self from snap points 21339 // SITE-8241 21340 // $.extract(this.position, this._model._snapPoints); 21341 21342 // dereference self from parent 21343 if (this.parent) { 21344 this.parent.removeComponent(this); 21345 this.parent = false; 21346 } 21347 21348 // deregister self from model surfaces 21349 $.extract(this._surface, this._model._surfaces); 21350 21351 this.fireEvent('onRemove', true); 21352 21353 if (this.type === 'component') { 21354 myDeco.body.trigger('componentRemoved', [this]); 21355 } 21356 } 21357}); 21358/*globals $, AppModel, myDeco, shapeMethods, Point, Image, componentMethods, 21359 cornerMethods, wallMethods, cameraMethods, diffuse2Hex, 21360 weilerAtherton, window, findWallIntersection, emptyRoom */ 21361/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 21362 sloppy: true, white: true */ 21363/** 21364 * FPE room model 21365 */ 21366var FpeModel = AppModel.extend({ 21367 /** 21368 * Tolerancy value for finding common points of different polygons 21369 * @type {Number} 21370 */ 21371 _tolerancy: 0.5, 21372 21373 /** 21374 * Collection of anchor points for snapping 21375 * all points with type defined will work for draw/corner movement 21376 * @type {Array} 21377 */ 21378 _snapPoints: [], 21379 21380 /** 21381 * List of registered top surfaces, grouped by level 21382 * @type {Array} 21383 */ 21384 _surfaces: [], 21385 21386 /** 21387 * Model, stored for simple, one step undo 21388 * @type {Model} 21389 */ 21390 _undoModel: false, 21391 _currModel: false, 21392 _redoModel: false, 21393 21394 /** 21395 * Object used for storage all used components in a clear state 21396 * @type {Object} 21397 * @private 21398 */ 21399 _componentsCache: {}, 21400 21401 /** 21402 * Object used for storage all inexistent components 21403 * @type {Object} 21404 * @private 21405 */ 21406 _componentsFailed: {}, 21407 21408 /** 21409 * List of selected entities 21410 * @type {Array} 21411 * @private 21412 */ 21413 _selected: [], 21414 21415 /** 21416 * Selection box, third point is a center of selection 21417 * @type {Array} 21418 */ 21419 _selectionCenter: new Point(), 21420 rotationAvailable: false, 21421 21422 /** 21423 * Is floorplan locked for change or not 21424 * @type {Boolean} 21425 */ 21426 _locked: false, 21427 21428 /** 21429 * Masking mode flag 21430 * @type {Boolean} 21431 */ 21432 _masking: false, 21433 21434 /** 21435 * Model settings 21436 * @type {Object} 21437 */ 21438 settings: { 21439 defaultMaterials: { 21440 'wall': { 21441 "shader": "combi_material", 21442 "ID": "materialWallDefault", 21443 "use_diffuse_map": false, 21444 "ambient": [0, 0, 0], 21445 "shiny": 0, 21446 "diffuse": [0.93333333333333333, 0.9372549019607843, 0.9176470588235294] 21447 }, 21448 'floor': { 21449 "diffuse_texture": "floor_and_wall/original/Pine_Wood_Floor_Planks2.jpg", 21450 "shader": "combi_material", 21451 "diffuse": [1, 1, 1], 21452 "ambient": [0.050000000000000003, 0.050000000000000003, 0.050000000000000003], 21453 "use_diffusemap": "on", 21454 "shiny": 0.20000000000000001, 21455 "ID": "decor-2542", // materialFloorDefault 21456 "texture_width": 90, 21457 "texture_height": 179 21458 }, 21459 'ceiling': { 21460 "shader": "combi_material", 21461 "ID": "materialCeilingDefault", 21462 "use_diffuse_map": false, 21463 "ambient": [0, 0, 0], 21464 "shiny": 0, 21465 "diffuse": [0.97599999999999998, 0.97599999999999998, 0.97599999999999998] 21466 } 21467 }, 21468 // http request timeout in seconds 21469 renderRequestTimeout: 60 21470 }, 21471 21472 _metadata: { 21473 "rating": 0, 21474 "description": "", 21475 "category": "", 21476 "view_count": 0, 21477 "background_image": null, 21478 "name": "", 21479 "public": 1, 21480 "comment_count": 0, 21481 "type": "3d", 21482 "image": "" 21483 }, 21484 21485 /** 21486 * Model initialization 21487 */ 21488 init: function() { 21489 var self = this; 21490 21491 this._super.apply(this, arguments); 21492 21493 myDeco.body 21494 .bind('roomSaved', function(e, roomType, roomId, options) { 21495 if (self._masking) { 21496 self._ws_url = options.originalURL; 21497 self.deleteRoom(); 21498 } 21499 }); 21500 }, 21501 21502 // TBD: remove when properties panel are ready 21503 /** 21504 * Fill room with metadada 21505 * @param {Event} e 21506 * @param {Function|Object} callbackOrData 21507 */
21508 metadata: function(e, callbackOrData) { 21509 if ($.isFunction(callbackOrData)) { 21510 var match = this._ws_url.match(/\/(\d+)\/?/), 21511 parent_id = match ? parseInt(match[1], 10) : this._room_id || 0; 21512 21513 callbackOrData( 21514 $.extend({ 21515 hq_renderer: this.camera.hq_renderer, 21516 exposure: this.camera.exposure 21517 }, this._metadata), 21518 parent_id, e); 21519 } else { 21520 this._metadata = $.extend(true, this._metadata, callbackOrData); 21521 this.camera.hq_renderer = this._metadata.hq_renderer || myDeco.settings.DEFAULT_HQ_RENDERER; 21522 this.camera.exposure = this._metadata.exposure || 0; 21523 delete this._metadata.hq_renderer; 21524 delete this._metadata.exposure; 21525 } 21526 }, 21527 21528 /** 21529 * Reset the model to initial state 21530 * @param {Boolean} keepInfo Keep room info 21531 */ 21532 reset: function(keepInfo) { 21533 keepInfo = keepInfo || false; 21534 21535 if (!keepInfo) { 21536 this._super(); 21537 this._renderUrl = myDeco.urls.renderws; 21538 21539 // cleanup memory cache 21540 myDeco.App.ws.proxy.clear(); 21541 } 21542 21543 this.corners = []; 21544 this.walls = []; 21545 this.shapes = []; 21546 this.components = []; 21547 this.camera = {}; 21548 this.materials = []; 21549 this.doors = []; 21550 this.windows = []; 21551 this.ceilingHeight = 265; 21552 this.environment = {}; 21553 21554 this._surfaces = []; 21555 this._snapPoints = []; 21556 this._currModel = false; 21557 this._selected = []; 21558 21559 this._componentCounter = 0; 21560 this._wallCounter = 0; 21561 this._areaCounter = 0; 21562 this.light_env = undefined; 21563 21564 myDeco.body.trigger('roomCleared'); 21565 }, 21566 21567 /** 21568 * Set new height for ceiling 21569 * @param {Number} height New height to set 21570 */ 21571 setCeilingHeight: function(height) { 21572 var components = this.doors.concat(this.windows, 21573 $.grep(this.components, function(component) { 21574 return component.level === -1; 21575 })), 21576 heights = $.map(components, function(component) { 21577 return component.height; 21578 }), 21579 ceilingComponents = $.grep(this.components, function(component) { 21580 return component.level === -2; 21581 }); 21582 // limit min wall height to 2m 21583 heights.push(height, 200); 21584 this.ceilingHeight = Math.max.apply(Math, heights); 21585 $.each(components, function() { 21586 this.move(0, 0, 0); 21587 }); 21588 for (var i = 0; i < ceilingComponents.length; i++) { 21589 ceilingComponents[i].move(0, 0, ceilingComponents[i]._calculateZposition() - ceilingComponents[i].position.z); 21590 } 21591 }, 21592 21593 /** 21594 * Find common wall which belongs to both corners 21595 * @param {Corner} c1 First corner 21596 * @param {Corner} c2 Second corner 21597 * @return {Wall} 21598 */ 21599 _findWall: function(c1, c2) { 21600 return c1.getWalls && c2.getWalls ? $.intersect(c1.getWalls(), c2.getWalls()).shift() : false; 21601 }, 21602 21603 /** 21604 * Generate component ID for a new one 21605 * @return {String} 21606 */ 21607 generateComponentId: function() { 21608 return 'model-' + this._componentCounter++; 21609 }, 21610 21611 /** 21612 * Process component to add/update some fields 21613 * @param {Component} component 21614 * @param {Boolean} preserveID 21615 */ 21616 processComponent: function(component, preserveID) { 21617 var j, jlen, side, rotation, 21618 sizeGrep = function(size) { 21619 return $.values(size[2]).indexOf(component.component_id) !== -1; 21620 }; 21621 21622 if (preserveID && component.ID) { 21623 j = +component.ID.match(/(\d+)/)[1]; 21624 if (j >= this._componentCounter) { 21625 this._componentCounter = j + 1; 21626 } 21627 } else { 21628 component.ID = this.generateComponentId(); 21629 } 21630 21631 if (component._isProcessed) { 21632 return; 21633 } 21634 21635 for (j = 0, jlen = component.bounding_box.length; j < jlen; j++) { 21636 component.bounding_box[j] = new Point(component.bounding_box[j]); 21637 } 21638 21639 for (j = 0, jlen = component.rotation.length; j < jlen; j++) { 21640 rotation = Math.round(component.rotation[j]) % 360; 21641 component.rotation[j] = (rotation < 0 ? 360 + rotation : rotation); 21642 } 21643 21644 side = { 21645 '0': {x: 'x', y: 'y', z: 'z', x0yRender: 'render_top'}, 21646 '90': {x: 'x', y: 'z', z: 'y', x0yRender: 'render_back'}, 21647 '180': {x: 'x', y: 'y', z: 'z', x0yRender: 'render_bottom'}, 21648 '270': {x: 'x', y: 'z', z: 'y', x0yRender: 'render_front'} 21649 }[component.rotation[0]]; 21650 21651 // determining prerendered image for plan view. 21652 component.x0yRender = side.x0yRender; 21653 21654 if (!component.configuration) {
21655 component.width = Math.abs(component.bounding_box[0][side.x] - component.bounding_box[1][side.x]); 21656 component.depth = Math.abs(component.bounding_box[0][side.y] - component.bounding_box[1][side.y]); 21657 component.height = Math.abs(component.bounding_box[0][side.z] - component.bounding_box[1][side.z]); 21658 component.centerZ = -component.bounding_box[0][side.z]; 21659 } 21660 21661 // component shift from central point 21662 component.shift = { 21663 dx: component.bounding_box[1][side.x] - component.width / 2, 21664 dy: component.bounding_box[1][side.y] - component.depth / 2, 21665 dz: component.bounding_box[1][side.z] - component.height / 2 21666 }; 21667 21668 // find aperture 21669 if (typeof component.aperture === 'string' && 21670 (component.category === 'Door' || component.category === 'Window')) { 21671 component.aperture = { 21672 type: component.aperture, 21673 bounds: [component.width, component.height] 21674 }; 21675 } 21676 21677 component._isProcessed = true; 21678 }, 21679 21680 /** 21681 * Assign environment to a model 21682 * @param {String} type Env type, 'image' is a good one 21683 * @param {String} value 21684 */ 21685 assignEnvironment: function(type, value) { 21686 this.environment[type] = value; 21687 }, 21688 21689 /** 21690 * Remove specified environment from model 21691 * @param {String} type Env type 21692 */ 21693 removeEnvironment: function(type) { 21694 if (typeof this.environment[type] !== 'undefined') { 21695 delete this.environment[type]; 21696 } 21697 }, 21698 21699 /** 21700 * Assign photo to the wall 21701 * @param {Object} target Object assign photo to, usually Wall 21702 * @param {Object} photo Photo to assign, should have id and image in properties 21703 * If not specified will change properties if photo is applied 21704 * @param {Object} properties Photo positioning properties 21705 */ 21706 assignPhoto: function(target, photo, properties) { 21707 // duplicate material if refcounter more than one 21708 var material = target.material, 21709 self = this, 21710 image; 21711 21712 // no photo given and material hasn't image at all 21713 if (!photo && typeof material.photo === 'undefined') { 21714 return; 21715 } 21716 21717 function setProperties(material, properties) { 21718 $.extend(true, material.photo, properties || {}); 21719 var p = material.photo; 21720 // prevent of settings too small image 21721 p.width = p.width < 1 ? 1 : p.width; 21722 p.height = p.height < 1 ? 1 : p.height; 21723 21724 material.decal_repeat_u = p.repeatX; 21725 material.decal_repeat_v = p.repeatY; 21726 material.decal_transform = [ 21727 [100 / p.width, 0, -(p.x / p.width) ], 21728 [0, 100 / p.height, -(p.y / p.height)] 21729 ]; 21730 } 21731 21732 // fix ID if a new photo given 21733 if (photo) { 21734 material = $.extend(true, {}, material); 21735 delete material._refcounter; 21736 21737 // remove complex material id if exist 21738 if (typeof material.photo !== 'undefined') { 21739 material.ID = material.ID.substring(material.ID.indexOf(':') + 1); 21740 } 21741 21742 image = new Image(); 21743 21744 image.onload = function() { 21745 // construct unique ID 21746 material.ID = target.ID + '-' + photo.component_id + ':' + material.ID; 21747 21748 material.photo = { 21749 orig: { 21750 width: image.width, 21751 height: image.height 21752 }, 21753 x: (target.getAttribute('width') - image.width) / 2, 21754 y: (target.getAttribute('height') - image.height) / 2, 21755 width: image.width, 21756 height: image.height, 21757 repeatX: false, 21758 repeatY: false 21759 }; 21760 material.decal_texture = photo.texture; 21761 setProperties(material); 21762 self.assignMaterial(target, material); 21763 self.operationEnd(['material']); 21764 }; 21765 image.src = photo.image; 21766 } else { 21767 setProperties(material, properties); 21768 // notify target about changed properties 21769 target.fireEvent('onMaterial'); 21770 } 21771 }, 21772 21773 /** 21774 * Remove photo from wall 21775 * @param {Object} target Source which have an photo attached 21776 */ 21777 removePhoto: function(target) { 21778 if (typeof target.material.photo !== 'undefined') { 21779 var material = $.extend(true, {}, target.material, { 21780 ID: target.material.ID.substring(target.material.ID.indexOf(':') + 1) 21781 }); 21782 delete material.photo; 21783 delete material.decal_texture; 21784 delete material.decal_transform; 21785 delete material.decal_repeat_u; 21786 delete material.decal_repeat_v; 21787 this.assignMaterial(target, material, 'wall', true); 21788 } 21789 }, 21790 21791 /** 21792 * Assign material to the specified entity 21793 * @param {Entity} target Target material assign to 21794 * @param {Material} material 21795 * @param {String} type Target destination type, allowed 'ceiling', 'floor' and 'wall' 21796 * @param {Boolean}
21796 skipPhoto Skip photo value on assigning 21797 */ 21798 assignMaterial: function(target, material, type, skipPhoto) { 21799 var found = false, oldMaterial, i, len; 21800 type = type || target.type; 21801 21802 if (material) { 21803 if (target.material && target.material.photo && !skipPhoto) { 21804 material = $.extend(true, {}, material, material.photo ? {} : { 21805 photo: target.material.photo, 21806 decal_texture: target.material.decal_texture, 21807 decal_transform: target.material.decal_transform, 21808 decal_repeat_u: target.material.decal_repeat_u, 21809 decal_repeat_v: target.material.decal_repeat_v, 21810 ID: target.material.ID.substring(0, target.material.ID.indexOf(':')) + ':' + material.ID 21811 }); 21812 } else { 21813 // Checking if we already have such material in model 21814 for (i = 0, len = this.materials.length; i < len; i++) { 21815 if (this.materials[i].ID === material.ID) { 21816 material = this.materials[i]; 21817 found = true; 21818 break; 21819 } 21820 } 21821 } 21822 21823 if (!material._refcounter) { 21824 material._refcounter = 0; 21825 } 21826 21827 material._refcounter++; 21828 if (!found) { 21829 this.materials.push(material); 21830 myDeco.body.trigger('materialAdded', [material]); 21831 } 21832 } 21833 21834 switch (type) { 21835 case 'ceiling': 21836 oldMaterial = target.ceilingMaterial; 21837 target.ceilingMaterial = material; 21838 break; 21839 21840 case 'floor': 21841 oldMaterial = target.floorMaterial; 21842 target.floorMaterial = material; 21843 break; 21844 21845 default: 21846 oldMaterial = target.material; 21847 target.material = material; 21848 } 21849 21850 // Do not notify entities if material was removed from them 21851 // (usually just before deletion of the entity) 21852 if (typeof material !== 'undefined') { 21853 target.fireEvent('onMaterial'); 21854 } 21855 21856 // do not change order in conditional statement! 21857 if (oldMaterial && material && oldMaterial.ID !== material.ID && (--oldMaterial._refcounter) === 0) { 21858 myDeco.body.trigger('materialRemoved', this.materials.splice(this.materials.indexOf(oldMaterial), 1)); 21859 } 21860 21861 return material; 21862 }, 21863 21864 /** 21865 * Check for entity is locked 21866 * @param {Object} entity 21867 * @returns {Boolean} 21868 */ 21869 isLocked: function(entity) { 21870 return this._locked && (entity.type === 'corner' || 21871 entity.type === 'wall'); 21872 }, 21873 21874 /** 21875 * Toggle floorplan lock 21876 * @param {Boolean} state 21877 */ 21878 toggleLock: function(state) { 21879 var selectedWalls; 21880 this._locked = typeof state !== 'undefined' ? state : !this._locked; 21881 21882 // unselect selected walls 21883 if (this._locked) { 21884 selectedWalls = $.grep(this._selected, function(item) { 21885 return item.type === 'wall'; 21886 }); 21887 21888 if (selectedWalls.length > 0) { 21889 this.unselect(selectedWalls); 21890 } 21891 } 21892 }, 21893 21894 /** 21895 * Return list of unique material IDs in model 21896 */ 21897 uniqueMaterialIds: function() { 21898 return $.map(this.materials, function(material) { 21899 return material.component_id || null; 21900 }).unique(); 21901 }, 21902 21903 /** 21904 * Identify is given material or its id is in model or not 21905 * @param {Object|String} id Material or its ID 21906 */ 21907 isMaterialInModel: function(id) { 21908 id = typeof id === 'object' ? id.component_id : id; 21909 return $.any(this.materials, function(material) { 21910 return id === material.component_id; 21911 }); 21912 }, 21913 21914 /** 21915 * Return list of unique component IDs in model 21916 */ 21917 uniqueComponentIds: function() { 21918 return $.map(this.components, function(component) { 21919 return component.component_id || null; 21920 }).unique(); 21921 }, 21922 21923 /** 21924 * Identify is selected component or its id is in model or not 21925 * @param {Object|String} id Component or its ID 21926 */ 21927 isComponentInModel: function(id) { 21928 id = typeof id === 'object' ? id.component_id : id; 21929 return $.any(this.components, function(component) { 21930 return id === component.component_id; 21931 }); 21932 }, 21933 21934 /** 21935 * Add camera to room 21936 * @param {Object} 21937 * @returns {Camera} 21938 */ 21939 addCamera: function(camera) { 21940 this.camera = {}; 21941 $.extend(true, this.camera, camera, cameraMethods).init(this); 21942 myDeco.body.trigger('cameraAdded', [this.camera]); 21943 return this.camera; 21944 }, 21945 21946 /** 21947 * Add component to the room 21948 * @param {Object} component Component to add 21949 * @param {Number} x 21950 * @param {Number} y 21951 * @param {Number} z 21952 * @param {Boolean} noselect Prevent of component selection 21953 * @param {Boolean} preserveID 21954 * @returns {Camera} Added component 21955 */ 21956 addComponent: function(component, x, y, z, noselect, preserveID) { 21957 // assign component position if specified 21958 component.position[0] = typeof x === 'undefined' ? (+component.position[0] || 0) : +x; 21959 component.position[1] = typeof y === 'undefined' ? (+component.position[1] || 0) : +y; 21960 component.position[2] = typeof z === 'undefined' ? (+component.position[2] || 0) : +z; 21961 21962 component.position = new Point(component.position); 21963 21964 this.processComponent(component, preserveID); 21965 21966 var pID; 21967 21968 // Searching for parent wall for the window/door 21969 if (!component.parent && component.parentID) { 21970 pID = 'wall' + component.parentID.replace('wall', ''); 21971 21972 component.parent = $.grep(this.walls, function(wall) { 21973 return wall.ID === pID; 21974 }).shift(); 21975 21976 delete component.parentID; 21977 } 21978 // window or door 21979 $.extend(true, component, componentMethods).init(this); 21980 myDeco.body.trigger('componentAdded', [component]); 21981 21982 if (this.loaded && !noselect) { 21983 this.select(component); 21984 } 21985 21986 return component; 21987 }, 21988 21989 /** 21990 * Add room 21991 * @param {String} type Type of room shape 21992 * @param {Float} x X position of the center of the room 21993 * @param {Float} y Y position of the center of the room 21994 * @param {Float} width width of the room 21995 * @param {Float} depth depth of the room 21996 * @returns {Shape} added shape 21997 */ 21998 addRoom: function(type, x, y, width, depth) { 21999 var shape = [], 22000 i, ilen, j, 22001 dx = 0, dy = 0, 22002 // jlen, distance = Number.MAX_VALUE, d, 22003 model = this.settings.shapes[type]; 22004 22005 if (!model) { 22006 return; 22007 } 22008 22009 width = width || model.width; 22010 depth = depth || model.depth; 22011 22012 for (i = 0, ilen = model.shape.length; i < ilen; i++) { 22013 shape.push(new Point(x + (model.shape[i][0] - 0.5) * width, 22014 y + (model.shape[i][1] - 0.5) * depth)); 22015 } 22016 22017 /* Snap to the nearest point 22018 for (i = 0, ilen = this.corners.length; i < ilen; i++) { 22019 for (j = 0, jlen = shape.length; j < jlen; j++) { 22020 d = this.corners[i].distance(shape[j]); 22021 if (d < distance && d < this.settings.searchDistance) { 22022 distance = d; 22023 dx = this.corners[i].x - shape[j].x; 22024 dy = this.corners[i].y - shape[j].y; 22025 } 22026 } 22027 } 22028 */ 22029 22030 for (i = 0, ilen = shape.length; i < ilen; i++) { 22031 shape[i] = this.addCorner(shape[i].translate(dx, dy)); 22032 } 22033 22034 shape = this.addShape(shape); 22035 22036 for (i = 0, j = shape.length - 1, ilen = shape.length; i < ilen; j = i++) { 22037 shape.attachWall(this.addWall([shape[i], shape[j]], [shape])); 22038 } 22039 22040 this.select(shape); 22041 22042 return shape; 22043 }, 22044 22045 /** 22046 * Add corner to the model 22047 * @param {Point} corner. Corner to add. 22048 * @returns {Corner} 22049 */ 22050 addCorner: function(corner) { 22051 corner = $.extend(true, corner, cornerMethods).init(this); 22052 myDeco.body.trigger('cornerAdded', [corner]); 22053 return corner; 22054 }, 22055 22056 /** 22057 * Add wall to the model 22058 * @param {Array of Point} wall. Wall to add. 22059 * @param {Array of Shape} shape. Parent shapes of the wall 22060 * @returns {Wall} Added wall. 22061 */ 22062 addWall: function(wall, shapes) { 22063 wall = $.extend(true, wall, wallMethods).init(this, shapes); 22064 myDeco.body.trigger('wallAdded', [wall]); 22065 return wall; 22066 }, 22067 22068 /** 22069 * Add shape to the model 22070 * @param {Array of Point} shape. Shape to add. 22071 * @param {Array of Wall} walls List of walls connected to shape 22072 * @param {Object} floor Floor material 22073 * @oaram {Object} ceiling Ceiling material
22074 * @returns {Shape} Added shape. 22075 */ 22076 addShape: function(shape, walls, floor, ceiling) { 22077 if (floor) { 22078 shape.floorMaterial = floor; 22079 } 22080 22081 if (ceiling) { 22082 shape.ceilingMaterial = ceiling; 22083 } 22084 shape = $.extend(true, shape, shapeMethods).init(this, walls); 22085 myDeco.body.trigger('shapeAdded', [shape]); 22086 return shape; 22087 }, 22088 22089 /** 22090 * Replace existing shape with new one (when combining rooms) 22091 * — find and add newly created corners 22092 * — 22093 * @param {Shape} oldShape. Old shape. It's ceiling/flooring materials will be used for new one. 22094 * @param {Array of Point} newShape. Shape to add. 22095 * @returns {Shape} Added shape. 22096 */ 22097 replaceShape: function(oldShape, newShape, restrictWalls) { 22098 var i1, i2, len1 = newShape.length, walls = [], wall, rShape = [], 22099 newCorners = [], corner; 22100 22101 // Remove identical points from shape as well as short wall segments 22102 // and prepare new shape 22103 for (i1 = 0, i2 = len1 - 1; i1 < len1; i2 = i1++) { 22104 // remove small walls 22105 if (newShape[i1].distance(newShape[i2]) < 22106 this.settings.searchDistance) { 22107 wall = this._findWall(newShape[i1], newShape[i2]); 22108 if (wall) { 22109 wall.remove(); 22110 } 22111 } else { 22112 // create corner if it doesn't exist 22113 if (!newShape[i1].type) { 22114 corner = this.findNearestCorner(newShape[i1], 22115 this.settings.searchDistance); 22116 newShape[i1] = corner || this.addCorner(newShape[i1]); 22117 newCorners.push(newShape[i1]); 22118 } 22119 rShape.push(newShape[i1]); 22120 } 22121 } 22122 22123 this.addShape(rShape, [], oldShape.floorMaterial, 22124 oldShape.ceilingMaterial); 22125 22126 len1 = rShape.length; 22127 walls = oldShape.getWalls(); 22128 22129 for (i1 = 0, i2 = len1 - 1; i1 < len1; i2 = i1++) { 22130 // Reuse wall if possible and was available on the old shape 22131 wall = this._findWall(rShape[i1], rShape[i2]); 22132 if (wall) { 22133 wall.attachShape(rShape); 22134 } else if(restrictWalls || (newCorners.indexOf(rShape[i1]) !== -1 || newCorners.indexOf(rShape[i2]) !== -1)) { 22135 this.addWall([rShape[i1], rShape[i2]], [rShape]); 22136 } 22137 } 22138 22139 return rShape; 22140 }, 22141 22142 /** 22143 * Save current model to backend 22144 * 22145 * @param {Object} options Save options, expected keys/values are: 22146 * {Boolean} forceNew Create new room or replace old one 22147 * {Object} settings Top-level settings of model 22148 * {Object} data Already wrapped room data 22149 * {Function} successCallback Callback which will be executed after success of ajax request 22150 * {String} urlParams Params which will be added to the save url 22151 * {Boolean} hqRender Should backend render HQ render or not 22152 */ 22153 saveRoom: function(options) { 22154 //forceNew, settings, data, callback, urlParams 22155 var saveRoutine = this._super, 22156 self = this, 22157 width = 960, 22158 height = 540; 22159 22160 options = options || {}; 22161 22162 options.data = options.data || this._wrapRoom(options.settings); 22163 22164 if (!options.urlParams) { 22165 options.urlParams = 'snapshot=1'; 22166 } 22167 22168 if (this._metadata.force_thickness) { 22169 options.urlParams += '&with_thickness=1'; 22170 } 22171 22172 if (this._masking) { 22173 options.urlParams += '&' + $.param({ 22174 send_alert: true, 22175 event_type: 'floor_plan_ready' 22176 }); 22177 options.restoreOwner = true; 22178 options.forceNew = true; 22179 options.originalURL = this._ws_url; 22180 } 22181 22182 options.hqRender = options.forceNew || options.hqRender; 22183 22184 if (options.hqRender) { 22185 options.urlParams += '&hq_render=' + this.camera.hq_renderer; 22186 } 22187 22188 if (myDeco.settings.WL_PROFILE) { 22189 options.urlParams += '&profile=' + myDeco.settings.WL_PROFILE; 22190 } 22191 22192 // retrieving rendered image 22193 this.render(function(result) { 22194 if (result) { 22195 options.data.image = result.data; 22196 options.urlParams += '&file=image'; 22197 } 22198 saveRoutine.call(self, options); 22199 }, options.data.model, width, height, 'normal', '?', true); 22200 }, 22201 22202 /** 22203 * Delete active room 22204 * @param {Object} options 22205 */ 22206 deleteRoom: function(options) { 22207 options = options || {}; 22208 if (this._masking && options.reason) { 22209 options.urlParams = (options.urlParams ? options.urlParams + '&' : '') + 22210 $.param({ 22211 send_alert: true, 22212 event_type: 'floor_plan_rejected', 22213 reason: options.reason 22214 }); 22215 } 22216 this._super.call(this, options); 22217 }, 22218 22219 /** 22220 * Load room and convert low level (raw) model to high level 22221 * @param {Room} rawModel Raw model of the room which comes from room WS 22222 */ 22223 load: function(rawModel) { 22224 this._masking = !rawModel.model; 22225 if (!rawModel.model) { 22226 rawModel.model = $.extend(true, {}, emptyRoom.model); 22227 } 22228 this.reset(); 22229 this._backup = $.extend(true, {}, rawModel); 22230 this._super(rawModel); 22231 this.loadModel(rawModel.model); 22232 this._parent_created = rawModel.parent_created ? new Date(rawModel.parent_created) : new Date(); 22233 }, 22234 22235 /** 22236 * Strip snapshot ID from url 22237 * @param {String} url 22238 * @returns {String} 22239 */ 22240 _fixUrl: function(url) { 22241 return this._super(url).replace(/(\/\d+)\/\d+(\/)?$/, '$1$2'); 22242 }, 22243 22244 /** 22245 * Load raw model 22246 * @param {Room} rawModel Raw model of the room which comes from room WS 22247 * @param {Boolean} sameRoom Will model send event "roomLoaded" with 22248 * sameRoom flag rised or not 22249 */ 22250 loadModel: function(rawModel, sameRoom) { 22251 if (!sameRoom) { 22252 this.loaded = false;
22253 } 22254 22255 var self = this, x, image, w, m, width, height, name, i, id, component, 22256 materialRe = /(material|decor-)(\d+)$/, 22257 idToRequest = [], materials = [], 22258 rawComponents = rawModel.components || [], 22259 replaceComponents = this.settings.replacements || {}, 22260 stage = 0, 22261 camera = {}, 22262 onLoad = function() { 22263 m.photo.orig.width = image.width; 22264 m.photo.orig.height = image.height; 22265 }, 22266 loadingFinished = function() { 22267 if (++stage === 2) { 22268 if (self.loaded === false) { 22269 self.loaded = true; 22270 } 22271 self._currModel = JSON.stringify(self.getModel(2, true)); 22272 myDeco.body.trigger('roomLoaded', [!!sameRoom]); 22273 } 22274 }; 22275 22276 materials = rawModel.version === 2 ? 22277 this._loadPlanV2(rawModel) : 22278 this._loadPlanV1(rawModel); 22279 22280 // use numerical ID for the default floor material 22281 // so that its settings are fetched from the index 22282 var defaultFloorMaterial = materials.materialFloorDefault; 22283 22284 if (defaultFloorMaterial) { 22285 var numericalFloorMaterialID = 'decor-' + (defaultFloorMaterial.diffuse_texture.match('Natural_Travertine_TILE.jpg') ? 3708 : 2542); 22286 materials.materialFloorDefault.ID = numericalFloorMaterialID; 22287 materials[numericalFloorMaterialID] = materials.materialFloorDefault; 22288 delete materials.materialFloorDefault; 22289 } 22290 22291 // update material positions 22292 // wall materials are already in model 22293 for (x = this.walls.length - 1; x >=0; x--) { 22294 if (this.walls[x].material.decal_texture && typeof this.walls[x].material.photo === 'undefined') { 22295 image = new Image(); 22296 w = this.walls[x]; 22297 m = w.material; 22298 width = 100 / m.decal_transform[0][0]; 22299 height = 100 / m.decal_transform[1][1]; 22300 m.photo = { 22301 orig: { 22302 width: width, 22303 height: height 22304 }, 22305 x: -m.decal_transform[0][2] * width, 22306 y: -m.decal_transform[1][2] * height, 22307 width: width, 22308 height: height, 22309 repeatX: m.decal_repeat_u, 22310 repeatY: m.decal_repeat_v 22311 }; 22312 image.onload = onLoad; 22313 image.src = myDeco.settings.MEDIA_URL + m.decal_texture; 22314 } 22315 } 22316 22317 camera.position = new Point(rawModel.camera.position); 22318 camera.focal_point = new Point(rawModel.camera.focal_point); 22319 camera.hq_renderer = rawModel.camera.hq_renderer || myDeco.settings.DEFAULT_HQ_RENDERER; 22320 camera.exposure = rawModel.camera.exposure || 0; 22321 camera.fieldofview = rawModel.camera.fieldofview || myDeco.App.settings.model.cameraFov; 22322 this.addCamera(camera); 22323 22324 // light environment settings 22325 this.light_env = rawModel.light_env; 22326 22327 if (!this.light_env) { 22328 this.light_env = {setting: 'day', intensity: 30, azimuth: 45, altitude: 60}; 22329 } 22330 22331 // azimuth/altitude were introduced later, so there are rooms 22332 // with light env but without these two attributes 22333 if (!this.light_env.azimuth) { 22334 $.extend(this.light_env, {azimuth: 45, altitude: 60}); 22335 } 22336 22337 // assign environment 22338 if (typeof rawModel.environment === 'object') { 22339 for (name in rawModel.environment) { 22340 this.assignEnvironment(name, rawModel.environment[name]); 22341 } 22342 } 22343 22344 // unique list of components 22345 idToRequest = $.map(rawComponents, function(component) { 22346 id = component.component_id || null; 22347 if (id && replaceComponents[id]) { 22348 id = replaceComponents[id]; 22349 } 22350 return id; 22351 }).unique(); 22352 22353 // Processing components 22354 myDeco.App.ws.proxy.fetch('component', idToRequest, function(result) { 22355 for (i = rawComponents.length - 1; i !== -1; i--) { 22356 component = rawComponents[i]; 22357 id = component ? (replaceComponents[component.component_id] || component.component_id) : false; 22358 if (id && result[id]) { 22359 // result[...] is stored in cache, so extend its copy 22360 rawComponents[i] = $.extend(true, 22361 {}, 22362 result[id] || {}, 22363 component, 22364 { 22365 component_id: result[id].component_id, 22366 model: result[id].model, 22367 fromRoom: true 22368 }); 22369 rawComponents[i].bounding_box = result[id].bounding_box.slice(0); 22370 } else { 22371 // component service notified about not existing component 22372 // TODO: show message 22373 rawComponents.splice(i, 1); 22374 } 22375 } 22376 22377 if (rawComponents.length > 0) { 22378 rawComponents.sort(function(a, b) { 22379 var ret = 0; 22380 if (a.level < 0 || b.level < 0) { 22381 ret = b.level - a.level 22382 } else { 22383 ret = (a.level || 0) - (b.level || 0); 22384 } 22385 return ret; 22386 }); 22387 22388 $.each(rawComponents, function() { 22389 if (this.configuration && this.configuration.finish) { 22390 this.material = materials[this.configuration.finish]; 22391 } 22392 22393 var was_position = this.position[2]; 22394 22395 var comp = self.addComponent(this, undefined, undefined, 22396 undefined, true, sameRoom); 22397 22398 // restore z position for elevated doors 22399 if (comp.type == 'door') { 22400 comp.position.z = was_position; 22401 } 22402 }); 22403 } 22404 22405 loadingFinished(); 22406 }); 22407 22408 // fetch materials 22409 idToRequest = []; 22410 22411 $.each(materials, function() { 22412 if (this.ID && this.ID.indexOf('Default') === -1) { 22413 var match = this.ID.match(materialRe); 22414 idToRequest.push(match ? match[2] : '#' + diffuse2Hex(this.diffuse)); 22415 } 22416 }); 22417 22418 idToRequest = idToRequest.unique(); 22419 myDeco.App.ws.proxy.fetch('material', idToRequest, function(result) { 22420 $.each(materials, function() { 22421 if (this.ID) { 22422 var match = this.ID.match(materialRe), backup = $.extend(true, {}, this); 22423 if (match && result[match[2]]) { // for materials with an ID we override all with values from the index 22424 $.extend(true, this, result[match[2]], {ID: backup.ID}) 22425 } else if (this.diffuse) { // for other materials (such as colours) we mix in palette settings, but old values still have preference 22426 $.extend(true, this, result['#' + diffuse2Hex(this.diffuse)], backup) 22427 } 22428 } 22429 }); 22430 loadingFinished(); 22431 }); 22432 }, 22433 22434 /** 22435 * Loading algo or how to obtain high level data from low-level 22436 * @param {Room} rawModel Raw model of the room which comes from room WS 22437 */ 22438 _loadPlanV1: function(rawModel) { 22439 var self = this, 22440 height = 0, 22441 // normalize points 22442 points = $.map(rawModel.geometry.points, function(point) { 22443 if (point[2] < 0) { 22444 point[2] = 0; 22445 } 22446 height = Math.max(height, point[2]); 22447 return [point]; 22448 }), 22449 shape = [], 22450 floorMaterial, ceilingMaterial, 22451 i, j, ilen, jlen, w, wlen, wall, 22452 materials = {}, 22453 22454 // get actual walls 22455 walls = $.map(rawModel.geometry.polygons, function(polygon) { 22456 // find indices based on floor (z === 0) 22457 var p = $.map(polygon.indices, function(index) { 22458 return points[index][2] === 0 ? 22459 new Point(points[index]) : 22460 null; 22461 }), d, distance = 0; 22462 22463 // all indices on the floor - floor polygon 22464 if (p.length === polygon.indices.length) { 22465 shape = p; 22466 22467 // remove last point from shape 22468 if (shape[0].distance(shape[shape.length - 1]) < self._tolerancy) { 22469 shape.pop(); 22470 } 22471 22472 floorMaterial = polygon.material; 22473 return null; 22474 // some indices on the floor - wall 22475 } else if (p.length !== 0) { 22476 // find points with max distance between them 22477 for (i = 0, ilen = p.length - 1; i < ilen; i++) { 22478 for (j = i, jlen = ilen + 1; j < jlen; j++) { 22479 d = p[i].distance(p[j]); 22480 if (d > distance) { 22481 distance = d; 22482 // double array because map is opening brackets 22483 wall = [p[i], p[j]]; 22484 } 22485 } 22486 } 22487 wall.ID = polygon.ID; 22488 wall.material = polygon.material; 22489 22490 // as array to satisfy $.map 22491 return [wall]; 22492 // no one index on the floor - ceiling 22493 } else { 22494 // ceiling 22495 ceilingMaterial = polygon.material; 22496 return null; 22497 } 22498 }); 22499 22500 this.ceilingHeight = height; 22501 22502 // init materials cache 22503 $.each(rawModel.materials, function() { 22504 materials[this.ID] = this; 22505 }); 22506 22507 for (i = 0, ilen = shape.length; i < ilen; i++) { 22508 this.addCorner(shape[i]); 22509 } 22510 22511 // add shape 22512 shape = this.addShape(shape, [], materials[floorMaterial], 22513 materials[ceilingMaterial]); 22514 22515 // sort walls according to shape 22516 ilen = shape.length; 22517 22518 // walk thru shape, find corresponding walls and add them
22519 for (i = 0, j = ilen - 1; i < ilen; j = i++) { 22520 for (w = 0, wlen = walls.length; w < wlen; w++) { 22521 if ((shape[i].distance(walls[w][0]) < this._tolerancy && 22522 shape[j].distance(walls[w][1]) < this._tolerancy) || 22523 (shape[i].distance(walls[w][1]) < this._tolerancy && 22524 shape[j].distance(walls[w][0]) < this._tolerancy)) { 22525 wall = [shape[i], shape[j]]; 22526 wall.ID = walls[w].ID; 22527 wall.material = materials[walls[w].material]; 22528 this.addWall(wall, [shape]); 22529 walls.splice(w, 1); 22530 break; 22531 } 22532 } 22533 } 22534 22535 return materials; 22536 }, 22537 22538 /** 22539 * Load plan part of the room 22540 * @param {Room} rawModel Raw model of the room which comes from room WS (must have version=2) 22541 */ 22542 _loadPlanV2: function(rawModel) { 22543 var self = this, wall, shape, 22544 i, j, key, plan = rawModel.plan, len, 22545 materials = {}, 22546 indexesToCorners = function(index) { 22547 return self.corners[index]; 22548 }; 22549 22550 // init materials cache 22551 if (rawModel.materials) { 22552 $.each(rawModel.materials, function() { 22553 materials[this.ID] = this; 22554 }); 22555 } 22556 22557 this.ceilingHeight = plan.height; 22558 22559 if (plan.points) { 22560 len = plan.points.length; 22561 // Load/convert corners 22562 for (i = 0; i < len; i++) { 22563 this.addCorner(new Point(plan.points[i])); 22564 } 22565 } 22566 22567 // Load walls 22568 for (key in plan.walls) { 22569 wall = [ 22570 this.corners[plan.walls[key].indices[0]], 22571 this.corners[plan.walls[key].indices[1]] 22572 ]; 22573 wall.ID = key; 22574 wall.thickness = plan.walls[key].thickness; 22575 wall.material = materials[plan.walls[key].material]; 22576 this.addWall(wall); 22577 } 22578 22579 // Load areas 22580 for (key in plan.areas) { 22581 var indices = plan.areas[key].indices; 22582 if (!indices) continue; 22583 22584 shape = $.map(indices, indexesToCorners); 22585 22586 // shape 22587 shape = this.addShape(shape, [], 22588 materials[plan.areas[key].floor_material || 22589 plan.areas[key]['floor-material']], 22590 materials[plan.areas[key].ceiling_material || 22591 plan.areas[key]['ceiling-material']]); 22592 22593 len = shape.length; 22594 22595 // restore walls 22596 for (i = 0, j = len - 1; i < len; j = i++) { 22597 wall = this._findWall(shape[i], shape[j]); 22598 if (wall) { 22599 shape.attachWall(wall); 22600 } 22601 } 22602 } 22603 22604 return materials; 22605 }, 22606 22607 /** 22608 * remove selected items from model 22609 */ 22610 remove: function(param) { 22611 var j; 22612 22613 for (j = this._selected.length - 1; j !== -1; j--) { 22614 this._selected[j].remove(param); 22615 } 22616 22617 this.operationEnd(['remove']); 22618 }, 22619 22620 /** 22621 * Get high-level (new) representation of geometry for room format ver. 2.0 22622 */ 22623 _getHighGeometry: function() { 22624 var plan = { 22625 height: this.ceilingHeight, 22626 points: [], 22627 areas: {}, 22628 walls: {} 22629 }, 22630 i, len, indices, 22631 j, entity, elen, 22632 g, geometry = ['walls', 'shapes'], glen = geometry.length, gname; 22633 22634 len = this.corners.length; 22635 22636 for (i = 0; i < len; i++) { 22637 plan.points.push(this.corners[i].toArray(true)); 22638 } 22639 22640 for (g = 0; g < glen; g++) { 22641 gname = geometry[g]; 22642 22643 len = this[gname].length; 22644 for (i = 0; i < len; i++) { 22645 entity = this[gname][i]; 22646 elen = entity.length; 22647 indices = []; 22648 22649 for (j = 0; j < elen; j++) { 22650 indices.push($.inArray(entity[j], this.corners)); 22651 } 22652 22653 if (gname !== 'walls') { 22654 plan.areas[entity.ID] = {indices: indices, 22655 'floor_material': entity.floorMaterial ? entity.floorMaterial.ID : null, 22656 'ceiling_material': (entity.ceilingMaterial ? entity.ceilingMaterial.ID : null)}; 22657 } else { 22658 plan[gname][entity.ID] = { 22659 indices: indices, 22660 thickness: entity.thickness, 22661 material: entity.material.ID 22662 }; 22663 } 22664 } 22665 } 22666 22667 return plan; 22668 }, 22669 22670 /** 22671 * Get a fresh and clean copy of the component (without cached/precalculated values, methods, etc) 22672 * @param {Boolean} dontRemoveMeta Do not remove meta and precalculated information from the components (needed for undo/redo and duplicate). 22673 * @return {Component} Clean copy of the component 22674 */ 22675 _cleanComponent: function(component, dontRemoveMeta) { 22676 // Save the parentID before clone component object 22677 // (cause clone operation is only 1 level deep). 22678 var parentID = component.parent ? component.parent.ID.replace('wall', '') : null, 22679 newComponent = { 22680 parentID: parentID, 22681 ID: component.ID, 22682 model: component.model, 22683 // Convert Points back to array of coords 22684 position: component.position.toArray(),
22685 bounding_box: [component.bounding_box[0].toArray(), component.bounding_box[1].toArray()], 22686 rotation: component.rotation, 22687 component_id: component.component_id, 22688 category: component.category, 22689 level: component.level, 22690 surface_height: component.surface_height 22691 }; 22692 22693 if (component._shape) { 22694 newComponent.area = component._shape.ID; 22695 } 22696 22697 if (component.aperture) { 22698 newComponent.aperture = { 22699 type: component.aperture.type, 22700 bounds: [component.width, component.height] 22701 }; 22702 } 22703 22704 if (component.configurations) { 22705 newComponent.configuration = {}; 22706 $.each(component.configurations, function(key) { 22707 newComponent.configuration[key] = component[key]; 22708 }); 22709 if (component.material) { 22710 newComponent.configuration.finish = component.material.ID; 22711 } 22712 } 22713 22714 // Preserve some highlevel data to satisfy undo/redo 22715 if (dontRemoveMeta) { 22716 $.extend(newComponent, { 22717 componentData: component.componentData, 22718 width: component.width, 22719 height: component.height, 22720 depth: component.depth, 22721 product_id: component.product_id, 22722 name: component.name, 22723 price: component.price, 22724 supplier: component.supplier, 22725 supplier_url: component.supplier_url, 22726 description: component.description, 22727 available: component.available, 22728 url: component.url, 22729 locale: component.locale 22730 }); 22731 } 22732 22733 return newComponent; 22734 }, 22735 // Stub method for get the room ready to save. 22736 /** 22737 * Wrap room model with some fpe-specific settings. 22738 * @param {Object} template Default room settings 22739 * @return {Object} Room ready for saving 22740 */ 22741 22742 _wrapRoom: function(template) { 22743 var data = this._super(template); 22744 22745 // remove original parent 22746 delete this._metadata.parent; 22747 this._metadata.type = '3d'; 22748 22749 data = $.extend(true, {}, this._metadata, { 22750 "model": this.getModel(), 22751 "products": this.uniqueProducts() 22752 }); 22753 22754 if (this._masking) { 22755 data.author = data.original_author; 22756 delete data.thumbnail; 22757 delete data.image; 22758 delete data.last_updated; 22759 delete data.id; 22760 } 22761 22762 // remove wall thickness toggle 22763 delete data.force_thickness; 22764 22765 return data; 22766 }, 22767 22768 /** 22769 * Convert the room back from hi-level model to low-level, required by render, old ME and rooms WS 22770 * @param {Integer} version Version of the room model (1 or 2). 22771 * @param {Boolean} dontRemoveMeta Do not remove meta and precalculated information from the components (needed for undo/redo). 22772 * @return {Object} Low level room model 22773 */ 22774 getModel: function(version, dontRemoveMeta, testMaterials) { 22775 if (!this.loaded) { 22776 return false; 22777 } 22778 22779 var model = { 22780 camera: { 22781 position: this.camera.position.toArray(), 22782 focal_point: this.camera.focal_point.toArray(), 22783 hq_renderer: this.camera.hq_renderer, 22784 exposure: this.camera.exposure, 22785 fieldofview: this.camera.fieldofview 22786 }, 22787 // Default values for lightning 22788 lights: [{ 22789 position: [10, 0, 500], 22790 direction: [0, 0, -500], 22791 type: "sun" 22792 }], 22793 materials: [], 22794 room_id: this._room_id || 0, 22795 components: [] 22796 }, 22797 i, n, c, clen, component, material, 22798 src = ['components', 'doors', 'windows'], 22799 materialIds = [], 22800 errMaterials = []; 22801 22802 model.plan = this._getHighGeometry(); 22803 model.version = version || 2; 22804 22805 // Saving components 22806 for (c = 0, clen = src.length; c < clen; c++) { 22807 for (i = 0, n = this[src[c]].length; i < n; i++) { 22808 // Make a copy of the current component 22809 component = this._cleanComponent(this[src[c]][i], dontRemoveMeta); 22810 22811 // Restore component type 22812 component.type = (src[c] === 'components' ? 'Component': 'ArchitecturalComponent'); 22813 model.components.push(component); 22814 } 22815 } 22816 22817 if (dontRemoveMeta) { 22818 model.materials = this.materials; 22819 } else { 22820 for (i = 0, n = this.materials.length; i < n; i++) { 22821 material = { 22822 ID: this.materials[i].ID, 22823 diffuse_texture: this.materials[i].diffuse_texture, 22824 shader: this.materials[i].shader, 22825 texture_width: this.materials[i].texture_width, 22826 texture_height: this.materials[i].texture_height, 22827 ambient: this.materials[i].ambient, 22828 shiny: this.materials[i].shiny, 22829 diffuse: this.materials[i].diffuse 22830 }; 22831 if (typeof this.materials[i].photo !== 'undefined') { 22832 $.extend(true, material, { 22833 decal_texture: this.materials[i].decal_texture, 22834 decal_repeat_u: this.materials[i].decal_repeat_u, 22835 decal_repeat_v: this.materials[i].decal_repeat_v, 22836 decal_transform: this.materials[i].decal_transform 22837 }); 22838 } 22839 22840 model.materials.push(material); 22841 } 22842 } 22843 22844 //test if all the materials used are in model.materials 22845 if (testMaterials) { 22846 materialIds = $.map(model.materials, function (m) { 22847 return m.ID; 22848 }); 22849 22850 for (i in model.plan.walls) { 22851 if ($.inArray(model.plan.walls[i].material, materialIds) === -1) { 22852 errMaterials.push(model.plan.walls[i].material); 22853 this.assignMaterial(model.plan.walls[i], this.settings.defaultMaterials['wall'], 'wall'); 22854 } 22855 } 22856 22857 for (i in model.plan.areas) { 22858 for (c in {'ceiling_material': 1, 'floor_material': 1}) { 22859 if ($.inArray(model.plan.areas[i][c], materialIds) === -1) { 22860 errMaterials.push(model.plan.areas[i][c]); 22861 if (c === 'ceiling_material') { 22862 this.assignMaterial(model.plan.areas[i][c], this.settings.defaultMaterials['ceiling'] , 'ceiling'); 22863 } else { 22864 this.assignMaterial(model.plan.areas[i][c], this.settings.defaultMaterials['floor'] , 'floor'); 22865 } 22866 } 22867 } 22868 } 22869 22870 if (errMaterials.length !== 0) { 22871 //trigger an error 22872 myDeco.body.trigger('materialsError', errMaterials.join(',')); 22873 } 22874 } 22875 22876 if ($.keys(this.environment).length !== 0) { 22877 model.environment = this.environment; 22878 } 22879 22880 return model; 22881 }, 22882 22883 /** 22884 * Get the list of unique products, used in room 22885 * @return {Array of Strings} List of unique guids 22886 */ 22887 uniqueProducts: function() { 22888 var result = [], 22889 i, len = this.components.length; 22890 22891 for (i = 0; i < len; i++) { 22892 if ($.inArray(this.components[i].product_id, result) === -1) { 22893 result.push(this.components[i].product_id); 22894 } 22895 } 22896 22897 return result; 22898 }, 22899 22900 /** 22901 * Get bounding box of the room (including outside components and possible door opening) 22902 * @param {Transform} transform Transform to apply 22903 * @param {String} transformName Transform name 22904 * @return {Object} BB object 22905 */ 22906 getBBox: function(transform, transformName) { 22907 var x1 = Number.MAX_VALUE, y1 = Number.MAX_VALUE, z1 = Number.MAX_VALUE, 22908 x2 = -Number.MAX_VALUE, y2 = -Number.MAX_VALUE, z2 = -Number.MAX_VALUE, 22909 src = ['corners', 'components', 'doors'], p, plen, i, ilen, bb; 22910 22911 // calculate components (can be outside of room) and doors (can has open_direction = o) 22912 22913 for (p = 0, plen = src.length; p < plen; p++) { 22914 for (i = 0, ilen = this[src[p]].length; i < ilen; i++) { 22915 bb = this[src[p]][i].getBBox(transform, transformName); 22916 22917 x1 = Math.min(x1, bb.x1, bb.x2); 22918 x2 = Math.max(x2, bb.x1, bb.x2); 22919 y1 = Math.min(y1, bb.y1, bb.y2); 22920 y2 = Math.max(y2, bb.y1, bb.y2); 22921 z1 = Math.min(z1, bb.z1, bb.z2); 22922 z2 = Math.max(z2, bb.z1, bb.z2); 22923 } 22924 } 22925 22926 22927 // calculate camera 22928 p = typeof transform !== 'undefined' ? 22929 this.camera.position.applyTransformation(transform, transformName) : 22930 this.camera.position; 22931 22932 x1 = Math.min(x1, p.x); 22933 y1 = Math.min(y1, p.y); 22934 z1 = Math.min(z1, p.z); 22935 x2 = Math.max(x2, p.x); 22936 y2 = Math.max(y2, p.y); 22937 z2 = Math.max(z2, p.z); 22938 22939 return { 22940 p1: new Point([x1, y1, z1]), 22941 p2: new Point([x2, y2, z2]), 22942 width: x2 - x1, 22943 depth: y2 - y1, 22944 height: z2 - z1, 22945 cx: x1 + (x2 - x1) / 2, 22946 cy: y1 + (y2 - y1) / 2, 22947 cz: z1 + (z2 - z1) / 2 22948 }; 22949 }, 22950 22951 /** 22952 * Render current model and run callback in a Model scope 22953 * 22954 * @param {Function} callback Function is running after success render 22955 * @param {Object} model Model to render, defaults to current 22956 * @param {Integer} width Image width to render 22957 * @param {Integer} height Image height to render 22958 * @param {String} type Render type, 'preview' or 'normal', 22959 * @param {String} renderCallback Callback name passed to render 22960 * @param {Number} errorCounter Errors counter, used to break infinite loop 22961 * 22962 * @return {Object} jQ image or response object if renderCallback is specified 22963 */ 22964 render: function(callback, model, width, height, type, renderCallback, errorCounter) { 22965 errorCounter = errorCounter || 0; 22966 type = type || 'preview'; 22967 model = model || this.getModel(); 22968 22969 // don't want to serve empty model 22970 if (!model) { 22971 return; 22972 } 22973 // prevent problems with the render when float values are passed 22974 width = Math.ceil(width || 0);
22975 height = Math.ceil(height || 0); 22976 22977 22978 var self = this, 22979 renderC = renderCallback ? '&callback=' + renderCallback : '', 22980 // render success callback 22981 renderResponse = function(response) { 22982 if ($.isFunction(callback)) { 22983 callback.call(self, response); 22984 } 22985 }, 22986 // callback for support render errors 22987 renderError = function() { 22988 // just re-request session if it already expired 22989 if (self._renderUrl !== myDeco.urls.renderws && errorCounter === 0) { 22990 myDeco.body.trigger('renderState', ['session']); 22991 self._renderUrl = myDeco.urls.renderws; 22992 self.render(callback, model, width, height, type, renderCallback); 22993 } else if (errorCounter > 0 || self._renderUrl === myDeco.urls.renderws) { 22994 renderResponse(false); 22995 } else { 22996 self.render(callback, model, width, height, type, renderCallback, ++errorCounter); 22997 } 22998 }; 22999 23000 $.ajax({ 23001 type: 'POST', 23002 url: this._renderUrl, 23003 contentType: "application/json", 23004 xhrFields: {withCredentials: true}, 23005 dataType: "json", 23006 timeout: this.settings.renderRequestTimeout * 1000, 23007 data: JSON.stringify(model), 23008 23009 success: function(data) { 23010 // handle disconnection 23011 if (!data) { 23012 return renderError(); 23013 } 23014 23015 // remove host from location because of ajax request limitations 23016 // self._renderUrl = data.location.replace(/https?:\/\/([^\/]+)/i, ''); 23017 self._renderUrl = data.location.replace(/http?:/, window.location.protocol); 23018 // Force HTTPS 23019 // self._renderUrl = data.location.replace(/^http:\/\//i, 'https://'); 23020 23021 if (width && height && $.isFunction(callback)) { 23022 var imageUrl = self._renderUrl + '/render/' + type + 23023 '?width=' + width + 23024 '&height=' + height + 23025 '&fieldofview=' + self.camera.fieldofview + 23026 renderC + 23027 /* Random param is needed to fool the browser, because if you are changing 23028 img src to the same url, browser doesn't reload it (nothing changed) 23029 */ 23030 '&' + Math.random(); 23031 23032 // serve JSONp request 23033 if (renderCallback) { 23034 $.ajax({ 23035 url: imageUrl, 23036 contentType: 'application/json', 23037 dataType: 'json', 23038 success: renderResponse, 23039 error: renderError 23040 }); 23041 } else { 23042 // run it in a separite thread? 23043 window.setTimeout(function() { 23044 // serve typical image request 23045 $('<img src="'+ imageUrl +'" />') 23046 .load(function() { 23047 renderResponse($(this)); 23048 }) 23049 .error(renderError); 23050 }, 100); 23051 } 23052 } 23053 }, 23054 error: renderError 23055 }); 23056 }, 23057 23058 /** 23059 * Check for rooms intersection and combine rooms if any intersection 23060 * were found. 23061 * 23062 * @param {Object} initiator Object which initiate possible intersection 23063 * (can be corner, wall or shape), for example - moved corner. 23064 */ 23065 checkForRoomsIntersection: function(initiator) { 23066 var clipShapes = [], i, j, k, shapesToCheck, 23067 clipLength, checkLength, clipResults, resultLength, cLength, 23068 self = this, gotIntersection = false; 23069 23070 // Check if Weiler-Atherton added corners on walls common to different shapes 23071 function checkForCommonWalls(inter) { 23072 var i, interLength = inter.length, wall; 23073 23074 for (i = 0; i < interLength; i++) { 23075 if (inter[i]) { 23076 wall = self._findWall(inter[i].from, inter[i].to); 23077 23078 if (wall && wall._shapes.length > 1) { 23079 return true; 23080 } 23081 } 23082 } 23083 return false; 23084 } 23085 23086 // One shape - nothing to intersect. 23087 if (this.shapes.length === 1) { 23088 return; 23089 } 23090 23091 // Collect list of shapes, which can be used as clip polygons. 23092 // It's shapes linked to moved wall or corner 23093 switch (initiator.type) { 23094 case 'wall': 23095 case 'corner': 23096 clipShapes = initiator.getShapes(); 23097 break; 23098 case 'shape': 23099 clipShapes = [initiator]; 23100 break; 23101 } 23102 23103 // Check if there are shapes available for intersection 23104 if (clipShapes.length === this.shapes.length) { 23105 return false; 23106 } 23107 23108 // Collect shapes which we are going to clip with given list of clip polygons 23109 shapesToCheck = $.grep(this.shapes, function(shape) { 23110 if (clipShapes.indexOf(shape) !== -1) { 23111 return false;
23112 } 23113 23114 // avoid intersection with shapes, which already linked to one of clip shapes. 23115 for (i = 0, clipLength = clipShapes.length; i < clipLength; i++) { 23116 for (j = 0, cLength = clipShapes[i].length; j < cLength; j++) { 23117 if (shape.indexOf(clipShapes[i][j]) !== -1) { 23118 return false; 23119 } 23120 } 23121 } 23122 return true; 23123 }); 23124 23125 for (i = 0, clipLength = clipShapes.length; i < clipLength; i++) { 23126 for (j = 0, checkLength = shapesToCheck.length; j < checkLength; j++) { 23127 clipResults = weilerAtherton(shapesToCheck[j], clipShapes[i], 'clip'); 23128 23129 // Intersections found? 23130 if (clipResults && !checkForCommonWalls(clipResults.added.subj) && !checkForCommonWalls(clipResults.added.clip)) { 23131 resultLength = clipResults.clipped.length; 23132 23133 if (resultLength !== 0) { 23134 // add new obtained shapes 23135 for (k = 0; k < resultLength; k++) { 23136 this.replaceShape(shapesToCheck[j], clipResults.clipped[k]); 23137 } 23138 23139 this.replaceShape(clipShapes[i], clipResults.clip, true); 23140 23141 shapesToCheck[j].remove(); 23142 clipShapes[i].remove(); 23143 23144 gotIntersection = true; 23145 } 23146 } 23147 } 23148 } 23149 23150 if (gotIntersection) { 23151 myDeco.body.trigger('shapesIntersected'); 23152 } 23153 }, 23154 23155 /** 23156 * Return string representation for latest changes. 23157 * @param {Number} latest Count of latest changes, leave empty for all 23158 */ 23159 changeLog: function(latest) { 23160 return $.map(typeof latest === 'undefined' ? 23161 this.history.changes : 23162 this.history.changes.slice(-latest), 23163 function(item) { 23164 return item[0] + (item[1] ? ' ' + item[1].type : ''); 23165 }); 23166 }, 23167 23168 /** 23169 * Undo the last action. 23170 */ 23171 undo: function() { 23172 if (this._undoModel !== false && this._undoModel !== 'false') { 23173 this._redoModel = this._currModel; 23174 this.reset(true); 23175 this.loadModel(JSON.parse(this._undoModel), true); 23176 myDeco.body.trigger('undo'); 23177 this._currModel = this._undoModel; 23178 this._undoModel = false; 23179 this._super(); 23180 } 23181 }, 23182 23183 /** 23184 * Redo the last action. 23185 */ 23186 redo: function() { 23187 if (this._redoModel !== false && this._redoModel !== 'false') { 23188 this._undoModel = this._currModel; 23189 this.reset(true); 23190 this.loadModel(JSON.parse(this._redoModel), true); 23191 myDeco.body.trigger('redo'); 23192 this._currModel = this._redoModel; 23193 this._redoModel = false; 23194 this._super(); 23195 } 23196 }, 23197 23198 /** 23199 * Locate nearest corner to specified coordinates 23200 * @param {Point} point 23201 * @param {Numner} maxDistance 23202 * @param {Array} except 23203 * @returns {Corner} nearest corner found 23204 */ 23205 findNearestCorner: function(point, maxDistance, except) { 23206 var i, d, 23207 candidate, 23208 distance = Number.MAX_VALUE; 23209 23210 for (i = this.corners.length - 1; i >= 0; i--) { 23211 if (!except || except.indexOf(this.corners[i]) === -1) { 23212 d = this.corners[i].distance(point); 23213 if (d < maxDistance && d < distance) { 23214 distance = d; 23215 candidate = this.corners[i]; 23216 } 23217 } 23218 } 23219 23220 return candidate; 23221 }, 23222 23223 /** 23224 * Find point which can stick to already existing entites 23225 * @param {Point} newPoint 23226 * @param {Point} lastCorner 23227 * @param {Boolean} denyShapes Deny points inside existing shapes 23228 * @returns {Object} 23229 */ 23230 findAllowedPoint: function(newPoint, lastCorner, denyShapes) { 23231 var corner, wall, length = Number.MAX_VALUE, len, i, ilen, pretender, 23232 point, walls, angle, angleDiff; 23233 23234 if (!lastCorner || 23235 lastCorner.distance(newPoint) > this.settings.minDistance) { 23236 23237 // check angles with attached walls 23238 if (lastCorner) { 23239 angle = lastCorner.angleTo(newPoint); 23240 walls = lastCorner.getWalls(); 23241 23242 for (i = 0, ilen = walls.length; i < ilen; i++) { 23243 angleDiff = Math.abs( 23244 lastCorner.angleTo(walls[i].getOpposite(lastCorner)) - 23245 angle 23246 ); 23247 23248 if (angleDiff < this.settings.minAngle || 23249 angleDiff > 360 - this.settings.minAngle) { 23250 return false; 23251 } 23252 } 23253 } 23254 23255 if (lastCorner) { 23256 for (i = 0, ilen = this.walls.length; i < ilen; i++) { 23257 pretender = findWallIntersection( 23258 [lastCorner, newPoint], 23259 this.walls[i], 23260 true); 23261 23262 len = pretender ? 23263 pretender.distance(lastCorner) : 23264 Number.MAX_VALUE; 23265 23266 if (len > this.settings.minDistance && len < length) { 23267 length = len; 23268 wall = this.walls[i]; 23269 point = pretender; 23270 } 23271 } 23272 23273 // check for nearest corners in case of wall intersection 23274 if (point && wall) { 23275 // look for nearest point around point of intersection 23276 corner = this.findNearestCorner(point, 23277 this.settings.searchDistance); 23278 } 23279 } 23280 23281 if (!corner && !point && !wall) { 23282 corner = this.findNearestCorner(newPoint, 23283 this.settings.searchDistance, lastCorner ? 23284 lastCorner.getCorners(false, true, true) : false); 23285 } 23286 23287 // if not corner is found, lets find nearest wall to attach 23288 if (!corner && !point) { 23289 wall = this.findNearestWall(newPoint, 23290 this.settings.searchDistance, 0, lastCorner ? 23291 lastCorner.getWalls() : false); 23292 23293 if (wall) { 23294 point = newPoint.projection2line(wall); 23295 } 23296 } 23297 23298 // check for point inside existing shape 23299 if (!corner && !wall && !point && denyShapes) { 23300 if ($.grep(this.shapes, function(shape) { 23301 return newPoint.inPoly(shape); 23302 }).length !== 0) { 23303 return false;
23304 } 23305 } 23306 23307 return { 23308 corner: corner, 23309 point: corner ? undefined : (point || newPoint), 23310 wall: wall 23311 }; 23312 } 23313 }, 23314 23315 /** 23316 * Locate nearest wall to a specified coordinates 23317 * @param {Point} point 23318 * @param {Number} maxDistance 23319 * @param {Number} minWidth Minimal wall width 23320 * @param {Array} except Walls to exclude 23321 * @returns {Wall} nearest wall found 23322 */ 23323 findNearestWall: function(point, maxDistance, minWidth, except) { 23324 maxDistance = +(maxDistance || Number.MAX_VALUE); 23325 minWidth = +(minWidth || 0); 23326 23327 if ($.isArray(point)) { 23328 point = new Point(point); 23329 } 23330 23331 var wall, distance = Number.MAX_VALUE, d; 23332 $.each(this.walls, function() { 23333 // filter by minimal width 23334 if ((minWidth && this.getAttribute('width') < minWidth) || 23335 (except && $.inArray(this, except) !== -1)) { 23336 return; 23337 } 23338 23339 d = Math.abs(point.distance2segment(this)); 23340 if (d < distance && d <= maxDistance) { 23341 distance = d; 23342 wall = this; 23343 } 23344 }); 23345 23346 return wall; 23347 }, 23348 23349 /** 23350 * Select entity in model 23351 * @param {Object} entity Entity to select 23352 * @param {Boolean} append Will entity append to already selected items 23353 * @returns {Boolean} selection result, successful or not 23354 */ 23355 select: function(entity, append) { 23356 var ret = false;
23357 23358 if (!append) { 23359 this.unselect(); 23360 } 23361 23362 if (entity && !entity.selected) { 23363 this._selected.push(entity); 23364 ret = entity._select(); 23365 23366 // update measurers on walls 23367 if (ret && (entity.type === 'wall' || entity.type === 'corner' || entity.type == 'shape')) { 23368 $.each(entity.getWalls(), function() { 23369 this.updateMeasurer(true); 23370 }); 23371 } 23372 } 23373 23374 this._updateSelection(); 23375 myDeco.body.trigger('selectEnd'); 23376 23377 return ret; 23378 }, 23379 23380 /** 23381 * Unselect all or specified entity 23382 * @param {Object} entity Entity to unselect. If not specified, all selected items will be unselected. 23383 */ 23384 unselect: function(entity) { 23385 var entities = entity ? 23386 // entity SHOULD have type defined, in other case this is an array 23387 // of entities 23388 (entity.type ? [entity] : entity) : this._selected, 23389 i, before = [], after = []; 23390 23391 // collect walls with enabled measurers 23392 // if any of entities are corner or wall 23393 if ($.any(entities, function(element) { return element.type === 'corner' || element.type === 'wall' || element.type == 'shape'; })) { 23394 for (i = this._selected.length - 1; i >= 0; i--) { 23395 if (this._selected[i].type === 'wall' || this._selected[i].type === 'corner' || this._selected[i].type === 'shape') { 23396 $.merge(before, this._selected[i].getWalls()); 23397 } 23398 } 23399 23400 before = before.unique(); 23401 } 23402 23403 for (i = entities.length - 1; i >= 0; i--) { 23404 if (entities[i].selected) { 23405 $.extract(entities[i], this._selected)._unselect(); 23406 } 23407 } 23408 23409 // if have previously selected walls with measurers 23410 // disable no more affected 23411 if (before.length > 0) { 23412 // gather walls which should have measurers 23413 for (i = this._selected.length - 1; i >= 0; i--) { 23414 if (this._selected[i].type === 'wall' || this._selected[i].type === 'corner' || this._selected[i].type == 'shape') { 23415 $.merge(after, this._selected[i].getWalls()); 23416 } 23417 } 23418 23419 after = after.unique(); 23420 23421 // compare both lists and hide measurers for not afftected walls 23422 $.each($.substract(before, after), function() { this.updateMeasurer(false); }); 23423 } 23424 23425 this._updateSelection(); 23426 23427 myDeco.body.trigger('unselectEnd'); 23428 }, 23429 23430 _updateSelection: function() { 23431 var x1 = Number.MAX_VALUE, x2 = -Number.MAX_VALUE, 23432 y1 = Number.MAX_VALUE, y2 = -Number.MAX_VALUE, 23433 z1 = Number.MAX_VALUE, z2 = -Number.MAX_VALUE, 23434 selected = this.selected(), i, ilen = selected.length, 23435 j, jlen, p, found = false; 23436 23437 for (i = 0; i < ilen; i++) { 23438 if (selected[i]._bbox) { 23439 for (j = 0, jlen = selected[i]._bbox.length; j < jlen; j++) { 23440 p = selected[i]._bbox[j]; 23441 x1 = Math.min(x1, p.x); 23442 x2 = Math.max(x2, p.x); 23443 y1 = Math.min(y1, p.y); 23444 y2 = Math.max(y2, p.y); 23445 z1 = Math.min(z1, p.z); 23446 z2 = Math.max(z2, p.z); 23447 } 23448 found = true; 23449 } else if (selected[i].position) { 23450 x1 = Math.min(x1, selected[i].position.x); 23451 x2 = Math.max(x2, selected[i].position.x); 23452 y1 = Math.min(y1, selected[i].position.y); 23453 y2 = Math.max(y2, selected[i].position.y); 23454 z1 = Math.min(z1, selected[i].position.z); 23455 z2 = Math.max(z2, selected[i].position.z); 23456 found = true; 23457 } 23458 } 23459 23460 if (found) { 23461 this._selectionCenter.update((x1 + x2) / 2, (y1 + y2) / 2, (z1 + z2) / 2); 23462 } 23463 23464 this.rotationAvailable = selected.length !== 0 && 23465 !$.any(selected, function(entity) { 23466 return !!(entity.parent || 23467 ['component', 'camera'].indexOf(entity.type) === -1); 23468 }); 23469 }, 23470 23471 /** 23472 * Returns list of selected entities 23473 * @param {Boolean} first Return only the first item in selected list 23474 * @returns {Array||Object} 23475 */ 23476 selected: function(first) { 23477 return first ? this._selected[0] : this._selected; 23478 }, 23479 23480 /** 23481 * Batch move of entity or selected entities 23482 * @param {Number} dx 23483 * @param {Number} dy 23484 * @param {Number} dz 23485 * @param {Object} entity Entity to operate, default are selected entities 23486 * @param {Boolean} forceRestrictions Force restrictions for on-wall components 23487 * @returns {Object} this 23488 */ 23489 move: function(dx, dy, dz, entity, forceRestrictions) { 23490 var entities = entity ? [entity] : this._selected, 23491 i; 23492 23493 for (i = entities.length - 1; i >= 0; i--) { 23494 entities[i].move(dx, dy, dz, !forceRestrictions && 23495 entities[i].level === -1 && !entities[i].category); 23496 } 23497 23498 this._updateSelection(); 23499 23500 return this; 23501 }, 23502 23503 /** 23504 * Batch rotate entities 23505 * @param dangle 23506 * @returns {Object} this 23507 */ 23508 rotate: function(dangle) { 23509 var i, ilen, point, multiple = this._selected.length > 1; 23510 23511 if (this.rotationAvailable) { 23512 for (i = 0, ilen = this._selected.length; i < ilen; i++) { 23513 this._selected[i].rotate(this._selected[i].getAngle() + dangle, 23514 multiple); 23515 if (ilen !== 1) { 23516 point = this._selected[i].position.clone() 23517 .rotate(this._selectionCenter, -dangle); 23518 this._selected[i].move( 23519 point.x - this._selected[i].position.x, 23520 point.y - this._selected[i].position.y); 23521 } 23522 } 23523 } 23524 return this; 23525 }, 23526 23527 /** 23528 * Operation end notify method 23529 * @param {Array} data Data to notify, first item usually is operation name 23530 */ 23531 operationEnd: function(data) { 23532 var name = data[0], i, item; 23533 23534 $.each(this._selected, function() { 23535 this.operationEnd(name); 23536 }); 23537 23538 if (name === 'remove' || ((name === 'drag' || name === 'add') && 23539 $.any(this._selected, function(element) { 23540 return element.level === 0 || element.level === 1; 23541 }))) { 23542 $.each(this.components, function() { 23543 // each on-top components shold relocate after movement of any of component 23544 // move method by default make all checks 23545 if (this.level >= 1) { 23546 this.move(0, 0); 23547 } 23548 }); 23549 } 23550 23551 this._super(data); 23552 23553 if (name !== 'material') { 23554 // Check for rooms intersections 23555 for (i = this._selected.length - 1; i >= 0; i--) { 23556 item = this._selected[i]; 23557 23558 if ((name === 'add' && item.type === 'shape') || 23559 (name === 'drag' && (item.type === 'wall' || item.type === 'corner'))) { 23560 this.checkForRoomsIntersection(item); 23561 } 23562 } 23563 } 23564 23565 // store undo/redo information
23566 this._undoModel = this._currModel; 23567 this._currModel = JSON.stringify(this.getModel(2, true)); 23568 this._redoModel = false; 23569 23570 myDeco.body.trigger('operationEnd', [data]); 23571 23572 // fire operation reset 23573 this.operationReset(); 23574 }, 23575 23576 /** 23577 * Operation start notify method 23578 * @param {Array} data Data to notify, first item usually is operation name 23579 */ 23580 operationStart: function(data) { 23581 data = $.isArray(data) ? data : (typeof data === 'undefined' ? [] : [data]); 23582 23583 $.each(this._selected, function() { 23584 this.operationStart(data[0]); 23585 }); 23586 23587 myDeco.body.trigger('operationStart', [data]); 23588 }, 23589 23590 /** 23591 * Operation reset notify method 23592 */ 23593 operationReset: function() { 23594 $.each(this._selected, function() { 23595 this.operationReset(); 23596 }); 23597 23598 myDeco.body.trigger('operationReset'); 23599 }, 23600 23601 /** 23602 * Duplicate method 23603 * Operates on already selected items 23604 */ 23605 duplicate: function() { 23606 this.operationStart('duplicate'); 23607 23608 var self = this, x, y, 23609 entities = $.grep(this._selected, function(entity) { 23610 return entity.type === 'component' || entity.type === 'window' || 23611 entity.type === 'door'; 23612 }), 23613 // gather component IDs 23614 ids = $.map(entities, function(entity) { 23615 return entity.component_id; 23616 }).unique(); 23617 23618 myDeco.App.ws.proxy.fetch('component', ids, function(result) { 23619 var components = []; 23620 23621 $.each(entities, function() { 23622 // shift new entity position 23623 x = this.position.x + 50; 23624 y = this.position.y + 50; 23625 23626 var component = $.cloneObject(result[this.component_id]), 23627 _self = this; 23628 23629 // make component identical by original by position, parent (for on-wall components) and rotation 23630 $.extend(component, { 23631 parent: this.parent, 23632 rotation: $.merge([], this.rotation), 23633 category: this.category 23634 }); 23635 23636 if (this.configuration) { 23637 $.each(this.configuration, function(key) { 23638 component[key] = _self[key]; 23639 }); 23640 component.bounding_box = [ 23641 $.merge([], component.bounding_box[0]), 23642 $.merge([], component.bounding_box[1]) 23643 ]; 23644 } 23645 23646 components.push(self.addComponent(component, x, y, this.position.z)); 23647 }); 23648 23649 // notify related entities etc about new items 23650 self.operationEnd(['add']); 23651 23652 // unselect all entities 23653 self.unselect(); 23654 23655 $.each(components, function() { 23656 self.select(this, true); 23657 }); 23658 }); 23659 }, 23660 23661 /** 23662 * Split wall by corner 23663 * @param {Wall} wall Wall to split 23664 * @param {Point} point by split with, will split in the middle if omitted 23665 * @returns {Corner} 23666 */ 23667 splitWall: function(wall, point) { 23668 // TODO: check for wall components, store them and restore 23669 // on the new walls 23670 point = point || wall.middle; 23671 23672 var self = this, 23673 // add corner 23674 corner = this.addCorner(point.projection2line(wall)), 23675 wallX, 23676 thickness = wall.thickness, 23677 material = wall.material, 23678 // copy shapes instead of linking 23679 shapes = wall.getShapes().slice(), 23680 nwall, i, halfW, wallsC = [[], []], 23681 components = wall.getComponents(), 23682 restoreWallProps = function(wall, components) { 23683 wall.material = material; 23684 wall.thickness = thickness; 23685 self.addWall(wall, shapes); 23686 for (i = components.length - 1; i >= 0; i--) { 23687 components[i].placeToWall(wall, components[i]._shape); 23688 } 23689 }; 23690 23691 // filter components which should remain on walls 23692 for (i = components.length - 1; i >= 0; i--) { 23693 wallX = wall.getCorners()[0].distance(point); 23694 halfW = components[i].width / 2; 23695 23696 if (components[i].wallX + halfW < wallX) { 23697 wallsC[0].push(components[i]); 23698 wall.removeComponent(components[i]); 23699 } else if (components[i].wallX - halfW > wallX) { 23700 wallsC[1].push(components[i]); 23701 wall.removeComponent(components[i]); 23702 } 23703 } 23704 23705 // remove old wall with remaining components 23706 // leave all points 23707 wall.remove(true); 23708 23709 // insert point to a shape 23710 $.each(shapes, function() { 23711 this.addCorner(corner, wall); 23712 }); 23713 23714 // create new walls 23715 nwall = [wall[0], corner]; 23716 restoreWallProps(nwall, wallsC[0]); 23717 23718 nwall = [corner, wall[1]]; 23719 restoreWallProps(nwall, wallsC[1]); 23720 23721 return corner; 23722 }, 23723 23724 /** 23725 * Place components inside first shape 23726 * @param {Array} components List of components 23727 * @param {Shape} shape Target shape 23728 * @param {Function} callback 23729 */ 23730 dropComponents: function(components, shape, callback) { 23731 var self = this; 23732 23733 if ($.isFunction(shape)) { 23734 callback = shape; 23735 shape = this.shapes[0]; 23736 } 23737 23738 if (!shape && !Boolean(shape = this.shapes[0])) { 23739 if ($.isFunction(callback)) { 23740 callback.call(null); 23741 } 23742 return; 23743 } 23744 23745 myDeco.App.ws.proxy.fetch('component', components, 23746 function(components) { 23747 var shapeBox = shape.getBBox(), 23748 walls = shape.getWalls(), 23749 // variable holding list of walls allowed for onwall 23750 // components
23751 wallSet = walls.slice(), 23752 23753 i, wall, width, depth, component, x, 23754 corners, angle, componentSet, 23755 23756 wallThickness = self.walls[0].thickness, 23757 23758 // function for placing component on wall 23759 placeComponent = function(component, x, y, z) { 23760 var wall = component.parent; 23761 23762 component = $.cloneObject(component); 23763 23764 if (wall) { 23765 // restore parent as it was destroyed by clone 23766 component.parent = wall; 23767 if (typeof x === 'undefined' || 23768 typeof y === 'undefined') { 23769 x = (wall[0].x + wall[1].x) / 2; 23770 y = (wall[0].y + wall[1].y) / 2; 23771 } 23772 23773 if (typeof z === 'undefined') { 23774 z = component.category === 'Door' ? 0 : 23775 wall.getAttribute('height') / 2; 23776 } 23777 23778 component.rotation[0] = 270; 23779 23780 if (component.category === 'Window' || component.category === 'Door') { 23781 component.configuration = { 23782 'width': component.width, 23783 'height': component.height, 23784 'depth': component.depth 23785 }; 23786 } 23787 } 23788 self.addComponent(component, x, y, z); 23789 }, 23790 23791 sortFunction = function(a, b) { 23792 return b.width - a.width; 23793 }, 23794 23795 isComponentInShape = function(point, component) { 23796 var i, j, p; 23797 23798 for (i = 0; i < 2; i++) { 23799 for (j = 0; j < 2; j++) { 23800 p = new Point( 23801 point.x - component.width / 2 + component.width * i, 23802 point.y - component.depth / 2 + component.depth * j 23803 ); 23804 if (!p.inPoly(self.shapes[0])) { 23805 return false; 23806 } 23807 } 23808 } 23809 23810 return true; 23811 }, 23812 putRandomly = function(component) { 23813 var i, n = 0, x, y, h, points, edge, intersection, 23814 found = false, 23815 v = [new Point(), new Point()], 23816 cwidth = component.width / 2, 23817 cdepth = component.depth / 2, 23818 // by default place component in the center of 23819 // the shape 23820 point = new Point( 23821 shapeBox.x + shapeBox.width / 2, 23822 shapeBox.y + shapeBox.depth / 2 23823 ), 23824 x1 = shapeBox.x + cwidth, 23825 x2 = shapeBox.x + shapeBox.width - cwidth, 23826 y1 = shapeBox.y + cdepth, 23827 y2 = shapeBox.y + shapeBox.depth - cdepth, 23828 width = x2 - x1, 23829 depth = y2 - y1; 23830 // first of all check possibility of component fit 23831 if (width > 0 && depth > 0) { 23832 do { 23833 // get a random value for x between x1 and x2 23834 x = x1 + width * Math.random(); 23835 // 10 is additional for sure to intersect 23836 v[0].update(x, y1 - cdepth - 10); 23837 v[1].update(x, y2 + cdepth + 10); 23838 points = []; 23839 23840 // find closest y1 and y2 values where vertical 23841 // line crosses shape edges 23842 for (i = shape.length; i !== 0; i--) { 23843 edge = [shape[i % shape.length], shape[i - 1]]; 23844 intersection = findWallIntersection(edge, v, true); 23845 if (intersection) { 23846 points.push(intersection); 23847 } 23848 } 23849 23850 for (i = Math.floor(points.length / 2) - 1; i >= 0; i--) { 23851 y = Math.min(points[i * 2].y, points[i * 2 + 1].y); 23852 h = Math.max(points[i * 2].y, points[i * 2 + 1].y) - y; 23853 if (h > component.depth) { 23854 y = y + (h - component.depth) * Math.random(); 23855 point.update(x, y); 23856 if (isComponentInShape(point, component)) { 23857 found = true; 23858 break; 23859 } 23860 } 23861 } 23862 23863 n++; 23864 23865 // try for first 50 attemts 23866 } while(!found && n < 50); 23867 } 23868 23869 $.extract(component, components); 23870 placeComponent(component, point.x, point.y); 23871 }, 23872 wallGrep = function(size) { 23873 return function(wall) { 23874 return wall.getAttribute('width') > size; 23875 }; 23876 }; 23877 23878 components = $.values(components); 23879 23880 /* ok, let's roll 23881 1. extract from product list components which should place on 23882 walls and apply them 23883 2. find components which have big difference between width and 23884 depth assuming to place it near walls; sort walls and 23885 components by length and place them near that walls 23886 3. place reamined components randomly in the room 23887 */ 23888 23889 // add onwall components including windows/doors 23890 for (i = components.length - 1; i >= 0; i--) { 23891 component = components[i]; 23892 if (component.level === -1) { 23893 wall = $.grep(wallSet, wallGrep(component.width)); 23894 wall = wall[Math.floor(Math.random() * wall.length)]; 23895 23896 if (wall && wall.getAttribute('height') > component.depth) { 23897 // don't allow to place any component alongside this wall 23898 if (component.category === 'Door') { 23899 $.extract(wall, walls); 23900 } 23901 $.extract(wall, wallSet); 23902 23903 component.parent = wall; 23904 //in the center of the wall 23905 placeComponent(component); 23906 } 23907 23908 $.extract(component, components); 23909 } 23910 } 23911 23912 // sort components by width/depth ratio 23913 components.sort(function(a, b) { 23914 if (!a.ratio) { 23915 a.ratio = a.width / a.depth; 23916 if (a.ratio < 1) { 23917 a.ratio = 1 / a.ratio; 23918 } 23919 } 23920 23921 if (!b.ratio) { 23922 b.ratio = b.width / b.depth; 23923 if (b.ratio < 1) { 23924 b.ratio = 1 / b.ratio; 23925 } 23926 } 23927 23928 return a.ratio - a.ratio; 23929 }); 23930 23931 // place components (not onwall ones) along the walls 23932 for (i = components.length - 1; i >= 0; i--) { 23933 component = components[i]; 23934 if (components[i].level !== 2) { 23935 if (component.ratio >= 2) { 23936 // find wall with length equal or more than max size 23937 wallSet = $.grep(walls, wallGrep(Math.max(component.width, component.depth))); 23938 23939 if (wallSet.length > 0) { 23940 // yes, we have walls with desired length 23941 wall = wallSet[Math.floor(Math.random() * 23942 wallSet.length)]; 23943 23944 // extract wall from list of available walls 23945 $.extract(wall, walls); 23946 23947 corners = wall.getCorners(); 23948 angle = corners[0].angleTo(corners[1]); 23949 23950 width = component.width; 23951 depth = component.depth; 23952 component.rotation[2] = 180 - angle; 23953 23954 // rotate component in case of depth more that width 23955 if (component.depth > component.width) { 23956 component.rotation[2] += 90; 23957 width = component.depth; 23958 depth = component.width; 23959 } 23960 23961 x = Math.random() * (wall.getAttribute('width') - width - wallThickness) + 23962 width / 2 + wall.thickness / 2; 23963 23964 x = corners[0].clone().translate(-x, 0).rotate(corners[0], angle); 23965 23966 x = x.clone().translate(- depth / 2 - wallThickness / 2, 0).rotate(x, angle + 90); 23967 $.extract(component, components); 23968 placeComponent(component, x.x, x.y); 23969 } 23970 // else we don't want to put this component at all 23971 } else { 23972 putRandomly(component); 23973 } 23974 } 23975 } 23976 23977 // get wall components (level === 1) with height less than 1m 23978 componentSet = $.grep(self.components.slice(), function(component) { 23979 return component.level === 1 && component.height <= 100; 23980 }); 23981
23982 // and sort them by size 23983 componentSet.sort(sortFunction); 23984 components.sort(sortFunction); 23985 23986 23987 // place 2nd level components over other components with level === 1 23988 for (i = components.length - 1; i >= 0; i--) { 23989 if (componentSet[i]) { 23990 components[i].rotation = componentSet[i].rotation.slice(); 23991 placeComponent(components[i], 23992 componentSet[i].position.x, 23993 componentSet[i].position.y, 23994 componentSet[i].height + components[i].height / 2 23995 ); 23996 } else { 23997 putRandomly(components[i]); 23998 } 23999 } 24000 24001 if ($.isFunction(callback)) { 24002 callback.call(null); 24003 } 24004 }); 24005 }, 24006 24007 /** 24008 * Check availability of floorplan tab and floor button: 24009 */ 24010 checkFloorPermissions: function (showPopin, callback) { 24011 var self = this, 24012 tabs = $('#sidebar').find('.build-tabs-selector'), 24013 user = myDeco.username, 24014 buttonAvailable = false, 24015 popin = showPopin ? true : false, 24016 systemUsers = new Array( 24017 'home3d_properties', 24018 'mask_monkey' 24019 ); 24020 24021 if (user) { 24022 $.ajax({ 24023 url: '/3dplanner/can_upload_floorplans', 24024 success: function (data) { 24025 if (data && data['can_upload_floorplans']) { 24026 if ($.isFunction(callback)) { 24027 callback(); 24028 } 24029 } else { 24030 self.disableFloorPlanUpload(popin); 24031 } 24032 }, 24033 error: function (request, status, error) { 24034 self.disableFloorPlanUpload(popin); 24035 } 24036 }); 24037 } 24038 24039 if (myDeco.settings.WL_PROFILE === 166 && $.inArray(user, systemUsers) === -1) { 24040 $('#sidebar').find('.build-walls-container, .build-floor-container').toggle(); 24041 } else { 24042 buttonAvailable = true; 24043 } 24044 24045 tabs.html(tmpl('build-tabs', { 24046 available: buttonAvailable 24047 })); 24048 24049 // always start with walls (Gert-Jan) 24050 $('#sidebar').find('.build-walls-container, .build-floor-container').toggle(); 24051 24052 myDeco.tabSwitcher.initTabSwitcher(tabs.find('.tabs')); 24053 }, 24054 24055 /** 24056 * Disable floor plan button and show popin if it's needed 24057 */ 24058 disableFloorPlanUpload: function (showPopin) { 24059 $('#sidebar') 24060 .find('.build-floor-container .upload-container') 24061 .html(tmpl('floor-plan-disabled')); 24062 24063 if (showPopin) { 24064 myDeco.popin.open({ 24065 template: gettext("Sorry, you have no free floor plan upload allowance. We'll launch soon the ability to buy them."), 24066 className: 'login-popin', 24067 holderClassName: 'login-content-holder' 24068 }); 24069 } 24070 } 24071 24072}); 24073/*globals $, AppView, myDeco, Cloud, Raphael, Point, window */ 24074/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 24075 sloppy: true, white: true */ 24076/** 24077 * @fileOverview Base class for all model-editor based views which uses canvas and cloud 24078 */ 24079 24080var FpeView = AppView.extend({ 24081 /** 24082 * View type, should be static 24083 * @type {String} 24084 * @static 24085 */ 24086 $type: 'me-view', 24087 24088 /** 24089 * Canvas SVG element, Raphael instance or something else 24090 * @type {Object} 24091 */ 24092 canvas: false, 24093 24094 /** 24095 * Entity cloud, used for batch processing 24096 * @type {Array} 24097 */ 24098 _cloud: undefined, 24099 24100 /** 24101 * Canvas zoom factor 24102 * @type {Number} 24103 */ 24104 zoom: 1, 24105 24106 /** 24107 * Full (not limited) zoom factor 24108 * @type {Number} 24109 */ 24110 _fullZoom: 1, 24111 24112 /** 24113 * Canvas offset 24114 * @type {Object} 24115 */ 24116 _offset: false, 24117 24118 /** 24119 * Button-like targets for decorating all walls/ceiling 24120 * @type {Object} 24121 */ 24122 _decorateTargets: false, 24123 24124 /** 24125 * Cache for bound listeners 24126 * @type {Object} 24127 */ 24128 _listenersCache: {}, 24129 24130 /** 24131 * Flag indicating initial zoom of view is needed 24132 * @type {Boolean} 24133 */ 24134 _initialZoom: true, 24135 24136 /** 24137 * Init function 24138 * 24139 * @param {Object} options Init options 24140 * 24141 * required are: 24142 * - {FpeModel} model 24143 * - {FpeController} controller 24144 */ 24145 init: function(options) { 24146 var self = this; 24147 24148 this._draggablesObserver = $.proxy(options.controller.registerDraggables, 24149 options.controller); 24150 24151 this._droppablesObserver = $.proxy(options.controller.registerDroppables, 24152 options.controller); 24153 24154 this._super(options); 24155 this._cloud = new Cloud(this); 24156 this.canvas = new Raphael(this.canvasElement.get(0), this.canvasWidth, this.canvasHeight, myDeco.browser.isMobileSafari); 24157 this._initCanvasRelated(); 24158 this._decorateTargets = this.viewHolder.find('.decorate-target'); 24159
24160 this._listenersCache = { 24161 load: $.proxy(function(e, sameRoom) { 24162 this.load(sameRoom); 24163 }, this), 24164 add: $.proxy(function(e, entity) { 24165 this['add' + e.type.replace('Added', '') 24166 .capitalize()].call(this, entity); 24167 }, this), 24168 pan: $.proxy(function(e, dx, dy) { 24169 this.pan(dx * this.zoom, dy * this.zoom); 24170 }, this), 24171 scale: $.proxy(function(e, factor) { 24172 this.scale(factor); 24173 }, this), 24174 clear: $.proxy(function() { 24175 this.clear(); 24176 }, this), 24177 zoom: $.proxy(function(e, coeff) { 24178 coeff = coeff || 0.05; 24179 this.scale((e.type === 'zoomIn' ? +1 : -1) * coeff); 24180 }, this), 24181 show: $.proxy(function(e) { 24182 this.showAll(); 24183 }, this) 24184 }; 24185 24186 myDeco.body.bind('loadRoom', function() { 24187 self._initialZoom = !self._controller._viewActive; 24188 }); 24189 24190 this.processEvents(true); 24191 this.load(); 24192 }, 24193 24194 processEvents: function(type) { 24195 if (type) { 24196 myDeco.body 24197 .bind('roomLoaded', this._listenersCache.load) 24198 .bind('componentAdded shapeAdded wallAdded cornerAdded cameraAdded', this._listenersCache.add) 24199 .bind('pan', this._listenersCache.pan) 24200 .bind('scale', this._listenersCache.scale) 24201 .bind('roomCleared', this._listenersCache.clear) 24202 .bind('zoomIn zoomOut', this._listenersCache.zoom); 24203 24204 if (myDeco.browser.isMobileSafari) { 24205 $(window).bind('orientationchange', this._listenersCache.show); 24206 } 24207 24208 } else { 24209 myDeco.body 24210 .unbind('roomLoaded', this._listenersCache.load) 24211 .unbind('componentAdded shapeAdded wallAdded cornerAdded cameraAdded', this._listenersCache.add) 24212 .unbind('pan', this._listenersCache.pan) 24213 .unbind('scale', this._listenersCache.scale) 24214 .unbind('roomCleared', this._listenersCache.clear) 24215 .unbind('zoomIn zoomOut', this._listenersCache.zoom); 24216 24217 if (myDeco.browser.isMobileSafari) { 24218 $(window).unbind('orientationchange', this._listenersCache.show); 24219 } 24220 } 24221 }, 24222 24223 /** 24224 * Function to init something canvas related, objects, arrows etc 24225 */ 24226 _initCanvasRelated: function() { 24227 24228 }, 24229 24230 /** 24231 * Load from model and draw room representation on canvas 24232 * @param {Boolean} sameRoom Flag for calls for room load from undo/redo routines. Disables room auto positioning 24233 */ 24234 load: function(sameRoom) { 24235 this.clear(); 24236 if (!sameRoom) { 24237 this.reset(); 24238 } 24239 }, 24240 24241 /** 24242 * Reset room 24243 */ 24244 reset: function() { 24245 this.zoom = 1; 24246 this._fullZoom = 1; 24247 this._offset = {x: 0, y: 0}; 24248 this.showAll(); 24249 }, 24250 24251 /** 24252 * Clear canvas, keep any objects with Grid and Camera type 24253 */ 24254 clear: function() { 24255 this._cloud.batch('erase'); 24256 }, 24257 24258 /** 24259 * Abstract function for cloud sorting 24260 */ 24261 sortCloud: function() { 24262 }, 24263 24264 /** 24265 * Load the given entities asynchronously to the cloud and exec callback 24266 * when done. 24267 * 24268 * @param {Array} data Cloud append arguments list 24269 * @param {Function} callback Callback run when all items are populated 24270 */ 24271 populateCloud: function(data, callback) { 24272 var loadQueue = $({}), 24273 self = this, 24274 items, sets; 24275 24276 $.map(data, function(args) { 24277 // split items to a sets 24278 items = args.shift().slice(); 24279 sets = []; 24280 24281 if (items.length !== 0) { 24282 do { 24283 sets.push(items.splice(0, 30)); 24284 } while (items.length !== 0); 24285 24286 $.map(sets, function(set) { 24287 loadQueue.queue(function(next) { 24288 window.setTimeout(function() { 24289 self._cloud.append.apply(self._cloud, 24290 [set].concat(args)); 24291 next(); 24292 }, 0); 24293 }); 24294 }); 24295 } 24296 }); 24297 24298 loadQueue.queue(function() { 24299 callback.apply(self); 24300 }); 24301 }, 24302 24303 /** 24304 * Pan all items on canvas 24305 * 24306 * @param {Number} dx DeltaX 24307 * @param {Number} dy DeltaY 24308 * @param {Boolean} absolute Use DeltaX/DeltaY as absolute coords 24309 */ 24310 pan: function(dx, dy, absolute) { 24311 if (absolute) { 24312 this._offset.x = dx; 24313 this._offset.y = dy; 24314 } else { 24315 this._offset.x += dx; 24316 this._offset.y += dy; 24317 } 24318 24319 this.canvas.pan(dx, dy, absolute); 24320 }, 24321 24322 /** 24323 * Scale all item on canvas 24324 * 24325 * @param {Number} zoom 24326 */ 24327 scale: function(zoom, absolute) { 24328 this._fullZoom = (absolute ? 0 : this._fullZoom) + zoom; 24329 zoom = Math.min(Math.max(this._fullZoom, 0.05), 5); 24330 24331 if (this.zoom !== zoom) { 24332 // this.processing is used for preventing race conditions 24333 // on zoom using wheel 24334 if (!this.processing) { 24335 this.processing = true; 24336 this.zoom = zoom; 24337 this.canvas.scale(zoom); 24338 this._cloud.batch('scale', zoom); 24339 myDeco.body.trigger('view-' + this.$type + '-scaled'); 24340 this.processing = false;
24341 } 24342 } 24343 }, 24344 24345 /** 24346 * Highlight off all items on canvas 24347 */ 24348 highlightOff: function() { 24349 this._cloud.batch('highlightOff'); 24350 }, 24351 24352 /** 24353 * Add camera stub 24354 * @param {Camera} 24355 */ 24356 addCamera: function() { 24357 }, 24358 24359 /** 24360 * Add component stub 24361 * @param {Object} entity Component which has been added 24362 */ 24363 addShape: function() { 24364 }, 24365 24366 /** 24367 * Add wall stub 24368 * @param {Wall} wall 24369 */ 24370 addWall: function() { 24371 }, 24372 24373 /** 24374 * Add corner stub 24375 * @param {Corner} 24376 */ 24377 addCorner: function() { 24378 }, 24379 24380 /** 24381 * Add component stub 24382 * @param {Component} component Component which has been added 24383 */ 24384 addComponent: function() { 24385 }, 24386 24387 /** 24388 * Auto scale/pan current room to fit view area 24389 */ 24390 showAll: function() { 24391 this.zoom = 1; 24392 this._offset = {x: 0, y: 0}; 24393 24394 var parent = this.canvasElement.parent(); 24395 this.canvasWidth = parent.innerWidth(); 24396 this.canvasHeight = parent.innerHeight(); 24397 this.canvas.setSize(this.canvasWidth, this.canvasHeight); 24398 24399 delete this._canvasOffset; 24400 }, 24401 24402 /** 24403 * Show/hide decorate target buttons 24404 * @param {String} state Type of targets to show (decor for now) 24405 * @param {String} type Subtype of a targets (photo or all other) 24406 */ 24407 toggleDecorateTargets: function(state, type) { 24408 this._decorateTargets.hide(); 24409 24410 var filter = state === 'decor' ? 24411 (type === 'photo' ? '.scenery, .walls' : '.ceiling, .walls') : 24412 false, 24413 targets = filter ? this._decorateTargets.filter(filter) : false, 24414 positions, 24415 listener; 24416 24417 if (targets) { 24418 targets.show(); 24419 } 24420 24421 // ipad mouseover workaround 24422 if (myDeco.browser.isMobileSafari) { 24423 listener = function(e) { 24424 $.each(positions, function() { 24425 var active = e.pageX >= this[0] && e.pageX <= this[1] && 24426 e.pageY >= this[2] && e.pageY <= this[3], 24427 dataActive = this[4].data('active'); 24428 24429 if (!active && dataActive) { 24430 this[4].data('active', false); 24431 this[4].mouseout(); 24432 } else if (active && !dataActive) { 24433 this[4].data('active', true); 24434 this[4].mouseover(); 24435 } 24436 }); 24437 }; 24438 24439 if (targets) { 24440 positions = $.map(targets, function(target) { 24441 target = $(target).show(); 24442 var pos = target.offset(); 24443 24444 return [[pos.left, pos.left + target.width(), 24445 pos.top, pos.top + target.height(), 24446 target]]; 24447 }); 24448 24449 myDeco.body.bind('mousemove', listener); 24450 } else { 24451 myDeco.body.unbind('mousemove', listener); 24452 } 24453 } 24454 }, 24455 24456 /** 24457 * Get bounds of decorated targets on screeen (required for mousemove emulation in mobile safari) 24458 * @returns {Array} Array decorated targets or null 24459 */ 24460 getDecorateTargets: function() { 24461 if (this.state) { 24462 return $.map(this._decorateTargets, function(target) { 24463 target = $(target); 24464 var offset = target.offset(); 24465 24466 return { 24467 x1: offset.left, 24468 y1: offset.top, 24469 x2: offset.left + target.width(), 24470 y2: offset.top + target.height(), 24471 target: target.eq(0) 24472 }; 24473 }); 24474 } else { 24475 return null; 24476 } 24477 }, 24478 24479 /** 24480 * Toggle view visibility state 24481 * @param {Boolean} state View state 24482 */ 24483 toggle: function(state) { 24484 this._super(state); 24485 if (state) { 24486 // just load the same room and add listeners 24487 this.load(true); 24488 if (this._initialZoom === true) { 24489 this.showAll(); 24490 this._initialZoom = false;
24491 } 24492 } else { 24493 this.clear(); 24494 } 24495 this.processEvents(state); 24496 } 24497}); 24498/*globals myDeco, $, FpeView, ObjectsLibrary, Grid, Shape, Wall, Corner, Window, 24499 Door, Component, Camera, window, buildThumbnailLink, Point, T */ 24500/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 24501 sloppy: true, white: true */ 24502/** 24503 * @fileOverview FPE Floor-view 24504 */ 24505 24506var FloorView = FpeView.extend({ 24507 /** 24508 * View type, should be static 24509 * @type {String} 24510 * @static 24511 */ 24512 $type: 'floor', 24513 24514 /** 24515 * Cursor disability flag 24516 * @type {Boolean} 24517 */ 24518 disableCursors: false, 24519 24520 /** 24521 * Canvas bounds, limited to size in 20x20 metzers 24522 * @type {Number} 24523 */ 24524 bounds: 20 * 100, 24525 24526 /** 24527 * Common drawings library 24528 * @type {Object} 24529 */ 24530 library: false, 24531 24532 /** 24533 * Processing flag 24534 * @type {Boolean} 24535 */ 24536 processing: false, 24537 24538 /** 24539 * Line used for drawing walls 24540 * @type {Object} 24541 */ 24542 _drawLine: false, 24543 24544 /** 24545 * Corner for visual representation of the destination point 24546 * @type {Object} 24547 */ 24548 _drawCorner: false, 24549 24550 /** 24551 * Points which drawn when setscale in progress 24552 * @type {Array} 24553 */ 24554 _scalePoints: [], 24555 24556 /** 24557 * Measurer shown while drawing 24558 * @type {Object} 24559 */ 24560 _drawMeasurer: false, 24561 24562 /** 24563 * Rotating arrow holder 24564 * @type {Object} 24565 */ 24566 _rotateArrow: null, 24567 24568 /** 24569 * Masking backgound data holder 24570 * @type {Object} 24571 */ 24572 _background: { 24573 src: '', 24574 x: 0, 24575 y: 0, 24576 width: 0, 24577 height: 0, 24578 scale: 1 24579 }, 24580 24581 /** 24582 * Reference to view's Grid object 24583 * @type {Object} 24584 */ 24585 _grid: null, 24586 24587 /** 24588 * View init 24589 * @param {Object} options 24590 */ 24591 init: function(options) { 24592 var self = this; 24593 24594 this.measurers = []; 24595 24596 this._rotablesObserver = options.controller.registerRotatables 24597 .bind(options.controller); 24598 24599 this._super(options); 24600 24601 // allow drawing of ruler and accept point only for ipad 24602 if (!myDeco.browser.isMobileSafari) { 24603 // drawing line 24604 this._drawLine = this.canvas.path({ 24605 'stroke': '#000', 24606 'stroke-width': 2 24607 }) 24608 .moveTo(0, 0) 24609 .lineTo(10, 10) 24610 .hide(); 24611 24612 // drawing corner 24613 this._drawCorner = this.canvas.circle(0, 0, 30).attr({ 24614 'stroke': '#000', 24615 'fill': '#469CDA', 24616 'opacity': 0.5 24617 }).hide(); 24618 24619 myDeco.body.bind('backgroundChange', function(e, action, multiplier) { 24620 multiplier = multiplier || 1; 24621 switch (action) { 24622 case 'left': 24623 self._background.x -= multiplier; 24624 break; 24625 case 'up': 24626 self._background.y -= multiplier; 24627 break; 24628 case 'right': 24629 self._background.x += multiplier; 24630 break; 24631 case 'down': 24632 self._background.y += multiplier; 24633 break; 24634 case 'zoom-in': 24635 self._background.scale += 0.005 * multiplier; 24636 break; 24637 case 'zoom-out': 24638 self._background.scale -= 0.005 * multiplier; 24639 break; 24640 } 24641 self._grid.drawBackground(); 24642 }); 24643 } 24644 24645 // remove possibility of drawing measurers 24646 if (window.location.hash.indexOf('norulers') !== -1) { 24647 this.settings.measurer = false; 24648 } 24649 24650 // drawing measurer 24651 this._drawMeasurer = this.registerMeasurer([new Point(), new Point()]); 24652 24653 this.sortCloud(); 24654 24655 myDeco.body 24656 .bind('operationEnd shapesIntersected', function(e, operation) { 24657 operation = operation ? operation[0] : false; 24658 if (!operation || operation === 'add' || 24659 operation === 'remove') { 24660 self._cloud.findByField('type', 'Wall') 24661 .batch('updateEdges'); 24662 self.sortCloud(); 24663 } 24664 }) 24665 .bind('debug', function(e, type, index) { 24666 var model = self._model[type + 's'][index], 24667 visual = self._cloud.findByField('_model', [model]).shift(), 24668 circle, bbox; 24669 24670 if (visual) { 24671 bbox = visual.getBBox(); 24672 circle = self.canvas.circle(bbox.cx, bbox.cy, 20) 24673 .attr({fill: '#f00', 'stroke-color': '#000'}); 24674 window.setTimeout(function() { 24675 circle.remove(); 24676 }, 1000); 24677 } 24678 }); 24679 }, 24680 24681 /** 24682 * Init canvas related stuff 24683 */ 24684 _initCanvasRelated: function() { 24685 this.library = ObjectsLibrary.init(this.canvas, this.settings.library); 24686 this._rotateArrow = this.library.get('objectRotate'); 24687 }, 24688 24689 /** 24690 * Load room to view 24691 * @param {Boolean} sameRoom 24692 */ 24693 load: function(sameRoom) { 24694 this._super(sameRoom); 24695 var self = this, 24696 bg = this._background; 24697 24698 bg.src = this._model._metadata.image; 24699 this._grid = new Grid(this); 24700 24701 if (this._model._masking && bg.src) { 24702 if (!sameRoom) { 24703 bg.x = bg.y = bg.width = bg.height = 0; 24704 bg.scale = 1; 24705 24706 myDeco.tracking.trackingImage(bg.src, function() { 24707 var image = $(this), 24708 width = self.canvasWidth, 24709 height = self.canvasHeight, 24710 scale; 24711 24712 bg.width = image.width(); 24713 bg.height = image.height(); 24714 24715 scale = bg.width / bg.height; 24716 24717 if (scale > width / height) { 24718 bg.scale = width / bg.width; 24719 height = width / scale; 24720 } else { 24721 bg.scale = height / bg.height; 24722 width = height * scale; 24723 } 24724 24725 bg.scale /= self.zoom; 24726 24727 bg.x = (self.canvasWidth / 2 - self._offset.x - width / 2) / self.zoom; 24728 bg.y = (self.canvasHeight / 2 - self._offset.y - height / 2) / self.zoom; 24729
24730 self._grid.drawBackground(); 24731 }); 24732 } else { 24733 this._grid.drawBackground(); 24734 } 24735 } 24736 24737 this._cloud.push(this._grid); 24738 24739 var floor_items = [ 24740 [[this._model.camera], Camera, this._rotablesObserver], 24741 [this._model.shapes, Shape, this._droppablesObserver], 24742 [this._model.walls, Wall, this._draggablesObserver], 24743 [this._model.corners, Corner, this._draggablesObserver], 24744 [this._model.windows, Window, this._draggablesObserver], 24745 [this._model.doors, Door, this._draggablesObserver], 24746 [this._model.components, Component, this._rotablesObserver] ]; 24747 24748 if (myDeco.settings.WL && myDeco.settings.WL.HIDE_CAMERA) { 24749 floor_items.shift(); 24750 }; 24751 24752 this.populateCloud(floor_items, 24753 $.proxy(function() { 24754 this.sortCloud(); 24755 $.each(this._model.components, function(i, component) { 24756 if (component.level === -1 && component.type === 'component') { 24757 this.rotation[2] = this.rotation[2] - 180; 24758 } 24759 }) 24760 }, this)); 24761 }, 24762 24763 /** 24764 * Add camera to view 24765 * @param {Camera} 24766 */ 24767 addCamera: function(camera) { 24768 this._cloud.append([camera], Camera, this._rotablesObserver, '_model'); 24769 }, 24770 24771 /** 24772 * Add shape to view 24773 * @param {Shape} 24774 */ 24775 addShape: function(shape) { 24776 this._cloud.append([shape], Shape, this._droppablesObserver, '_model'); 24777 }, 24778 24779 /** 24780 * Add wall to view 24781 * @param {Wall} 24782 */ 24783 addWall: function(wall) { 24784 this._cloud.append([wall], Wall, this._draggablesObserver, '_model'); 24785 }, 24786 24787 /** 24788 * Add corner to view 24789 */ 24790 addCorner: function(corner) { 24791 this._cloud.append([corner], Corner, this._draggablesObserver, 24792 '_model'); 24793 }, 24794 24795 /** 24796 * Add component to cloud and canvas 24797 * @param {Component} component 24798 */ 24799 addComponent: function(entity) { 24800 this._cloud.append([entity], window[entity.type.capitalize()], 24801 this._rotablesObserver); 24802 }, 24803 24804 /** 24805 * Calculate zoom for fit room to canvas and pan/scale all items 24806 */ 24807 showAll: function() { 24808 this._super(); 24809 24810 var settings = this.settings.offset || {}, 24811 top = settings.top || 0, 24812 bottom = settings.bottom || 0, 24813 bb = this._model.getBBox(), 24814 wzoom = (bb.width || 500) / (this.canvasWidth), 24815 hzoom = (bb.depth || 500) / (this.canvasHeight - top - bottom), 24816 // set some space around model 24817 zoom = 0.80 / Math.max(wzoom, hzoom), 24818 cx = this.canvasWidth / 2, 24819 cy = (this.canvasHeight + top - bottom) / 2; 24820 24821 this.pan(cx - bb.cx * zoom, cy + bb.cy * zoom, true); 24822 this.scale(zoom, true); 24823 }, 24824 24825 /** 24826 * Scale view 24827 * @param {Number} zoom 24828 * @param {Boolean} absolute 24829 */ 24830 scale: function(zoom, absolute) { 24831 this._super(zoom, absolute); 24832 24833 var abox = this._rotateArrow.getBBox(), 24834 ax = abox.x + abox.width / 2, 24835 ay = abox.y + abox.height / 2; 24836 24837 zoom = Math.max(this.settings.rotateArrow.scale, 24838 this.settings.rotateArrow.scale / 24839 (this.zoom * this.settings.rotateArrow.zoom)); 24840 24841 this._rotateArrow.scale(zoom, zoom, ax, ay); 24842 }, 24843 24844 /** 24845 * Generate draggable HTML element for display while dragging 24846 * 24847 * @param {Object} component Display component 24848 * @param {String} kind Kind of entity: furniture, decor, build element 24849 */ 24850 generateElementForDrag: function(component, kind) { 24851 var result, 24852 side = 'front', 24853 dimension = 'height', 24854 w, h; 24855 24856 switch (kind) { 24857 case 'furniture': 24858 case 'element': 24859 if (kind === 'furniture' && component.level !== -1) { 24860 side = 'top'; 24861 dimension = 'depth'; 24862 } 24863 24864 if (component.type !== 'room') { 24865 w = Math.round(component.width * this.zoom); 24866 h = Math.round(component[dimension] * this.zoom); 24867 result = { 24868 'ox': Math.round(w * 0.5), 24869 'oy': Math.round(h * 0.5), 24870 'visual': $('<img src="' + buildThumbnailLink( 24871 component.componentData['3d']['render_' + side]) + 24872 '" style="width:' + w + 'px;height:' + h + 24873 'px;display:none" class="floor" />') 24874 }; 24875 } else { 24876 result = { 24877 'ox': Math.round(component.width * this.zoom * 0.5), 24878 'oy': Math.round(component.depth * this.zoom * 0.5), 24879 'visual': $('<div class="floor ' + component.subtype + 24880 '" style="display: none">' + 24881 '<span class="room-lt"></span>' + 24882 '<span class="room-rt"></span>' + 24883 '<span class="room-lb"></span>' + 24884 '<span class="room-rb"></span>' + 24885 '</div>') 24886 .width(component.width * this.zoom) 24887 .height(component.depth * this.zoom) 24888 }; 24889 } 24890 break; 24891 case 'decor': 24892 result = { 24893 'ox': -10, 24894 'oy': -10, 24895 'visual': false 24896 }; 24897 break; 24898 } 24899 24900 return result; 24901 }, 24902 24903 /** 24904 * Sort cloud and reposition components by Z index on view whenever the scene is changed, 24905 * for example when a component is added to the scene. 24906 */ 24907 sortCloud: function() { 24908 var map = {'Grid': 0, 'Shape': 1, 'Wall': 2, 'Corner': 3, 'Window': 4, 24909 'Door': 5, 'Component': 6, 'Camera': 7}, 24910 i, ilen, drawFlag = true; 24911 24912 24913 this._cloud.sort(function(a, b) { 24914 var ret = 0; 24915 if (a.type === 'Component' && b.type === 'Component') { 24916 if (a._model.level !== b._model.level) { 24917 if (a._model.level < 0 || b._model.level < 0) { 24918 ret = b._model.level - a._model.level 24919 } else { 24920 ret = a._model.level - b._model.level; 24921 } 24922 } else { 24923 ret = a._model._surface[0].z - b._model._surface[0].z; 24924 } 24925 } else { 24926 ret = map[a.type] - map[b.type]; 24927 } 24928 return ret; 24929 }); 24930 24931 for (i = 0, ilen = this._cloud.length; i < ilen; i++) { 24932 if (drawFlag && this._drawLine && this._drawCorner && 24933 this._controller._operation.type === 'draw' && 24934 this._cloud[i].type === 'Corner') { 24935 // keep drawLine under corners 24936 drawFlag = false;
24937 this._drawLine.toFront(); 24938 this._drawCorner.toFront(); 24939 } 24940 this._cloud[i].element.toFront(); 24941 } 24942 24943 // active measurers 24944 for (i = 0, ilen = this.measurers.length; i < ilen; i++) { 24945 if (this.measurers[i].measurer) { 24946 this.measurers[i].measurer.rulers.toFront(); 24947 this.measurers[i].measurer.text.toFront(); 24948 } 24949 } 24950 24951 // no walls 24952 if (this._drawLine && this._drawCorner && drawFlag) { 24953 this._drawLine.toFront(); 24954 this._drawCorner.toFront(); 24955 } 24956 24957 if (this._grid.image) { 24958 this._grid.image.toBack(); 24959 } 24960 this._rotateArrow.toFront(); 24961 }, 24962 24963 /** 24964 * Calculate real model coordinates from given screen coordinates 24965 * @param {Float} screenX X screen coord 24966 * @param {Float} screenY Y screen coord 24967 * @param {Float} sceneZ height of the plane, parallel to the floor, on which coords should be projected (used for isometry) 24968 * @param {Wall} wall Parent wall to which screen coordinates should be projected (used for isometry) 24969 * @returns {Object} in format {x: ..., y: ...} 24970 */ 24971 toModelPosition: function(screenX, screenY) { 24972 var offset = this.getCanvasOffset(); 24973 screenX = screenX || 0; 24974 screenY = screenY || 0; 24975 24976 return new Point((screenX - offset.left - this._offset.x) / this.zoom, 24977 (- (screenY - offset.top) + this._offset.y) / this.zoom); 24978 }, 24979 24980 /** 24981 * Toggle view to maximize/minimize state 24982 * @param {Boolean} state View state, will toggle if not specified 24983 * 24984 * If state is not specified, will maximize if viewType eq this one and 24985 * minimize if not. 24986 * Will hide search by dimension facet on switch 24987 */ 24988 toggle: function(state) { 24989 this._super(state); 24990 if (!this.state) { 24991 myDeco.body.trigger('controller-facet-toggle', 24992 [false, 'dimension', false]); 24993 } 24994 }, 24995 24996 /** 24997 * Register measurer in the view stack 24998 * @param {Array} corners Array of corners 24999 * @param {Number} distance Distance from baseline to measurer 25000 * @returns {Array} 25001 */ 25002 registerMeasurer: function(corners, distance) { 25003 var measurer = corners.slice(); 25004 25005 if (this.settings.measurer) { 25006 measurer._view = this; 25007 measurer._distance = typeof distance !== 'undefined' ? distance : 17; 25008 measurer.update = this.updateMeasurer; 25009 this.measurers.push(measurer); 25010 } else { 25011 measurer.update = $.noop; 25012 } 25013 25014 return measurer; 25015 }, 25016 25017 /** 25018 * Remove and pop measurer from view stack 25019 * @param {Array} measurer 25020 */ 25021 deregisterMeasurer: function(measurer) { 25022 measurer.update(false); 25023 $.extract(measurer, this.measurers); 25024 }, 25025 25026 /** 25027 * Draw/remove/update measurer method 25028 * @param {Boolean} state Show/hide measurer. In case of undefined and 25029 * enabled measurer just update its position 25030 */ 25031 updateMeasurer: function(state) { 25032 var style = this._view.settings.measurer, 25033 distance, fontSize, rulersStyle, 25034 m0, m1, text, angle, textAngle, l0, p0, l1, p1, tp; 25035 25036 if ((typeof state === 'undefined' && this.measurer) || state) { 25037 distance = this._distance + style.distance / this._view.zoom; 25038 fontSize = parseInt(+(style.text['font-size'] || 12) / 25039 (this._view.canvas.raphael.type === 'SVG' ? 25040 this._view.zoom : 1), 10) + 'px'; 25041 25042 // rulers thickness 25043 rulersStyle = $.extend({}, style.rulers, { 25044 'stroke-width': (style.rulers['stroke-width'] || 1) / 25045 (this._view.canvas.raphael.type === 'SVG' ? 25046 this._view.zoom : 1) 25047 }); 25048 25049 m0 = this[0]; 25050 m1 = this[1]; 25051 text = [m0.distance(m1)].toUnits(); 25052 angle = T.atan2(this[1].y - this[0].y, this[1].x - this[0].x); 25053 textAngle = (angle + 360) % 360; 25054 25055 l0 = m0.clone().translate(0, distance).rotate(m0, angle); 25056 p0 = l0.clone().translate(0, distance * 0.2).rotate(l0, angle); 25057 25058 l1 = m1.clone().translate(0, distance).rotate(m1, angle); 25059 p1 = l1.clone().translate(0, distance * 0.2).rotate(l1, angle); 25060 25061 tp = new Point((p0.x + p1.x) / 2, (p0.y + p1.y) / 2, 0); 25062 25063 // correct text rotation for better reading 25064 textAngle = textAngle >= 91 && textAngle <= 271 ? 25065 textAngle - 180 : textAngle; 25066 25067 if (!this.measurer) { 25068 this.measurer = { 25069 text: this._view.canvas.text(tp.x, -tp.y, text), 25070 rulers: this._view.canvas.set().push( 25071 this._view.canvas.path(rulersStyle) 25072 .moveTo(l0.x, -l0.y).lineTo(l1.x, -l1.y), 25073 this._view.canvas.path(rulersStyle) 25074 .moveTo(m0.x, -m0.y).lineTo(p0.x, -p0.y), 25075 this._view.canvas.path(rulersStyle) 25076 .moveTo(m1.x, -m1.y).lineTo(p1.x, -p1.y) 25077 ) 25078 }; 25079 } 25080 25081 // reposition measurer and update text 25082 this.measurer.rulers[0].update([l0, l1]); 25083 this.measurer.rulers[1].update([m0, p0]);
25084 this.measurer.rulers[2].update([m1, p1]); 25085 this.measurer.rulers.attr(rulersStyle); 25086 25087 if (this.measurer.text.attr('font-size') !== fontSize) { 25088 this.measurer.text.attr('font-size', fontSize); 25089 } 25090 25091 this.measurer.text.attr($.extend({}, style.text, { 25092 text: text, 25093 x: tp.x, 25094 y: -tp.y 25095 })).rotate(-textAngle, true); 25096 25097 if (state) { 25098 this.measurer.text.toFront().show(); 25099 this.measurer.rulers.toFront().show(); 25100 } 25101 } else if (this.measurer) { 25102 this.measurer.text.remove(); 25103 this.measurer.rulers.remove(); 25104 this.measurer = null; 25105 } 25106 }, 25107 25108 /** 25109 * Updates rotation arrow position and angle 25110 */ 25111 updateRotationArrow: function() { 25112 var distance, entity, selected, 25113 abox, ax, ay, angle = 0; 25114 25115 if (this._controller._viewActive && this._model.rotationAvailable) { 25116 abox = this._rotateArrow.getBBox(); 25117 ax = abox.x + abox.width / 2; 25118 ay = abox.y + abox.height / 2; 25119 distance = this.settings.rotateArrow.distance; 25120 selected = this._model.selected(); 25121 25122 if (selected.length === 1) { 25123 entity = selected[0]; 25124 entity = this._cloud.findByField('_model', [entity]).shift(); 25125 distance = entity._settings.arrowDistance || 25126 this.settings.rotateArrow.distance; 25127 } 25128 25129 switch (this._controller._operation.type) { 25130 case 'rotate': 25131 this._rotateArrow.rotate(this._controller._rotationAngle, 25132 this._model._selectionCenter.x, 25133 -this._model._selectionCenter.y); 25134 break; 25135 case 'drag': 25136 this._rotateArrow 25137 .translate(this._model._selectionCenter.x - ax, 25138 -this._model._selectionCenter.y - ay + distance); 25139 break; 25140 default: 25141 this._rotateArrow 25142 .rotate(0, ax, ay) 25143 .translate(this._model._selectionCenter.x - ax, 25144 -this._model._selectionCenter.y - ay + distance) 25145 .rotate(this._controller._rotationAngle, 25146 this._model._selectionCenter.x, 25147 -this._model._selectionCenter.y); 25148 } 25149 } 25150 }, 25151 25152 /** 25153 * Draw point for setScale method 25154 * @param {Point} point 25155 */ 25156 drawScalePoint: function(point) { 25157 this._scalePoints.push(this.canvas.circle(point.x, -point.y, 10).attr({ 25158 'stroke': '#f00' 25159 }).toFront()); 25160 } 25161}); 25162 25163// register view in dispatched views list 25164myDeco.App.views.push(FloorView); 25165/*globals $, myDeco, AppController, document, KEY, xor, Point */ 25166/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 25167 sloppy: true, white: true */ 25168/** 25169 * @fileOverview FPE abstract room controller 25170 */ 25171 25172var FpeController = AppController.extend({ 25173 /** 25174 * Controller type 25175 * @type {String} 25176 * @static 25177 */ 25178 $type: 'base', 25179 25180 /** 25181 * Flag which representing that cursor is inside of associated view and 25182 * only over it (since other views can flying over this view) 25183 * @type {Boolean} 25184 * @private 25185 */ 25186 _viewActive: false, 25187 25188 /** 25189 * Dict of currently active snaps 25190 * @type {Object} 25191 */ 25192 _snaps: {}, 25193 25194 /** 25195 * Init function 25196 * @param {Object} model FPE model 25197 */ 25198 init: function(model, settings) { 25199 this._super(model, settings); 25200 this._model.operationReset(); 25201 25202 myDeco.body 25203 .bind('entity-drag-started', this.entityDrag.bind(this)) 25204 .mousemove(this.globalMouseMove.bind(this)) 25205 .bind('mouseup', this.entityDrop.bind(this)) 25206 .bind('actionRequest', this.keyboardAction.bind(this)); 25207 }, 25208 25209 /** 25210 * Entity (room shape/door/window/component/material) drop observer 25211 * @param {Event} e Mouseup event 25212 */ 25213 entityDrop: function(e) { 25214 if (this._viewActive && this._operation.type === 'add') { 25215 e.preventDefault(); 25216 25217 var entity, operationName, decorateTarget, i, z, 25218 pos = this._view.toModelPosition(e.pageX, e.pageY, 0); 25219 25220 operationName = 'add'; 25221 entity = $.cloneObject(this._operation.data.entity); 25222 25223 if (this._operation.data.parent) { 25224 z = this._model.ceilingHeight / 2; 25225 } 25226 25227 // fix for ipad which touchend doesn't support real target 25228 if (myDeco.browser.isMobileSafari && 25229 this._operation.data.kind === 'decor') { 25230 decorateTarget = $.grep(this._view._decorateTargets, function(element) { 25231 element = $(element); 25232 var pos = element.offset(); 25233 return e.pageX >= pos.left && 25234 e.pageX <= pos.left + element.width() && 25235 e.pageY >= pos.top && 25236 e.pageY <= pos.top + element.height() && 25237 element.is(':visible'); 25238 }).shift(); 25239 25240 if (decorateTarget) { 25241 decorateTarget = $(decorateTarget); 25242 } 25243 } 25244 25245 switch (this._operation.data.kind) { 25246 case 'furniture': 25247 delete entity.ID; 25248 25249 // so, if this component has parent 25250 if (this._operation.data.parent) { 25251 // rotate component over oX axis 25252 entity.rotation[0] = -90; 25253 25254 // blur selected wall
25255 this._operation.data.parent.highlightOff(); 25256 25257 // set parent for it 25258 entity.parent = this._operation.data.parent; 25259 } 25260 25261 // add on-wall components only if parent is defined 25262 // all other components will add also 25263 if (xor(this._operation.data.parent, entity.level !== -1)) { 25264 entity = this._model.addComponent(entity, pos.x, pos.y, z); 25265 } 25266 break; 25267 case 'decor': 25268 if (this._operation.data.type === 'photo') { 25269 if (this._operation.data.target) { 25270 if (this._operation.data.target.type) { 25271 this._model.assignPhoto(this._operation.data.target, this._operation.data.entity); 25272 operationName = 'material'; 25273 } else { 25274 decorateTarget = $(this._operation.data.target); 25275 } 25276 } else { 25277 decorateTarget = decorateTarget || $(e.target); 25278 } 25279 25280 if (decorateTarget && decorateTarget.hasClass('decorate-target')) { 25281 if (decorateTarget.hasClass('walls')) { 25282 for (i = this._model.walls.length - 1; i >= 0; i = i - 1) { 25283 this._model.assignPhoto(this._model.walls[i], entity); 25284 } 25285 } else { 25286 myDeco.App.model.assignEnvironment('image', entity.texture); 25287 } 25288 operationName = 'material'; 25289 } 25290 } else { 25291 if (this._operation.data.target && 25292 this._checkDecorAcceptance(this._operation.data.target)) { 25293 if (this._operation.data.target.type === 'shape') { 25294 this._model.assignMaterial(this._operation.data.target, entity, 'floor'); 25295 } else if (this._operation.data.target.type) { 25296 this._model.assignMaterial(this._operation.data.target, entity); 25297 } else { 25298 decorateTarget = $(this._operation.data.target); 25299 } 25300 25301 operationName = 'material'; 25302 } else { 25303 decorateTarget = decorateTarget || $(e.target); 25304 } 25305 25306 if (decorateTarget && decorateTarget.hasClass('decorate-target')) { 25307 if (decorateTarget.hasClass('walls')) { 25308 for (i = this._model.walls.length - 1; i >= 0; i = i - 1) { 25309 this._model.assignMaterial(this._model.walls[i], entity, 'wall'); 25310 } 25311 } else { 25312 for (i = this._model.shapes.length - 1; i >= 0; i = i - 1) { 25313 this._model.assignMaterial(this._model.shapes[i], entity, 'ceiling'); 25314 } 25315 } 25316 25317 operationName = 'material'; 25318 } 25319 } 25320 break; 25321 case 'element': 25322 switch (entity.type) { 25323 case 'room': 25324 entity = this._model.addRoom(entity.subtype, 25325 pos.x, 25326 pos.y, 25327 entity.width, 25328 entity.depth); 25329 break; 25330 case 'ArchitecturalComponent': 25331 delete entity.ID; 25332 25333 // so, if this component has parent 25334 if (this._operation.data.parent) { 25335 // rotate component over oX axis 25336 entity.rotation[0] = -90; 25337 25338 // blur selected wall 25339 this._operation.data.parent.highlightOff(); 25340 25341 // set parent for it 25342 entity.parent = this._operation.data.parent; 25343 25344 entity = this._model.addComponent(entity, pos.x, pos.y, z); 25345 } else { 25346 operationName = ''; 25347 } 25348 25349 break; 25350 } 25351 break; 25352 } 25353 25354 if (operationName) { 25355 this._model.operationEnd([operationName]); 25356 } else { 25357 this._model.operationReset(); 25358 } 25359 25360 this._view.toggleDecorateTargets(false); 25361 25362 } 25363 }, 25364 25365 /** 25366 * Global mouse move observer for tracking nearest walls for onwall components 25367 * @param {Event} e Mousemove event 25368 */ 25369 globalMouseMove: function(e) { 25370 var wall, i, len, point, currTarget; 25371 25372 if (this.state && 25373 this._viewActive && 25374 typeof this._operation.data !== 'undefined') { 25375 if (this._operation.data.addingComponentOnWall) { 25376 wall = this.findNearestWall(e, false, this._operation.data.entity.width); 25377 25378 if (wall && wall !== this._operation.data.parent) { 25379 if (this._operation.data.parent) { 25380 this._operation.data.parent.highlightOff(); 25381 } 25382 25383 this._operation.data.parent = wall; 25384 wall.highlightOn(); 25385 } 25386 } 25387 // Special case for iPad, since there are impossible to track mouseover events in some cases 25388 else if (myDeco.browser.isMobileSafari && 25389 this._operation.type === 'add' && 25390 this._operation.data.kind === 'decor') { 25391 25392 // Searching for decorate targets (buttons on canvas) under the finger 25393 if (this._operation.data.targets) { 25394 for (i = 0, len = this._operation.data.targets.length; i < len; i = i + 1) { 25395 if (e.pageX >= this._operation.data.targets[i].x1 && 25396 e.pageX <= this._operation.data.targets[i].x2 && 25397 e.pageY >= this._operation.data.targets[i].y1 && 25398 e.pageY <= this._operation.data.targets[i].y2) { 25399 25400 currTarget = this._operation.data.targets[i].target; 25401 break; 25402 } 25403 } 25404 } 25405 25406 // Searching for wall nearest to finger to decorate 25407 if (typeof currTarget === 'undefined') { 25408 point = this._view.toModelPosition(e.pageX, e.pageY); 25409 currTarget = this._model.findNearestWall(point, 22); 25410 } 25411 25412 // Searching for floor under the finger to decorate 25413 if (typeof currTarget === 'undefined' && this._operation.data.type !== 'photo') { 25414 for (i = 0, len = this._model.shapes.length; i < len; i = i + 1) { 25415 if (point.inPoly(this._model.shapes[i])) { 25416 currTarget = this._model.shapes[i]; 25417 break; 25418 } 25419 } 25420 } 25421 25422 if (currTarget !== this._operation.data.target) { 25423 if (this._operation.data.target && this._operation.data.target.highlightOff) { 25424 this._operation.data.target.highlightOff(); 25425 } 25426 25427 this._operation.data.target = currTarget; 25428 if (currTarget) { 25429 if (currTarget.highlightOn) { 25430 currTarget.highlightOn(); 25431 } 25432 25433 this._operation.data.visual.addClass('yes').removeClass('no'); 25434 } else { 25435 this._operation.data.visual.addClass('no').removeClass('yes'); 25436 } 25437 } 25438 } 25439 } 25440 }, 25441 25442 /** 25443 * Register view 25444 * @param {Object} view View associated to controller 25445 */ 25446 registerView: function(view) { 25447 var self = this; 25448 this._super(view); 25449 25450 this.toggle(this._view.viewHolder.is(':visible'), this.$type); 25451 25452 this._view._decorateTargets 25453 .mouseup(this.entityDrop.bind(this)) 25454 .click(function(e) { 25455 e.preventDefault(); 25456 }) 25457 .bind('mouseover mouseout', function(e) { 25458 if (self._operation.type === 'add' && 25459 (self._operation.data.kind === 'decor')) { 25460 if (e.type === 'mouseover') { 25461 self._operation.data.visual.removeClass('no').addClass('yes'); 25462 } else { 25463 self._operation.data.visual.removeClass('yes').addClass('no'); 25464 } 25465 } 25466 }); 25467 25468 this._view.viewHolder 25469 .bind('canvasIn', this.canvasIn.bind(this)) 25470 .bind('canvasOut', this.canvasOut.bind(this)); 25471 25472 // register observers 25473 this._view.canvasElement 25474 .mousedown(this.mouseDown.bind(this)) 25475 .mousewheel(this.wheelScroll.bind(this)) 25476 .mousemove(this.mouseMove.bind(this)) 25477 .mouseup(this.mouseUp.bind(this)) 25478 .bind('gesturestart', function() { 25479 self._operation = { 25480 type: 'scale', 25481 data: { 25482 zoom: self._view.zoom 25483 } 25484 }; 25485 }) 25486 .bind('gesturechange', function(e) { 25487 if (self._operation.type === 'scale') { 25488 self._view.scale(self._operation.data.zoom * e.scale, true); 25489 } 25490 }) 25491 .bind('gestureend', function() { 25492 self._model.operationReset(); 25493 }); 25494 25495 myDeco.body 25496 .bind('setscaleStart', this.setscaleStart.bind(this)) 25497 .bind('drawStart', this.drawStart.bind(this)) 25498 .bind('dblclick', this.doubleClick.bind(this)); 25499 }, 25500 25501 /** 25502 * Check for locked visual entity 25503 * @param {Object} visual Visual to check 25504 * @param {Boolean} triggerEvent Trigger event on locked item 25505 * @returns {Boolean} 25506 */ 25507 isLocked: function(visual, triggerEvent) { 25508 var locked = this._model.isLocked(visual._model); 25509 if (locked && triggerEvent) { 25510 myDeco.body.trigger('locked-move', [visual._model]); 25511 } 25512 return locked; 25513 }, 25514 25515 /** 25516 * Shorthand for registering draggable elements 25517 * @param {Object} visual Visual element to register 25518 * @returns {Object} input visual entity 25519 */ 25520 registerDraggables: function(visual) { 25521 var self = this; 25522 // we want to drag only visual base 25523 visual.getBase() 25524 .mouseover(function(e) { 25525 self.highlightStart(e, visual); 25526 }) 25527 .mouseout(function(e) { 25528 self.highlightEnd(e, visual); 25529 }); 25530 visual.element 25531 .mousedown(function(e) { 25532 if (!self.isLocked(visual, true)) { 25533 self.dragStart(e, visual); 25534 } else { 25535 e.preventDefault(); 25536 } 25537 }, true) 25538 .click(function(e) { 25539 e.preventDefault(); 25540 }) 25541 .dblclick(function(e) { 25542 e.preventDefault(); 25543 self.doubleClick(e, visual); 25544 }); 25545 return visual; 25546 }, 25547 25548 /** 25549 * Shorthand for registering droppable elements 25550 * @param {Object} visual Visual element to register 25551 * @returns {Object} input visual element 25552 */ 25553 registerDroppables: function(visual) { 25554 var self = this; 25555 visual.element 25556 .mouseover(function(e) { 25557 self.activateDroppable(e, visual); 25558 }) 25559 .mouseout(function(e) { 25560 self.deactivateDroppable(e, visual); 25561 }) 25562 .mousedown(function(e) { 25563 self.mouseDown(e, visual); 25564 }, true); 25565 return visual; 25566 }, 25567 25568 /** 25569 * Callback for resetting current operation 25570 */ 25571 operationReset: function() { 25572 if (this._view) { 25573 switch (this._operation.type) { 25574 case 'drag': 25575 case 'pan': 25576 this._view.cursor('move'); 25577 break; 25578 25579 case 'draw': 25580 $('.draggable, #sidebar').fadeIn(); 25581 this._model.unselect(); 25582 this._view._cloud.findByField('type', 'Component') 25583 .batch('toggle', true); 25584 25585 // find all corners which do not connected to any wall or shape 25586 // and remove it 25587 $.each(this._model.corners, function() { 25588 if (this.getWalls().length === 0 &&
25589 this.getShapes().length === 0) { 25590 this.remove(); 25591 } 25592 }); 25593 myDeco.body.trigger('status'); 25594 break; 25595 25596 case 'setscale': 25597 myDeco.body.trigger('status'); 25598 break; 25599 25600 default: 25601 this._view.cursor('default'); 25602 25603 } 25604 25605 this._view.toggleDecorateTargets(false); 25606 25607 if (myDeco.browser.isMobileSafari) { 25608 this._view.canvas.safari(); 25609 } 25610 } 25611 25612 this._operation = {type: '', data: {}}; 25613 this._snaps = {}; 25614 myDeco.body.trigger('snappingOff'); 25615 }, 25616 25617 /** 25618 * Sort cloud on each operation 25619 * @param {Event} e 25620 * @param {Array} data 25621 */ 25622 operationEnd: function(e, data) { 25623 if (data && data[0] === 'add') { 25624 this._view.sortCloud(); 25625 } 25626 return this._super(e, data); 25627 }, 25628 25629 /** 25630 * Event listener for pan elements 25631 * @param {Event} e Event 25632 * @param {Object} visual Visual part of entity which started pan 25633 */ 25634 mouseDown: function(e, visual) { 25635 if (e.isDefaultPrevented()) { 25636 return; 25637 } 25638 25639 e.preventDefault(); 25640 25641 this._operation = { 25642 type: 'pan', 25643 data: { 25644 timestamp: +(new Date()), 25645 timeDifference: myDeco.browser.isIE ? 300 : 150, 25646 x: e.pageX, 25647 y: e.pageY, 25648 visual: visual 25649 } 25650 }; 25651 }, 25652 25653 /** 25654 * Mouse move event listener 25655 * @param {Event} e Mousemove event 25656 */ 25657 mouseMove: function(e) { 25658 if (this._operation.type === 'pan') { 25659 this._view.pan((e.pageX - this._operation.data.x), (e.pageY - this._operation.data.y)); 25660 this._operation.data.x = e.pageX; 25661 this._operation.data.y = e.pageY; 25662 } 25663 }, 25664 25665 /** 25666 * Mouse up button listener 25667 * @param {Event} e Mouseup event 25668 */ 25669 mouseUp: function(e) { 25670 var shortClick = false; 25671 // Add operation will be processed by body mouseUp watcher 25672 if (this.state && this._operation.type !== 'add') { 25673 e.preventDefault(); 25674 25675 if (this._operation.type) { 25676 if (this._operation.type === 'pan' || 25677 this._operation.type === 'drag') { 25678 25679 // Time difference between mousedown and up was too short 25680 // so we can call it click 25681 shortClick = +(new Date()) - 25682 this._operation.data.timestamp <= 25683 this._operation.data.timeDifference; 25684 25685 if (shortClick) { 25686 this._model.unselect(); 25687 if (this._operation.data.visual) { 25688 this.select(this._operation.data.visual._model); 25689 myDeco.body.trigger('showInfo', [this._operation.data.visual._model]); 25690 } 25691 } 25692 } 25693 25694 if (this._operation.type === 'pan' || shortClick) { 25695 this._model.operationReset(); 25696 } else { 25697 this._model.operationEnd([this._operation.type]); 25698 } 25699 } 25700 } 25701 }, 25702 25703 /** 25704 * Visual element highlighting observer 25705 * @param {Event} e Mouseover event 25706 * @param {Object} visual Visual element to highlight 25707 */ 25708 highlightStart: function(e, visual) { 25709 this._view.cursor('default'); 25710 visual._model.highlightOn(); 25711 myDeco.body.trigger('highlightOn', [e, visual]); 25712 }, 25713 25714 /** 25715 * Visual element dishighliting observer 25716 * @param {Event} e Mouseout event 25717 * @param {Object} visual Visual element to dishighlight 25718 */ 25719 highlightEnd: function(e, visual) { 25720 visual._model.highlightOff(); 25721 }, 25722 25723 /** 25724 * Visual element drag observer 25725 * @param {Event} e Mousedown event 25726 * @param {Object} entity operating on 25727 */ 25728 dragStart: function(e, visual) { 25729 if (e.isDefaultPrevented()) { 25730 return; 25731 } 25732 e.preventDefault(); 25733 25734 if (this._operation.type) { 25735 return; 25736 } 25737 25738 if (this.select(visual._model, e.shiftKey)) { 25739 var selected = this._model.selected(); 25740 25741 this._operation = { 25742 type: 'drag', 25743 data: { 25744 timestamp: +(new Date()), 25745 timeDifference: myDeco.browser.isIE ? 300 : 150, 25746 x: e.pageX, 25747 y: e.pageY, 25748 // custom data storage, used in isometry controller at least 25749 d: {}, 25750 // set up parent only for on-wall component 25751 parent: selected.length === 1 && !selected[0].category ? 25752 selected[0].parent : undefined, 25753 visual: visual 25754 } 25755 }; 25756 25757 this._model.operationStart(this._operation.type); 25758 } 25759 }, 25760 25761 /** 25762 * Start draw function 25763 * @param {Event} e 25764 */ 25765 drawStart: function(e) { 25766 }, 25767 25768 /** 25769 * Start set scale function 25770 * @param {Event} e 25771 */ 25772 setscaleStart: function(e) { 25773 }, 25774 25775 /** 25776 * Global document observer, used for misc keyboard operations 25777 * @param {Event} e 25778 * @param {Array} action 25779 */ 25780 keyboardAction: function(e, action) { 25781 var entity; 25782 25783 // don't want to serve not fullscreen windows 25784 if (!this._view || !this.state) { 25785 return; 25786 } 25787 if (!this._operation || !this._operation.type || 25788 action[0] === 'reset') { 25789 25790 switch(action[0]) { 25791 case 'remove': 25792 // delete some element 25793 if (!$.any(this._model.selected(), 25794 function(element) { 25795 return ['component', 'window', 'door', 'wall', 'shape'] 25796 .indexOf(element.type) === -1; 25797 }) 25798 ) { 25799 // action[2] is always false for the 'del' key but can be manually set 25800 this._model.remove(action[2]); 25801 } 25802 break; 25803 case 'move': 25804 // move components 25805 this.moveByKey(action); 25806 break; 25807 case 'rotate': 25808 this.rotateByKey(action); 25809 break; 25810 case 'redo': 25811 this._model.redo(); 25812 break; 25813 case 'undo': 25814 this._model.undo(); 25815 break; 25816 case 'duplicate': 25817 this._model.duplicate(); 25818 break; 25819 case 'split': 25820 entity = $.grep(this._model.selected(), function(entity) { 25821 return entity.type === 'wall'; 25822 }); 25823 if (entity.length !== 0) { 25824 this._model.splitWall(entity[0]); 25825 this._model.operationEnd(['add']); 25826 this._model.unselect(); 25827 } 25828 break; 25829 case 'find': 25830 $('#search-query').focus(); 25831 break; 25832 case 'draw': 25833 this.drawStart(); 25834 break; 25835 case 'setscale': 25836 this.setscaleStart(); 25837 break; 25838 case 'info': 25839 // show only for the first selected component 25840 if (this._model.selected().length === 1) { 25841 myDeco.body.trigger('showInfo', [this._model.selected(true)]); 25842 } 25843 break; 25844 case 'reset': 25845 this._model.operationReset(); 25846 break; 25847 case 'debug': 25848 alert('user_id:' + myDeco.userid + ', room_id:' + myDeco.App.model._room_id); 25849 break; 25850 } 25851 } 25852 }, 25853 25854 /** 25855 * Move items by keypress 25856 * @param {Object} action 25857 */ 25858 moveByKey: function(action) { 25859 var shift = this._view.settings.keyboardShiftValue || 10,
25860 dx = -1 * (action[1] === KEY.Left) + (action[1] === KEY.Right), 25861 dy = -1 * (action[1] === KEY.Down) + (action[1] === KEY.Up); 25862 25863 if (action[2]) { 25864 shift /= 10; 25865 } 25866 25867 this._model.move(dx * shift, dy * shift); 25868 this._model.operationEnd(['drag']); 25869 }, 25870 25871 /** 25872 * Rotate items by keypress 25873 * @param {Object} action 25874 */ 25875 rotateByKey: function(action) { 25876 var shift = this._view.settings.keyboardRotateValue || 15; 25877 25878 if (action[2]) { 25879 shift /= 3; 25880 } 25881 25882 if (action[1] === KEY.L) { 25883 shift *= -1; 25884 } 25885 25886 this._model.rotate(shift); 25887 25888 this._model.operationEnd(['rotate']); 25889 }, 25890 25891 /** 25892 * Observer for mouse wheel scrolling, bound to canvas zooming 25893 * @param {Event} e Wheel event 25894 * @param {Number} delta Scrolling delta 25895 */ 25896 wheelScroll: function(e, delta) { 25897 this._view.scale(delta / 50.0); 25898 e.stop(); 25899 }, 25900 25901 /** 25902 * Observer for canvas in event 25903 */ 25904 canvasIn: function(e, ee) { 25905 this._viewActive = true; 25906 switch (this._operation.type) { 25907 case 'add': 25908 if (this._operation.data.kind === 'decor') { 25909 // no dropables under canvas 25910 this._operation.data.visual.removeClass('yes').addClass('no'); 25911 } else { 25912 this._operation.data.visual 25913 .css({ 25914 left: ee.pageX - (this._operation.data.ox || 0) + 'px', 25915 top: ee.pageY - (this._operation.data.oy || 0) + 'px' 25916 }) 25917 .data('position', { x: this._operation.data.ox, 25918 y: this._operation.data.oy}); 25919 25920 var current = this._operation.data.visual.find('.' + this.$type); 25921 25922 if (!current.length) { 25923 current = this._operation.data.visual.find('.original'); 25924 } 25925 25926 current 25927 .show() 25928 .siblings().hide(); 25929 } 25930 break; 25931 25932 case 'draw': 25933 case 'setscale': 25934 this._view.cursor('crosshair'); 25935 break; 25936 25937 default: 25938 this._view.cursor('move'); 25939 } 25940 }, 25941 25942 /** 25943 * Observer for canvas out event 25944 */ 25945 canvasOut: function() { 25946 this._view.cursor('default'); 25947 this._viewActive = false; 25948 25949 if (this._operation.type === 'add') { 25950 if (this._operation.data.kind === 'decor') { 25951 delete this._operation.data.target; 25952 // do not show any acceptance icons out of canvas 25953 } else { 25954 this._operation.data.visual 25955 .removeData('position') 25956 .find('.original').show() 25957 .siblings().hide(); 25958 } 25959 } 25960 }, 25961 25962 /** 25963 * Select entity 25964 * @param {Object} entity Entity to select 25965 * @param {Object} toggle Will entity toggled in selected list or not 25966 */ 25967 select: function(entity, toggle) { 25968 var ret = true, 25969 exclusive = ['camera', 'shape'], 25970 selected = $.grep(this._model.selected(), function(entity) { 25971 return exclusive.indexOf(entity.type) !== -1; 25972 }).length !== 0; 25973 25974 // check for exclusive selection 25975 if (selected || exclusive.indexOf(entity.type) !== -1) { 25976 this._model.unselect(); 25977 } 25978 25979 if (toggle) { 25980 if (entity.selected) { 25981 this._model.unselect(entity); 25982 } else { 25983 this._model.select(entity, true); 25984 } 25985 ret = false;
25986 } else if (!entity.selected) { 25987 ret = this._model.select(entity); 25988 } 25989 25990 return ret; 25991 }, 25992 25993 /** 25994 * Entity (room shape/door/window/component/material) start dragging observer 25995 * @param {Event} e Click event 25996 */ 25997 entityDrag: function(e, draggable) { 25998 var res = this._view.generateElementForDrag(draggable.entity, draggable.kind), 25999 addingComponentOnWall = 26000 draggable.entity.level === -1 && 26001 (draggable.kind === 'furniture' || 26002 (draggable.kind === 'element' && draggable.entity.type === 'ArchitecturalComponent')); 26003 26004 if (res.visual) { 26005 draggable.visual.append(res.visual); 26006 } 26007 26008 this._operation = { type: 'add', 26009 data: { visual: draggable.visual, 26010 entity: draggable.entity, 26011 kind: draggable.kind, 26012 ox: res.ox, 26013 oy: res.oy, 26014 addingComponentOnWall: addingComponentOnWall, 26015 type: draggable.type || '' }}; 26016 26017 if (draggable.kind === 'decor') { 26018 this._view.toggleDecorateTargets(draggable.kind, draggable.type); 26019 this._operation.data.targets = this._view.getDecorateTargets(); 26020 } 26021 }, 26022 26023 /** 26024 * Locate nearest wall to a specified coordinates 26025 * @param {Event} e 26026 * @param {Number} x 26027 * @param {Number} y 26028 * @parma {Number} minWidth Minimal width of the wall 26029 */ 26030 findNearestWall: function(e, minWidth) { 26031 return this._model.findNearestWall(this._view.toModelPosition(e.pageX, e.pageY), false, minWidth); 26032 }, 26033 26034 /** 26035 * Funtion for finding nearest coordinates to given 26036 * @param {Array} points Point snap to 26037 * @param {Array} types Look only for specified item types 26038 * @param {Array} except Points to omit from search result 26039 * @returns {Object} object with x, y and z (if given) coordinates 26040 */ 26041 findSnappingCoords: function(snapPoints, types, exclude) { 26042 var tolerancy = this._model.settings.snappingMoveTolerancy, 26043 // list of axes by which find snaps 26044 coords = ['x', 'y'], 26045 response = {}, distance, distances = {}, 26046 p = 0, plen = this._model._snapPoints.length, point, points = {}, 26047 s = 0, slen = snapPoints.length, snap, snaps = {}, 26048 c = 0, clen = coords.length, coord; 26049 26050 for (p = 0; p < plen; p++) { 26051 point = this._model._snapPoints[p]; 26052 26053 // filter by point type and exlude list 26054 if ((!types || types.indexOf(point.type) !== -1) && 26055 (!exclude || exclude.indexOf(point) === -1)) { 26056 for (s = 0; s < slen; s++) { 26057 snap = snapPoints[s]; 26058 26059 for (c = 0; c < clen; c++) { 26060 coord = coords[c]; 26061 distance = point.distance(snap, coord); 26062 26063 // filter by distance 26064 if ((!snaps[coord] && distance < tolerancy) || 26065 distances[coord] > distance) { 26066 snaps[coord] = snap; 26067 points[coord] = point; 26068 distances[coord] = distance; 26069 } 26070 } 26071 } 26072 } 26073 } 26074 26075 for (c = 0; c < clen; c++) { 26076 coord = coords[c]; 26077 response['d' + coord] = snaps[coord] ? 26078 points[coord][coord] - snaps[coord][coord] : 0; 26079 if (this._snaps[coord] && !snaps[coord]) { 26080 this._snaps[coord] = false; 26081 myDeco.body.trigger('snappingOff', [coord]); 26082 } else if (snaps[coord] && (!this._snaps[coord] || 26083 this._snaps[coord] !== points[coord][coord])) { 26084 this._snaps[coord] = points[coord][coord]; 26085 myDeco.body.trigger('snappingOn', [coord, this._snaps[coord]]); 26086 } 26087 } 26088 26089 return response; 26090 }, 26091 26092 /** 26093 * Switch cotroller state 26094 * @param {Event} e 26095 * @param {String} type Controller to enable 26096 */ 26097 toggle: function(e, type) { 26098 this._viewActive = type === this.$type; 26099 this._model.unselect(); 26100 this._super(e, type); 26101 }, 26102 26103 /** 26104 * Execute default action by dblclick 26105 */ 26106 doubleClick: function() { 26107 }, 26108 26109 /** 26110 * Check for acceptance of the currect operation and meterial to the specified visual element 26111 * @param {Object} visual Visual or Entity element 26112 * @returns {Boolean} 26113 */ 26114 _checkDecorAcceptance: function(visual) { 26115 var model = visual._view ? visual._model : visual; 26116 return this._operation.type === 'add' && 26117 this._operation.data.kind === 'decor' && 26118 model.acceptDecor && 26119 (this._operation.data.type !== 'photo' || 26120 (this._operation.data.type === 'photo' && 26121 !!model.acceptPicture)); 26122 }, 26123 26124 /** 26125 * Change acceptance icon on mouseover for droppable components/decor etc 26126 * @param {Event} e Mouseover event 26127 * @param {Object} visual Visual element operating on 26128 */ 26129 activateDroppable: function(e, visual) { 26130 if (this._checkDecorAcceptance(visual)) { 26131 this._operation.data.target = visual._model; 26132 this._operation.data.visual.addClass('yes').removeClass('no'); 26133 } 26134 }, 26135 26136 /** 26137 * Change acceptance icon on mouseout for droppable components/decor etc 26138 * @param {Event} e Mouseout event 26139 * @param {Object} visual Visual element operating on 26140 */ 26141 deactivateDroppable: function(e, visual) { 26142 if (this._checkDecorAcceptance(visual)) { 26143 delete this._operation.data.target; 26144 this._operation.data.visual.removeClass('yes').addClass('no'); 26145 } 26146 } 26147}); 26148/*globals myDeco, $, FpeController, T, findShapeArea, findPointInShape, Point, 26149 numberInputKeyPress */ 26150/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 26151 sloppy: true, white: true */ 26152/** 26153 * @fileOverview FPE floor plan controller 26154 */ 26155 26156var FloorController = FpeController.extend({ 26157 /** 26158 * Controller type 26159 * @type {String} 26160 * @static 26161 */ 26162 $type: 'floor', 26163 26164 /** 26165 * Initial rotation angle for selected components 26166 * @type {Number} 26167 */ 26168 _rotationAngle: 0, 26169 26170 /** 26171 * Drag start listener 26172 * @param {Event} e 26173 * @param {Object} visual 26174 */ 26175 dragStart: function(e, visual) { 26176 if (this._operation.type !== 'draw' && 26177 this._operation.type !== 'setscale') { 26178 this._super(e, visual); 26179 if (this._operation.type === 'drag' && 26180 visual._model.type === 'corner') { 26181 this._operation.data.pointsToExclude = 26182 $.map(visual._model.getWalls(), function(wall) { 26183 return wall.middle; 26184 }).concat([visual._model]); 26185 } 26186 } 26187 }, 26188 26189 /** 26190 * View registering function 26191 * @param {Object} view 26192 */ 26193 registerView: function(view) { 26194 this._super(view); 26195 var self = this; 26196
26197 // Initial support for component dropping outside of austin 26198 view.canvasElement 26199 .bind('dragover', function(e) { 26200 e.stop(); 26201 e.originalEvent.dataTransfer.dropEffect = 'copy'; 26202 self.canvasIn(false, e); 26203 }) 26204 .bind('drop', function(e) { 26205 e.stop(); 26206 26207 var info, component, 26208 data = e.originalEvent.dataTransfer.getData('URL') || 26209 e.originalEvent.dataTransfer.getData('Text'); 26210 if (data) { 26211 info = data.match(/([0-9a-z]{40})/i); 26212 if (info) { 26213 myDeco.App.ws.proxy.fetch('component', info[1], 26214 function(result) { 26215 component = result[info[1]]; 26216 if (component) { 26217 self._operation = { 26218 type: 'add', 26219 data: { 26220 entity: component, 26221 visual: $(), 26222 kind: 'furniture' 26223 } 26224 }; 26225 self.entityDrop(e); 26226 } 26227 }); 26228 } 26229 } 26230 }); 26231 26232 this.initDomElements(); 26233 26234 this._view._rotateArrow 26235 .mouseover(function() { 26236 self._view.cursor('default'); 26237 }) 26238 .mouseout(function() { 26239 self._view.cursor('move'); 26240 }) 26241 .mousedown(function(e) { 26242 self.rotateStart(e); 26243 }, true); 26244 26245 myDeco.body.bind('selectEnd unselectEnd', 26246 this.toggleSelection.bind(this)); 26247 }, 26248 26249 /** 26250 * Init various dom elements and attach observers 26251 */ 26252 initDomElements: function() { 26253 var self = this; 26254 26255 // panel remove button 26256 this.registerButtons(this._view.library.get('closeButton'), 26257 function(e) { 26258 if (e.isDefaultPrevented()) { 26259 return; 26260 } 26261 e.preventDefault(); 26262 self._model.remove(); 26263 }); 26264 26265 // panel info button 26266 this.registerButtons(this._view.library.get('infoButton'),
26267 function(e) { 26268 if (e.isDefaultPrevented()) { 26269 return; 26270 } 26271 e.preventDefault(); 26272 var entity = self._model.selected(true); 26273 if (entity && entity.componentData) { 26274 myDeco.body.trigger('showInfo', [entity]); 26275 } 26276 }); 26277 26278 // duplicate button 26279 this.registerButtons(this._view.library.get('duplicateButton'), 26280 function(e) { 26281 if (e.isDefaultPrevented()) { 26282 return; 26283 } 26284 e.preventDefault(); 26285 self._model.duplicate(); 26286 }); 26287 26288 // split button 26289 this.registerButtons(this._view.library.get('splitButton'), 26290 function(e) { 26291 if (e.isDefaultPrevented()) { 26292 return; 26293 } 26294 e.preventDefault(); 26295 var entity = self._model.selected(true); 26296 if (entity && entity.type === 'wall') { 26297 self._model.splitWall(entity); 26298 self._model.operationEnd(['add']); 26299 } 26300 }); 26301 26302 // draw button 26303 this.registerButtons(this._view.library.get('drawButton'), 26304 function(e) { 26305 if (e.isDefaultPrevented()) { 26306 return; 26307 } 26308 e.preventDefault(); 26309 var entity = self._model.selected(true); 26310 if (entity && entity.type === 'corner') { 26311 myDeco.body.trigger('preventOperationReset'); 26312 self.drawStart(e, entity); 26313 } 26314 }); 26315 }, 26316 26317 /** 26318 * Shorthand for registering rotatable elements 26319 * @param {Object} visual Visual element to register 26320 * @returns {Object} input visual element 26321 */ 26322 registerRotatables: function(visual) { 26323 var self = this; 26324 visual.getBase() 26325 .mouseover(function(e) { 26326 if (self._checkDecorAcceptance(visual) || 26327 self._operation.type !== 'add') { 26328 self.highlightStart(e, visual); 26329 } 26330 }) 26331 .mouseout(function(e) { 26332 if (self._checkDecorAcceptance(visual) || 26333 self._operation.type !== 'add') { 26334 self.highlightEnd(e, visual); 26335 } 26336 }); 26337 visual.element 26338 .mousedown(function(e) { 26339 self.dragStart(e, visual); 26340 }, true) 26341 .click(function(e) { 26342 e.preventDefault(); 26343 }); 26344 26345 return visual; 26346 }, 26347 26348 /** 26349 * Shorthand for registering button click listeners 26350 * @param {Object} button DOM element 26351 * @param {Function} clickCallback 26352 */ 26353 registerButtons: function(button, clickCallback) { 26354 var self = this; 26355 button 26356 .mouseover(function() { 26357 self._view.cursor('default'); 26358 }) 26359 .mouseout(function() { 26360 self._view.cursor('move'); 26361 }) 26362 // remove component callback 26363 .mousedown(clickCallback, true); 26364 }, 26365 26366 /** 26367 * Mousedown observer 26368 * @param {Event} e 26369 * @param {Object} visual Visual element 26370 */ 26371 mouseDown: function(e, visual) { 26372 var self = this, 26373 data, lastCorner, firstCorner, corner, corners, shapes, i, ilen, j, 26374 ceilingMaterial, floorMaterial, walls, walls1, wall, 26375 model = this._model, 26376 26377 /** 26378 * Function for check closed paths 26379 * @param {Array} wall 26380 * @returns {Array} array of shapes included given wall 26381 */ 26382 findShapes = function(path) { 26383 var shapes = [], 26384 lastPoint = path.get(-1), 26385 idx, newPath, points; 26386 26387 while (lastPoint) { 26388 idx = path.indexOf(lastPoint); 26389 if (idx === 0) { 26390 // remove last from array because it's identical to the 26391 // first one 26392 path.pop(); 26393 shapes.push(path); 26394 break; 26395 } else if (idx > 0 && idx < path.length - 1) { 26396 // closed with point from the path 26397 break; 26398 } 26399 26400 // get attached walls except current 26401 points = lastPoint.getCorners([path.get(-2)], false, true); 26402 26403 // fork searching in case of walls.length > 1 26404 while (points.length > 1) { 26405 newPath = path.slice(); 26406 newPath.push(points.shift()); 26407 shapes = shapes.concat(findShapes(newPath)); 26408 } 26409 26410 // continue for the first wall 26411 lastPoint = points.shift(); 26412 path.push(lastPoint); 26413 } 26414 26415 return shapes; 26416 }, 26417 grepWalls = function(point, j) { 26418 var wall = model._findWall(point, 26419 shapes[i][(j + 1) % shapes[i].length]); 26420 return wall ? [wall] : null; 26421 }; 26422 26423 if (e.isDefaultPrevented()) { 26424 return; 26425 } 26426 26427 if (this._operation.type === 'draw') { 26428 e.preventDefault(); 26429 lastCorner = this._operation.data.corners.get(-1); 26430 firstCorner = this._operation.data.corners[0]; 26431 26432 // ipad have no mouseevents, so we need to specify last point 26433 // in it exact position 26434 if (myDeco.browser.isMobileSafari) { 26435 this._operation.data.point.copy(this._view.toModelPosition(e.pageX, e.pageY)); 26436 } 26437 26438 data = this._model.findAllowedPoint( 26439 this._operation.data.point ? new Point( 26440 this._operation.data.point.x, 26441 this._operation.data.point.y) : this._view.toModelPosition(e.pageX, e.pageY), 26442 lastCorner, this._operation.data.n === 0); 26443 26444 // forbid adding a point in restricted area 26445 if (!data) { 26446 return; 26447 } 26448 26449 if (data.point && data.wall && !data.corner) { 26450 data.corner = this._model.splitWall(data.wall, data.point); 26451 } else if (data.point) { 26452 corner = this._model.addCorner(data.point); 26453 } 26454 26455 if (data.corner || corner) { 26456 this._operation.data.corners.push(corner || data.corner); 26457 this._model.select(this._operation.data.corners.get(-1)); 26458 26459 this._view._drawMeasurer.update(false); 26460 26461 // add wall 26462 if (lastCorner && lastCorner !== (corner || data.corner)) { 26463 wall = this._model.addWall([lastCorner, corner || data.corner]); 26464 26465 // show measurer for recently created wall
26466 $.each(this._view.measurers, function() { 26467 if ((this[0] === wall[0] && this[1] === wall[1]) || 26468 (this[0] === wall[1] && this[1] === wall[0])) { 26469 this.update(true); 26470 } 26471 }); 26472 } 26473 26474 // we have a last corner already defined on plan 26475 // stop drawing 26476 if (this._operation.data.n !== 0 && data.corner) { 26477 // detect and create shape only if we have a closed path, 26478 if (data.corner) { 26479 // find all present shapes starting from last wall in 26480 // reverse order 26481 corners = this._operation.data.corners.slice(); 26482 corners.reverse(); 26483 shapes = findShapes(corners.slice(0, 2)); 26484 26485 // sort shapes by area size, usage of shortest path is 26486 // incorrect there 26487 shapes.sort(function(a, b) { 26488 var a1 = Math.abs(findShapeArea(a)), 26489 b1 = Math.abs(findShapeArea(b)); 26490 26491 if (a1 < b1) { 26492 return -1; 26493 } else if (a1 > b1) { 26494 return 1; 26495 } else { 26496 return 0; 26497 } 26498 }); 26499 26500 // 2 is the maximum number of produced shapes 26501 shapes = shapes.slice(0, 2); 26502 26503 for (i = 0, ilen = shapes.length; i < ilen; i++) { 26504 // check if this shape is not intersected with other 26505 // new shape 26506 // if so that means this shape is combined from 26507 // new shape and old one, skip it 26508 // check only for second one 26509 if (i === 1) { 26510 // corner for the first shape is already defined 26511 // there! 26512 if (corner.inPoly(shapes[i])) { 26513 break; 26514 } 26515 } 26516 26517 // find point in the new shape to check for 26518 // intersections with other shapes 26519 corner = findPointInShape(shapes[i]); 26520 26521 // remove old orphaned shapes (under new one) 26522 for (j = model.shapes.length - 1; j >= 0; j--) { 26523 if (corner.inPoly(model.shapes[j])) { 26524 ceilingMaterial = 26525 $.cloneObject(model.shapes[j].ceilingMaterial); 26526 floorMaterial = 26527 $.cloneObject(model.shapes[j].floorMaterial); 26528 } 26529 } 26530 26531 // gather all walls available for given points 26532 walls = $.map(shapes[i], grepWalls); 26533 26534 // finally we add a new shape 26535 shapes[i] = model.addShape(shapes[i], walls, 26536 floorMaterial, ceilingMaterial); 26537 26538 // find shape which already have all walls from current 26539 // and remove it, updating current with material 26540 for (j = this._model.shapes.length - 1; j >= 0; j--) { 26541 walls1 = this._model.shapes[j].getWalls(); 26542 26543 if (this._model.shapes[j] !== shapes[i] && 26544 $.intersect(walls, walls1).length === walls1.length) { 26545 // set shape mateiral from old one 26546 this._model.assignMaterial(shapes[i], 26547 this._model.shapes[j].ceilingMaterial, 26548 'ceiling'); 26549 this._model.assignMaterial(shapes[i], 26550 this._model.shapes[j].floorMaterial, 26551 'floor'); 26552 26553 // remove it finally 26554 this._model.shapes[j].remove(); 26555 } 26556 } 26557 } 26558 } 26559 26560 this._model.operationEnd(['add']); 26561 if (this._view._drawLine && this._view._drawCorner) { 26562 this._view._drawLine.hide(); 26563 this._view._drawCorner.hide(); 26564 } 26565 } else { 26566 this._operation.data.n += 1; 26567 myDeco.body.trigger('preventOperationReset'); 26568 this.updateDrawLine(data.corner || corner); 26569 } 26570 26571 this._view.sortCloud(); 26572 } 26573 } else if (this._operation.type === 'setscale') { 26574 if (this._operation.data.points.length < 2) { 26575 myDeco.body.trigger('preventOperationReset'); 26576 this._operation.data.points 26577 .push(this._view.toModelPosition(e.pageX, e.pageY)); 26578 this._view.drawScalePoint(this._operation.data.points.get(-1)); 26579 26580 if (this._operation.data.points.length === 2) { 26581 // current distance between points 26582 data = this._operation.data.points[0] 26583 .distance(this._operation.data.points[1]); 26584 26585 myDeco.popin.open({ 26586 url: '#view-build-setscale-popup', 26587 afterOpen: function(div) { 26588 var form = div.find('form'), 26589 value = data,
26590 input = form.find('input:text').numberInput({ 26591 formatFunction: function() { 26592 return Array.prototype.slice.call(arguments).toUnits(); 26593 }, 26594 parseFunction: function(v) { 26595 return v.fromUnits().shift(); 26596 }, 26597 positive: true, 26598 increment: function(shift) { 26599 var a = myDeco.settings 26600 .UNITS_LIST[myDeco.settings.UNITS][+shift + 2] 26601 .fromViewUnits(); 26602 return a; 26603 }, 26604 keyPress: numberInputKeyPress, 26605 value: data 26606 }) 26607 .change(function() { 26608 value = $(this).data('value'); 26609 }); 26610 26611 myDeco.buttons.initSubmitButtons(form.find('input:submit')); 26612 form 26613 .find('.units').unitsSelector().end() 26614 .submit(function(e) { 26615 e.stop(); 26616 var input = $(e.target).find('input:text'); 26617 input.blur(); 26618 value = input.data('value'); 26619 if (value !== 0) { 26620 self._view._background.scale *= (value / data); 26621 self._view._grid.drawBackground(); 26622 } 26623 myDeco.popin.close(); 26624 }); 26625 26626 myDeco.body.bind('unitsChanged', function() { 26627 input.trigger('setValue'); 26628 }); 26629 } 26630 }); 26631 26632 // we don't want have this in history 26633 this._model.operationReset(); 26634 } 26635 } 26636 } else { 26637 this._super(e, visual); 26638 } 26639 }, 26640 26641 /** 26642 * Method for updating draw line after point placing 26643 * @param {Point} point 26644 */ 26645 updateDrawLine: function(point) { 26646 var line = this._view._drawLine, i; 26647 if (line) { 26648 line.hold(); 26649 for (i = 0; i < 2; i++) { 26650 line.pathPoints[i].dropCache(); 26651 line.pathPoints[i].x = point.x; 26652 line.pathPoints[i].y = -point.y; 26653 } 26654 line.render().show(); 26655 } 26656 }, 26657 26658 /** 26659 * Mouse move event listener 26660 * @param {Event} e Mousemove event 26661 */ 26662 mouseMove: function(e) { 26663 var visual = this._operation.data.visual, model, 26664 pos, angle, evenAngle, dx, dy, wall, data, tolerancy, 26665 lastCorner; 26666 switch (this._operation.type) { 26667 case 'rotate': 26668 tolerancy = this._model.settings.snappingRotationTolerancy; 26669 pos = this._view.toModelPosition(e.pageX, e.pageY); 26670 angle = -T.atan2( 26671 this._model._selectionCenter.y -pos.y, 26672 this._model._selectionCenter.x - pos.x 26673 ) + 90; 26674 26675 evenAngle = Math.abs(angle % 90); 26676 if (evenAngle < tolerancy || 90 - evenAngle < tolerancy) { 26677 angle = Math.round(angle / 90) * 90; 26678 } 26679 26680 this._model.rotate(angle - this._rotationAngle); 26681 this._rotationAngle = angle; 26682 this._view.updateRotationArrow(); 26683 break; 26684 26685 case 'drag': 26686 dx = (e.pageX - this._operation.data.x) / this._view.zoom; 26687 dy = -(e.pageY - this._operation.data.y) / this._view.zoom; 26688 26689 // parent is set only for one on-wall non-architectural 26690 // selected component 26691 if (this._operation.data.parent) { 26692 wall = this.findNearestWall(e); 26693 26694 if (wall) { 26695 if (wall !== this._operation.data.parent) { 26696 this._operation.data.parent.highlightOff(); 26697 this._operation.data.parent = wall; 26698 } 26699 26700 wall.highlightOn(); 26701 } 26702 } 26703 26704 // SITE-8241 26705 if (visual.type === 'Corner'/* || visual.type === 'Component'*/) { 26706 model = visual._model; 26707 switch (visual.type) { 26708 case 'Corner': 26709 data = this.findSnappingCoords( 26710 [new Point(model.x + dx, model.y + dy)], 26711 ['corner', 'wall'], 26712 this._operation.data.pointsToExclude 26713 ); 26714 break; 26715 26716 case 'Component': 26717 data = this.findSnappingCoords( 26718 [new Point(model.position.x + dx, model.position.y + dy)], 26719 ['component'], 26720 [model.position] 26721 ); 26722 break; 26723 } 26724 26725 dx += data.dx; 26726 dy += data.dy; 26727 26728 if (!data.dx) { 26729 this._operation.data.x = e.pageX; 26730 } 26731 if (!data.dy) { 26732 this._operation.data.y = e.pageY; 26733 } 26734 } else { 26735 this._operation.data.x = e.pageX; 26736 this._operation.data.y = e.pageY; 26737 } 26738 26739 this._model.move(dx, dy); 26740 this._view.updateRotationArrow(); 26741 break; 26742 26743 case 'draw': 26744 pos = this._view.toModelPosition(e.pageX, e.pageY); 26745 lastCorner = this._operation.data.corners.get(-1); 26746 26747 if (lastCorner) { 26748 if (e.shiftKey) { 26749 angle = pos.angleTo(lastCorner); 26750 evenAngle = Math.round(angle / 15) * 15; 26751 if ((angle > 45 && angle < 135) || 26752 (angle > 225 && angle < 315)) { 26753 pos.x = lastCorner.x + (pos.y - lastCorner.y) / T.tan(evenAngle); 26754 } else { 26755 pos.y = lastCorner.y + (pos.x - lastCorner.x) * T.tan(evenAngle); 26756 } 26757 } else if (!e.ctrlKey) { 26758 data = this.findSnappingCoords([pos], ['corner', 'wall']); 26759 pos.x += data.dx; 26760 pos.y += data.dy; 26761 } 26762 } 26763 26764 this._operation.data.point.copy(pos); 26765 data = this._model.findAllowedPoint(pos, lastCorner, 26766 this._operation.data.n === 0); 26767 26768 if (this._view._drawLine && this._view._drawCorner) { 26769 if (lastCorner) { 26770 if (this._view._drawMeasurer[0] !== lastCorner || 26771 this._view._drawMeasurer[1] !== this._operation.data.point) { 26772 this._view._drawMeasurer[0] = lastCorner; 26773 this._view._drawMeasurer[1] = this._operation.data.point; 26774 this._view._drawMeasurer.update(true); 26775 } else {
26776 this._view._drawMeasurer.update(); 26777 } 26778 } 26779 26780 this._view._drawLine.hold(); 26781 this._view._drawLine.pathPoints[1].dropCache(); 26782 this._view._drawLine.pathPoints[1].x = pos.x; 26783 this._view._drawLine.pathPoints[1].y = -pos.y; 26784 this._view._drawLine.render(); 26785 26786 if (data) { 26787 if (data.corner || data.wall) { 26788 pos = this._view._drawCorner.getBBox(); 26789 pos = { 26790 x: pos.x + pos.width / 2, 26791 y: pos.y + pos.height / 2 26792 }; 26793 this._view._drawCorner.translate( 26794 (data.corner || data.point).x - pos.x, 26795 -(data.corner || data.point).y - pos.y 26796 ).show(); 26797 } else { 26798 this._view._drawCorner.hide(); 26799 } 26800 26801 if (!this._operation.data.allowed) { 26802 this._view._drawLine.attr('stroke', '#000'); 26803 this._operation.data.allowed = true; 26804 } 26805 } else if (this._operation.data.allowed) { 26806 this._view._drawLine.attr('stroke', '#f00'); 26807 this._operation.data.allowed = false; 26808 } 26809 } 26810 26811 break; 26812 } 26813 26814 if (this._operation.type !== 'add') { 26815 this._super(e); 26816 } 26817 }, 26818 26819 /** 26820 * Mouse up button listener 26821 * @param {Event} e Mouseup event 26822 */ 26823 mouseUp: function(e) { 26824 if (this._operation.type !== 'draw' && 26825 this._operation.type !== 'setscale') { 26826 this._super(e); 26827 } 26828 }, 26829 26830 /** 26831 * Operation reset function 26832 */ 26833 operationReset: function() { 26834 if (this._operation.type === 'draw' && 26835 this._view._drawLine && 26836 this._view._drawCorner) { 26837 26838 this._view._drawMeasurer.update(false); 26839 this._view._drawLine.hide(); 26840 this._view._drawCorner.hide(); 26841 26842 $.each(this._view.measurers, function() { 26843 this.update(false); 26844 }); 26845 } else if (this._operation.type === 'setscale') { 26846 $.each(this._view._scalePoints, function() { 26847 this.remove(); 26848 }); 26849 this._view._scalePoints = []; 26850 } 26851 26852 this._view.cursor('move'); 26853 this._super(); 26854 }, 26855 26856 /** 26857 * Entity rotating observer 26858 * @param {Event} e Mousedown event 26859 */ 26860 rotateStart: function(e) { 26861 if (!e.isDefaultPrevented()) { 26862 e.preventDefault(); 26863 26864 var self = this, 26865 selected = this._model.selected(), 26866 multiple = selected.length !== 1; 26867 26868 if (!this._operation.type) { 26869 this._operation = { 26870 type: 'rotate', 26871 data: {} 26872 }; 26873 26874 $.each(selected, function() { 26875 this.operationStart(self._operation.type); 26876 }); 26877 } 26878 } 26879 }, 26880 26881 /** 26882 * Set Scale start function 26883 */ 26884 setscaleStart: function() { 26885 if (this._model._masking) { 26886 // if drawing requested switch view to floor 26887 if (!this.state) { 26888 myDeco.body.trigger('viewToggle', [this.$type]); 26889 } 26890 26891 this._view.cursor('crosshair'); 26892 26893 this._operation = { 26894 type: 'setscale', 26895 data: { 26896 points: [] 26897 } 26898 }; 26899 myDeco.body.trigger('status', [$('<span>' + gettext('Point two corners to define real distance between them and set the scale. Press ESC key to escape') + '</span>'), -1]); 26900 this._model.operationStart(this._operation.type); 26901 } 26902 }, 26903 26904 /** 26905 * Start draw function 26906 * @param {Event} e 26907 * @param {Corner} corner Starting corner 26908 */ 26909 drawStart: function(e, corner) { 26910 var text; 26911 26912 // if drawing requested switch view to floor 26913 if (!this.state) { 26914 myDeco.body.trigger('viewToggle', [this.$type]); 26915 } 26916 26917 this._operation = { 26918 type: 'draw', 26919 data: { 26920 n: 0, 26921 point: new Point(), 26922 corners: [] 26923 } 26924 }; 26925 26926 if (corner) { 26927 this._operation.data.corners.push(corner); 26928 this._operation.data.n++; 26929 this.updateDrawLine(corner); 26930 } 26931 26932 // this part can be common for both floor and iso views 26933 this._model.unselect(); 26934 26935 this._view.cursor('crosshair'); 26936 26937 // hide all components 26938 this._view._cloud.findByField('type', 'Component').batch('toggle', false); 26939 26940 // show message 26941 text = '<span class="info">' + gettext('Drawing mode enabled:') + '</span><ul>' + 26942 '<li>' + gettext('1. Draw your shape point by point. '); 26943 26944 if (this._operation.data.n !== 0) { 26945 text += gettext('Click to add a wall.'); 26946 } else { 26947 text += gettext('Click on the grid to start.'); 26948 } 26949 26950 text += '</li><li>' + gettext('2. Complete your drawing by closing your shape or '); 26951 if (myDeco.browser.isMobileSafari) { 26952 text += gettext('long tap (1s)'); 26953 } else { 26954 text += gettext('double clicking'); 26955 } 26956 text += '</li></ul>'; 26957 text = '<span>
26957' + text + '</span>'; 26958 myDeco.body.trigger('status', [$(text), -1]); 26959 26960 $('.draggable, #sidebar').fadeOut(); 26961 26962 this._model.operationStart(this._operation.type); 26963 }, 26964 26965 /** 26966 * Entity highlighting observer 26967 * @param {Event} e Mouseover event 26968 * @param {Object} visual Visual element to highlight 26969 */ 26970 highlightStart: function(e, visual) { 26971 if (this._operation.type === 'drag' || 26972 this._operation.type === 'rotate' || 26973 this._operation.type === 'draw') { 26974 return; 26975 } 26976 26977 // Hack to don't change cursor when dragging material over the wall 26978 if (this._operation.type === 'add') { 26979 this._view.disableCursors = true; 26980 } 26981 26982 this._super(e, visual); 26983 this._view.disableCursors = false; 26984 26985 // change acceptance icon 26986 if (this._checkDecorAcceptance(visual)) { 26987 this._operation.data.target = visual._model; 26988 this._operation.data.visual.addClass('yes').removeClass('no'); 26989 } 26990 }, 26991 26992 /** 26993 * Entity dishighliting observer 26994 * @param {Event} e Mouseout event 26995 * @param {Object} visual Visual element to dishighlight 26996 */ 26997 highlightEnd: function(e, visual) { 26998 this._super(e, visual); 26999 27000 // change acceptance icon 27001 if (this._checkDecorAcceptance(visual)) { 27002 delete this._operation.data.target; 27003 this._operation.data.visual.removeClass('yes').addClass('no'); 27004 } 27005 27006 if (this._operation.type !== 'draw' && 27007 $.inArray(this._operation.type, 27008 ['drag', 'rotate', 'add']) === -1) { 27009 this._view.cursor('move'); 27010 } 27011 }, 27012 27013 /** 27014 * default action on doubleclick: slipt wall under cursor 27015 * @param {Event} e 27016 * @param {Wall} visual 27017 */ 27018 doubleClick: function(e, visual) { 27019 if (!this._operation && visual && visual.type === 'Wall' && 27020 !this.isLocked(visual)) { 27021 this._model.splitWall(visual._model, 27022 this._view.toModelPosition(e.pageX, e.pageY)); 27023 this._model.operationEnd(['add']); 27024 } else if (this._operation && this._operation.type === 'draw') { 27025 this._model.operationReset(); 27026 } 27027 }, 27028 27029 toggleSelection: function(state) { 27030 var selected = this._model.selected(), 27031 multiple = selected.length > 1, 27032 camera = this._view._cloud.findByField('type', ['Camera']).shift(); 27033 27034 if (camera) { 27035 camera._decorate(multiple ? 'hidden' : 'normal'); 27036 } 27037 27038 if (this._model.rotationAvailable) { 27039 this._rotationAngle = multiple ? 0 : selected[0].getAngle(); 27040 this._view.updateRotationArrow(); 27041 this._view._rotateArrow.show(); 27042 } else { 27043 this._view._rotateArrow.hide(); 27044 } 27045 }, 27046 27047 operationEnd: function(data) { 27048 this._super(data); 27049 this._view.updateRotationArrow(); 27050 } 27051 27052}); 27053 27054myDeco.App.controllers.push(FloorController); 27055/*globals myDeco, $, Class, T, ObjectsLibrary, Draggable, Rotatable */ 27056/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 27057 sloppy: true, white: true */ 27058/** 27059 * @fileOverview Base class for drawable entities 27060 */ 27061var Drawable = Class.extend({ 27062 /** 27063 * Instance type 27064 * @type {String} 27065 */ 27066 type: 'Drawable', 27067 27068 /** 27069 * Highlighted flag 27070 * @type {Boolean} 27071 */ 27072 _highlighted: false, 27073 27074 /** 27075 * Operation flag 27076 * @type {Boolean} 27077 */ 27078 _operation: false, 27079 27080 /** 27081 * Controls panel flag 27082 * @type {Boolean} 27083 */ 27084 _panel: false, 27085 27086 /** 27087 * Is controls visible on plan 27088 * @type {Boolean} 27089 */ 27090 _controlsVisible: false, 27091 27092 /** 27093 * List of controls to show in control panel 27094 * @type {Object} 27095 */ 27096 controls: {}, 27097 27098 /** 27099 * View object 27100 * @type {FpeView} 27101 */ 27102 _view: false, 27103 27104 /** 27105 * Model object 27106 * @type {FpeModel} 27107 */ 27108 _model: false, 27109 27110 /** 27111 * Visibility state 27112 * @type {Boolean} 27113 */ 27114 state: true, 27115 27116 /** 27117 * Component type settings 27118 * @type {Object} 27119 */ 27120 _settings: {}, 27121 27122 /** 27123 * Init function 27124 * @param {Object} view FPE view 27125 */ 27126 init: function(view, model) { 27127 this._view = view; 27128 this._model = model; 27129 27130 this.settings(); 27131 27132 if (this._model) { 27133 this._model 27134 .attachEvent('onSelect', this.select.bind(this), 27135 this._view.$type) 27136 .attachEvent('onUnselect', this.unselect.bind(this), 27137 this._view.$type) 27138 .attachEvent('onRemove', this.erase.bind(this), 27139 this._view.$type) 27140 .attachEvent('onHightlightOn', this.highlightOn.bind(this), 27141 this._view.$type) 27142 .attachEvent('onHightlightOff', this.highlightOff.bind(this), 27143 this._view.$type) 27144 .attachEvent('onOperationStart', this.operationStart.bind(this), 27145 this._view.$type) 27146 .attachEvent('onOperationEnd', this.operationEnd.bind(this), 27147 this._view.$type) 27148 .attachEvent('onOperationReset', this.operationReset.bind(this), 27149 this._view.$type); 27150 } 27151 this.draw(); 27152 }, 27153 27154 /** 27155 * Instance drawing function 27156 */ 27157 draw: function() { 27158 if (this.element) { 27159 this.updateMaterial(); 27160 27161 if ($.keys(this.controls).length === 0) { 27162 return; 27163 } 27164 27165 // draw control panel 27166 var settings = this._settings.controlPanel || {}, 27167 w = settings.padding || 0, 27168 h = 0, 27169 element, bbox, control; 27170 27171 for (control in this.controls) { 27172 element = this._view.library.get((control === 'remove' ? 27173 'close' : control) + 'Button'); 27174 27175 // scale button back to original size 27176 bbox = element.scale(1, 1, 0, 0).getBBox(); 27177 27178 this.controls[control] = { 27179 x: w, 27180 y: settings.padding, 27181 element: element, 27182 target: 'panel' 27183 }; 27184 27185 // panel width 27186 w += settings.padding + bbox.width; 27187 // panel height 27188 h = Math.max(h, settings.padding * 2 + bbox.height); 27189 } 27190 27191 // create control panel 27192 this._panel = this._view.canvas.set() 27193 .push(this._view.canvas.path(settings.bg) 27194 .hold() 27195 .moveTo(-w/2.0 + settings.curveOffset, 0) 27196 .lineTo( w/2.0 - settings.curveOffset, 0) 27197 .arcTo(h/2.0, h/2.0, 0, 1, w/2.0 - settings.curveOffset, h) 27198 .lineTo(-w/2.0 + settings.curveOffset, h) 27199 .arcTo(h/2.0, h/2.0, 0, 1, -w/2.0 + settings.curveOffset, 0) 27200 .andClose() 27201 .render()); 27202 27203 this.element.push(this._panel.hide()); 27204 } 27205 }, 27206 27207 /** 27208 * Pan instance 27209 * @param {Number} dx DeltaX 27210 * @param {Number} dy DeltaY 27211 */ 27212 pan: function(dx, dy) { 27213 if (this.element) { 27214 this.element.translate(dx, dy); 27215 } 27216 }, 27217 27218 /** 27219 * Scale current instance 27220 * @param {Number} coeff Scaling factor 27221 */ 27222 scale: function() { 27223 this.toggleControls(); 27224 }, 27225 27226 /** 27227 * Erase drawing instance from canvas 27228 */ 27229 erase: function() { 27230 this.unselect(); 27231 27232 // remove from cloud 27233 if (this._view._cloud.remove(this).length !== 0) { 27234 // unlink events 27235 if (this._model) { 27236 this._model.dropEvents(this._view.$type); 27237 } 27238 this.element.remove(); 27239 } 27240 }, 27241 27242 /** 27243 * Toggle controls for entity 27244 * @param {Boolean} state [true||false||undefined] for show/hide/update 27245 * respectively 27246 */ 27247 toggleControls: function(state) { 27248 var name, control, ebb, pbb, zoom, angle,
27249 btn, btnbox; 27250 27251 // Do we have panel at all? 27252 if (this._panel) { 27253 // hide panel 27254 if (state === false && this._controlsVisible) { 27255 this._panel.hide(); 27256 27257 for (name in this.controls) { 27258 control = this.controls[name]; 27259 if (control.target === 'panel') { 27260 this._panel.pop(control.element); 27261 } else { 27262 this.element.pop(control.element); 27263 } 27264 } 27265 } 27266 27267 // do not want to reposition hidden element 27268 if (this._controlsVisible || state === true) { 27269 // reposition panel 27270 ebb = this.getBBox(); 27271 27272 zoom = 1 / this._view.zoom; 27273 angle = this._model.getAngle(); 27274 27275 this._panel.rotate(0, ebb.cx, ebb.cy); 27276 this._panel.scale(zoom, zoom, ebb.cx, ebb.cy); 27277 27278 pbb = this._panel.getBBox(); 27279 27280 this._panelPosition(ebb, pbb, angle, zoom).toFront(); 27281 } 27282 27283 // show panel 27284 if (state === true && !this._controlsVisible) { 27285 pbb = this._panel.getBBox(); 27286 pbb.width += this._settings.controlPanel.curveOffset * 2 * zoom; 27287 pbb.x -= this._settings.controlPanel.curveOffset * zoom; 27288 27289 for (name in this.controls) { 27290 btn = this.controls[name]; 27291 btnbox = btn.element.scale(zoom, zoom).getBBox(); 27292 27293 if (btn.target === 'panel') { 27294 this._panel.push(btn.element 27295 .translate(pbb.x + btn.x * zoom - btnbox.x, 27296 pbb.y + btn.y * zoom - btnbox.y).toFront()); 27297 } else { 27298 this.element.push(btn); 27299 } 27300 } 27301 27302 this._panel.show(); 27303 this._controlsVisible = true; 27304 } 27305 } 27306 27307 if (typeof state !== 'undefined') { 27308 this._controlsVisible = state; 27309 } 27310 }, 27311 27312 /** 27313 * Calculate panel position for visual 27314 * @param {Object} componentBBox 27315 * @param {Object} panelBBox 27316 * @param {Number} angle 27317 * @param {Number} zoom 27318 */ 27319 _panelPosition: function(componentBBox, panelBBox, angle, zoom) { 27320 return this._panel.translate(componentBBox.cx - panelBBox.width / 2.0 - 27321 panelBBox.x, componentBBox.cy - 27322 (Math.abs(componentBBox.width / 2.0 * T.sin(angle)) + 27323 Math.abs(componentBBox.height / 2.0 * T.cos(angle))) - 27324 this._settings.controlPanel.distance * zoom - panelBBox.y 27325 ); 27326 27327 }, 27328 27329 /** 27330 * Select current instance 27331 * @param {Array} selected List of currently selected entities 27332 * @returns {Object|Boolean===false} 27333 */ 27334 select: function(selected) { 27335 if (selected) { 27336 // hide controls on multiple selection 27337 if (selected.length > 1) { 27338 this._view._cloud 27339 .findByField('_model', selected) 27340 .batch('toggleControls', false); 27341 } else if (selected.length === 1) { 27342 this.toggleControls(true); 27343 } 27344 } 27345 27346 this._decorate('selected'); 27347 }, 27348 27349 /** 27350 * Unselect current instance 27351 * @param {Array} selected List of currently selected entities 27352 * @returns {Object|Boolean===false} 27353 */ 27354 unselect: function(selected) { 27355 if (selected) { 27356 // show panel for the last selected element 27357 if (selected.length === 1) { 27358 this._view._cloud 27359 .findByField('_model', selected) 27360 .batch('toggleControls', true); 27361 } 27362 } 27363 27364 this.toggleControls(false); 27365 27366 this._operation = false; 27367 this._decorate('normal'); 27368 }, 27369 27370 /** 27371 * Highlight current instance 27372 * @returns {Object|Boolean===false} 27373 */ 27374 highlightOn: function() { 27375 if (this._highlighted) { 27376 return false; 27377 } 27378 27379 this._decorate('highlighted'); 27380 this._highlighted = true; 27381 return this; 27382 }, 27383 27384 /** 27385 * Dishighlight current instance 27386 * @returns {Object|Boolean===false} 27387 */ 27388 highlightOff: function() { 27389 if (!this._highlighted) { 27390 return false; 27391 } 27392 27393 this._decorate(this._model.selected ? 'selected' : 'normal'); 27394 this._highlighted = false; 27395 return this; 27396 }, 27397 27398 /** 27399 * Return current highlighting state 27400 * @returns {Boolean} 27401 */ 27402 highlighted: function() { 27403 return this._highlighted; 27404 }, 27405 27406 /** 27407 * Set status to operating for current instance 27408 * @param {String} operation Operation type 27409 * @returns {Object|Boolean===false} 27410 */ 27411 operationStart: function(operation) { 27412 if (this._model.selected && !this._operation) { 27413 this._operation = operation; 27414 this._decorate('operation'); 27415 return this; 27416 } 27417 return false; 27418 }, 27419 27420 /** 27421 * Remove operation status 27422 * @returns {Object|Boolean===false} 27423 */ 27424 operationEnd: function() { 27425 if (this._operation) { 27426 this._operation = false;
27427 this._decorate(this._highlighted ? 'highlighted' : 'selected'); 27428 return this; 27429 } 27430 return false; 27431 }, 27432 27433 /** 27434 * Set status for current instance in case of operation reset 27435 * @returns {Object|Boolean===false} 27436 */ 27437 operationReset: function() { 27438 if (this._operation) { 27439 this._operation = false; 27440 this._decorate(this._highlighted ? 'highlighted' : 27441 (this._model.selected ? 'selected' : 'normal')); 27442 return this; 27443 } 27444 return false; 27445 }, 27446 27447 /** 27448 * Return current operation 27449 * @returns {String} 27450 */ 27451 operation: function() { 27452 return this._operation; 27453 }, 27454 27455 /** 27456 * Return element base 27457 * @returns {Object} 27458 */ 27459 getBase: function() { 27460 return this._base || this.element; 27461 }, 27462 27463 /** 27464 * Return bounding box for element 27465 * @returns {Object} 27466 */ 27467 getBBox: function() { 27468 var bb = this.getBase().getBBox(); 27469 bb.cx = bb.x + bb.width / 2.0; 27470 bb.cy = bb.y + bb.height / 2.0; 27471 return bb; 27472 }, 27473 27474 /** 27475 * Move instance 27476 * @param {Number} dx DeltaX 27477 * @param {Number} dy DeltaY 27478 * @returns {Object} this 27479 */ 27480 move: function(dx, dy) { 27481 this.element.translate(dx, -dy); 27482 this.updateRender(); 27483 return this; 27484 }, 27485 27486 /** 27487 * Rotate instance 27488 * @param {Number} angle Rotating angle in degrees 27489 * @returns {Object} this 27490 */ 27491 rotate: function(angle) { 27492 var bb = this.getBBox(); 27493 this.element.rotate(angle, bb.cx, bb.cy); 27494 this.toggleControls(); 27495 return this; 27496 }, 27497 27498 /** 27499 * Stub for update displaying of element material 27500 */ 27501 updateMaterial: function() { 27502 }, 27503 27504 /** 27505 * Detect event type settings depend on event type 27506 * @param {String} type Event type 27507 * @returns {Object} event settings 27508 */ 27509 _decorate: function(state) { 27510 if (!this._settings) { 27511 return false; 27512 } 27513 27514 state = state || 'normal'; 27515 // check for entity specific settings depend on z-level 27516 var settings = typeof this._model !== 'undefined' && 27517 typeof this._model.level !== 'undefined' && 27518 this._settings[this._model.level] ? 27519 this._settings[this._model.level] : 27520 this._settings; 27521 27522 return settings[state] || false; 27523 }, 27524 27525 /** 27526 * Update settings for current type 27527 */ 27528 settings: function() { 27529 this._settings = {}; 27530 $.extend(true, this._settings, this._view.settings.Drawable || {}); 27531 if (this instanceof Draggable) { 27532 $.extend(true, this._settings, this._view.settings.Draggable || {}); 27533 } 27534 if (this instanceof Rotatable) { 27535 $.extend(true, this._settings, this._view.settings.Rotatable || {}); 27536 } 27537 $.extend(true, this._settings, this._view.settings[this.type] || {}); 27538 }, 27539 27540 /** 27541 * Misc functions for render view update 27542 */ 27543 updateRender: function() { 27544 }, 27545 27546 /** 27547 * Toggle element visibility 27548 * @param {Boolean} state Visual state. If omitted visual changes to a 27549 * semitransparent state described in settings 27550 */ 27551 toggle: function(state) { 27552 this.state = typeof state === 'undefined' || state; 27553 if (this.state) { 27554 this.element 27555 .attr('opacity', typeof state === 'undefined' && 27556 this._settings.visual && 27557 typeof this._settings.visual.opacity !== 'undefined' ? 27558 this._settings.visual.opacity : 1) 27559 .show(); 27560 if (this._panel && this.state && !this._model.selected) { 27561 this._panel.hide(); 27562 } 27563 27564 this.updateMaterial(); 27565 } else { 27566 this.element.hide(); 27567 } 27568 return this.state; 27569 } 27570 27571}); 27572/*globals myDeco, $, Drawable */ 27573/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 27574 sloppy: true, white: true */ 27575/** 27576 * @fileOverview Base class for draggable instances 27577 */ 27578var Draggable = Drawable.extend({ 27579 /** 27580 * Instance type 27581 * @type {String} 27582 */ 27583 type: 'Draggable', 27584 27585 /** 27586 * Init function 27587 */ 27588 init: function(view, model) { 27589 this._super(view, model); 27590 this._model.attachEvent('onMove', this.move.bind(this), 27591 this._view.$type); 27592 }, 27593 27594 /** 27595 * Decorate draggable visual 27596 * @param {Boolean} state 27597 * @returns {Object} settings 27598 */ 27599 _decorate: function(state) { 27600 var settings = this._super(state); 27601 27602 if (this._panel) { 27603 switch (this.operation()) { 27604 case 'drag': 27605 case 'rotate': 27606 this._panel.hide(); 27607 break; 27608 default: 27609 if (this._controlsVisible) { 27610 // reposition panel 27611 this.toggleControls(true); 27612 // and show it finally 27613 this._panel.show(); 27614 } 27615 } 27616 } 27617 27618 return settings; 27619 } 27620}); 27621/*globals myDeco, $, Draggable */ 27622/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 27623 sloppy: true, white: true */ 27624/** 27625 * @fileOverview Base class for rotatable instances 27626 */ 27627var Rotatable = Draggable.extend({ 27628 /** 27629 * Instance type 27630 * @type {String} 27631 */ 27632 type: 'Rotatable', 27633 /** 27634 * init function 27635 * @param {Object} view FPE view 27636 * @param {Object} model Model entity 27637 */ 27638 init: function(view, model) { 27639 this._super(view, model); 27640 27641 model.attachEvent('onRotate', this.rotate.bind(this), this._view.$type); 27642 }, 27643 27644 /** 27645 * Drawing function 27646 */ 27647 draw: function() { 27648 this._super(); 27649 this.rotate(this._model.getAngle()); 27650 } 27651}); 27652/*globals myDeco, $, Drawable */ 27653/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 27654 sloppy: true, white: true */ 27655/** 27656 * @fileOverview Canvas grid class 27657 */ 27658var Grid = Drawable.extend({ 27659 /** 27660 * Intsance type 27661 * @type {String} 27662 */ 27663 type: 'Grid', 27664 27665 /** 27666 * Grid drawing function 27667 */ 27668 draw: function() { 27669 var view = this._view, 27670 self = this, 27671 settings, gridNo, grid, i, image; 27672 27673 if (this.element) { 27674 this.element.remove(); 27675 // image is reamoved here as it part of element 27676 this.image = false;
27677 this.element = false; 27678 } 27679 27680 this.element = view.canvas.set(); 27681 27682 for (gridNo = 0; gridNo < 2; gridNo++) { 27683 settings = this._settings[gridNo]; 27684 grid = view.canvas.path({ 27685 stroke: settings.strokeColor, 27686 'stroke-width': settings.strokeWidth 27687 }).hold(); 27688 27689 // draw both vertical & horizontal lines 27690 for (i = -view.bounds; i <= view.bounds; i = i + settings.step) { 27691 grid.moveTo(-view.bounds + settings.offset, 27692 i + settings.offset); 27693 grid.lineTo(view.bounds + settings.offset, i + settings.offset); 27694 27695 grid.moveTo(i + settings.offset, -view.bounds + 27696 settings.offset); 27697 grid.lineTo(i + settings.offset, view.bounds + settings.offset); 27698 } 27699 27700 this.element.push(grid.render()); 27701 } 27702 27703 this._super(); 27704 }, 27705 27706 /** 27707 * Draw masking background, references to view data 27708 */ 27709 drawBackground: function() { 27710 var bg = this._view._background; 27711 27712 if (bg.src) { 27713 if (!this.image) { 27714 this.image = this._view.canvas 27715 .image(bg.src, bg.x, bg.y, bg.width * bg.scale, bg.height * bg.scale); 27716 this.element.push(this.image); 27717 } else { 27718 this.image.attr({ 27719 x: bg.x, 27720 y: bg.y, 27721 width: bg.width * bg.scale, 27722 height: bg.height * bg.scale 27723 }); 27724 } 27725 this.image.toBack(); 27726 } 27727 } 27728}); 27729/*globals myDeco, $, Drawable, location, diffuse2RGB, textureFill */ 27730/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 27731 sloppy: true, white: true */ 27732/** 27733 * @fileOverview Base class for all shape-based objects (floor, ceiling etc) 27734 */ 27735var Shape = Drawable.extend({ 27736 /** 27737 * Instance type 27738 * @type {String} 27739 */ 27740 type: 'Shape', 27741 27742 /** 27743 * Measurer holder 27744 * @type {Object} 27745 */ 27746 measurer: null, 27747 27748 /** 27749 * Init function 27750 * @param {FpeView} view FPE view 27751 * @param {FpeModel} model Model entity 27752 */ 27753 init: function(view, model) { 27754 this._super(view, model); 27755 27756 var i, len = this._model.length, 27757 self = this; 27758 27759 this._cornerMoveFunc = function(dx, dy) { 27760 self.cornerMove(this, dx, dy); 27761 }; 27762 27763 this._model.attachEvent('onMaterial', function() { 27764 self.updateMaterial(); 27765 self._decorate(); 27766 }, this._view.$type); 27767 27768 // attach listeners on corners moving 27769 for (i = 0; i < len; i++) { 27770 this._model[i].attachEvent('onMove', this._cornerMoveFunc, 27771 this._view.$type); 27772 } 27773 27774 this._model.attachEvent('onCornerAdd', function(corner) { 27775 corner.attachEvent('onMove', self._cornerMoveFunc, 27776 self._view.$type); 27777 self.element.clear(); 27778 self.draw(); 27779 }, this._view.$type); 27780 }, 27781 27782 /** 27783 * Drawing function 27784 */ 27785 draw: function() { 27786 if (!this.element) { 27787 this.element = this._view.canvas.path({ 27788 'stroke-width': 0, 27789 'stroke-opacity': 0, 27790 'fill-opacity': this._model._model._masking ? 0.5 : 1 27791 }).hold(); 27792 } 27793 this.element.poly(this._model).render(); 27794 this._super(); 27795 }, 27796 27797 /** 27798 * Change drawing on corner moving 27799 * @param {Object} point Point to move 27800 * @param {Number} dx DeltaX 27801 * @param {Number} dy DeltaY 27802 */ 27803 cornerMove: function(point, dx, dy) { 27804 point = this._model.indexOf(point); 27805 if (point === -1) { 27806 return; 27807 } 27808 27809 this.element.pathPoints[point].dropCache(); 27810 this.element.pathPoints[point].x += dx; 27811 this.element.pathPoints[point].y += -dy; 27812 this.element.render(); 27813 }, 27814 27815 /** 27816 * Shape material update 27817 * @param {String} type ceiling or floor 27818 */ 27819 updateMaterial: function(type) { 27820 if (type !== 'ceiling') { 27821 var floormat = this._model.floorMaterial; 27822 var scaling = $.extend(true, {}, 27823 this._view.settings.Shape.options, 27824 {width: floormat.texture_width, height: floormat.texture_height} 27825 ); 27826 delete scaling.percent; 27827 this.element.attr({fill: textureFill(floormat, scaling)}); 27828 } 27829 }, 27830 27831 /** 27832 * End operation callback 27833 * @param {String} operation 27834 */ 27835 operationEnd: function(operation) { 27836 this._super(operation); 27837 this.element.pathPoints._updateCoords(); 27838 }, 27839 27840 erase: function() { 27841 var i, len = this._model.length; 27842 for (i = 0; i < len; i++) { 27843 this._model[i].detachEvent('onMove', this._cornerMoveFunc, 27844 this._view.$type); 27845 } 27846 27847 this._super(); 27848 } 27849}); 27850/*globals myDeco, $, Draggable */ 27851/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 27852 sloppy: true, white: true */ 27853/** 27854 * @fileOverview Corner class 27855 */ 27856var Corner = Draggable.extend({ 27857 /** 27858 * Instance type 27859 * @type {String} 27860 */ 27861 type: 'Corner', 27862 27863 controls: { 27864 draw: false 27865 }, 27866 /** 27867 * Drawing function 27868 */ 27869 draw: function() { 27870 this._base = this._view.canvas.circle(this._model.x, - this._model.y,
27871 this._view.settings.Corner.radius) 27872 .attr(this._view.settings.Corner.normal.attr).toFront(); 27873 this.element = this._view.canvas.set([this._base]); 27874 27875 this._super(); 27876 }, 27877 27878 /** 27879 * Decorate corner 27880 * @param {String} settings Event type 27881 * @returns {Object} Event settings 27882 */ 27883 _decorate: function(settings) { 27884 if (settings === 'highlighted') { 27885 this.element.toFront(); 27886 } 27887 27888 settings = this._super(settings); 27889 this._base.attr(settings.attr); 27890 return settings; 27891 }, 27892 27893 /** 27894 * Toggle controls for corners 27895 * @param {Boolean} state 27896 */ 27897 toggleControls: function(state) { 27898 if (this._view._controller._operation && 27899 this._view._controller._operation.type === 'draw') { 27900 state = false; 27901 } 27902 27903 this._super(state); 27904 } 27905}); 27906/*globals myDeco, $, Draggable, T, diffuse2RGB, Point, window */ 27907/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 27908 sloppy: true, white: true */ 27909/** 27910 * @fileOverview Wall instance class 27911 */ 27912var Wall = Draggable.extend({ 27913 /** 27914 * Instance type 27915 * @type {String} 27916 */ 27917 type: 'Wall', 27918 27919 /** 27920 * Measurer 27921 * @type {Object} 27922 */ 27923 measurer: null, 27924 27925 /** 27926 * Used controls 27927 * @type {Object} 27928 */ 27929 controls: { 27930 remove: false, 27931 split: false 27932 }, 27933 27934 /** 27935 * Init function 27936 * @param {Object} view FPE view 27937 * @param {Object} model Model entity 27938 */ 27939 init: function(view, model) { 27940 var i, self = this; 27941 27942 this._super(view, model); 27943 27944 this._cornerMoveListener = function(dx, dy) { 27945 self.cornerMove(this, dx, dy); 27946 }; 27947 27948 this._updateMeasurerListener = this.updateMeasurer.bind(this); 27949 27950 this._model.attachEvent('onMaterial', function() { 27951 self.updateMaterial(); 27952 self._decorate(); 27953 }, this._view.$type); 27954 27955 this._model.attachEvent('onThickness', function() { 27956 self.updateEdges(); 27957 for (i = 0; i < 2; i++) { 27958 self.updateNeighborEdges(self._model[i]); 27959 } 27960 }, this._view.$type); 27961 27962 this.updateEdges(); 27963 27964 // attach events to wall corners and update neighbor walls 27965 for (i = 0; i < 2; i++) { 27966 this.updateNeighborEdges(this._model[i]); 27967 this._model[i].attachEvent('onMove', this._cornerMoveListener, 27968 this._view.$type); 27969 } 27970 27971 this.measurer = this._view.registerMeasurer( 27972 this._model.getCorners(this._model.getShapes()[0]), 27973 this._model.thickness 27974 ); 27975 27976 // Can't use bound function here because fireEvent passes parameter, 27977 // and unitsChanged don't. 27978 myDeco.body.bind('unitsChanged', function() {
27979 self.measurer.update(); 27980 }); 27981 27982 this._model.attachEvent('onUpdateMeasurer', this._updateMeasurerListener); 27983 }, 27984 27985 /** 27986 * Instance draw function 27987 */ 27988 draw: function() { 27989 // update wall decoration depend on entity data 27990 this.updateMaterial(); 27991 27992 var attr = $.extend({}, this._settings.normal, { 27993 'stroke-width': 0.5, 27994 'fill': this._fill 27995 }); 27996 27997 this._base = this._view.canvas.path(attr).hold() 27998 .poly(this._model.getJoints()).render(); 27999 this.element = this._view.canvas.set([this._base]); 28000 this._super(); 28001 28002 this._decorate('normal'); 28003 }, 28004 28005 /** 28006 * Move wall function 28007 * @param {Number} dx DeltaX 28008 * @param {Number} dy DeltaY 28009 * @returns {Object} this object 28010 */ 28011 move: function() { 28012 this.toggleControls(); 28013 return this; 28014 }, 28015 28016 /** 28017 * Corner move function, recalculate self points and trigger to recalculate 28018 * points of neighbor walls 28019 * @param {Object} point Draggable point 28020 * @param {Number} dx DeltaX 28021 * @param {Number} dy DeltaY 28022 */ 28023 cornerMove: function(point) { 28024 this.updateEdges(); 28025 this.updateNeighborEdges(point);
28026 this.measurer.update(); 28027 }, 28028 28029 /** 28030 * Function for update already drawn points 28031 */ 28032 updateEdges: function() { 28033 this._base.hold().update(this._model.getJoints()).render(); 28034 }, 28035 28036 /** 28037 * Update edges for neighbor walls 28038 * @param {Corner} point Opposite point 28039 */ 28040 updateNeighborEdges: function(point) { 28041 // get opposite point to current one 28042 var p = this._model.getOpposite(point); 28043 // update attached points on the attached walls 28044 this._view._cloud.findByField('_model', p.getWalls(this._model)) 28045 .batch('updateEdges', p); 28046 }, 28047 28048 /** 28049 * Update already defined measurer 28050 * @param {Boolean} state 28051 */ 28052 updateMeasurer: function(state) { 28053 if (this._view._controller._operation && 28054 this._view._controller._operation.type === 'draw' && 28055 state === false) { 28056 return; 28057 } 28058 28059 this.measurer.update(state); 28060 }, 28061 28062 /** 28063 * Update wall material display 28064 */ 28065 updateMaterial: function() { 28066 this._fill = this._settings.normal.fill || 28067 'rgb(' + diffuse2RGB(this._model.material.average_colour || 28068 this._model.material.diffuse || [0.5, 0.5, 0.5]) + ')'; 28069 }, 28070 28071 /** 28072 * Decorate wall depending on event type 28073 * @param {String} type Event type 28074 * @returns {Object} Decorate settings for current event type 28075 */ 28076 _decorate: function(type) { 28077 var settings = this._super(type); 28078 if (settings) { 28079 // Wall top should be drawn with the color wall surface 28080 // has. But there is one exeption from this rule: if wall 28081 // color is default, we draw it in dark grey instead of 28082 // white. 28083 var fill = this._fill; 28084 if (this._model && this._model.material && this._model.material.ID == 'materialWallDefault') fill = "#222"; 28085 this._base.attr($.extend({'fill': fill}, settings)); 28086 } 28087 28088 return settings; 28089 }, 28090 28091 /** 28092 * Scale wall 28093 * @param {Number} scale Scale factor 28094 */ 28095 scale: function(scale) { 28096 this._super(scale);
28097 this.measurer.update(); 28098 }, 28099 28100 /** 28101 * Erase current wall 28102 */ 28103 erase: function() { 28104 this._view.deregisterMeasurer(this.measurer); 28105 this._model[0].detachEvent('onMove', this._cornerMoveListener, 28106 this._view.$type); 28107 this._model[1].detachEvent('onMove', this._cornerMoveListener, 28108 this._view.$type); 28109 this._model.detachEvent('onUpdateMeasurer', this._updateMeasurerListener); 28110 this._super(); 28111 }, 28112 28113 /** 28114 * Calculate panel position for wall 28115 * @param {Object} componentBBox 28116 * @param {Object} panelBBox 28117 * @param {Number} angle 28118 * @param {Number} zoom 28119 */ 28120 _panelPosition: function(componentBBox, panelBBox, angle, zoom) { 28121 return this._panel.translate(componentBBox.cx - panelBBox.width / 2.0 - 28122 panelBBox.x, componentBBox.cy - panelBBox.height / 2.0 - 28123 panelBBox.y - this._settings.controlPanel.distance * zoom 28124 ); 28125 } 28126}); 28127/*globals myDeco, $, Rotatable, buildThumbnailLink, T */ 28128/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 28129 sloppy: true, white: true */ 28130/** 28131 * @fileOverview Class for components 28132 */ 28133var Component = Rotatable.extend({ 28134 /** 28135 * Instance type 28136 * @type {String}} 28137 */ 28138 type: 'Component', 28139 28140 /** 28141 * Available controls for instance 28142 * @type {Object} 28143 */ 28144 controls: { 28145 remove: false, 28146 duplicate: false 28147 }, 28148 28149 /** 28150 * Draw instance 28151 */ 28152 draw: function() { 28153 var src = buildThumbnailLink(this._model.componentData['3d'][this._model.x0yRender], 28154 this._view.settings.furniture.componentThumbnail), 28155 minSize = this._settings.minsize, 28156 width = Math.max(this._model.width, minSize), 28157 depth = Math.max(this._model.depth, minSize), 28158 x0 = this._model.position.x - width / 2, 28159 y0 = -this._model.position.y - depth / 2; 28160 28161 this.element = this._view.canvas.set(); 28162 this._base = this._view.canvas.image(src, x0, y0, width, depth); 28163 this._outline = this._view.canvas.rect(x0, y0, width, depth).toBack(); 28164 28165 this.element.push(this._base, this._outline); 28166 28167 this.updateRender(); 28168 this._decorate(); 28169 this._super(); 28170 }, 28171 28172 /** 28173 * Update render view on canvas 28174 */ 28175 updateRender: function() { 28176 if (this._model.parent) { 28177 var bbox = this.getBBox(), 28178 dx = this._model.position.x - bbox.cx, 28179 dy = this._model.position.y + bbox.cy, 28180 angle = this._model.getAngle(), 28181 th = this._model.parent.thickness / 2 + 28182 this._model.shift.dy; 28183 28184 this.element.translate(th * T.sin(angle) + dx, 28185 -th * T.cos(angle) - dy); 28186 this.toggleControls(); 28187 } 28188 }, 28189 28190 /** 28191 * Decorate component depend on event type 28192 * @param {String} type Event type 28193 * @returns {Object} Event settings 28194 */ 28195 _decorate: function(type) { 28196 var settings = this._super(type); 28197 28198 if (this._settings.hideOutline) { 28199 switch (this.operation()) { 28200 case 'drag': 28201 case 'rotate': 28202 this._outline.hide(); 28203 break; 28204 default: 28205 this._outline.show(); 28206 } 28207 } 28208 28209 if (settings.outline) { 28210 this._outline.attr(settings.outline).insertAfter(this._base); 28211 } 28212 28213 return settings; 28214 } 28215}); 28216/*globals myDeco, $, Draggable, T, Point */ 28217/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 28218 sloppy: true, white: true */ 28219/** 28220 * @fileOverview Base class for all draggable items 28221 */ 28222var Window = Draggable.extend({ 28223 /** 28224 * Instance type 28225 * @type {String} 28226 */ 28227 type: 'Window', 28228 28229 /** 28230 * Used controls 28231 * @type {Object} 28232 */ 28233 controls: { 28234 remove: false, 28235 duplicate: false, 28236 info: false 28237 }, 28238 28239 /** 28240 * Init function 28241 * @param {Object} view FPE view 28242 * @param {Object} model Model entity 28243 */ 28244 init: function(view, model) { 28245 this._super(view, model); 28246 this._model 28247 .attachEvent('onRotate', this.rotate.bind(this), this._view.$type) 28248 .attachEvent('onAttributeChange', this.draw.bind(this), this._view.$type); 28249 }, 28250 28251 /** 28252 * Draw function for window instance 28253 */ 28254 draw: function() { 28255 var position = this._model.position, 28256 attr = this._settings.normal, 28257 median = this._model.parent.thickness / 3, 28258 width = this._model.width / 2, 28259 thickness = this._model.parent.thickness / 2; 28260 28261 if (!this.element) { 28262 this._rect = this._view.canvas 28263 .path($.extend({}, attr, {'stroke-width': 0.5})) 28264 .poly([ 28265 new Point( width, thickness), 28266 new Point(-width, thickness), 28267 new Point(-width, -thickness), 28268 new Point( width, -thickness) 28269 ]); 28270 this._median = this._view.canvas.path() 28271 .attr($.extend({}, attr, {'stroke-width': median})).hold() 28272 .moveTo(-width, 0) 28273 .lineTo( width, 0).render(); 28274 this._base = this._view.canvas.set([this._rect, this._median]); 28275 this.element = this._view.canvas.set([this._base]) 28276 .translate(this._model.position.x, -this._model.position.y) 28277 .rotate(this._model.getAngle()); 28278 this._super(); 28279 } else { 28280 this._rect.hold().update([ 28281 new Point(position.x + width, position.y + thickness), 28282 new Point(position.x - width, position.y + thickness), 28283 new Point(position.x - width, position.y - thickness), 28284 new Point(position.x + width, position.y - thickness) 28285 ]).render(); 28286 this._median.hold().attr('stroke-width', median).update([ 28287 new Point(position.x - width, position.y), 28288 new Point(position.x + width, position.y) 28289 ]).render(); 28290 } 28291 }, 28292 28293 /** 28294 * Decorate function 28295 * @param {String} type Event type 28296 * @returns {Object} Event settings 28297 */ 28298 _decorate: function(type) { 28299 var settings = this._super(type); 28300 if (settings) { 28301 this._base.attr(settings); 28302 } 28303 return settings; 28304 } 28305}); 28306/*globals myDeco, $, Draggable, T, Point, xor */ 28307/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 28308 sloppy: true, white: true */ 28309/** 28310 * @fileOverview Door class 28311 */ 28312var Door = Draggable.extend({ 28313 /** 28314 * Instance type 28315 * @type {String} 28316 */ 28317 type: 'Door', 28318 28319 /** 28320 * Used controls 28321 * @type {Object} 28322 */ 28323 controls: { 28324 remove: false, 28325 duplicate: false, 28326 info: false 28327 }, 28328 28329 /** 28330 * Drawing function 28331 * @param {Object} view FPE view 28332 * @param {Object} model Model entity 28333 */ 28334 init: function(view, model) { 28335 this._super(view, model); 28336 var self = this; 28337
28338 this._model 28339 .attachEvent('onRotate', this.rotate.bind(this), this._view.$type) 28340 .attachEvent('onAttributeChange', function(name) { 28341 switch (name) { 28342 case 'width': 28343 case 'height': 28344 case 'depth': 28345 self.draw(); 28346 break; 28347 case 'open_side': 28348 case 'open_direction': 28349 self.drawHinge(); 28350 break; 28351 } 28352 }, this._view.$type); 28353 28354 }, 28355 28356 /** 28357 * Drawing function 28358 */ 28359 draw: function() { 28360 var position = this._model.position, 28361 attr = this._settings.normal, 28362 width = this._model.width / 2, 28363 thickness = this._model.parent.thickness / 2; 28364 28365 if (!this.element) { 28366 this._rect = this._view.canvas 28367 .path($.extend({}, attr, {'stroke-width': 0.5})) 28368 .poly([ 28369 new Point( width, thickness), 28370 new Point(-width, thickness), 28371 new Point(-width, -thickness), 28372 new Point( width, -thickness) 28373 ]); 28374 this._base = this._view.canvas.set([this._rect]); 28375 this.element = this._view.canvas.set([this._base]) 28376 .translate(this._model.position.x, -this._model.position.y) 28377 .rotate(this._model.getAngle()); 28378 this._super(); 28379 } else { 28380 this._rect.hold().update([ 28381 new Point(position.x + width, position.y + thickness), 28382 new Point(position.x - width, position.y + thickness), 28383 new Point(position.x - width, position.y - thickness), 28384 new Point(position.x + width, position.y - thickness) 28385 ]).render(); 28386 } 28387 28388 this.drawHinge(); 28389 }, 28390 28391 /** 28392 * Draw hinge for door 28393 */ 28394 drawHinge: function() { 28395 if (!this._model.open_side) { 28396 return; 28397 } 28398 28399 if (this._hinge) { 28400 this.element.pop(this._hinge); 28401 this._hinge.remove(); 28402 } 28403 28404 if (this._clearance) { 28405 this.element.pop(this._clearance); 28406 this._clearance.remove(); 28407 } 28408 28409 var settings = this._settings, 28410 hingeAngle = settings.openedAngle, 28411 thickness = this._model.parent.thickness, 28412 th = thickness / 2, 28413 // base points 28414 angle = this._model.getAngle(), 28415 x0 = this._model.position.x - this._model.width / 2 - 28416 th * T.sin(angle), 28417 y0 = -this._model.position.y - th + th * T.cos(angle), 28418 bbox = this.getBBox(), 28419 side_correction, direction_correction, 28420 hingeX0, hingeY0, hingeX1, hingeY1, hingeX2, dx, dy; 28421 28422 // door with one hingle 28423 if (this._model.open_side === 'l' || this._model.open_side === 'r') { 28424 side_correction = +(this._model.open_side === 'l'); 28425 direction_correction = +(this._model.open_direction === 'o'); 28426 28427 hingeX0 = x0 + this._model.width * side_correction; 28428 hingeY0 = y0 + direction_correction * thickness; 28429 hingeX1 = hingeX0 - (side_correction || -1) * 28430 this._model.width * T.cos(hingeAngle); 28431 hingeY1 = hingeY0 + (direction_correction || -1) * 28432 this._model.width * T.sin(hingeAngle); 28433 hingeX2 = x0 + this._model.width * +(!side_correction); 28434 28435 this._hinge = this._view.canvas.path(settings.normal) 28436 .moveTo(hingeX0, hingeY0) 28437 .lineTo(hingeX1, hingeY1); 28438 28439 this._clearance = this._view.canvas.path(settings.clearance) 28440 .moveTo(hingeX0, hingeY0) 28441 .lineTo(hingeX1, hingeY1) 28442 .arcTo(this._model.width, this._model.width, 28443 0, +!xor(direction_correction, side_correction), 28444 hingeX2, hingeY0) 28445 .andClose() 28446 .translate(th * T.sin(angle), -th * T.cos(angle)) 28447 .rotate(angle, bbox.cx, bbox.cy); 28448 28449 this.element.push(this._clearance); 28450 } else if (this._model.open_side === 'b') { 28451 // door with two hingles 28452 hingeAngle *= 0.5; 28453 dx = this._model.width * T.cos(hingeAngle) * 0.5; 28454 dy = this._model.width * T.sin(hingeAngle) * 0.5; 28455 28456 y0 += thickness; 28457 28458 this._hinge = this._view.canvas.path(settings.normal) 28459 .moveTo(x0, y0) 28460 .lineTo(x0 + dx, y0 + dy) 28461 .moveTo(x0 + this._model.width, y0) 28462 .lineTo(x0 + this._model.width - dx, y0 + dy); 28463 } 28464 28465 this._hinge.translate( 28466 th * T.sin(angle), 28467 -th * T.cos(angle)); 28468 28469 this._hinge.rotate(angle, bbox.cx, bbox.cy); 28470 28471 this.element.push(this._hinge); 28472 }, 28473 28474 /** 28475 * Decorate door 28476 * @param {String} type Event type 28477 * @returns {Object} Event settings 28478 */ 28479 _decorate: function(type) { 28480 var settings = this._super(type); 28481 if (settings) { 28482 this._base.attr(settings); 28483 28484 if (this._hinge) { 28485 this._hinge.attr(settings); 28486 } 28487 } 28488 return settings; 28489 } 28490}); 28491/*globals myDeco, $, Rotatable */ 28492/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 28493 sloppy: true, white: true */ 28494/** 28495 * @fileOverview Camera class 28496 */ 28497var Camera = Rotatable.extend({ 28498 /** 28499 * Instance type 28500 * @type {String} 28501 */ 28502 type: 'Camera', 28503 28504 init: function(view, model) { 28505 this._super(view, model); 28506 model.attachEvent('onFov', this.update_cone.bind(this), this._view.$type); 28507 }, 28508 28509 /** 28510 * Drawing function 28511 */ 28512 draw: function() { 28513 this.element = this._view.library.get('camera', true) 28514 .translate(this._model.getCX(), -this._model.getCY()); 28515 this._hover = this.element[0]; 28516 this._base = this.element[1]; 28517 this._bbase = this.element[2][5]; 28518 this._lens = this.element[2][1]; 28519 this._speck = this.element[2][2]; 28520 28521 this.update_cone(); 28522 this._hover.hide(); 28523 28524 this._super(); 28525 }, 28526 28527 update_cone: function() { 28528 var fov = this._model.fieldofview, 28529 hover = this._view.library.camera_cone(fov), 28530 bb = this.getBBox(); 28531 hover.translate(this._model.getCX(), -this._model.getCY()); 28532 hover.rotate(this._model.getAngle(), bb.cx, bb.cy); 28533 28534 var idx = this.element.items.indexOf(this._hover); 28535 this._hover.remove(); 28536 this.element.items[idx] = hover; 28537 hover.insertBefore(this._lens); 28538 this._hover = hover; 28539 }, 28540 28541 /** 28542 * Decorate camera 28543 * @param {String} type Event type 28544 * @returns {Object} Event settings 28545 */ 28546 _decorate: function(type) { 28547 var settings = this._super(type); 28548 28549 if (type === 'hidden') { 28550 this.element.hide(); 28551 } else { 28552 this.element.show(); 28553 if (type !== 'normal') { 28554 this._hover.show(); 28555 } else { 28556 this._hover.hide(); 28557 } 28558 28559 if (!(type === 'highlighted' && this._model.selected) && settings) { 28560 this._bbase.attr(settings.base); 28561 this._lens.attr(settings.lens); 28562 this._speck.attr(settings.speck); 28563 } 28564 28565 switch (this.operation()) { 28566 case 'drag': 28567 this._hover.hide(); 28568 break; 28569 case 'rotate': 28570 this._hover.hide(); 28571 break; 28572 default: 28573 if (this._controlsVisible) { 28574 this._hover.show(); 28575 } 28576 } 28577 } 28578 28579 return settings; 28580 } 28581}); 28582/*globals myDeco, $, Class */ 28583/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 28584 sloppy: true, white: true */ 28585/**
28586 * @fileOverview FPE competiton support plugin 28587 */ 28588 28589var FpePlugin = Class.extend({ 28590 /** 28591 * Default plugin settings 28592 * @type {Object} 28593 */ 28594 _settings: { 28595 // plugin doesn't block view autostart 28596 autostart: true 28597 }, 28598 28599 /** 28600 * Associated view 28601 * @type {FpeView} 28602 */ 28603 _view: false, 28604 28605 /** 28606 * Plugin init method 28607 * @param {FpeView} view Associated view 28608 * @param {Object} settings Plugin settings 28609 */ 28610 init: function(view, settings) { 28611 this._view = view; 28612 this._settings = $.extend(true, this._settings, settings || {}); 28613 28614 // merge view autostart view plugin settings 28615 this._view.autostart = this._view.autostart && this._settings.autostart !== false; 28616 28617 // bind runPlugin event with run method 28618 myDeco.body.bind('runPlugins', this.run.bind(this)); 28619 }, 28620 28621 /** 28622 * Run plugin method, will run before finishing of view starting 28623 * @param {Event} e runPlugin event 28624 */ 28625 run: function() { 28626 } 28627}); 28628/*globals myDeco, $ */ 28629/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 28630 sloppy: true, white: true */ 28631/** 28632 * @fileOverview Settings for FPE project 28633 */ 28634myDeco.App.settings = $.extend(true, myDeco.App.settings, { 28635 className: 'fpe', 28636 model: { 28637 shapes: {}, 28638 cameraFov: 78, 28639 minDistance: 10, 28640 searchDistance: 30, 28641 minAngle: 10, 28642 snappingRotationTolerancy: 10, 28643 snappingMoveTolerancy: 10 28644 }, 28645 views: { 28646 isometric: { 28647 decor: { 28648 listingThumbnail: {postfix: '_50.jpg'} 28649 }, 28650 28651 IsoWall: { 28652 visual: { 28653 'fill': '#eeeeee', 28654 'stroke': '#dddddd', 28655 'stroke-width': 2 28656 }, 28657 options: { 28658 defaultColor: [0.5, 0.5, 0.5], 28659 noTexture: false, 28660 percent: 30 28661 } 28662 }, 28663 IsoFloor: { 28664 visual: { 28665 'stroke-opacity': 0 28666 }, 28667 options: { 28668 defaultColor: [0.5, 0.5, 0.5], 28669 noTexture: false, 28670 percent: 30 28671 } 28672 }, 28673 IsoComponent: { 28674 visual: { 28675 opacity: 0.2, 28676 active: { 28677 stroke: '#459fda' 28678 }, 28679 inactive: { 28680 stroke: 'none' 28681 } 28682 } 28683 } 28684 }, 28685 floor: { 28686 penWidth: 7, 28687 28688 rotateArrow: { 28689 distance: 100, 28690 scale: 0.25, 28691 zoom: 2, 28692 decoration: { 28693 normal: { 28694 rotatingArrows: { 28695 fill: '#6b6b6b', 28696 stroke: '#6b6b6b' 28697 } 28698 }, 28699 highlighted: { 28700 rotatingArrows: { 28701 fill: '#6b6b6b', 28702 stroke: '#6b6b6b' 28703 } 28704 }, 28705 selected: { 28706 rotatingArrows: { 28707 fill: '#6b6b6b', 28708 stroke: '#6b6b6b' 28709 } 28710 }, 28711 operation: { 28712 rotatingArrows: { 28713 fill: '#29a6e6', 28714 stroke: '#29a6e6' 28715 } 28716 } 28717 } 28718 }, 28719 28720 offset: { 28721 top: 0 28722 }, 28723 28724 furniture: { 28725 perPage: 4, 28726 canvasThumbnail: false, 28727 dragThumbnail: {percent: 30, shadow: 70, offset: 2} 28728 }, 28729 28730 decor: { 28731 // pagination settings for different kind of materials 28732 'wall-colour': {perPage: 24, maxWidth: 70, maxHeight: 70}, 28733 wallpaper: {perPage: 4, maxWidth: 70, maxHeight: 70}, 28734 flooring: {perPage: 4, maxWidth: 70, maxHeight: 70}, 28735 listingThumbnail: {postfix: '_50.jpg'}, 28736 dragThumbnail: {width: 50, height: 50, postfix: '_50.jpg'} 28737 }, 28738 28739 render: { 28740 width: 640, 28741 height: 480 28742 }, 28743 28744 Drawable: { 28745 controlPanel: { 28746 distance: 30, 28747 padding: 1, 28748 curveOffset: 10, 28749 bg: { 28750 fill: '#fff', 28751 stroke: '#fff', 28752 opacity: 0.5 28753 } 28754 } 28755 }, 28756 Wall: { 28757 normal: { 28758 'stroke': '#222' 28759 }, 28760 highlighted: { 28761 'fill': '#3eb3de', 28762 'stroke': '#3eb3de' 28763 }, 28764 selected: { 28765 'fill': '#3eb3de', 28766 'stroke': '#3eb3de' 28767 } 28768 }, 28769 Window: { 28770 thickness: 17, 28771 normal: { 28772 'fill': '#fff', 28773 'stroke': '#000' 28774 }, 28775 highlighted: { 28776 'fill': '#67afdf', 28777 'stroke': '#000' 28778 }, 28779 selected: { 28780 'fill': '#39617b', 28781 'stroke': '#000' 28782 } 28783 }, 28784 Door: { 28785 openedAngle: 30, 28786 normal: { 28787 'fill': '#fff', 28788 'stroke': '#000' 28789 }, 28790 highlighted: { 28791 'fill': '#67afdf', 28792 'stroke': '#000' 28793 }, 28794 selected: { 28795 'fill': '#39617b', 28796 'stroke': '#000' 28797 }, 28798 clearance: { 28799 'fill': '#fff', 28800 'opacity': 0.4, 28801 'stroke-width': 1 28802 } 28803 }, 28804 floor: { 28805 fill: 'red', 28806 fillOpacity: 0.2 28807 }, 28808 Corner: { 28809 radius: 9, 28810 normal: { 28811 attr: { 28812 'stroke': 'black', 28813 'fill': '#469CDA', 28814 'opacity': 0 28815 } 28816 }, 28817 highlighted: { 28818 attr: { 28819 'opacity': 1 28820 } 28821 }, 28822 selected: { 28823 attr: { 28824 'opacity': 1 28825 } 28826 } 28827 }, 28828 Grid: [ 28829 { 28830 strokeColor: '#C8C8C8', 28831 strokeWidth: .5, 28832 step: 200, 28833 offset: 0 28834 }, 28835 { 28836 strokeColor: '#eaeaea', 28837 strokeWidth: 0.5, 28838 step: 50, 28839 offset: 50 28840 } 28841 ], 28842 Camera: { 28843 arrowDistance: 200, 28844 normal: { 28845 base: { 28846 fill: '#9bddf2' 28847 }, 28848 lens: { 28849 fill: '#5bb9e8', 28850 stroke: '#5bb9e8' 28851 }, 28852 speck: { 28853 fill: '#b8e1ec', 28854 stroke: '#b8e1ec' 28855 } 28856 }, 28857 highlighted: { 28858 base: { 28859 fill: '#35b4e9' 28860 }, 28861 lens: { 28862 fill: '#1e88bd', 28863 stroke: '#1e88bd' 28864 }, 28865 speck: { 28866 fill: '#1e88bd', 28867 stroke: '#28a6e5' 28868 } 28869 }, 28870 // TODO: renamed to unused state to avaiod of change style on select 28871 selected_: { 28872 base: { 28873 fill: '#9fa03d' 28874 }, 28875 lens: { 28876 fill: '#9fa03d', 28877 stroke: '#aaad35' 28878 }, 28879 speck: { 28880 fill: '#ced021', 28881 stroke: '#ced021' 28882 } 28883 }, 28884 fov: 180 / 3.0 28885 }, 28886 Component: { 28887 arrowDistance: 100, 28888 minsize: 4, 28889 hideOutline: false, 28890 normal: { 28891 outline: { 28892 stroke: '#999', 28893 'stroke-width': 2, 28894 opacity: 0 28895 } 28896 }, 28897 highlighted: { 28898 outline: { 28899 stroke: '#459fda', 28900 'stroke-width': 2, 28901 opacity: 1 28902 } 28903 }, 28904 selected: { 28905 outline: { 28906 stroke: '#459fda', 28907 'stroke-width': 2, 28908 opacity: 1 28909 } 28910 }, 28911 // settings for on-wall components 28912 '-1': { 28913 normal: { 28914 outline: { 28915 stroke: '#333', 28916 'stroke-width': 1, 28917 opacity: 1 28918 } 28919 }, 28920 highlighted: { 28921 outline: { 28922 stroke: '#459fda', 28923 'stroke-width': 2, 28924 opacity: 1 28925 } 28926 }, 28927 selected: { 28928 outline: { 28929 stroke: '#459fda', 28930 'stroke-width': 2, 28931 opacity: 1 28932 } 28933 } 28934 } 28935 }, 28936 Shape: { 28937 options: { 28938 defaultColor: [0.5, 0.5, 0.5], 28939 percent: 30 28940 } 28941 }, 28942 // will be merged with ObjectsLibrary._settings 28943 library: { } 28944 } 28945 }, 28946 28947 plugins: { 28948 advertisement: { 28949 enabled: false, 28950 timeout: 2 28951 }, 28952 28953 competition: { 28954 enabled: false, 28955 // seconds 28956 timeout: 120 28957 } 28958 }, 28959 28960 controllers: {} 28961}); 28962/* 28963 * WARNING !!!
28964 * original code was improved! 28965 * it has more precise regexp for id attribute 28966 * and dramatically faster (up to 10x times) 28967 * when str contain a template already, not just 28968 * its element id, due to caching in this mode. 28969 */ 28970 28971// Simple JavaScript Templating 28972// John Resig - http://ejohn.org/ - MIT Licensed 28973(function(){ 28974 var cache = {}; 28975 this.tmpl = function tmpl(str, data){ 28976 // Figure out if we're getting a template, or if we need to 28977 // load the template - and be sure to cache the result. 28978 var fn = cache[str] || (/^[A-z][\w-]*$/.test(str) ? 28979 // we have element id as str, so will read from tag, 28980 // setting data as false will prevent caching for this case 28981 tmpl((document.getElementById(str) ? 28982 document.getElementById(str) 28983 .innerHTML 28984 .replace(/^\s*\/\/\s*<!--\s*/, '') 28985 .replace(/\s*\/\/\s*-->$/, '') 28986 : ''), 28987 false) : 28988 // str contain template 28989 new Function("obj", 28990 "var p=[];" + 28991 28992 // Introduce the data as local variables using with(){} 28993 "with(obj){p.push('" + 28994 28995 // Convert the template into pure JavaScript 28996 str 28997 .replace(/[\r\t\n]/g, " ") 28998 .split("<%").join("\t") 28999 .replace(/((^|%>)[^\t]*)'/g, "$1\r") 29000 .replace(/\t=(.*?)%>/g, "',$1,'") 29001 .split("\t").join("');") 29002 .split("%>").join("p.push('") 29003 .split("\r").join("\\'") 29004 29005 + "');}return p.join('');")); 29006 29007 // Do not cache in self recall mode 29008 if (data !== false) 29009 cache[str] = fn; 29010 // Provide some basic currying to the user 29011 return data ? fn( data ) : fn; 29012 }; 29013})(); 29014/** 29015 * @fileOverview Common animation routines 29016 */ 29017 29018myDeco.animate = { 29019 29020 /** 29021 * Visualize transition from one element to other by blue bordered rectangle, 29022 * used to add item into favbar for example. 29023 * @param {jQuery element} start Starting element 29024 * @param {jQuery element} finish Finish element 29025 * @param {function} callback Will be called when animation is complete 29026 * @return {Boolean} False in case of wrong arguments 29027 */ 29028 transition: function(start, finish, callback) { 29029 if (!start['length'] || !finish['length']) return false; 29030 var border = 4, 29031 startDims = $.extend(start.offset(), { 29032 width: start.outerWidth(), 29033 height: start.outerHeight(), 29034 border: parseInt(start.css('borderLeftWidth')) || 0 29035 }), 29036 finishDims = $.extend(finish.offset(),{ 29037 width: finish.outerWidth(), 29038 height: finish.outerHeight() 29039 }); 29040 $('<div></div>') 29041 .css({ 29042 position: 'absolute', 29043 zIndex: 5000, 29044 left: startDims.left + startDims.border - border + 'px', 29045 top: startDims.top + startDims.border - border + 'px', 29046 border: border + 'px solid #459FDB', 29047 width: startDims.width + 'px', 29048 height: startDims.height + 'px' 29049 }) 29050 .appendTo('body') 29051 .animate({ 29052 width: finishDims.width + 'px', 29053 height: finishDims.height + 'px', 29054 left: finishDims.left - border + 'px', 29055 top: finishDims.top - border + 'px' 29056 }, { 29057 duration: 500, 29058 complete: function(){ 29059 $(this).remove(); 29060 if ($.isFunction(callback)) callback(); 29061 } 29062 }); 29063 } 29064 29065}; 29066/* globals $, myDeco, window */ 29067/* jslint white: false, forin: true, fragment: true, undef: true, nomen: true, 29068 plusplus: true, bitwise: false, regexp: false, newcap: true, sloppy: true */ 29069 29070/** 29071* @fileOverview Wishlisting functionality 29072*/ 29073myDeco.shortlist = { 29074 guids : [], 29075 buttons: {}, 29076 data : [], 29077 folders : [], 29078 sending : false, 29079 removing : false, 29080 storeName: 'wishlist', 29081 storeFolders: 'user_folders', 29082 wrapper: myDeco.body, 29083 29084 init: function (root) { 29085 if ($('.wishlist-block').length || $('#wishlist-items').length) { 29086 var self = this; 29087 self.wrapper = root || myDeco.body; 29088 if (myDeco.username) { 29089 this.load(true, true); 29090 this.periodicalSync(); 29091 this.checkShortlistOwner(); 29092 this.initWishlistPageHandlers(); 29093 this.checkState(); 29094 } else { 29095 self.wrapper.find('.add-to-wishlist').click(function (e) { 29096 myDeco.user.ajaxLoginClickHandler(e, null, 'login'); 29097 }); 29098 self.removeGroupsPopup(); 29099 } 29100 myDeco.body.bind('mydeco.login', function() { 29101 self.init(); 29102 }); 29103 } 29104 }, 29105 29106 /** 29107 * Load products and folders from localStorage 29108 * @param {Boolean} products (true - to load products) 29109 * @param {Boolean} folders (true - to load folders) 29110 */ 29111 load: function (products, folders) { 29112 var self = this; 29113 29114 if (products) { 29115 myDeco.storage.get(this.storeName, function (ok, data) { 29116 self.data = JSON.parse(data || 'null') || {}; 29117 }); 29118 } 29119 29120 if (folders) { 29121 myDeco.storage.get(this.storeFolders, function (ok, data) { 29122 self.folders = JSON.parse(data || 'null') || {}; 29123 self.loadGroups(); 29124 }); 29125 } 29126 }, 29127 29128 update: function (item) { 29129 var self = this, i, 29130 guid = item.data('product'), 29131 groups = $(self.data[guid]); 29132 29133 for (i = 0; i < groups.length; i++) { 29134 self.updateButton(item.find('a[data-group="' + groups[i] + '"]'), true); 29135 } 29136 }, 29137 29138 updateButton: function (button, action) { 29139 button.toggleClass('inactive', action); 29140 this.checkState(button.closest('.wishlist-block')); 29141 }, 29142 29143 checkState: function (button) { 29144 var items, i, guid, item, self = this; 29145 29146 items = button || this.wrapper.find('.wishlist-block'); 29147 29148 for (i = 0; i < items.length; i++) { 29149 item = $(items[i]); 29150 guid = item.data('product'); 29151 item.toggleClass('wishlisted', !!self.data[guid]); 29152 } 29153 }, 29154 29155 save: function (guid, group) { 29156 // guid can be an array of guids or one guid 29157 var self = this, i, arr = []; 29158 if (typeof guid === 'string') { 29159 guid = [guid]; 29160 } 29161 29162 for (i = 0; i < guid.length; i++) { 29163 arr = self.data[guid[i]] || []; 29164 if (arr.indexOf(group) < 0) { 29165 arr.push(group); 29166 self.data[guid[i]] = arr; 29167 } 29168 } 29169 29170 myDeco.storage.set(self.storeName, JSON.stringify(self.data)); 29171 }, 29172 29173 remove: function (guid, group) { 29174 // guid can be an array of guids or one guid 29175 var self = this, i, arr = [], index; 29176 if (typeof guid === 'string') { 29177 guid = [guid]; 29178 } 29179 29180 for (i = 0; i < guid.length; i++) { 29181 arr = self.data[guid[i]] || []; 29182 index = arr.indexOf(group); 29183 arr.splice(index, 1); 29184 if (arr.length <= 0) { 29185 delete self.data[guid[i]]; 29186 } 29187 } 29188 29189 myDeco.storage.set(self.storeName, JSON.stringify(self.data)); 29190 }, 29191 29192 removeGroup: function (group) { 29193 var self = this, guid, data; 29194 29195 for (guid in self.data) { 29196 data = self.data[guid]; 29197 index = data.indexOf(group); 29198 if (index !== -1) { 29199 data.splice(index, 1); 29200 } 29201 if (data.length <= 0) { 29202 delete (self.data[guid]); 29203 } 29204 } 29205 29206 delete self.folders[group]; 29207 myDeco.storage.set(self.storeName, JSON.stringify(self.data)); 29208 myDeco.storage.set(self.storeFolders, JSON.stringify(self.folders)); 29209 }, 29210 29211 sendOne: function (button, product) { 29212 // Add item to wishlist group 29213 var self = this, 29214 guid = product || button.closest('.wishlist-block').data('product'), 29215 group_id = button.data('group'), 29216 data = { 29217 'wishlist': guid || '', 29218 'group_id': group_id || '' 29219 }, 29220 firstKey; 29221 29222 if (!self.sending) { 29223 $.ajax({ 29224 url: myDeco.urls.addShortlistProduct, 29225 type: 'POST', 29226 dataType: 'json', 29227 data: $.param(data), 29228 beforeSend: function() { self.sending = true; }, 29229 complete: function() { self.sending = false; }, 29230 success: function (data) { 29231 if (data.wishlisted) { 29232 // get first element from object 29233 firstKey = $.firstElementKey(data.wishlisted); 29234 // result has map of guid to list of group ids 29235 self.save(guid, data.wishlisted[firstKey][0]); 29236 if (button.length) { 29237 self.updateButton(button, true); 29238 } 29239 } 29240 } 29241 }); 29242 29243 myDeco.tracking.trackEvent('shortlist', 'product', guid); 29244 } 29245 }, 29246 29247 removeOne: function (button) { 29248 // Delete item from wishlist group 29249 var self = this, 29250 guid = button.closest('.wishlist-block').data('product'), 29251 group_id = button.data('group'), 29252 data = { 29253 'guid': guid, 29254 'group_id': group_id, 29255 'current_group_id': group_id 29256 }; 29257 if (!self.removing) { 29258 $.ajax({ 29259 url: myDeco.urls.removeShortlistedProduct, 29260 type: 'POST', 29261 dataType: 'json', 29262 data: $.param(data), 29263 beforeSend: function() { self.removing = true; }, 29264 complete: function() { self.removing = false; }, 29265 success: function (data) { 29266 if (data.success) { 29267 self.remove(guid, group_id); 29268 self.updateButton(button, false); 29269 } 29270 } 29271 }); 29272 } 29273 }, 29274 29275 checkShortlistOwner: function() { 29276 var self = this; 29277 myDeco.storage.get('wishlist_last_username', function (ok, data) { 29278 if (data !== myDeco.username) { 29279 myDeco.storage.remove(self.storeName); 29280 myDeco.storage.remove(self.storeFolders); 29281 self.sync(true, true); 29282 } 29283 }); 29284 }, 29285 29286 periodicalSync: function() { 29287 var self = this, hours; 29288 myDeco.storage.get('wishlist_last_sync', function (ok, data) { 29289 hours = Math.round(new Date() / (1000 * 60 * 60)); 29290 if ((!data || hours > +data + myDeco.settings.SHORTLIST_SYNC_INTERVAL)) { 29291 self.sync(true, true); 29292 } 29293 }); 29294 }, 29295 29296 /** 29297 * Retrieve shortlisted product guids and user folders from server. 29298 * @param {Boolean} products (true - to load products) 29299 * @param {Boolean} folders (true - to load folders) 29300 */ 29301 sync: function (folders, products) { 29302 var self = this; 29303 29304 if (myDeco.username) { 29305 if (folders) { 29306 // Get list of folders 29307 $.ajax({ 29308 url: myDeco.urls.wishlistGroupPopinList, 29309 type: 'GET', 29310 data: $.param({'username': myDeco.username}), 29311 dataType: 'html', 29312 success: function (data) { 29313 myDeco.storage.remove(self.storeFolders); 29314 myDeco.storage.set(self.storeFolders, data); 29315 self.load(false, true); 29316 } 29317 }); 29318 } 29319 if (products) { 29320 // Get wishlisted products 29321 $.ajax({ 29322 url: myDeco.urls.addShortlistProduct, 29323 type: 'GET', 29324 dataType: 'json', 29325 success: function (data) { 29326 if (data.wishlisted) { 29327 myDeco.storage.remove(self.storeName); 29328 myDeco.storage.set(self.storeName, JSON.stringify(data.wishlisted)); 29329 self.load(true, false); 29330 } 29331 } 29332 }); 29333 } 29334 // Set last user name
29335 myDeco.storage.set('wishlist_last_username', myDeco.username); 29336 } else { 29337 myDeco.storage.remove(self.storeFolders); 29338 myDeco.storage.remove(self.storeName); 29339 } 29340 29341 // Set last time sync 29342 myDeco.storage.set('wishlist_last_sync', Math.round(new Date() / (1000 * 60 * 60))); 29343 }, 29344 29345 reset: function() { 29346 myDeco.storage.remove(self.storeName); 29347 myDeco.storage.remove(self.storeFolders); 29348 myDeco.storage.remove('wishlist_last_sync'); 29349 }, 29350 29351 loadGroups: function() { 29352 var self = this, folders_list = '', wishlist_form; 29353 self.removeGroupsPopup(); 29354 29355 for (folder_id in self.folders) { 29356 folders_list += '<a class="wishlist-action" data-group="' + folder_id + '" href="#">' + self.folders[folder_id] + '</a>'; 29357 } 29358 29359 wishlist_form = '<div id="wislist-links-data" style="display: none;">' 29360 + ' <div class="wishlist-links">' 29361 + ' <div class="wishlist-inner">' 29362 + ' <div class="wishlist-items">' 29363 + ' <div class="add-title">Add to wishlist</div>' 29364 + ' <div class="move-title">Move to wishlist</div>' 29365 + ' <div class="list">' 29366 + folders_list 29367 + ' </div>' 29368 + ' <div class="loader"></div>' 29369 + ' </div>' 29370 + ' <a href="" class="wishlist-create">Create new wishlist</a>' 29371 + ' <div class="wishlist-form-add">' 29372 + ' <form method="POST" class="wishlistFormAdd">' 29373 + ' <input type="text" name="name" class="text-field wishlist-name" autocomplete="off" value="" placeholder="Wishlist name" />' 29374 + ' <input type="submit" class="mdtwo-button no-decorate" value="Create" />' 29375 + ' </form>' 29376 + ' </div>' 29377 + ' </div>' 29378 + ' </div>' 29379 + '</div>'; 29380 29381 myDeco.body.append(wishlist_form); 29382 self.buttonHover(); 29383 }, 29384 29385 /* 29386 * Add new folder and include or move product to it 29387 */ 29388 wishlistAddGroup: function (e) { 29389 e.stop(); 29390 var self = this, params, 29391 form = $(e.target), 29392 name = form.find('.wishlist-name').val(), 29393 itemsWrap = form.closest('.wishlist-links').find('.wishlist-items'), 29394 itemsList = itemsWrap.find('.list'), 29395 itemsLoader = itemsWrap.find('.loader'), 29396 block = form.closest('.wishlist-block'), 29397 _data = block.data(), 29398 guid = _data.product, 29399 wishlist_product = _data.wishlist_product, 29400 current_group_id = _data.current_group_id, 29401 moving = block.hasClass('wishlist-move'), 29402 data = moving ? {'name': name} : {'name': name, 'guid': guid}, 29403 error_message = 'Please type Name of wishlist'; 29404 29405 if (!name) { 29406 myDeco.popin.error({ 29407 message: error_message 29408 }); 29409 } else { 29410 itemsList.fadeTo('fast', 0); 29411 itemsLoader.show(); 29412 29413 $.ajax({ 29414 url: myDeco.urls.wishlistGroupEdit, 29415 type: 'POST', 29416 dataType: 'json', 29417 data: $.param(data), 29418 success: function (data) { 29419 if (data.success) { 29420 // Save folder to localStorage 29421 self.saveGroup(data.folder_id, name); 29422 if (moving) { 29423 // Move item to new folder 29424 params = { 29425 'shortlist_items': wishlist_product, 29426 'group_id': data.folder_id, 29427 'current_group_id': current_group_id 29428 }; 29429 self.moveItem(params); 29430 } else { 29431 // Add item to new folder 29432 self.save(guid, data.folder_id); 29433 itemsLoader.hide(); 29434 itemsList 29435 .prepend('<a class="wishlist-action inactive" data-group="' + data.folder_id + '" href="">' + name + '</a>') 29436 .fadeTo('fast', 1); 29437 } 29438 29439 self.wrapper = myDeco.body; 29440 self.checkState(); 29441 29442 $('.wishlist-block').removeClass('active'); 29443 } else { 29444 myDeco.popin.error({ 29445 message: data.message.name[0] 29446 }); 29447 itemsLoader.hide(); 29448 itemsList.fadeTo('fast', 1); 29449 } 29450 } 29451 }); 29452 } 29453 }, 29454 29455 /* 29456 * Save folder to localStorage 29457 */ 29458 saveGroup: function (id, name) { 29459 this.folders[id] = name; 29460 myDeco.storage.set(this.storeFolders, JSON.stringify(this.folders)); 29461 this.load(false, true); 29462 }, 29463 29464 /* 29465 * Remove popup 29466 */ 29467 removeGroupsPopup: function() { 29468 $('#wislist-links-data').remove(); 29469 }, 29470 29471 wishlistPopupInit: function (button) { 29472 // Init popup links for wishlist popup 29473 if (!button.hasClass('active')) { 29474 var self = this, 29475 data = $('#wislist-links-data').html(); 29476 29477 button 29478 .addClass('active') 29479 .find('.wishlist-links-wrapper') 29480 .html(data); 29481 29482 self.initHandlers(button); 29483 self.update(button); 29484 self.setActiveWishlist(button); 29485 } 29486 }, 29487 29488 buttonHover: function() { 29489 var self = this, button; 29490 // Show/hide wishlist popup 29491 self.wrapper.find('.wishlist-block') 29492 .hover(function() { 29493 self.wishlistPopupInit($(this).addClass('hover')); 29494 }, function() { 29495 $(this) 29496 .removeClass('hover') 29497 .find('.wishlist-create') 29498 .show() 29499 .next('.wishlist-form-add') 29500 .hide(); 29501 }); 29502 }, 29503 29504 initHandlers: function (button) { 29505 // Add events handlers for wishlist popup 29506 var self = this, item, block, _data, wishlist_product, current_group_id, new_group_id; 29507 29508 button 29509 .find('.wishlist-action') 29510 .click(function (e) { 29511 e.stop(); 29512 item = $(this); 29513 if (button.hasClass('wishlist-add')) { 29514 // Handler for adding new item to wishlist group 29515 if (item.hasClass('inactive')) { 29516 self.removeOne(item); 29517 } else { 29518 self.sendOne(item); 29519 } 29520 } else { 29521 // Handler for moving item to another wishlist 29522 block = item.closest('.wishlist-block'); 29523 _data = block.data(); 29524 wishlist_product = _data.wishlist_product; 29525 current_group_id = _data.current_group_id; 29526 new_group_id = item.data('group'); 29527 29528 if (item.hasClass('inactive')) { 29529 return; 29530 } 29531 29532 params = { 29533 'shortlist_items': wishlist_product, 29534 'group_id': new_group_id, 29535 'current_group_id': current_group_id 29536 }; 29537 self.moveItem(params); 29538 } 29539 }); 29540 29541 // Show new group form and adding new wishlist group 29542 button 29543 .find('.wishlist-create') 29544 .click(function (e) { 29545 e.stop(); 29546 $(this) 29547 .hide() 29548 .next('.wishlist-form-add') 29549 .show(); 29550 }) 29551 .end() 29552 .find('.wishlistFormAdd') 29553 .submit(self.wishlistAddGroup.bind(self)); 29554 }, 29555 29556 /** 29557 * My wishlist page functions 29558 */ 29559 initWishlistPageHandlers: function() { 29560 var self = this, button, 29561 popupProp = { 29562 yesButtonText: 'Confirm', 29563 noButtonText: 'No thanks!', 29564 className: 'mdtwo-alert-popin', 29565 holderClassName: 'mdtwo-alert-content-holder', 29566 backgroundClassName: 'mdtwo-alert-background', 29567 width: 440 29568 }; 29569 29570 // Handler for delete product from wishlist 29571 $('.delete-product') 29572 .click(function (e) { 29573 e.stop(); 29574 button = $(this); 29575 myDeco.popin.confirm($.extend({}, popupProp, { 29576 message: 'Are you sure you want to remove this product from wishlist?', 29577 yesButton: function (e) { 29578 self.deleteWishlistItem(button); 29579 } 29580 })); 29581 }); 29582 29583 // Handler for delete wishlist 29584 $('.wishlist-del') 29585 .click(function (e) { 29586 e.stop(); 29587 button = $(this); 29588 myDeco.popin.confirm($.extend({}, popupProp, { 29589 message: 'Are you sure you want to delete the entire wishlist?', 29590 yesButton: function (e) { 29591 self.deleteWishlist(button); 29592 } 29593 })); 29594 }); 29595 29596 // Callback for edit title of wishlist 29597 if (myDeco.editable) { 29598 myDeco.editable.callback = function (form) { 29599 if (form.hasClass('resync')) { 29600 self.sync(true, false); 29601 $('.wishlist-block').removeClass('active'); 29602 } 29603 }; 29604 } 29605 }, 29606 29607 setActiveWishlist: function (block) { 29608 var scope = $(block).find('.wishlist-action'), 29609 wrapper = $('#wishlist-items'), 29610 current_group_id = wrapper.data('group'); 29611 29612 if (wrapper.length > 0) { 29613 scope.each(function() { 29614 var button = $(this); 29615 if (button.data('group') === current_group_id) { 29616 button.addClass('inactive'); 29617 } 29618 }); 29619 } 29620 }, 29621 29622 deleteWishlistItem: function (item) { 29623 var self = this, 29624 _data = item.data(), 29625 guid = _data.product, 29626 group_id = _data.group, 29627 container = $('#wishlist-items'), 29628 data = { 29629 'guid': guid, 29630 'group_id': group_id, 29631 'current_group_id': group_id 29632 }; 29633 29634 $.ajax({ 29635 url: myDeco.urls.removeShortlistedProduct, 29636 type: 'POST', 29637 dataType: 'json', 29638 data: $.param(data), 29639 success: function (data) { 29640 if (data.success) { 29641 self.remove(guid, group_id); 29642 container.html(data.wishlist_products_content); 29643 self.initButtons(); 29644 } 29645 } 29646 }); 29647 }, 29648 29649 moveItem: function (params) { 29650 var self = this,
29651 container = $('#wishlist-items'); 29652 29653 $.ajax({ 29654 url: myDeco.urls.wishlistGroupMoveItems, 29655 type: 'POST', 29656 data: $.param(params), 29657 dataType: 'json', 29658 success: function (data) { 29659 if (data.success) { 29660 container.html(data.wishlist_products_content); 29661 self.sync(false, true); 29662 self.initButtons(); 29663 } else { 29664 myDeco.popin.error({ 29665 message: data.message 29666 }); 29667 } 29668 }, 29669 error: function() { 29670 myDeco.popin.error({ 29671 message: 'Error of relocation of a product' 29672 }); 29673 } 29674 }); 29675 }, 29676 29677 deleteWishlist: function (item) { 29678 var self = this, 29679 group_id = item.data('group'); 29680 29681 $.ajax({ 29682 url: myDeco.urls.wishlistGroupRemove, 29683 type: 'POST', 29684 dataType: 'json', 29685 data: $.param({'group_id': group_id}), 29686 success: function (data) { 29687 if (data.success) { 29688 self.removeGroup(group_id); 29689 window.location = myDeco.addCacheMissParam(myDeco.urls.profilePublicWishlist + 'edit/'); 29690 } 29691 } 29692 }); 29693 }, 29694 29695 initButtons: function() { 29696 myDeco.sharethis.init(); 29697 myDeco.editable._inited = false; 29698 myDeco.editable.init(); 29699 myDeco.basketUI.init(); 29700 this.buttonHover(); 29701 this.initWishlistPageHandlers(); 29702 myDeco.infoPopups.init(); 29703 myDeco.shortlist.initScroll($('.wishlist-products-list')); 29704 }, 29705 29706 /** 29707 * Init lazy scroll for wishlist page 29708 */ 29709 initScroll: function (resultsContainer) { 29710 if (resultsContainer.length) { 29711 resultsContainer.lazyLoad({ 29712 url: myDeco.urls.resultsUrl, 29713 step: 1, 29714 startFrom: 1, 29715 loadParam: 'page', 29716 scrollingOffset: 600, 29717 hasNext: resultsContainer.data('next'), 29718 appendMethod: function (container, data) { 29719 var wishlistData = $(data).children(); 29720 this.hasNext = $(data).data('next'); 29721 container.find('.wishlist-column').each(function (i, el) { 29722 $(el).append($(wishlistData[i]).children()); 29723 }); 29724 }, 29725 callback: function() { 29726 myDeco.basketUI.buttonClick(resultsContainer); 29727 myDeco.shortlist.init(resultsContainer); 29728 myDeco.infoPopups.init(resultsContainer); 29729 myDeco.editable._inited = false; 29730 myDeco.editable.init(); 29731 } 29732 }); 29733 } 29734 } 29735}; 29736 29737myDeco.storage.onready(myDeco.shortlist.init.bind(myDeco.shortlist)); 29738 29739$(function() { 29740 myDeco.shortlist.initScroll($('.wishlist-products-list')); 29741}); 29742/*globals $, window, myDeco, document, tmpl */ 29743/*jslint forin: true, fragment: true, onevar: true, undef: true, nomen: false, eqeqeq: true, plusplus: true, bitwise: true, newcap: true, immed: true, regexp: false, white: false, plusplus: false */ 29744/** 29745 * @fileOverview Component for replacing lightwindow with a little small functionality 29746 */ 29747 29748if (typeof myDeco.popin === 'undefined') { 29749 myDeco.popin = { 29750 /** 29751 * Default options 29752 */ 29753 options: { 29754 base: 'popin', 29755 width: 616, 29756 height: 200, 29757 url: '', 29758 afterOpen: null, 29759 beforeClose: null, 29760 closeIn: 0, 29761 fullscreen: false, 29762 fullscreenMargin: 30, 29763 autoResizeX: false, 29764 autoResizeY: true, 29765 ajaxLoaderClass: 'ajaxLoader', 29766 template: null, 29767 borders: 50, 29768 showClose: true, 29769 closeText: '', 29770 className: '', 29771 holderClassName: '', 29772 backgroundClassName: '', 29773 tracking: false, 29774 context: false 29775 }, 29776 29777 /** 29778 * observers cache 29779 */ 29780 _cclose: null, 29781 _ccloseByKeyPress: null, 29782 _cresize: null, 29783 29784 /** 29785 * Current window options 29786 */ 29787 windowOptions: {}, 29788 29789 /** 29790 * Visibility marker 29791 */ 29792 visible: false, 29793 29794 /** 29795 * Content holder variable 29796 */ 29797 holder: '', 29798 29799 /** 29800 * Content layer 29801 */ 29802 contentContainer: '', 29803 29804 /** 29805 * Background layer 29806 */ 29807 background: '', 29808 29809 /** 29810 * Background opacity level, will fetch from css 'opacity' property; default is 1 29811 * @type {Number} 29812 */ 29813 backgroundOpacity: 1, 29814 29815 /** 29816 * Init function 29817 * @param {Object} options Defailt options for initialisation 29818 */ 29819 init: function(options) { 29820 if (this._cclose) { 29821 return; 29822 } 29823 this.options = $.extend(true, this.options, options || {}); 29824 this._cclose = this.close.bind(this); 29825 this._ccloseByKeyPress = this._closeByKeyPress.bind(this); 29826 this._cresize = this.resize.bind(this); 29827 }, 29828 29829 /** 29830 * Open popin with specified parameters, or set a new parameters and content on already opened popin 29831 * @param {Object} options Options for new window 29832 */ 29833 open: function(options) { 29834 // lazy load 29835 this.init(); 29836 if (this.visible && $.isFunction(this.windowOptions.beforeClose)) { 29837 this.windowOptions.beforeClose(); 29838 } 29839 29840 // extend options to full size 29841 this.windowOptions = $.extend(true, {}, this.options, options || {}); 29842 29843 // return if url is empty 29844 if (this.windowOptions.url === '' && this.windowOptions.template === null) { 29845 return; 29846 } 29847 29848 // content holder node 29849 var holderId = this.windowOptions.base + '-content-holder', 29850 // content node 29851 contentId = this.windowOptions.base + '-content', 29852 self = this, 29853 closeTitle; 29854 29855 if (!this.visible) {
29856 this.holder = $('<div id="' + holderId + '" class="' + holderId + '" style="display:none;position:absolute"></div>') 29857 .appendTo(myDeco.body); 29858 29859 this.contentContainer = $('<div class="clearfix ' + contentId + '" id="' + contentId + '"></div>') 29860 .appendTo(this.holder); 29861 29862 if (this.windowOptions.showClose) { 29863 this.holder.append('<a href="#" class="' + this.windowOptions.base + 29864 '-close-title">' + this.windowOptions.closeText + '</a>'); 29865 } 29866 29867 // push it into page 29868 this.contentContainer.hide(); 29869 29870 this.holder.css({ 29871 display: 'block', 29872 left: parseInt(myDeco.window.scrollLeft() + myDeco.window.width() / 2, 10) + 'px', 29873 top: parseInt(myDeco.window.scrollTop() + myDeco.window.height() / 2, 10) + 'px', 29874 width: 0, 29875 height: 0 29876 }); 29877 29878 this._showBackground(); 29879 29880 // document wide listeners 29881 myDeco.window.keypress(this._ccloseByKeyPress).bind('resize', this._cresize); 29882 29883 myDeco.body.bind('close.popin', this._cclose); 29884 } else { 29885 closeTitle = this.holder.find('>.' + this.windowOptions.base + '-close-title'); 29886 if (this.windowOptions.showClose && closeTitle.size() === 0) { 29887 this.holder.append('<a href="#" class="' + this.windowOptions.base + '-close-title">' + this.windowOptions.closeText + '</a>'); 29888 } else if (!this.windowOptions.showClose && closeTitle.size() !== 0) { 29889 closeTitle.remove(); 29890 } else { 29891 closeTitle.text(this.windowOptions.closeText); 29892 } 29893 29894 if ($.isFunction(this.windowOptions.beforeCreate)) { 29895 this.windowOptions.beforeCreate(this.contentContainer); 29896 } 29897 this.holder.find('.' + this.windowOptions.base + '-close-title').unbind('click', this._cclose); 29898 this.contentContainer.find('.' + this.windowOptions.base + '-close').unbind('click', this._cclose); 29899 this._hideContent(); 29900 } 29901 29902 // cleanup class names 29903 this.holder.attr('className', holderId); 29904 this.contentContainer.attr('className', contentId); 29905 29906 this.holder.toggleClass(this.windowOptions.base + '-fullscreen', this.windowOptions.fullscreen); 29907 29908 if (this.windowOptions.className) { 29909 this.contentContainer.addClass(this.windowOptions.className); 29910 } 29911 29912 if (this.windowOptions.holderClassName) { 29913 this.holder.addClass(this.windowOptions.holderClassName); 29914 } 29915 29916 // add custom class name for popup's background 29917 if (this.windowOptions.backgroundClassName) { 29918 this.background.addClass(this.windowOptions.backgroundClassName); 29919 } 29920 29921 // hide problem elements in IE 29922 this._hideProblemElements(); 29923 29924 // update content 29925 if (this.windowOptions.template || this.windowOptions.url.indexOf('#') === 0) { 29926 // inline content 29927 this.windowOptions.template = this.windowOptions.template || $(this.windowOptions.url).html(); 29928 if (this.windowOptions.url.substring(0, 1) === '#') { 29929 self.contentContainer.addClass(this.windowOptions.url.substring(1)); 29930 } 29931 this._processContent(); 29932 } else { 29933 this.contentContainer.html('').addClass(this.windowOptions.ajaxLoaderClass); 29934 $.get(this.windowOptions.url, function(template) { 29935 self.windowOptions.template = template; 29936 self.contentContainer.removeClass(self.windowOptions.ajaxLoaderClass); 29937 self._processContent(); 29938 }); 29939 } 29940 29941 return this; 29942 }, 29943 29944 _processContent: function() { 29945 var self = this, trackingLabel = this.windowOptions.tracking; 29946 29947 this.contentContainer.html(this.windowOptions.context !== false ? 29948 tmpl(this.windowOptions.template, this.windowOptions.context || {}) : 29949 this.windowOptions.template); 29950 29951 // show content 29952 this._showContent(); 29953 29954 // listeners 29955 this.holder.find('.' + this.windowOptions.base + '-close-title').bind('click', this._cclose); 29956 this.contentContainer.find('.' + this.windowOptions.base + '-close').bind('click', this._cclose); 29957 29958 // after create callback 29959 if ($.isFunction(this.windowOptions.afterOpen)) { 29960 this.windowOptions.afterOpen(this.contentContainer); 29961 } 29962 29963 // track this popin as a page view in google analytics 29964 if (trackingLabel) { 29965 if (typeof trackingLabel !== 'string') { 29966 trackingLabel = this.windowOptions.className || this.windowOptions.base; 29967 } 29968 myDeco.tracking.trackPageview(window.location.pathname + '#' + trackingLabel); 29969 } 29970 29971 // resize area 29972 this.resize(); 29973 29974 this.visible = true; 29975 29976 // set autoclose callback 29977 if (this.windowOptions.closeIn > 0) { 29978 // autoclose marker to content 29979 this.contentContainer.timed = true; 29980 window.setTimeout(function() { 29981 if (self.contentContainer.timed) { 29982 self.close(); 29983 } 29984 }, this.windowOptions.closeIn * 1000); 29985 } 29986 }, 29987 29988 /** 29989 * Close active popin 29990 * @param {Event} event Click event from elements or nothing in case of manual hitting 29991 */ 29992 close: function(e) { 29993 if (e) { 29994 e.stop(); 29995 } 29996 29997 // we do not want close an already closed popin 29998 if (!this.visible) { 29999 return false;
30000 } 30001 30002 // run before close callback 30003 if ($.isFunction(this.windowOptions.beforeClose)) { 30004 this.windowOptions.beforeClose(this.contentContainer); 30005 } 30006 30007 // stop our observers 30008 myDeco.window 30009 .unbind('keypress', this._ccloseByKeyPress) 30010 .unbind('resize', this._cresize); 30011 30012 myDeco.body.unbind('close.popin', this._cclose); 30013 this.holder.find('.' + this.windowOptions.base + '-close-title').unbind('click', this._cclose); 30014 this.contentContainer.find('.' + this.windowOptions.base + '-close').unbind('click', this._cclose); 30015 30016 // hide popin 30017 this.visible = false; 30018 this._hideHolder(); 30019 this._hideBackground(); 30020 // show problem elements 30021 this._showProblemElements(); 30022 }, 30023 30024 /** 30025 * Return content element, usable for manipulating with content elements 30026 */ 30027 content: function() { 30028 return this.contentContainer; 30029 }, 30030 30031 /** 30032 * Return content holder element 30033 */ 30034 contentHolder: function() { 30035 return this.holder; 30036 }, 30037 30038 /** 30039 * Return current state of window 30040 */ 30041 isOpen: function() { 30042 return this.visible; 30043 }, 30044 30045 /** 30046 * Resize current popin to the new size 30047 * @param {Number} width New popin width 30048 * @param {Number} height New popin height 30049 */ 30050 resize: function(width, height) { 30051 if (typeof width === 'object'){ 30052 width = undefined; 30053 } 30054 if (typeof height === 'object') { 30055 height = undefined; 30056 } 30057 var offsetLeft = myDeco.window.scrollLeft(), 30058 offsetTop = myDeco.window.scrollTop(), 30059 wWidth = myDeco.window.width(), 30060 wHeight = myDeco.window.height(), 30061 difference = 0, 30062 to = { 30063 width: width || (this.windowOptions.fullscreen ? 30064 wWidth - this.windowOptions.fullscreenMargin * 2 : 30065 this.windowOptions.width), 30066 height: height || (this.windowOptions.fullscreen ? 30067 wHeight - this.windowOptions.fullscreenMargin * 2 : 30068 this.windowOptions.height) 30069 }, 30070 corner = { 30071 left: parseInt(offsetLeft + (wWidth - to.width) / 2, 10), 30072 top: parseInt(offsetTop + (wHeight - to.height) / 2, 10) 30073 }; 30074 30075 // move and resize 30076 this.holder.css({ 30077 left: corner.left + 'px', 30078 top: corner.top + 'px', 30079 width: to.width + 'px', 30080 height: to.height + 'px' 30081 }); 30082 30083 if (!this.windowOptions.fullscreen) { 30084 if (this.windowOptions.autoResizeX) { 30085 to.width = this.contentContainer.innerWidth(); 30086 difference = wWidth - this.contentContainer.outerWidth(true) - this.windowOptions.borders * 2; 30087 if (difference < 0) { 30088 to.width += difference; 30089 } 30090 corner.left = parseInt(offsetLeft + (wWidth - to.width) / 2, 10); 30091 } 30092 30093 if (this.windowOptions.autoResizeY) { 30094 to.height = this.contentContainer.innerHeight(); 30095 difference = wHeight - this.contentContainer.outerHeight(true) - this.windowOptions.borders * 2; 30096 if (difference < 0) { 30097 to.height += difference; 30098 } 30099 corner.top = parseInt(offsetTop + (wHeight - to.height) / 2, 10); 30100 } 30101 30102 if (this.windowOptions.autoResizeX || this.windowOptions.autoResizeY) { 30103 this.holder.css({ 30104 left: corner.left + 'px', 30105 top: corner.top + 'px', 30106 width: to.width + 'px', 30107 height: to.height + 'px' 30108 }); 30109 } 30110 } 30111 30112 // finally fix background 30113 this._showBackground(); 30114 }, 30115 30116 /** 30117 * Show content layer, maybe smooth in feature 30118 * 30119 * @private 30120 */ 30121 _showContent: function() { 30122 this.contentContainer.show(); 30123 }, 30124 30125 /** 30126 * Hide content layer, maybe smooth in feature 30127 * 30128 * @private 30129 */ 30130 _hideContent: function() { 30131 this.contentContainer.hide(); 30132 }, 30133 30134 /** 30135 * Hide content holder, maybe smooth in feature 30136 */ 30137 _hideHolder: function() { 30138 this.holder.remove(); 30139 }, 30140 30141 /** 30142 * Show background overlay 30143 * @param {Object} options New popin options 30144 * 30145 * @private 30146 */ 30147 _showBackground: function() { 30148 var xScroll, yScroll, dimensions = {}, windowWidth, windowHeight, 30149 backgroundId = this.windowOptions.base + '-background'; 30150 30151 if (window.innerHeight && window.scrollMaxY) { 30152 xScroll = document.body.scrollWidth; 30153 yScroll = window.innerHeight + window.scrollMaxY; 30154 } else if (document.body.scrollHeight > document.body.offsetHeight) { 30155 xScroll = document.body.scrollWidth; 30156 yScroll = document.body.scrollHeight; 30157 } else { 30158 xScroll = document.body.offsetWidth; 30159 yScroll = document.body.offsetHeight; 30160 } 30161 30162 if (document.documentElement && document.documentElement.clientHeight) { 30163 windowWidth = document.documentElement.clientWidth; 30164 windowHeight = document.documentElement.clientHeight; 30165 } else if (document.body) { 30166 windowWidth = document.body.clientWidth; 30167 windowHeight = document.body.clientHeight; 30168 } 30169 30170 dimensions.height = Math.max(windowHeight, yScroll); 30171 dimensions.width = Math.max(windowWidth, xScroll); 30172 30173 if (!this.background || this.background.size() === 0) { 30174 this.background = $('<div id="' + backgroundId + '" class="' + backgroundId + '" />').css({ 30175 position: 'absolute', 30176 display: 'none', 30177 left: 0, 30178 top: 0 30179 }); 30180 myDeco.body.append(this.background); 30181 30182 // now try to find original opacity 30183 this.backgroundOpacity = (this.background[0].currentStyle ? 30184 this.background[0].currentStyle.opacity : 30185 // Chromium returns opcity as a string with the 30186 // comma as delimiter 30187 this.background.css('opacity')).toString().replace(',', '.'); 30188 30189 this.background 30190 .css({ 30191 opacity: 0, 30192 display: 'block' 30193 }) 30194 .show(); 30195 } else { 30196 this.background 30197 .stop(true, false) 30198 .attr('className', backgroundId + ' ' + this.windowOptions.backgroundClassName) 30199 .show(); 30200 } 30201 30202 this.background 30203 .width(dimensions.width) 30204 .height(dimensions.height) 30205 .fadeTo('fast', this.backgroundOpacity); 30206 }, 30207 30208 /** 30209 * Hide background 30210 * 30211 * @private 30212 */
30213 _hideBackground: function() { 30214 this.background.fadeTo('fast', 0, function() { 30215 $(this).hide(); 30216 }); 30217 }, 30218 30219 /** 30220 * Hide problem elements in IE 6 30221 * 30222 * @private 30223 */ 30224 _hideProblemElements: function() { 30225 if (myDeco.browser.isIE6 && !this.visible) { 30226 $.each($('select, object, embed, iframe'), function(i, element) { 30227 element = $(element); 30228 element 30229 .data('visibility', element.css('visibility')) 30230 .css('visibility', 'hidden'); 30231 }); 30232 } 30233 }, 30234 30235 /** 30236 * Restore problem elements to its previous state 30237 * 30238 * @private 30239 */ 30240 _showProblemElements: function() { 30241 if (myDeco.browser.isIE6) { 30242 $.each($('select, object, embed, iframe'), function(i, element) { 30243 element = $(element); 30244 var visibility = element.data('visibility'); 30245 if (visibility) { 30246 element.css('visibility', visibility); 30247 } 30248 }); 30249 } 30250 }, 30251 30252 /** 30253 * Keypress observer, waits for ESC key and close popin 30254 * @param {Event} e Keypress event 30255 */ 30256 _closeByKeyPress: function(e) { 30257 if (e.keyCode === 27) { 30258 this.close(); 30259 } 30260 }, 30261 30262 /** 30263 * Extend current object with plugin. 30264 * Plugin will be available as myDeco.popin.name 30265 * @param {String} name Plugin name 30266 * @param {Object} defoptions Default options for plugin 30267 * @param {Function} callback Callback which will be started before afterOpen callback 30268 */ 30269 plugin: function(name, defoptions, callback) { 30270 this.init(); 30271 var self = this; 30272 if (typeof this[name] !== 'undefined') { 30273 throw "Name is already occuped"; 30274 } 30275 30276 defoptions = defoptions || {}; 30277 callback = callback || function() {}; 30278 this[name] = function(options) { 30279 options = options || {}; 30280 var afterOpen = options.afterOpen ? function(div) { 30281 (callback.bind(self))(div); 30282 options.afterOpen(div); 30283 } : callback.bind(self); 30284 this.open($.extend(true, {}, defoptions, options, {afterOpen: afterOpen})); 30285 }; 30286 } 30287 }; 30288 30289 $(function() { 30290 var popin = myDeco.popin; 30291 popin.init.apply(popin); 30292 30293 /** 30294 * Replacement for standard alert() function 30295 * @param {Object} options Additionally to default options are: 30296 * closeButtonText {String} default 'Close' text will replace with this value 30297 * title {String} replacement for title, will remove if no specified 30298 * message {String} replacement for message, will remove if no specified 30299 */ 30300 popin.plugin('alert', { 30301 template: '<h2 class="top-line title">Alert</h2><p class="message"></p>' + 30302 '<div class="toolbar"><a href="#" class="button green-button popin-close">' + 30303 '<span class="close-button-text">Close</span></a></div>' 30304 }, function(div) { 30305 if (this.windowOptions.closeButtonText) { 30306 div.find('.close-button-text').html(this.windowOptions.closeButtonText); 30307 } 30308 if (this.windowOptions.title) { 30309 div.find('.title').html(this.windowOptions.title); 30310 } else { 30311 div.find('.title').remove(); 30312 } 30313 if (this.windowOptions.message) { 30314 div.find('.message').html(this.windowOptions.message); 30315 } else { 30316 div.find('.message').remove(); 30317 } 30318 }); 30319 30320 /** 30321 * Replacement for standard alert() function 30322 * @param {Object} options Additionally to default options are: 30323 * yesButtonText {String} default 'Yes' text will replace with this value 30324 * noButtonText {String} default 'No' text will replace with this value 30325 * yesButton {Function} defaults to nothing 30326 * noButton {Function} defaults to close popin 30327 * title {String} replacement for title, will remove if no specified 30328 * message {String} replacement for message, will remove if no specified 30329 */ 30330 popin.plugin('confirm', { 30331 template: '<h2 class="top-line title">Confirm</h2><p class="message"></p>' + 30332 '<div class="toolbar"><a href="#" class="button green-button yes-button">' + 30333 '<span class="yes-button-text">' + gettext('Yes') + '</span></a>' + 30334 '<a href="#" class="button button-no-arrow no-button">' + 30335 '<span class="no-button-text">' + gettext('No') + '</span>' + 30336 '</a></div>' 30337 }, function(div) { 30338 var self = this; 30339 if (this.windowOptions.yesButtonText) { 30340 div.find('.yes-button-text').html(this.windowOptions.yesButtonText); 30341 } 30342 if (this.windowOptions.noButtonText) { 30343 div.find('.no-button-text').html(this.windowOptions.noButtonText); 30344 } 30345 div.find('.no-button, .yes-button').click(function(e) { 30346 e.preventDefault(); 30347 var callback = (self.windowOptions[ 30348 $(this).hasClass('yes-button') ? 'yesButton' : 'noButton' 30349 ]) || self._cclose; 30350 myDeco.popin.close(); 30351 callback.apply(self, arguments); 30352 }); 30353 if (this.windowOptions.title) { 30354 div.find('.title').html(this.windowOptions.title); 30355 } else { 30356 div.find('.title').remove(); 30357 } 30358 if (this.windowOptions.message) { 30359 div.find('.message').html(this.windowOptions.message); 30360 } else { 30361 div.find('.message').remove(); 30362 } 30363 }); 30364 30365 /** 30366 * Replacement for lightwindow image show 30367 * @param {Object} options Additionally to default options are: 30368 * title {String} replacement for title, will remove if no specified 30369 */ 30370 popin.plugin('image', { 30371 autoResizeX: true, 30372 template: '<h2 class="top-line title">Image</h2><div class="popin-image" style="position:relative" />'
30373 }, function(div) { 30374 var self = this; 30375 if (this.windowOptions.title) { 30376 div.find('.title').html(this.windowOptions.title); 30377 } else { 30378 div.find('.title').remove(); 30379 } 30380 30381 $('<img src="' + this.windowOptions.url + '" />').load(function(e) { 30382 var img = $(e.target), 30383 width, height, ccw, cch, chw, chh, wd, hd; 30384 div.find('.popin-image').css('height', 'auto').prepend(img); 30385 width = img.width(); 30386 height = img.height(); 30387 self.contentContainer.width(width); 30388 self.resize(); 30389 30390 // resize image to fit to content container 30391 ccw = self.contentContainer.outerWidth(true); 30392 cch = self.contentContainer.outerHeight(true); 30393 chw = self.holder.innerWidth(); 30394 chh = self.holder.innerHeight(); 30395 30396 if (ccw > chw || cch > chh) { 30397 wd = ccw - chw; 30398 hd = cch - chh; 30399 if (wd > hd) { 30400 img.width(width - wd); 30401 self.contentContainer.width(width - wd); 30402 } else { 30403 img.height(height - hd); 30404 self.contentContainer.width(parseInt((height - hd) * width / height, 10)); 30405 } 30406 30407 self.resize(); 30408 } 30409 }); 30410 }); 30411 30412 popin.plugin('error', { 30413 template: '<div class="message"><div></div><div class="buttons"><a class="button popin-close">' + 30414 '<span class="close-button-text">Close</span></a></div></div>', 30415 showClose: false 30416 }, function(div) { 30417 if (this.windowOptions.closeButtonText) { 30418 div.find('.close-button-text').html(this.windowOptions.closeButtonText); 30419 } 30420 div.find('.message div:first').html(this.windowOptions.message || "Something goes wrong"); 30421 }); 30422 }); 30423}; 30424/** 30425 * @fileOverview Small wrapper for sharing 30426 */ 30427/*globals myDeco, $, tmpl, window, screen */ 30428/*jslint white: false, forin: true, fragment: true, undef: true, nomen: true, plusplus: true, bitwise: false, regexp: false, newcap: true, sloppy: true */ 30429 30430myDeco.sharethis = { 30431 init: function (scope) { 30432 var self = this, 30433 url = window.location.href; 30434 scope = scope && !$.isFunction(scope) ? scope : $('.share-it'); 30435 30436 //function that return an url depending of event target classname 30437 function sharedUrl(element) { 30438 return element.hasClass('url-source') ? element.attr('href') : url; 30439 } 30440 30441 scope.find('.facebook').click(function (e) { 30442 e.stop(); 30443 var href = sharedUrl($(e.target)); 30444 self.stats(e, href, 'share-on-facebook'); 30445 self.openFacebook(href); 30446 }); 30447 30448 scope.find('.twitter').click(function (e) { 30449 e.stop(); 30450 var target = $(e.target), 30451 url = sharedUrl(target), 30452 ogp = $.ogp(), 30453 subj = target.attr('rel') ? target.attr('rel').split(':')[2] : window.document.title, 30454 message, 30455 container = $(this), 30456 dataMessage = container.data('message'); 30457 30458 if (!ogp.owner) ogp.owner = false; 30459 30460 if (!target.attr('rel') && !$.isEmptyObject(ogp) && ogp.type) { 30461 // OGP driven message 30462 message = tmpl('I love this: "<%= title %>" <%= type %> <% if (owner) { %>by <%= owner %><% } %>', ogp); 30463 } else if (myDeco.user.inviteSuccessCallback) { 30464 // I&E message 30465 message = 'I just joined mydeco.com to get a £10 voucher. Click ' + myDeco.urls.main + 'inviteuser/' + myDeco.username + '/ to get your own £10 voucher'; 30466 } else { 30467 message = 'I love this: ' + subj; 30468 } 30469 30470 if (dataMessage) { 30471 message = dataMessage + ' ' + subj; 30472 } 30473 30474 self.stats(e, url, 'share-on-twitter'); 30475 self.openTwitter(url, {text: message}); 30476 }); 30477 30478 scope.find('.stumble').click(function (e) { 30479 e.stop(); 30480 var href = sharedUrl($(e.target)); 30481 self.stats(e, href, 'share-on-stumble'); 30482 self.openStumble(href); 30483 }); 30484 30485 scope.find('.pinterest').click(function (e) { 30486 e.stop(); 30487 var href = sharedUrl($(e.target)); 30488 self.stats(e, href, 'share-on-pinterest'); 30489 self.openPinterest(href); 30490 }); 30491 30492 scope.find('.email').click(function (e) { 30493 e.stop(); 30494 30495 var element = $(e.target).closest('*[rel], *[href]'), 30496 meta = $('head meta[name="description"]'), 30497 params = $.extend(true, { 30498 subject: 'Check out ' + window.document.title.replace(/[\r\n\s]+/, ' '), 30499 body: (meta.size() > 0 ? meta.attr('content') + '\r\n\r\n' : '') + url 30500 }, (element.attr('rel') || element.attr('href')).toQueryParams()), 30501 values = [], 30502 name; 30503 30504 // can't use $.param here because returned result has %20 replaced with "+" 30505 for (name in params) { 30506 values.push(encodeURIComponent(name) + '=' + encodeURIComponent(params[name])); 30507 } 30508 self.stats(e, null, 'share-by-email'); 30509 window.location = 'mailto:?' + values.join('&'); 30510 }); 30511 30512 scope.find('.room_embed').click(function(e) { 30513 e.stop(); 30514 var rel = this.rel; 30515 getTitle = $('.for-embed-title').text(); 30516 30517 $.ajax({ 30518 url: myDeco.urls.blogRoomEmbedForm, 30519 dataType: 'json', 30520 type: 'get', 30521 data: {'id': rel}, 30522 success: function (data) { 30523 self.getEmbedCode(data); 30524 } 30525 }); 30526 }); 30527 30528 scope.find('.folder_embed').click(function(e) { 30529 e.stop(); 30530 var rel = this.rel; 30531 getTitle = $('.for-embed-title').text(); 30532 30533 $.ajax({ 30534 url: myDeco.urls.blogFolderEmbedForm, 30535 dataType: 'json', 30536 type: 'get', 30537 data: {'id': rel}, 30538 success: function(data) { 30539 self.getEmbedCode(data); 30540 self.colorPicker(); 30541 } 30542 }); 30543 30544 }); 30545 }, 30546 30547 stats: function (e, url, action) { 30548 var target = $(e.target), 30549 params = { 30550 url: url || window.location.href, 30551 action: action, 30552 ctype: target.data('ctype') || 'unknown', 30553 object_id: target.data('id') 30554 }, 30555 data = e ? target.closest('*[rel]').attr('rel') : false;
30556 30557 if (data) { 30558 data = data.split(':'); 30559 params.ctype = data[0]; 30560 params.object_id = data[1]; 30561 params.title = data[2] || window.document.title; 30562 } 30563 30564 myDeco.tracking.trackEvent(params.action, params.ctype, params.object_id); 30565 $.post(myDeco.urls.saveSharedItem, params, null, 'json'); 30566 }, 30567 30568 openFacebook: function (url) { 30569 window.open('http://www.facebook.com/sharer.php?' + $.param({u: url}), 30570 'facebook_share', 'toolbar=0,status=0,width=626,height=436'); 30571 }, 30572 30573 openTwitter: function (url, params) { 30574 // code extracted from twitter's share bookmarklet 30575 var width = 550, height = 450, screenHeight = screen.height, screenWidth = screen.width, 30576 left = Math.round((screenWidth / 2) - (width / 2)), 30577 top = screenHeight > height ? Math.round((screenHeight / 2) - (height / 2)) : 0; 30578 30579 params = $.param($.extend({ 30580 url: url, 30581 via: myDeco.settings.TWITTER_PARAMS.account, 30582 related: myDeco.settings.TWITTER_PARAMS.related 30583 }, params || {})); 30584 30585 window.open('http://twitter.com/share?' + params, '', 30586 ['left=', left, ',top=', top, ',width=', width, ',height=', height, ',personalbar=0,toolbar=0,scrollbars=1,resizable=1'].join('')); 30587 }, 30588 30589 openStumble: function (url) { 30590 window.open('http://www.stumbleupon.com/submit?' + $.param({url: url})); 30591 }, 30592 30593 openPinterest: function (url) { 30594 window.open(url, 'Share on Pinterest', 'toolbar=0,status=0,width=600,height=550'); 30595 }, 30596 30597 socialURL: function (id, type) { 30598 if (type === 'fb') { 30599 myDeco.facebook.loadConnect(function() { 30600 FB.XFBML.parse($('.social-row')[0]); 30601 }); 30602 return; 30603 } 30604 var urls = { 30605 tw: "//platform.twitter.com/widgets.js", 30606 pin: "http://assets.pinterest.com/js/pinit.js" 30607 }, js, fjs = document.getElementsByTagName('script')[0]; 30608 if (document.getElementById(id)) { 30609 return; 30610 } 30611 js = document.createElement('script'); 30612 js.id = id; 30613 js.src = urls[type]; 30614 fjs.parentNode.insertBefore(js, fjs); 30615 }, 30616 30617 /** 30618 * color picker function for Embed popin 30619 */ 30620 30621 colorPicker: function() { 30622 var colorList = [ '000000', '993300', '333300', '003300', '003366', '000066', '333399', '333333', 30623 '660000', 'FF6633', '666633', '336633', '336666', '0066FF', '666699', '666666', 30624 'CC3333', 'FF9933', '99CC33', '669966', '66CCCC', '3366FF', '663366', '999999', 30625 'CC66FF', 'FFCC33', 'FFFF66', '99FF66', '99CCCC', '66CCFF', '993366', 'CCCCCC', 30626 'FF99CC', 'FFCC99', 'FFFF99', 'CCffCC', 'CCFFff', '99CCFF', 'CC99FF', 'FFFFFF' ]; 30627 var picker = $('#color-picker'); 30628 30629 for (var i = 0; i < colorList.length; i++ ) { 30630 picker.append('<li class="color-item" data-hex="' + '#' + colorList[i] + '" style="background-color:' + '#' + colorList[i] + ';"></li>'); 30631 } 30632 30633 $('#popin-content-holder').click(function() { 30634 picker.fadeOut(100); 30635 }); 30636 30637 $('.call-picker').click(function(e) { 30638 e.stop(); 30639 picker.fadeIn(); 30640 picker.children('li').hover(function() { 30641 var codeHex = $(this).data('hex'); 30642 30643 $('.color-holder').css('background-color', codeHex); 30644 $('#pickcolor').val(codeHex); 30645 }); 30646 }); 30647 30648 }, 30649 30650 /** 30651 * function that open an Embed popin in dependence of params 30652 * grab and update embed code from template 30653 * @param {JSON} data - html of forms getting from ajax 30654 * @param {Array} params - set of options for js template 30655 **/ 30656 getEmbedCode: function(data, params) { 30657 var self = this; 30658 myDeco.popin.open({ 30659 //2 kinds of forms: form with options and embed code form itself 30660 template: params ? data.code_form : data.template, 30661 tracking: 'embed', 30662 showClose: false, 30663 width: 480, 30664 className: 'embed-form-popin', 30665 afterOpen: function (div) { 30666 var setWishName = $('#wishname').val(getTitle); 30667 // competition embed or second form 30668 if (params) { 30669 // get code from template and update code field 30670 var html = $.minifyHtml(tmpl('embed-tmpl', params)); 30671 div.find('textarea') 30672 .val(html) 30673 .select(); 30674 30675 // copy to clipboard with zclip plugin 30676 $('#copy-button').zclip({ 30677 path: "/media/ZeroClipboard.swf", 30678 copy: $('#copy-area').val(), 30679 afterCopy: function() { 30680 $('.copied').fadeIn(200).delay(400).fadeOut(200); 30681 } 30682 }); 30683 30684 } else { 30685 30686 var prepareParams = function (o) { 30687 //update popin if it was Select options form before 30688 var qparams = $(o).closest('form').serialize().toQueryParams(); 30689 var images = []; 30690 if (typeof(qparams.image) === 'string') { 30691 qparams.image = [qparams.image]; 30692 } 30693 for (var k = 0; k < qparams.image.length; k++) { 30694 var sp = qparams.image[k].split(';'); 30695 images.push({image_url: sp[0], 30696 title: sp[1], 30697 url: sp[2]}); 30698 } 30699 qparams.image = images; 30700 qparams.background = $.trim(qparams.custom_background); 30701 if (qparams.width == 'custom') { 30702 qparams.custom_width = $.trim(qparams.custom_width); 30703 if (isFinite(qparams.custom_width)) { 30704 qparams.width = qparams.custom_width <= 640 ? qparams.custom_width : 640; 30705 } else { 30706 qparams.width = 300; 30707 } 30708 } 30709 return qparams; 30710 } 30711 div.find('.continue').click(function (e) { 30712 e.stop(); 30713 var d = {data: prepareParams(this)}; 30714 self.getEmbedCode(data, d); 30715 30716 }); 30717 div.find('#preview').click(function (e) { 30718 e.stop(); 30719 var d = {data: prepareParams(this)}; 30720 var html = tmpl($('#embed-tmpl').html(), d); 30721 30722 myDeco.popin.open({ 30723 template: html, 30724 tracking: 'preview_embed', 30725 showClose: true, 30726 width: 710, 30727 height: 'auto', 30728 className: 'embed-form-popin'}); 30729 30730 $('.popin-close-title').click(function (e) { 30731 e.stop(); 30732 self.getEmbedCode(data, d); 30733 self.colorPicker(); 30734 }) 30735 }); 30736 div.find('input[name=product_links]').change(function (e) { 30737 var pl = $('#product_links_style'); 30738 if ($(this).is(':checked')) { 30739 pl.slideDown(); 30740 } else { 30741 pl.slideUp(); 30742 } 30743 }); 30744 div.find('select[name=width]').change(function (e) { 30745 var cwb = $('#custom_width_block'); 30746 if ($(this).val() == 'custom') { 30747 cwb.slideDown(); 30748 } else { 30749 cwb.slideUp(); 30750 } 30751 }); 30752 } 30753 } 30754 }); 30755 } 30756 30757 30758}; 30759 30760 30761 30762$(myDeco.sharethis.init.bind(myDeco.sharethis)); 30763myDeco.roomShare = { 30764 share: function(url) { 30765 $.get(url, function(template) { 30766 myDeco.popin.open({ 30767 template: template, 30768 width: 760, 30769 className: 'room-share', 30770 tracking: true, 30771 afterOpen: function(content) { 30772 content.parent().addClass('rounded'); 30773 30774 if ($.fn.iscroll) 30775 content.parent().iscroll(); 30776 30777 var layouts = content.find('ul li'), 30778 textarea = content.find('textarea, input').click(function(e) { 30779 $(this).select(); 30780 }).filter('textarea'); 30781 30782 layouts.click(function(e) { 30783 layouts.removeClass('selected'); 30784 textarea 30785 .val($.minifyHtml(content.find('.layout-type-' + (layouts.index($(this).addClass('selected')) + 1) + '-container').get(0).innerHTML)) 30786 .select(); 30787 }); 30788 } 30789 }); 30790 }); 30791 } 30792}; 30793/*globals myDeco, $ */ 30794/*jslint forin: true, fragment: true, onevar: true, undef: true, nomen: false, eqeqeq: true, plusplus: true, bitwise: true, newcap: true, immed: true, regexp: false, white: false, plusplus: false */ 30795myDeco.buttons = { 30796 30797 initSubmitButtons: function(elements) { 30798 var links = $(); 30799 30800 elements.each(function() { 30801 var button = $(this), 30802 link; 30803 30804 if (!button.hasClass('no-decorate')) { 30805 button.val(button.val() || "Submit"); 30806 30807 if (!button.attr('disabled')) { 30808 link = $('<a>').attr({ 30809 'href': '#', 30810 'class': button.attr('class') 30811 }); 30812 30813 links = links.add( 30814 link.addClass('mdtwo-button form-submit-button').html( 30815 $('<span>').html(button.val()) 30816 ) 30817 ); 30818 30819 button 30820 .after($('<div>').addClass('submit-button').html(link)) 30821 .css({ 30822 opacity: .01, 30823 position: 'absolute', 30824 left: '-9999em', 30825 top: '-9999em' 30826 }); 30827 } 30828 } 30829 }); 30830 30831 links.click(function(e) { 30832 e.stop(); 30833 var button = $(this); 30834 30835 // if button or some holder are not disabled, submit form 30836 if (!button.hasClass('no-submit') && 30837 button.closest('.disabled').size() === 0) { 30838 button.closest('form').submit(); 30839 } 30840 }); 30841 30842 return elements; 30843 }, 30844 30845 enable: function(form) { 30846 return form.removeClass('disabled'); 30847 }, 30848 30849 disable: function(form) { 30850 return form.addClass('disabled'); 30851 }, 30852 30853 isDisabled: function(form) { 30854 return form.hasClass('disabled'); 30855 } 30856 30857}; 30858 30859$(function(){ 30860 myDeco.buttons.initSubmitButtons($('input:submit')); 30861}); 30862/*globals $, myDeco, jQuery */ 30863/*jslint forin: true, fragment: true, onevar: true, undef: true, nomen: false, eqeqeq: true, plusplus: true, bitwise: false, newcap: true, immed: true, regexp: false, white: false, plusplus: false */ 30864/** 30865 * @fileOverview Support for draggable elements 30866 * Usage: <div class="position:absolute" class="draggable"><h1 class="handle">Drag me</h1></div> 30867 * Will start drag by key pressing on h1 element 30868 * If "restrict-bounds" class is added to draggable, element will check for c
30868ontainer bounds 30869 * Has three states: minimized, maximized and restored, each switch will fire event 30870 * maximized and minimized classNamed will be added to a element indicating state 30871 */ 30872 30873(function($) { 30874 $.fn.draggable = function(handleClass) { 30875 handleClass = '.' + (handleClass || 'handle'); 30876 30877 var updateSettings = function(e, element) { 30878 var values = element.data('settings') || {}, 30879 parent = element.offsetParent(), 30880 offset = element.offset(), 30881 offsetParent = parent.offset(), 30882 settings = $.extend(values, { 30883 element: element, 30884 offset: offset, 30885 parent: parent, 30886 offsetParent: offsetParent, 30887 restrict: element.hasClass('restrict-bounds'), 30888 offsetX: e ? e.pageX - offset.left + offsetParent.left : 0, 30889 offsetY: e ? e.pageY - offset.top + offsetParent.top : 0, 30890 offsetW: element.outerWidth(), 30891 offsetH: element.outerHeight(), 30892 width: parent.width(), 30893 height: parent.height() 30894 }); 30895 30896 element.data('settings', settings); 30897 return settings; 30898 }, 30899 getCoordinates = function(e, element, restrict) { 30900 var settings = element.data('settings'), 30901 x = e ? e.pageX - settings.offsetX : settings.offset.left, 30902 y = e ? e.pageY - settings.offsetY : settings.offset.top; 30903 30904 if (restrict || settings.restrict) { 30905 x = Math.max(0, Math.min(settings.width - settings.offsetW, x)); 30906 y = Math.max(0, Math.min(settings.height - settings.offsetH, y)); 30907 } 30908 30909 return {left: x, top: y}; 30910 }, 30911 setPosition = function(element, status) { 30912 var position; 30913 30914 myDeco.body.trigger('draggableGetPosition', [element, status, function(pos) { 30915 position = $.extend({ 30916 left: 'auto', 30917 top: 'auto', 30918 bottom: 'auto', 30919 right: 'auto' 30920 }, pos); 30921 }]); 30922 30923 if (typeof position !== 'object') { 30924 position = element.data('position'); 30925 } else { 30926 if (status !== 'minimize') { 30927 position = $.extend(position, { 30928 right: 'auto', 30929 bottom: 'auto' 30930 }); 30931 } 30932 element.css(position); 30933 } 30934 return element; 30935 }, 30936 minimize = function(element) { 30937 element.data('position', { 30938 left: element.css('left'), 30939 top: element.css('top'), 30940 right: element.css('right'), 30941 botom: element.css('bottom') 30942 }); 30943 30944 setPosition(element 30945 .trigger('minimize') 30946 .removeClass('maximized') 30947 .addClass('minimized'), 'minimize' 30948 ) 30949 .trigger('minimized'); 30950 }, 30951 restore = function(element) { 30952 setPosition(element 30953 .trigger('minimize') 30954 .removeClass('minimized maximized'), 'restore' 30955 ) 30956 .trigger('restored'); 30957 }, 30958 maximize = function(element) { 30959 setPosition(element 30960 .trigger('maximize') 30961 .removeClass('minimized') 30962 .addClass('maximized'), 'maximize' 30963 ) 30964 .trigger('maximized'); 30965 }, 30966 body = $('body'), 30967 self = this; 30968 30969 30970 $.each(this, function(i, element) { 30971 element = $(element); 30972 30973 var linkTo = $(), 30974 offset = false, 30975 pos, 30976 linkedListener = function(e, ee) { 30977 var el = $(this); 30978 30979 if (!ee) { 30980 return; 30981 } 30982 30983 if (e.type === 'move') { 30984 offset = element.offset(); 30985 offset.left = +ee.pageX - offset.left; 30986 offset.top = +ee.pageY - offset.top; 30987 } else if (e.type === 'moving') { 30988 element.css({ 30989 left: +ee.pageX - offset.left + 'px', 30990 top: +ee.pageY - offset.top + 'px' 30991 }); 30992 } else if (e.type === 'minimize' || e.type === 'maximize' || e.type === 'restore') { 30993 offset = el.offset(); 30994 offset.left += el.width(); 30995 offset.top += el.height(); 30996 pos = element.offset(); 30997 30998 if (offset.left < pos.left) { 30999 offset.left = pos.left - offset.left; 31000 offset.top = 0; 31001 } else if (offset.top < pos.top) { 31002 offset.left = 0; 31003 offset.top = pos.top - offset.top; 31004 } else { 31005 offset = false; 31006 } 31007 } else if (offset !== false && (e.type === 'minimized' || e.type === 'restored' || e.type === 'maximized')) { 31008 if (offset.top !== 0) { 31009 element.css('top', el.offset().top + el.height() + offset.top + 'px'); 31010 } else if (offset.left !== 0) { 31011 element.css('left', el.offset().left + el.width() + offset.left + 'px'); 31012 } 31013 offset = false;
31014 } else { 31015 offset = false; 31016 } 31017 }; 31018 31019 element 31020 .bind('linkTo', function(e, elements) { 31021 linkTo.unbind('move moving moved minimize minimized maximize maximized restore restored', linkedListener); 31022 linkTo = elements; 31023 linkTo.bind('move moving moved minimize minimized maximize maximized restore restored', linkedListener); 31024 }) 31025 .bind('checkRestrictions', function(e) { 31026 var element = $(this), 31027 coords; 31028 31029 updateSettings(e, element); 31030 31031 coords = getCoordinates(false, element, true); 31032 31033 element.css($.extend({ 31034 bottom: 'auto', 31035 right: 'auto' 31036 }, coords)); 31037 31038 }) 31039 31040 // rise window using zIndex on click 31041 .mousedown(function(e) { 31042 var element = $(this), 31043 elements, candidate = -1, cz = -1, ez; 31044 31045 // don't want to deal with non-windowed layers 31046 if (!element.hasClass('windowed')) { 31047 return; 31048 } 31049 31050 // find event owner zIndex 31051 ez = +element.css('zIndex') || 0; 31052 31053 // find candidate for zIndex switching 31054 elements = self 31055 // only windows except event owner 31056 .filter(function(i) { 31057 return self.eq(i).hasClass('windowed') && self.get(i) !== element.get(0); 31058 }) 31059 // with the max zIndex 31060 .each2(function(n, e) { 31061 var z = +e.css('zIndex') || 0; 31062 if (cz <= z) { 31063 cz = z; 31064 candidate = n; 31065 } 31066 }); 31067 31068 if (candidate !== -1) { 31069 // force zIndex increment in case of equality 31070 if (ez === cz) { 31071 ez++; 31072 } 31073 31074 // switch windows 31075 elements.eq(candidate).css('zIndex', Math.min(ez, cz)).trigger('lowered'); 31076 element.css('zIndex', Math.max(ez, cz)).trigger('raised'); 31077 } 31078 }) 31079 .find(handleClass) 31080 .mousedown(function(e) { 31081 e.preventDefault(); 31082 31083 if (element.hasClass('locked')) { 31084 return; 31085 } 31086 31087 element.removeData('position'); 31088 31089 // drop linking on self moving 31090 linkTo.unbind('move moving moved minimize minimized maximize maximized restore restored', linkedListener); 31091 element.trigger('move', [e]); 31092 31093 updateSettings(e, element); 31094 31095 var mousemove = function(e) { 31096 element.css($.extend({ 31097 bottom: 'auto', 31098 right: 'auto' 31099 }, getCoordinates(e, element))); 31100 element.trigger('moving', [e]); 31101 }, 31102 mouseup = function(e) { 31103 if (e.type !== 'mouseout' || e.type === 'mouseout' && e.target.tagName === 'BODY') { 31104 body 31105 .unbind('mousemove', mousemove) 31106 .unbind('mouseup', mouseup) 31107 .unbind('mouseout', mouseup); 31108 element.trigger('moved', [e]); 31109 } 31110 }; 31111 31112 body 31113 .mousemove(mousemove) 31114 .mouseout(mouseup) 31115 .mouseup(mouseup); 31116 31117 }) 31118 .dblclick(function(e) { 31119 if (element.hasClass('minimized')) { 31120 if (element.hasClass('maximized')) { 31121 maximize(element); 31122 } else { 31123 restore(element); 31124 } 31125 } else { 31126 minimize(element); 31127 } 31128 }) 31129 .find('.control').mousedown(function(e) { 31130 e.stop(); 31131 var button = $(e.target); 31132 31133 if (button.hasClass('minimize')) { 31134 minimize(element); 31135 } else if(button.hasClass('maximize')) { 31136 maximize(element); 31137 } else if(button.hasClass('restore')) { 31138 restore(element); 31139 } 31140 }); 31141 }); 31142 }; 31143}(jQuery)); 31144 31145jQuery(function($) { $('.draggable').draggable(); }); 31146/** 31147 * @fileOverview Component for customized file-upload fields 31148 */ 31149 31150myDeco.fileInputs = { 31151 /** 31152 * jQuery set of elements 31153 */ 31154 elements: [], 31155 /** 31156 * Init fileinputs, hide input[type=file] and create overlap input[type=text] with browse button 31157 * If class names on input[type=file] are specified, these class names will be added to browse button also 31158 * @params {Element} scope DOM element for searching thru or empty in case of global replacing 31159 */ 31160 init : function(scope) { 31161 31162 var self = this, 31163 elements = scope == undefined 31164 ? $('.fileinputs:has(input:file)') 31165 : scope.find('.fileinputs:has(input:file)'); 31166 31167 elements.each(function() { 31168 var holder = $(this), 31169 file = holder.find(':file'), 31170 val = holder.data('buttontext') || 'Browse', 31171 // used for styling upload button, remove all validation-related classes 31172 classNames = $.grep(file.attr('className').split(' '), function(c) { 31173 return c != 'validate' && c.substring(0, 2) != 'v-'; 31174 }).join(' '),
31175 overlay = holder.find('div.fakefile'), 31176 text = overlay.find('input:text'); 31177 31178 if (text.size() == 0) 31179 text = $('<input type="text" class="text" value="" />'); 31180 31181 if (overlay.size() == 0) { 31182 overlay = $('<div class="fakefile"></div>') 31183 .append($('<div class="field"></div>').append(text)) 31184 .append('<a class="button ' + classNames + '"><span>' + val + '</span></a>'); 31185 holder.prepend(overlay); 31186 } 31187 31188 file.addClass('file').bind('change', function(e) { 31189 text.addClass('withtext').val(file.val().replace(/^.*?([^\/\\]+)$/, '$1')); 31190 }); 31191 }); 31192 31193 this.elements = elements; 31194 } 31195} 31196 31197$(function() { myDeco.fileInputs.init(); }); 31198/*globals myDeco, $ */ 31199/*jslint forin: true, fragment: true, onevar: true, undef: true, nomen: false, eqeqeq: true, plusplus: true, bitwise: true, newcap: true, immed: true, regexp: false, white: false, plusplus: false */ 31200/** 31201 * @fileOverview Form field values validation tools 31202 */ 31203myDeco.formValidation = { 31204 /** 31205 * List of default messages for errors 31206 * @type {Object} 31207 */ 31208 _messages: { 31209 'required': 'Please ensure each field is complete before you re-submit.', 31210 'number': 'Please enter a number.', 31211 'required-inbox': 'Oops! You forgot your message!', 31212 'email': 'We\'d love you to bend the rules in room design, but not in our forms! Sorry, our system doesn\'t recognise your email address. Please check and try again.', 31213 'password': 'Passwords must be 5-15 characters and may contain numbers, letters and underscores.', 31214 'passwordconfirm': 'Your passwords must match.', 31215 'date': 'Must be a valid date of the form dd/mm/yyyy. Eg: 19/09/2007', 31216 'url': 'Please enter a valid url that begins with http://', 31217 'no-whitespace-only': 'Sorry, we do not accept whitespace-only input.', 31218 'html': 'Should not contain any HTML-like tags', 31219 'word-length': 'Oops! Please make sure words in your comment do not exceed %1 characters in length.', 31220 'letters': 'May contain only letters, numbers, spaces and undercores', 31221 'postcode': 'Please enter valid postcode' 31222 }, 31223 31224 /** 31225 * Initializes form validation by finding 31226 * non popup/inline forms with "validate" class. 31227 * @param {jQuery} scope Scope for form finding 31228 */ 31229 init: function(scope) { 31230 scope = typeof scope !== 'object' ? myDeco.body : scope; 31231 31232 var self = this; 31233 scope.find('form.validate').submit(function(e) { 31234 var form = $(this); 31235 if (!self.validate(form, form.hasClass('inline'))) { 31236 e.stop(); 31237 } 31238 }); 31239 }, 31240 31241 /** 31242 * Finds form elements to validate by "validate" class. 31243 * Calls validation routine and applies bubble if it fails. 31244 * 31245 * @param {jQuery} form Form to validate 31246 * @param {jQuery} inline use inline error messages, find them using .error.{input name} css selector 31247 * @param {Object} messages Associative massive of custom error messages 31248 * @returns {Boolean} Is form valid. 31249 */ 31250 validate: function(form, inline, messages) { 31251 var self = this, 31252 hasErrors = false; 31253 31254 if (inline) { 31255 form.find('.error').remove(); 31256 } 31257 31258 form.find('.validate').each(function() { 31259 var element = $(this), 31260 parent = element.parent(), 31261 message = self.isInvalid(element, messages); 31262 31263 if (message) { 31264 self.showError(element, message, inline); 31265 hasErrors = true; 31266 } 31267 31268 if (hasErrors) { 31269 parent.removeClass('valid') 31270 parent.addClass('invalid') 31271 } else { 31272 parent.addClass('valid'); 31273 parent.removeClass('invalid') 31274 } 31275 }); 31276 return !hasErrors; 31277 }, 31278 31279 /** 31280 * Shows error message for given element 31281 * @param {jQuery Element} element 31282 * @param {String} message 31283 * @param {Boolean} inline, message type (true = 'inline' or false = 'bubble') 31284 */ 31285 showError: function(element, message, inline) { 31286 if (inline) { 31287 element.before($('<div class="error" />').addClass(element.attr('name')).text(message)); 31288 } else { 31289 var form = element.parents('form'), 31290 useContainer = form.hasClass('bubble-container') || form.closest('#popin-content').size() > 0; 31291 // remove old bubble if exist 31292 this.clearError(element); 31293 if (message) { 31294 element.data('bubble', myDeco.bubbles.create(element, message, useContainer)); 31295 } 31296 } 31297 }, 31298 31299 /** 31300 * Remove old error bubble if exist 31301 * @param {jQuery Elements} Elements 31302 */ 31303 clearError: function(elements) { 31304 $.each(elements, function(){ 31305 var element = $(this); 31306 if (element.data('bubble')) { 31307 element.data('bubble').remove(); 31308 element.removeData('bubble'); 31309 } 31310 }); 31311 }, 31312 31313 /**
31314 * Performs validation of form element's value by class name. 31315 * Available validation classes are:<br /> 31316 * <ul> 31317 * <li>"v-required" - checks if field's value is not empty</li> 31318 * <li>"v-number" - checks if field's value is a number</li> 31319 * <li>"v-required-inbox" - checks if message field is not empty</li> 31320 * <li>"v-email" - checks if email is correct</li> 31321 * <li>"v-password" - checks if password is correct</li> 31322 * <li>"v-passwordconfirm" - checks if both password fields match</li> 31323 * <li>"v-date" - checks correctness of date input</li> 31324 * <li>"v-url" - checks if field's value is valid URL</li> 31325 * <li>"v-word-length-<b><maxLength></b>" - checks if words in the field 31326 * don't exceed <b><maxLength></b></li> 31327 * <li>"v-no-whitespace-only" - checks if field's value has visible symbols</li> 31328 * <li>"v-no-html" - checks if field value hasn't contain HTML-like tags</li> 31329 * <li>"v-letters" - checks if field value contains only letters and numbers</li> 31330 * <li>"v-postcode" - checks if postcode valid. Avaliable for US, UK, FR</li> 31331 * </ul> 31332 * 31333 * @param {jQuery} el Element to validate 31334 * @param {Object} messages Custom error messages 31335 * @returns {Boolean} Is element valid 31336 */ 31337 isInvalid : function(el, messages, countryCode) { 31338 if(!el.is(':visible')) { 31339 return false; 31340 } 31341 31342 countryCode = countryCode || 'United Kingdom'; 31343 var value = el.val(), 31344 params = [], p, 31345 error = false; 31346 31347 if(el.hasClass('v-required') && !value) { 31348 error = 'required'; 31349 } else if (el.hasClass('v-number') && value.match(/\D/)) { 31350 error = 'number'; 31351 } else if(el.hasClass('v-required-inbox') && !value) { 31352 error = 'required-inbox'; 31353 } else if(el.hasClass('v-email') && value && !this.validateEmail(value)) { 31354 error = 'email'; 31355 } else if(el.hasClass('v-password') && !this.validatePassword(value)) { 31356 error = 'password'; 31357 } else if(el.hasClass('v-passwordconfirm') && !this.validatePasswordConfirm(value)) { 31358 error = 'passwordconfirm'; 31359 } else if(el.hasClass('v-date') && !this.validateDate(value)) { 31360 error = 'date'; 31361 } else if(el.hasClass('v-url') && !this.validateUrl(value)) { 31362 error = 'url'; 31363 } else if(el.hasClass('v-no-whitespace-only') && !/[^\s]/.test(value)) { 31364 el.val(""); 31365 error = 'no-whitespace-only'; 31366 } else if (el.hasClass('v-no-html') && !this.validateNoHTML(value)) { 31367 error = 'no-html'; 31368 } else if ((params = el.attr('class').match(/v-word-length-(\d+)/)) && !this.validateWordLength(value, params[1])) { 31369 error = 'word-length'; 31370 // remove global match 31371 params.shift(); 31372 } else if (el.hasClass('v-letters') && !this.lettersOnly(value)) { 31373 error = 'letters'; 31374 } else if (el.hasClass('v-postcode') && !this.validPostcode(value, countryCode)) { 31375 error = 'postcode'; 31376 } 31377 31378 if (error !== false) { 31379 error = ($.extend({}, this._messages, messages || {})[error] || 'Error is occured'); 31380 if (params && params.length !== 0) { 31381 error = error.replace(/%?%(\d+)/, function(str, num) { 31382 return str.indexOf('%%') === 0 ? str : params[num - 1]; 31383 }); 31384 } 31385 } 31386 31387 return error; 31388 }, 31389 31390 /** 31391 * Checks email 31392 * 31393 * @param {String} sVal Validation value 31394 * @returns {Boolean} Is element valid 31395 */ 31396 validateEmail: function(sVal){ 31397 // do the comparison, if we have a match write thank you or else the email is invalid 31398 // {2,6} - 2 usual geo domains, 6 - museum 31399 return (/^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$/i).test(sVal); 31400 }, 31401 31402 /** 31403 * Checks password 31404 * 31405 * @param {String} sVal Validation value 31406 * @returns {Boolean} Is element valid 31407 */ 31408 validatePassword: function(sVal) { 31409 // Description: The password's first character must be a letter, it must contain at least 4 characters 31410 // and no more than 15 characters and no characters other than letters, numbers and the underscore may be used 31411 // Matches: [abcd], [aBc45DSD_sdf], [password] (http://www.regexlib.com) 31412 // do the comparison, if we have a match write thank you or else the email is invalid 31413 return (/^[a-zA-Z]\w{3,14}$/).test(sVal); 31414 }, 31415 31416 /** 31417 * Checks if passwords match 31418 * 31419 * @param {String} sVal Validation value 31420 * @returns {Boolean} Is element valid 31421 */ 31422 validatePasswordConfirm: function(sVal) { 31423 return $('#sign-up-password1, #id_password1').val() === sVal; 31424 }, 31425 31426 /** 31427 * Checks date 31428 * 31429 * @param {String} sVal Validation value 31430 * @returns {Boolean} Is element valid 31431 */ 31432 validateDate: function(sVal) { 31433 return (/(((0[13578]|10|12)([\-.\/])(0[1-9]|[12][0-9]|3[01])([\-.\/])(\d{4}))|((0[469]|11)([\-.\/])([0][1-9]|[12][0-9]|30)([\-.\/])(\d{4}))|((2)([\-.\/])(0[1-9]|1[0-9]|2[0-8])([\-.\/])(\d{4}))|((2)(\.|-|\/)(29)([\-.\/])([02468][048]00))|((2)([\-.\/])(29)([\-.\/])([13579][26]00))|((2)([\-.\/])(29)([\-.\/])([0-9][0-9][0][48]))|((2)([\-.\/])(29)([\-.\/])([0-9][0-9][2468][048]))|((2)([\-.\/])(29)([\-.\/])([0-9][0-9][13579][26])))/).test(sVal); 31434 }, 31435 31436 /** 31437 * Checks URL 31438 * 31439 * @param {String} sVal Validation value 31440 * @returns {Boolean} Is element valid 31441 */ 31442 validateUrl: function(sVal) { 31443 return (/(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/).test(sVal); 31444 }, 31445 31446 /** 31447 * Checks if words don't exceed <b>maxLength</b> 31448 * 31449 * @param {String} value Validation value 31450 * @param {Integer} length Max length of single word in text 31451 * @returns {Boolean} Is element valid 31452 */ 31453 validateWordLength: function(value, length) { 31454 var isUrl = this.validateUrl; 31455 return $.grep(value.split(/\s+/), function(value) { 31456 return value.length > length && !isUrl(value); 31457 }).length === 0; 31458 }, 31459 31460 /** 31461 * Check's that string hasn't contain any HTML-like symbols 31462 * 31463 * @param {String} sVal Validation value 31464 * @returns {Boolean} Is element hasn't contain HTML-like tags 31465 */ 31466 validateNoHTML: function(sVal) { 31467 return ! /<[a-zA-Z]+(\s[^>]*)?>/.test(sVal); 31468 }, 31469 31470 /** 31471 * Check's that string contain only letters and numbers 31472 * 31473 * @param {String} val Validation value 31474 * @returns {Boolean} contain or not 31475 */
31476 lettersOnly: function(val) { 31477 return (/^[a-zA-Z0-9 _]*$/).test(val) 31478 }, 31479 31480 validPostcode: function(val, countryCode) { 31481 if (val) { 31482 switch (countryCode) { 31483 case 'FR': 31484 return (/^FR?-\d{5}$/).test(val); 31485 break; 31486 case 'US': 31487 return (/^\d{5}(-\d{4})?$/).test(val); 31488 break; 31489 case 'UK': 31490 return (/(GIR 0AA)|(((A[BL]|B[ABDHLNRSTX]?|C[ABFHMORTVW]|D[ADEGHLNTY]|E[HNX]?|F[KY]|G[LUY]?|H[ADGPRSUX]|I[GMPV]|JE|K[ATWY]|L[ADELNSU]?|M[EKL]?|N[EGNPRW]?|O[LX]|P[AEHLOR]|R[GHM]|S[AEGKLMNOPRSTY]?|T[ADFNQRSW]|UB|W[ADFNRSV]|YO|ZE)[1-9]?[0-9]|((E|N|NW|SE|SW|W)1|EC[1-4]|WC[12])[A-HJKMNPR-Y]|(SW|W)([2-9]|[1-9][0-9])|EC[1-9][0-9]) [0-9][ABD-HJLNP-UW-Z]{2})/).test(val); 31491 break; 31492 default: 31493 return true; 31494 break; 31495 } 31496 } else { 31497 return 'empty'; 31498 } 31499 } 31500 31501 31502}; 31503 31504$(function() { 31505 myDeco.formValidation.init(); 31506}); 31507/*globals window, jQuery */ 31508/*jslint forin: true, fragment: true, onevar: true, undef: true, eqeqeq: true, bitwise: true, newcap: true, regexp: false, white: false, plusplus: false */ 31509(function($) { 31510 $.fn.slider = function(options) { 31511 options = $.extend({ 31512 min: 0, 31513 max: 100, 31514 value: null, 31515 borders: 0, 31516 direction: 'horizontal', 31517 formatData: function(value, label) { 31518 return $.isArray(value) ? value : value.toFixed(2); 31519 }, 31520 // Scale correction functions for custom scaling, example of functions that can be overridden in custom files (e.c. minisearch.js) 31521 positionToValueScale: function(x) { return x; }, 31522 valueToPositionScale: function(x) { return x; }, 31523 timeout: null, 31524 onChange: null, 31525 onSlide: null, 31526 invert: false 31527 }, options); 31528 31529 var sizeOp, posField, eventField, self = this; 31530 31531 switch (options.direction) { 31532 case 'horizontal': 31533 sizeOp = 'width'; 31534 posField = 'left'; 31535 eventField = 'clientX'; 31536 break; 31537 case 'vertical': 31538 sizeOp = 'height'; 31539 posField = 'top'; 31540 eventField = 'clientY'; 31541 options.invert = !options.invert; 31542 break; 31543 default: 31544 throw "Wrong value for slider direction"; 31545 } 31546 31547 if (options.max <= options.min) { 31548 options.max = options.min + 1; 31549 } 31550 31551 options.range = options.max - options.min; 31552 options.scaleRatio = options.max / (options.min ? options.min : 1); 31553 31554 function boundValue(value) { 31555 return Math.min(options.max, 31556 Math.max(options.min, value)); 31557 } 31558 31559 function updateClosestItem(value, array) { 31560 var delta, closest; 31561 $.each(array, function(i, val){ 31562 var d = Math.abs(val - value); 31563 if (typeof delta === 'undefined' || d <= delta) { 31564 delta = d; 31565 closest = i; 31566 } 31567 }); 31568 array[closest] = value; 31569 return array; 31570 } 31571 31572 function cloneValue(value) { 31573 return $.isArray(value) ? $.merge([], value) : value; 31574 } 31575 31576 // stop dragging if mouse is up outside of slider 31577 $('body').bind('mouseup mouseleave', function(){ 31578 self.trigger('stop-dragging'); 31579 }); 31580 31581 return this.each(function(i, element) { 31582 var slider = $(this), 31583 handlers = slider.find('.slider-handle'), 31584 ruler = slider.find('.slider-track'), 31585 rulerSize, handlerSize, rulerOffset, 31586 minValueLabel = slider.find('.min-value'), 31587 maxValueLabel = slider.find('.max-value'), 31588 currValueLabel = slider.find('.curr-value'), 31589 drag = false, 31590 sliderTimeout, 31591 oldSliderValue, 31592 sliderValue, 31593 setOffset = function() { 31594 rulerOffset = ruler.offset()[posField]; 31595 rulerOffset += options.borders; 31596 }, 31597 updateBounds = function() { 31598 minValueLabel.html(options.formatData(options.min)); 31599 maxValueLabel.html(options.formatData(options.max)); 31600 }, 31601 setHandlers = function(handlers, value) { 31602 handlers.each2(function(i, handler) { 31603 var val = (typeof(value) !== 'object') ? value : value[i], 31604 // relative position [0...1] 31605 pos = (options.invert ? options.max - val : val - options.min) / options.range; 31606 // apply custom scaling 31607 pos = options.valueToPositionScale(pos); 31608 // calculate absolute hanler position 31609 pos = Math.round(pos * rulerSize - handlerSize / 2 + options.borders) + 'px'; 31610 handler.css(posField, pos); 31611 currValueLabel 31612 .css(posField, pos); 31613 }); 31614 }, 31615 getHandlerPosition = function(handler) { 31616 return Math.round(handler.offset()[posField] + handlerSize / 2); 31617 }, 31618 prepareValue = function(value) { 31619 if ($.isArray(value)) { 31620 // direct value set in multihandler mode 31621 value = $.map(value, boundValue); 31622 } else { 31623 value = $.isArray(sliderValue) ? 31624 updateClosestItem(value, sliderValue) : 31625 boundValue(value); 31626 } 31627 return value; 31628 }, 31629 setValue = function(value) { 31630 sliderValue = prepareValue(value); 31631 setHandlers(handlers, sliderValue); 31632 currValueLabel 31633 .html(options.formatData(cloneValue(sliderValue))); 31634 31635 if ($.isFunction(options.onSlide)) { 31636 options.onSlide(cloneValue(sliderValue)); 31637 } 31638 }, 31639 positionToValue = function(position) { 31640 var value; 31641 31642 if (position - 1 <= rulerOffset) { 31643 value = (options.invert ? options.max : options.min); 31644 } else if (position >= rulerOffset + rulerSize) { 31645 value = (options.invert ? options.min : options.max); 31646 } else { 31647 // relative position 31648 value = (position - rulerOffset) / rulerSize; 31649 // custom scale correction 31650 value = options.positionToValueScale(value); 31651 // calculating absolute value 31652 value = options.invert ? 31653 options.max - value * options.range : 31654 options.min + value * options.range; 31655 } 31656 return value; 31657 }; 31658 31659 // no ruler — no slider 31660 if (ruler.size() === 0) { 31661 return false;
31662 } 31663 31664 // set slider dimentions 31665 handlerSize = handlers[sizeOp](); 31666 rulerSize = ruler[sizeOp]() - options.borders * 2; 31667 setOffset(); 31668 31669 updateBounds(); 31670 setValue($.isArray(options.value) || typeof options.value === 'number' ? 31671 options.value : [options.min, options.max]); 31672 31673 ruler 31674 .mousedown(function(event) { 31675 if (slider.hasClass('disabled')) return; 31676 setOffset(); 31677 drag = true; 31678 window.clearTimeout(sliderTimeout); 31679 setValue(positionToValue(event[eventField])); 31680 }) 31681 .mouseup(function(event) { 31682 var value; 31683 drag = false; 31684 if ($.isFunction(options.onChange) && oldSliderValue !== sliderValue.toString()) { 31685 value = cloneValue(sliderValue); 31686 if (options.timeout) { 31687 window.clearTimeout(sliderTimeout); 31688 sliderTimeout = window.setTimeout(function() { 31689 options.onChange(value); 31690 }, options.timeout); 31691 } else { 31692 options.onChange(value); 31693 } 31694 } 31695 oldSliderValue = sliderValue.toString(); 31696 event.stop(); 31697 }) 31698 .mousemove(function(event) { 31699 if (drag) { 31700 setValue(positionToValue(event[eventField])); 31701 } 31702 }); 31703 31704 slider 31705 .bind('set-slider-value', function(event, data) { 31706 setValue(data.value); 31707 }) 31708 .bind('update-slider-values', function(event) { 31709 updateBounds(); 31710 currValueLabel 31711 .html(options.formatData(cloneValue(sliderValue))); 31712 }) 31713 .bind('stop-dragging', function(){ 31714 if (drag) { 31715 ruler.mouseup(); 31716 } 31717 }); 31718 }); 31719 }; 31720}(jQuery)); 31721/** 31722 * Simple jQuery plugin for vertical finger scrolling on iPad 31723 */ 31724 31725(function($) { 31726 31727 var isMobile = navigator.platform.slice(0, 2) === "iP"; 31728 31729 $.fn.iscroll = function() { 31730 if (!isMobile) 31731 return this; 31732 31733 this.each(function() { 31734 var y, move = false, element = $(this); 31735 31736 element 31737 .bind('mousemove', function(e) { 31738 if (move) { 31739 element.scrollTop(element.scrollTop() + y - e.pageY); 31740 y = e.pageY; 31741 } 31742 }) 31743 .bind('mouseup', function(e) { 31744 move = false; 31745 }) 31746 .bind('mousedown', function(e) { 31747 // e.preventDefault(); 31748 move = true; 31749 y = e.pageY; 31750 }); 31751 }); 31752 31753 return this; 31754 }; 31755})(jQuery); 31756/* jQuery Autocomplete 31757 * Version 1.0 31758 * Written by Yehuda Katz ([email protected]) and Rein Henrichs ([email protected]) 31759 * @requires jQuery v1.2, jQuery dimensions plugin 31760 * 31761 * Copyright 2007 Yehuda Katz, Rein Henrichs 31762 * Dual licensed under the MIT and GPL licenses: 31763 * http://www.opensource.org/licenses/mit-license.php 31764 * http://www.gnu.org/licenses/gpl.html 31765 * 31766 */ 31767 31768/* 31769 * @description Form autocomplete plugin using preloaded or Ajax JSON data source 31770 * 31771 * @example $('input#user-name').autocomplete({list: ["quentin", "adam", "admin"]}) 31772 * @desc Simple autocomplete with basic JSON data source 31773 * 31774 * @example $('input#user-name').autocomplete({ajax: "/usernames.js"}) 31775 * @desc Simple autocomplete with Ajax loaded JSON data source 31776 * 31777 * Modified by Stanislav Yadykin at 22/12/2010 31778 * Added popup position and relative option passing for autocomplete event 31779 * Relative option will insert dropdown into offset parent instead of body 31780 */ 31781 31782 31783(function($) { 31784 $.ui = $.ui || {}; $.ui.autocomplete = $.ui.autocomplete || {}; var active; $.ui.autocomplete.ext = $.ui.autocomplete.ext || {}; 31785 31786 //Initializing autocomplete plugin for search (ajax callback and UI); moved here because ui.init.js is absent in Austin 31787 $.ui.autocomplete.ext.ajax = function(opt) { 31788 var ajax = opt.ajax; 31789 31790 return { getList: function(input) { 31791 var val = input.val(); 31792 if (!val) 31793 return false; 31794 31795 // uncomment for local testing search autocomplete 31796 // return input.trigger("updateList", [[{"match": "art"}, {"match": "arte"}, {"match": "arts"}, {"match": "artfulness"}, {"match": "armchair"}, {"match": "armchaire"}, {"match": "armchairs"}, {"match": "applicance"}, {"match": "application"}, {"match": "applicances"}]]); 31797 $.getJSON(ajax, opt.param + "=" + val, function(json) { 31798 input.trigger("updateList", [json.result || json]); 31799 }); 31800 } }; 31801 }; 31802 31803 $.fn.autocompleteMode = function(container, input, size, opt) { 31804 var original = input.val(); var selected = -1; var self = this; 31805 31806 input.parent().find('input.search-submit').click(function(e){ 31807 e.stopPropagation(); 31808 }); 31809 31810 $.data(document.body, "autocompleteMode", true); 31811 31812 $("body").one("cancelAutocomplete", function() { 31813 input.trigger("cancelAutocomplete"); $("body").trigger("offAutocomplete"); input.val(original); 31814 }); 31815 31816 $("body").one("activateAutocomplete", function() { 31817 if (active) 31818 input.trigger("activateAutocomplete", [$.data(active[0], "originalObject")]); $("body").trigger("offAutocomplete"); 31819 }); 31820 31821 $("body").one("offAutocomplete", function(e, reset) { 31822 container.remove(); 31823 $.data(document.body, "autocompleteMode", false);
31824 input.unbind("keydownAutocomplete"); 31825 $("body").add(window).unbind("click.autocomplete").unbind("cancelAutocomplete").unbind("activateAutocomplete"); 31826 }); 31827 31828 // If a click bubbles all the way up to the window, close the autocomplete 31829 $(window).bind("click.autocomplete", function(e) { 31830 $("body").trigger("cancelAutocomplete"); 31831 }); 31832 31833 var select = function() { 31834 if (selected == -1) 31835 return false; 31836 active = $("ul > *", container).removeClass("selected").slice(selected, selected + 1).addClass("selected"); 31837 input.trigger("itemSelected.autocomplete", [$.data(active[0], "originalObject")]); 31838 input.val(opt.insertText($.data(active[0], "originalObject"))); 31839 }; 31840 31841 container.mouseover(function(e) { 31842 // If you hover over the container, but not its children, return 31843 if(e.target == container[0]) return; 31844 // Set the selected item to the item hovered over and make it active 31845 selected = $("ul > *", container).index($(e.target).is('li') ? $(e.target)[0] : $(e.target).parents('li')[0]); select(); 31846 }).bind("click.autocomplete", function(e) { 31847 $("body").trigger("activateAutocomplete"); $.data(document.body, "suppressKey", false); 31848 $('#query').focus(); 31849 }); 31850 31851 input 31852 .bind("keydownAutocomplete", function(e, trueEvent) { 31853 e.keyCode = e.keyCode || trueEvent.keyCode; 31854 code = e.keyCode || e.charCode || e.which; 31855 if (code == 27) { 31856 $("body").trigger("cancelAutocomplete"); 31857 } else if (code == 40 || code == 9 || code == 38) { 31858 switch(code) { 31859 case 40: 31860 case 9: 31861 selected = selected >= size - 1 ? 0 : selected + 1; break; 31862 case 38: 31863 selected = selected <= 0 ? size - 1 : selected - 1; break; 31864 default: break; 31865 } 31866 select(); 31867 } else if (code == 13) { 31868 $("body").trigger("offAutocomplete"); 31869 } else { 31870 return true; 31871 } 31872 31873 $.data(document.body, "suppressKey", true); 31874 }); 31875 }; 31876 31877 $.fn.autocomplete = function(opt) { 31878 31879 opt = $.extend({}, { 31880 timeout: 1000, 31881 getList: function(input) { input.trigger("updateList", [opt.list]); }, 31882 template: function(str) { return "<li>" + opt.insertText(str) + "</li>"; }, 31883 insertText: function(str) { return str; }, 31884 fixedTop: false, // custom option refMDTWO-1381 31885 wrapper: "<ul class='jq-ui-autocomplete'></ul>" 31886 }, opt); 31887 31888 if ($.ui.autocomplete.ext) { 31889 for (var ext in $.ui.autocomplete.ext) { 31890 if (opt[ext]) { 31891 opt = $.extend(opt, $.ui.autocomplete.ext[ext](opt)); 31892 delete opt[ext]; 31893 } 31894 } 31895 } 31896 31897 return this.each(function() { 31898 $(this) 31899 .keyup(function(e) { 31900 var typingTimeout = $.data(this, "typingTimeout"); 31901 31902 $(this).trigger("keydownAutocomplete", e); 31903 31904 if (typingTimeout) 31905 window.clearInterval(typingTimeout); 31906 31907 if ($.data(document.body, "suppressKey")) { 31908 return $.data(document.body, "suppressKey", false); 31909 } else if ($.data(document.body, "autocompleteMode") && e.charCode < 32 && e.keyCode != 8 && e.keyCode != 46 && this.dataset && e.keyCode != this.dataset.customkeycode) { 31910 return false; 31911 } else { 31912 $.data(this, "typingTimeout", window.setTimeout(function() { 31913 $(e.target).trigger("autocomplete"); 31914 }, opt.timeout)); 31915 } 31916 }) 31917 .bind("autocomplete", function(e, opts) { 31918 var self = $(this); 31919 31920 opts = $.extend({ 31921 position: 'bottom', 31922 relative: false 31923 }, opts || {}); 31924 31925 self.one("updateList", function(e, list) { 31926 list = $(list) 31927 .map(function() { 31928 var node = $(opt.template(this))[0]; 31929 $.data(node, "originalObject", this); 31930 return node; 31931 }); 31932 31933 $("body").trigger("offAutocomplete"); 31934 31935 if (!list.length) 31936 return false; 31937 31938 var container = $('ul', opt.wrapper).html(list).end(), 31939 offset = opts.relative ? self.position() : self.offset(), 31940 css = {left: offset.left}, 31941 parent = opts.relative ? self.offsetParent() : $(document.body); 31942 31943 if (opts.position === 'bottom') { 31944 if (!opt.fixedTop) { 31945 css.top = offset.top + self.outerHeight(); 31946 } 31947 } else { 31948 css.bottom = parent.outerHeight() - offset.top - parseInt(self.css('marginTop') || 0); 31949 } 31950 31951 opt.container = container 31952 .css(css) 31953 .appendTo(parent) 31954 .mouseleave(function(){ 31955 $(this).data('hideTimeout', setTimeout(function(){ 31956 $("body").trigger("cancelAutocomplete"); 31957 }, 1000)); 31958 }) 31959 .mouseenter(function(){ 31960 clearTimeout($(this).data('hideTimeout')); 31961 }); 31962 31963 $("body").autocompleteMode(container, self, list.length, opt); 31964 }); 31965 opt.getList(self); 31966 }); 31967 }); 31968 }; 31969})(jQuery); 31970 31971(function($) { 31972 $.fn.htmlDropdown = function(opts) { 31973 opts = opts || {}; 31974 return this.each(function() { 31975 var select = $(this), 31976 id = select.attr('id'), 31977 input = $('<input>'); 31978 select.after( 31979 input.attr({ 31980 type: 'text', 31981 value: this.options[this.selectedIndex].text, 31982 readOnly: true, 31983 id: id + '-combo', 31984 className: 'combo' 31985 }) 31986 .data('parentSelect', select[0]).autocomplete($.extend({}, { 31987 list: $.map(this.options, function(option){return option.text}), 31988 insertText: function(obj) { return obj }, 31989 wrapper: '<div id="' + id + '_options"><ul></ul></div>' 31990 }, opts)) 31991 .click(function(e) { 31992 e.preventDefault(); 31993 e.stopPropagation(); 31994 $(this).trigger("autocomplete", [opts]).blur(); 31995 }) 31996 .bind("activateAutocomplete", function(e, d) { 31997 $.each($(e.target).data('parentSelect').options, function(i, option){ 31998 if (option.text == d) 31999 option.selected = true; 32000 }); 32001 }) 32002 ) 32003 .hide().change(function(e) { 32004 input.val(select.find('option:selected').text() || ''); 32005 }); 32006 }); 32007 } 32008})(jQuery); 32009 32010/** 32011 * jQuery plugin for init and positioning bubbles 32012 * Usage: 32013 * $('some-selector').rearrange(options) 32014 * Options are: 32015 * anchor {jQ} Element positioning will be related to 32016 * position {Array} Array of positioning elements 32017 * Values are: left, right, top, bottom, center, digits (including negative) 32018 * Default is to ['center', 'center'] 32019 * 32020 * If no anchor is specified positioning will be related to parent container 32021 */ 32022 32023jQuery(function($) { 32024 32025 $.fn.rearrange = function(options) { 32026 options = $.extend({ 32027 anchor: $(), 32028 // relative to offset parent position 32029 position: [false, false] 32030 }, options || {}); 32031 32032 var anchor = options.anchor.size() > 0 ? options.anchor : false, 32033 anchorSize = anchor ? [anchor.width(), anchor.height()] : false, 32034 anchorPosition = anchor ? (function() {var o = anchor.offset(); return [o.left, o.top]})() : false;
32035 32036 this.each(function() { 32037 var element = $(this), position = [], i, pos, 32038 elementSize = [element.outerWidth(true), element.outerHeight(true)], 32039 parent = element.offsetParent() || $(document.body), 32040 parentSize = [parent.width(), parent.height()]; 32041 32042 // calculate position 32043 for (i = 0; i < 2; i++) 32044 switch (options.position[i]) { 32045 case 'left': 32046 case 'top': 32047 position[i] = anchor ? anchorPosition[i] - elementSize[i] : 0; 32048 break; 32049 32050 case 'right': 32051 case 'bottom': 32052 position[i] = anchor ? anchorPosition[i] + anchorSize[i] : parentSize[i] - elementSize[i]; 32053 break; 32054 32055 case 'center': 32056 case 'middle': 32057 case undefined: 32058 case false: 32059 position[i] = anchor ? 32060 Math.round(anchorPosition[i] + anchorSize[i] / 2 - elementSize[i] / 2) : 32061 Math.round(parentSize[i] / 2 - elementSize[i] / 2); 32062 break; 32063 32064 default: 32065 pos = +options.position[i]; 32066 position[i] = anchor ? (pos < 0 ? 32067 anchorPosition[i] - elementSize[i] + pos : 32068 anchorPosition[i] + anchorSize[i] + pos 32069 ) : (pos < 0 ? parentSize[i] - elementSize[i] + pos : pos); 32070 } 32071 32072 element.css({ 32073 left: position[0] + 'px', 32074 top: position[1] + 'px' 32075 }); 32076 32077 }); 32078 32079 return this; 32080 }; 32081}); 32082/*globals jQuery, window */ 32083/*jslint forin: true, fragment: true, onevar: true, undef: true, nomen: false, eqeqeq: true, plusplus: true, bitwise: false, newcap: true, immed: true, regexp: false, white: false, plusplus: false */ 32084/** 32085 * Plugin to use ordinal input as a digit input with the inc/dec links 32086 */ 32087 32088jQuery(function($) { 32089 $.fn.numberInput = function(options) { 32090 // options are init-wide, so if you change postfix for one of 32091 // input initialized in set it will change for all 32092 options = $.extend(true, { 32093 // text to add after value 32094 postfix: '', 32095 // the fractional part length 32096 frac: 0, 32097 // incrementing value, can be function which receives true when 32098 // shift is pressed, uses increment multiplied by 10 in other case. 32099 increment: 1, 32100 // starting value 32101 value: false, 32102 // should value be only positive or not 32103 positive: false, 32104 // Add incremental controls or not 32105 addControls: true, 32106 // text for increment link 32107 incrementSign: '+', 32108 // text for decrement link 32109 decrementSign: '-', 32110 // custom function used for value formatting 32111 formatFunction: false, 32112 // custom function used for parse manually entered value 32113 parseFunction: parseFloat, 32114 // custom function used for syntax check additionally to 32115 // embedded one, defaults is to check fractional point 32116 keyPress: function(e) { 32117 var input = $(this), 32118 value = input.val(), 32119 pos; 32120 32121 switch (e.which) { 32122 // special keys 32123 case 0: 32124 switch (e.keyCode) { 32125 // up/down 32126 case 38: case 40: 32127 e.preventDefault(); 32128 eventListener.call(this, e, 32129 e.keyCode === 38 ? 1 : -1); 32130 break; 32131 } 32132 break; 32133 32134 // minus 32135 case 45: 32136 if (options.positive || value.indexOf('-') !== -1) { 32137 e.preventDefault(); 32138 } 32139 break; 32140 case 46: 32141 pos = value.indexOf('.'); 32142 if (e.which === 46 && options.frac !== 0 && pos !== -1) { 32143 e.preventDefault(); 32144 } 32145 break; 32146 32147 // digits 32148 case 48: case 49: case 50: case 51: case 52: case 53: 32149 case 54: case 55: case 56: case 57: 32150 32151 // backspace 32152 case 8: 32153 break; 32154 32155 // deny any changes by default 32156 default: 32157 e.preventDefault(); 32158 } 32159 } 32160 }, options || {}); 32161 32162 if (!$.isFunction(options.formatFunction)) { 32163 options.formatFunction = function(value) { 32164 return value.toFixed(options.frac) + options.postfix; 32165 }; 32166 } 32167 32168 var setValue = function(e, value) { 32169 var input = $(this); 32170 32171 value = typeof value === 'undefined' ? input.data('value') : value; 32172 32173 if (options.positive && value < 0) { 32174 value = 0; 32175 } 32176 32177 input.data('value', value).val(options.formatFunction(value)); 32178 }, 32179 32180 setOptions = function(e, name, value) { 32181 var opt = {}; 32182 32183 if (typeof name === 'string' && typeof value !== 'undefined') { 32184 opt[name] = value; 32185 } else if (typeof name === 'object') { 32186 opt = name; 32187 } 32188 32189 $.extend(true, options, opt); 32190 32191 setValue.call(this); 32192 }, 32193 32194 eventListener = function(e, delta) { 32195 if (e.type === 'mousewheel') { 32196 e.stop(); 32197 } 32198 var control = $(this), 32199 input = control.data('input') || control, 32200 increment = $.isFunction(options.increment) ? 32201 options.increment.call(window, e.shiftKey) : 32202 // increase ten times if shift is pressed 32203 (options.increment * (e.shiftKey ? 10 : 1)); 32204 setValue.call(input.get(0), e, (input.data('value') || 0) + 32205 (delta || (control.hasClass('decrement') ? -1 : 1)) * 32206 increment); 32207 input.trigger('change'); 32208 }, 32209 32210 onKeyUp = function(e) { 32211 var input = $(this), 32212 value; 32213 32214 if (e.which === 13 || e.which === 27) { 32215 e.preventDefault(); 32216 if (e.which === 27) { 32217 setValue.call(this, e); 32218 } 32219 32220 // use direct fire there because jQ fire event twice in IE 32221 this.blur(); 32222 // remove dash from any position except first 32223 } else if (!options.positive && (value = input.val()) && 32224 value.indexOf('-', 1) !== -1) { 32225 32226 input.val(value.substring(0, 1) + 32227 value.substring(1).replace(/-/, '')); 32228 } 32229 }, 32230 32231 onFocus = function(e) { 32232 this.select(); 32233 }, 32234 32235 onChange = function(e) { 32236 setValue.call(this, e, options.parseFunction($(this).val())); 32237 } 32238 32239 this.each(function(x, input) { 32240 if (input.tagName !== 'INPUT' || input.type.toUpperCase() !== 'TEXT') { 32241 return; 32242 } 32243 32244 var value = options.value || options.parseFunction(input.value || "0") || 0; 32245 32246 input = $(input) 32247 .attr('title', 'Use your mouse wheel to change value, hold down shift key to
32247speedup the changes') 32248 .data('value', value) 32249 .val(options.formatFunction(value)) 32250 .keyup(onKeyUp) 32251 .keypress(options.keyPress) 32252 .focus(onFocus) 32253 .bind('setValue', setValue) 32254 .bind('setOptions', setOptions) 32255 .bind('mousewheel', eventListener) 32256 .bind('change', onChange); 32257 32258 if (options.addControls) { 32259 // decrement value 32260 $('<a href="" class="decrement" />').html(options.decrementSign).insertAfter(input) 32261 .data('input', input) 32262 .click(false) 32263 .mousedown(eventListener); 32264 32265 // increment value 32266 $('<a href="" class="increment" />').html(options.incrementSign).insertAfter(input) 32267 .data('input', input) 32268 .click(false) 32269 .mousedown(eventListener); 32270 } 32271 }); 32272 32273 return this; 32274 }; 32275}); 32276/* jQuery SrollLoader plugin 32277 * Version 1.0 32278 * Written by Stanislav Yadykin ([email protected]) 32279 * @requires jQuery v1.5 32280 */ 32281 32282/* 32283 * @description Fires load event when scrolling is almost at the end 32284 */ 32285 32286(function($) { 32287 var rewindListener = function(e) { 32288 var holder = $(this); 32289 32290 holder.data('scroller').animate({ 32291 scrollTop: 0 32292 }, holder.data('options').rewindTime); 32293 }, 32294 scrollListener = function(e) { 32295 var element = $(e.target), 32296 scrollTop = element.scrollTop(), 32297 holder = element.data('holder'), 32298 options = holder.data('options'), 32299 oldScroll = holder.data('scroll') || 0, 32300 shiftValue = e.target.scrollHeight - options.deviation, 32301 visible = holder.data('visible'), 32302 controls = holder.data('controls'); 32303 32304 if (scrollTop > 0 && !visible) { 32305 options.showControls(controls); 32306 visible = true; 32307 } else if (scrollTop === 0 && visible) { 32308 controls.fadeOut(); 32309 visible = false; 32310 } 32311 32312 holder 32313 .scrollTop(scrollTop) 32314 .data({ 32315 visible: visible, 32316 scroll: scrollTop 32317 }); 32318 32319 if (element.height() + scrollTop > shiftValue && 32320 element.height() + oldScroll < shiftValue) { 32321 holder.trigger(options.loadEventName, [holder, element]); 32322 } 32323 }, 32324 clickListener = function(e) { 32325 e.preventDefault(); 32326 32327 $(this).data('holder').trigger('rewind'); 32328 }; 32329 32330 32331 $.fn.scrollLoader = function(options) { 32332 options = $.extend({ 32333 deviation: 100, 32334 scrollerSelector: '.scroller', 32335 rewinderSelector: '.scroll-rewinder', 32336 controlsSelector: '.scrolling-item', 32337 loadEventName: 'load', 32338 showControls: function(controls) { 32339 controls.fadeIn(); 32340 }, 32341 rewindTime: 500 32342 }, options); 32343 32344 32345 $.each(this, function(i, holder) { 32346 holder = $(holder); 32347 var scroller = holder 32348 .find(options.scrollerSelector) 32349 .data({holder: holder}) 32350 .bind('scroll', scrollListener), 32351 rewinder = holder 32352 .find(options.rewinderSelector) 32353 .data({holder: holder}) 32354 .bind('click', clickListener), 32355 controls = holder.find(options.controlsSelector); 32356 32357 holder 32358 .data({ 32359 options: options, 32360 scroller: scroller, 32361 controls: controls, 32362 rewinder: rewinder 32363 }) 32364 .bind('rewind', rewindListener); 32365 }); 32366 32367 return this; 32368 }; 32369})(jQuery); 32370/*globals buildThumbnailLink, diffuse2RGB, $, myDeco */ 32371/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 32372 sloppy: true, white: true */ 32373/** 32374 * @fileOverview Used for adding common method to all views that required it 32375 */ 32376var generateElementForDragMixin = { 32377 /** 32378 * Internal settings 32379 * @type {Object} 32380 */ 32381 settings: { 32382 // thumbnail sizes for component and scrapbook items 32383 thumbnailSize: { 32384 small: { 32385 width: 70, 32386 height: 70 32387 }, 32388 normal: { 32389 width: 110, 32390 height: 110 32391 } 32392 }, 32393 // settings for material drag thumbnail 32394 dragThumbnail: { 32395 width: 50, 32396 height: 50, 32397 percent: 50 32398 } 32399 }, 32400 32401 /** 32402 * Default thumbnail sizes mode, will override later (added for consistency) 32403 * @type {String} 32404 */ 32405 mode: 'normal', 32406 32407 /** 32408 * Generate visual representation of given component for drag-n-drop 32409 * @param {Object} component Component to render thumbnail for 32410 * @param {String} type Item type 32411 */ 32412 generateElementForDrag: function(component, type) { 32413 type = type || this._model.type; 32414 32415 // Setting width and height explicitly to avoid situation when 32416 // image is not loaded yet and width and height would be miscalculated
32417 var holder = $('<div id="new-component" />'), 32418 inner; 32419 switch (type) { 32420 case 'component': 32421 holder 32422 .width(this.settings.thumbnailSize[this.mode].width) 32423 .height(this.settings.thumbnailSize[this.mode].height) 32424 .append($('<img class="original" />') 32425 .attr('src', component.thumbnail || component.componentData['3d'].render_perspective)); 32426 break; 32427 case 'material': 32428 holder 32429 .width(this.settings.dragThumbnail.width) 32430 .height(this.settings.dragThumbnail.height) 32431 .addClass('no'); 32432 32433 inner = $('<div />').append('<div class="helper" />').appendTo(holder); 32434 32435 if (typeof component.thumbnail !== 'undefined') { 32436 inner.css('background-image', 'url(' + component.thumbnail + ')'); 32437 } else { 32438 inner.addClass('color').css('background-color', 'rgb(' + diffuse2RGB(component.diffuse) + ')'); 32439 } 32440 break; 32441 case 'scrapbook': 32442 holder 32443 .addClass('photo no') 32444 .append($('<img src="' + component.thumbnail + '" style="border: 2px solid #ccc; border-radius: 5px;" />')) 32445 .append('<div class="helper" />'); 32446 break; 32447 default: 32448 // For other items the width & height are not explicitly set because 32449 // they are not immediately known and may vary. 32450 holder.append('<img class="original" src="' + component.thumbnail + '"/>'); 32451 } 32452 return holder; 32453 } 32454}; 32455/*globals myDeco, $, hex2Diffuse */ 32456/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 32457 sloppy: true, white: true */ 32458 32459myDeco.App.palette = { 32460 palettes: {}, 32461 32462 settings: { 32463 palette: 'dulux' 32464 }, 32465 32466 init: function() { 32467 if (!this.palettes[this.settings.palette]) { 32468 throw gettext("No palette found"); 32469 } 32470 32471 $.extend(true, this, this.palettes[this.settings.palette].init()); 32472 }, 32473 32474 getIndex: function(color) { 32475 return this.colors.indexOf(color.toUpperCase().replace('#', '')); 32476 }, 32477 32478 getMaterial: function(key) { 32479 key = key.replace('#', '').toUpperCase(); 32480 var color = '#' + key, 32481 name = this.getName ? this.getName(key) : false; 32482 32483 return { 32484 'ID': 'color-' + key, 32485 'component_id': color, 32486 'name': name || 'Color ' + color, 32487 'ambient': [0, 0, 0], 32488 'diffuse': hex2Diffuse(key), 32489 'shader': 'combi_material', 32490 'shiny': 0, 32491 'use_diffuse_map': false, 32492 'class': 'color', 32493 'branded': myDeco.settings.DULUX_BRANDING && name, 32494 'url': this.getUrl ? this.getUrl(key) : false, 32495 'isColor': true 32496 }; 32497 } 32498 32499}; 32500 32501$(function() { 32502 myDeco.App.palette.init(); 32503}); 32504/*globals myDeco, $ */ 32505/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 32506 sloppy: true, white: true */ 32507 32508myDeco.App.palette.palettes.dulux = { 32509 // list of color elements 32510 colors: '684157,6F4350,6F3B42,583B3F,8A383C,7A3A3D,703A3D,6F423F,644540,72453E,53433D,8B4C3C,764B3C,6B4C3D,674E3E,605149,65483D,6A4C3D,6A533D,6A543B,845F38,785E3A,66543D,9F7A38,4D4B3E,4A5C3F,395642,2D5C46,376144,1F6349,1A5B4E,285B4B,0A636C,305B69,3B4E53,055B71,355061,37536C,3F5169,3F486E,414966,4F4D60,4C4769,594669,644466,5D3F4F,4F4446,4D4143,4F413F,61514A,55463F,53483D,48433E,3D3D3C,854662,874C5E,7B4353,7B3B46,9A363B,A7363E,BB434A,943E3B,83473F,8D463B,884E3A,A84E39,B25938,95563B,8D5B3B,9D6139,9A6142,996237,A86D34,996C40,AF7630,B17E2D,9D7235,E6B81D,8D9340,758443,4C6C41,356E3C,2E7349,2D7A58,007F61,22665A,00767C,2A6B79,05687D,006F89,255C7A,00618F,33608F,4F66AB,3C4B73,524A74,64568C,6A4B75,785A7A,74466D,5A4548,5D4543,62453F,6E5248,705849,745C47,5B5044,5E5E5D,9A4466,A44E6B,A0415B,A24351,A64048,B2414B,B24848,B03F3A,B3463B,D05534,CE5D39,D36232,D86B3C,D46F37,C76F33,D9762F,CC773F,CF8129,DC8D1D,E2983E,DC9D39,F2AE1E,E4B017,F7C61D,B6A931,A2AC31,719A41,399146,308F56,00925B,008868,008674,328E94,007B90,0084A9,007498,006F9B,3C7AAE,2C6FAE,5A709E,4D5A8D,5E5A81,66587E,775C8E,895C8D,804466,7F6968,7E6660,82695F,8E766C,8E796B,9F8E82,AC9D8E,777777,A05174,AB5974,A64D61,AE596A,BF5661,C1616A,C05F5C,B5473A,CF5141,DB6335,D46946,E56F2F,DE7649,D97944,D3824A,E48E54,D88135,F19828,FFA520,FAAB1D,FFBF25,FFC300,FFC900,FBCB43,CFC142,C2B91B,87B65B,75B875,5BAA75,4CAB7F,29AE89,00A58E,0095A0,008CA3,0092B6,3086A8,3E89B3,4A89BC,5678A2,7384AD,6E74A0,78729E,81709A,886DA0,9B6D9F,8B5B80,AA9A99,B6A6A1,C1B3AD,B8A8A0,B9AAA0,C1B4AB,C2B6AA,A0A09F,BD7494,BA7086,C97182,C56679,D97980,DE858A,D57974,D17867,CC6B5B,D87452,DC7F5F,DD8251,E58A62,E18B5D,D88B56,DF8647,F49B43,F9AA3D,FFB448,FFC054,FFC43C,FFCD59,FFC924,FFD251,CFC057,B9BD5C,96C16C,7EB17E,75BA8A,69B991,6EB79D,4ABDA7,69B4B8,69A6B6,35A3C3,579AB9,689EC1,66A1CD,7690B5,808FB5,8488AF,827DA5,9485AA,967BAD,A580A6,AE7DA2,D5CECC,D5CECA,D9CEC9,D8CFC9,DFD1C7,D6CEC7,D6CEC5,CDCDCC,C98AA4,D293A4,D7949E,D38894,E39A9D,E19094,DD908A,E29D8E,DE9080,E6A188,E3977B,EAAA86,EA9E7C,E89F78,E09C70,EAA06F,F7A955,FBBB5C,FDCB7A,FFD181,FFD877,F9D787,FFD25F,F6CE6F,D9C96B,CACC77,B1D18A,A9D2A8,83C396,84C8A5,9AD0BC,6FCCB8,8BCED1,95C2CD,64B3CF,8FBDCF,8DB4D0,9EC0D7,93A8C7,8D9ABC,999DBE,9691B5,A79BBA,AF98C4,BD9FBD,
32510CBA7C1,ECE9E6,EBE9E6,ECE9E5,EDE9E5,EDE9E4,EDE9E4,EEE9E2,E9EAE7,C16B9E,BB597E,C86D94,C25C77,CF5F6F,D87082,E17161,E47C6C,E66E5B,E98762,EC926F,F1995A,EE8C4D,EB8830,F9B465,FFB453,FAB44D,FDC46B,FFC657,F9C833,FFCF58,FFC300,FDCB3A,EDCA33,B6BF3C,98B641,89B555,75B65D,68AF6B,57B176,33AD7B,00AA8F,2BA8A8,35AFBA,00A2BE,008ABD,0097C8,4C84B8,5873A7,6883B6,797FA8,7571A4,8A7AA2,A389B8,A77AAC,B977A9,E2DCDC,E9DDD3,E4DCD4,EADED0,ECE9E3,E4DECF,E0E1DC,D9DDDB,D188B8,D689AE,E197B2,D57C99,E18C9E,EC9CA4,EB9688,EEA498,F5967B,EF9F7E,F2AB8C,EDA97B,F5AD74,FFB26B,FBC783,FFBF6D,FAC377,FFD37F,FFCB6D,FFD46A,FFCF3F,FFD865,FECF51,F0D458,C6CC5A,B0CA5F,A3C979,94C97E,87C486,8ECFA1,64C195,5CCAB4,6ECBC7,68C3CC,54B7CE,2EA3D1,47AED8,689DCC,7290C0,809BC8,8E92B6,9A97C4,B2A5C2,BCA6CC,BC95C1,D6A5CC,ECE9EA,F0E8E2,F0E8DF,F0E8DC,F2EAD7,EEE9DB,E9EBDF,E5EAE7,E4B7D6,E2A6C5,EBBECF,E5A7BE,EDABBB,F2BEC3,F2B4AA,F5C4BB,F8A78B,F5B99D,F1BDA5,F1BA96,F8C394,FBBD73,FFCB96,FFD295,FCD38D,FCDDA4,FFD996,FADA93,FCD36E,FFE08A,FFDD79,F5DC7B,DADD88,BFD47C,BAD795,BCE2AB,A3D5A3,A8DFB7,8FD5B0,93DECE,8ED7D3,98D7DD,89CDDE,61B9DE,86C9E5,94B7DB,92ABD3,AABEDC,BDBED5,BDBBDA,C6BDD3,CAB9D7,D5BAD8,E6BDDA,ECE9EA,ECE8E5,ECE9E5,F0EADD,EFE9DE,EBEAE1,E9EAE4,E7EBEB,E9C6DE,EABFD6,EFCAD8,EABACB,EFCAD2,EDD1D0,EECEC6,F6D1C9,F8CEBE,F4D2C2,F4D2C0,F4D4BF,F8D1AA,FCD297,F8D7B4,FFE1B7,FBE1B2,FEE5B9,FBE0AE,FFDD8E,FEE6A3,FDE8AF,FFE19B,F9E49B,E7E7A4,D7E5A3,D3E7B6,C6E8B6,BDE2BE,B9E4C3,B5E7CB,B3E5D8,99DBD8,A1DADC,B5E0EB,92CDE7,A0D4E8,B7D5E7,B4C6E0,CFD3E3,D4D4E3,CECCE2,D0C8DA,D7C8E0,DDC6DF,E5D2DF,EDE9EA,ECEAE6,ECE9E3,EFEADF,F3EFDD,EDEAE2,EEEFEA,EAEAE7,ECD3E3,EFD5E1,F2DFE3,F0D6DE,F3D8DD,EEDDDB,F6DCD4,F0DBD2,F6DDD2,F4DCD0,F3DDCF,F6E2D3,F6E1C7,FADCAF,F8DCBD,F9E5C3,F8E4BC,F9E7C3,F7E7C5,FDE5B6,FBEBBC,F9EDC9,F6E5AC,F7E8B7,E7E5A9,E0EABA,E2EDCF,D2E9C5,CAE6C7,C9E8D0,C8EAD5,BFEADF,CCEBE6,BAE3E5,C2E4EC,B4DCEA,C4DCE9,C6DBE6,BECEE4,DCDDE3,DDDDE3,DBD9E7,DAD3E3,DED3E1,E5D2E3,E6DCE2,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,EFDDE6,F2DEE6,F1E7E8,F1E7E7,F1E1E2,F0E8E6,F6E4DD,F1E7E2,F3E9DD,F4E6DE,F4E7DE,F6E8DC,F5E9D5,F9E8CA,F7E5CD,F7ECD6,F9EACA,F5E9D4,F7EDD5,F8EFD4,FAE9C1,F6E9CC,F6E9BD,F6E9C4,F0EDC9,E7ECCE,E8EFD8,DCEFD1,D9EFD6,D9EFDC,D4EFDE,CFEEE4,DAEEEA,DDEEEC,CEE5EA,C5E4EC,D3E5EB,CADCE8,DDE1E6,E9EAEB,EAE9EB,E2E0E9,EAE9EA,ECE9EA,EBDEE8,EFE9EB,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,865873,864E66,954D65,994D5C,9F5C60,AC545C,A34847,9D5351,AA5452,8C5247,995039,BE5F37,AE663F,B36836,AD6A33,C4742F,C07D38,C58329,D39140,B37C39,C98734,D59421,C58926,DAA01D,877E43,7C8B4C,688841,507941,577549,3F6941,3C7C53,1D866A,1F6569,1E727D,2A7186,14647F,0B688E,235678,2F5472,455E7D,425578,5E658C,5A5679,675372,6F5670,775470,D8C1B9,D7C3B8,DBC5B6,E2C9B2,E4CAB0,E0CBB1,EAD2AF,E6D2AD,AD7C98,AA7189,AF6F81,C0707F,BF7579,CD787D,CA7873,C17772,B97772,BF7769,CE7354,C1704D,C47C56,CE8759,D48C53,DD8A43,DF9B59,DA9F5B,E1A869,F7AB52,DBA966,E3A956,F5C064,E5B04F,AEA46C,A2AE76,8FAD6B,87AD75,9CB98D,799D77,619C73,65A991,51898D,67A4AD,5791A6,46809B,3E799B,456C8E,5B7792,5E7697,5B6988,767B9F,7F7A9E,897593,967A97,A07896,E9DCD5,EDDCD2,EBDDD1,EEDECF,F0DFCD,EAD6BE,F0DEC4,F0E1C6,C39CB0,CDA0B1,CA9EA9,D0949C,D09798,DA989A,DA9C97,D49C97,DA9C8F,D29B90,DFA089,D79A7F,DAA689,DAA17D,E1A77B,EBAB79,E7AB74,E6B682,F0BB80,F5BC76,E5B981,EBB873,F3C47B,ECC077,CEC389,BBC496,ACC48C,A7C697,B2CBA6,A1C1A0,95C2A0,90C3B0,7DAEB2,A2CCD3,93BAC9,80A9BF,7299B4,6C92B4,859CB4,8A9BB5,808EAF,999BB9,9E9AB7,A695AE,B29BB2,B99AB1,EEE8E4,F1E8E0,F2E7DB,F6E7D9,F4E8D5,F3E0C9,F3E5CE,F2E7D3,D7BDCA,D9B8C3,DCC2C7,DEB6BB,DEBAB9,E1AFAF,E2B5B0,DDB4B0,E3B5AA,DEB4AB,E6B7A6,E5BAA7,E3BBA5,EAB897,E8B894,F0BC95,EDBC91,F5CA9E,EABE90,F4C58C,EBC89E,F1CB9B,EECA8D,F0CB91,DFD49E,D7DEB8,BFD3A3,C8E0BC,C9DEBE,BED8BC,B2D2B9,ACD6C5,9DCACB,B4D7DC,B1CED9,9CBFD1,A2BCCE,9FB7CF,A5B6C8,A7B4C9,A8B0C9,B0B1C9,C0BCD0,C7BAC
32510B,CBBBCB,C3ADBD,EEE8E6,EFE9E4,F0E9E1,F0E9DE,EFE9DE,F2E9DA,F4EAD8,F8EDD6,E1D2D8,E6CFD6,EADBDD,E7CFD1,EAD1D0,E7C6C5,ECCECA,E9CECA,EBCEC7,E7CDC7,EBD0C5,EDD1C4,EBD1C2,F0CAB3,E9CAB2,F3D5BE,F2CDAE,F6D8B9,F0CFAD,FFE0B7,EED0AC,F4D9B8,F8DFB3,EFD5AF,EFE4B2,E7ECD2,D2E1BA,D4E6CA,D6E4CC,CEE2CC,BFDBC5,C9E6D9,B3D5D6,C5E2E4,BFD8E2,AEC9D7,C4D5E0,C3D0DF,C5D0DC,C7D0DE,CFD2DC,C7C8D9,D6D3E1,D9D1DA,DCD2DB,E1D2DC,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,EEE8E8,E9DBDE,EFE8E7,EBDBDB,EFE8E5,EBDDDB,F1E7E4,ECDBD7,F1E7E3,ECDAD5,EEE7E3,EEDCD3,F2E8E0,F5DECE,F4DFCD,F5E7DB,F5DFCB,F7DFC7,F3DFCA,F7E9D5,F3E0C9,EED7BC,F7E8CF,F5DFC0,F2EACB,E8ECD5,E4ECD6,E2EEDA,E5ECDF,DFEEDC,DCEFDE,D9EFE4,CAE1E0,CDDFE1,CFDFE5,CCDFE8,D7DDE1,D8DCE0,D7DDE2,D8DCE3,DCDDE2,D2D3DE,DFDDE2,E1DDE1,EDE9EB,E5DCE1,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,6E5F66,876B75,83696C,8C6C6C,916865,7C6664,8A6760,95685E,986C5C,906F5E,90725E,886753,8B6F5A,A37959,AE7D57,A1754C,AC8355,9D7B57,988268,9A7E5A,8C785A,826E4F,A08965,98805A,7B755B,82825E,797F60,768760,697B5F,577056,5D7562,59776B,557E7C,53757A,5B7F89,567B88,5F7784,546F82,63747E,687386,747D94,727590,5A5869,756B82,807180,5C5460,A0989A,A39993,AA9A8F,AA9F8F,A09A8E,929898,92989D,939393,8E8288,A58E96,988184,A99090,AE8D89,9D8B88,A88C86,B28D85,B29083,AC9182,AD8B75,A38877,B3957F,BB997E,C49B7C,C09D7F,CAAA88,C0A58A,BDA78C,BDA07D,A99880,AA9372,BDA47E,B49E7F,928C73,A1A080,8E9477,8D9C77,7F9076,7E947C,839786,728D82,729694,68888E,839FA6,7E9BA6,8099A7,7E92A1,87959D,838C9D,999FB2,9395AA,737083,9D96A7,A094A1,827885,C4C0C0,C7C0BC,CBC1B8,D0C6B7,D3CFC6,CAD1D2,C9CFD4,BDBDBB,B2A9AD,C4B4B9,B7A6A7,C5B5B3,C9B2B0,B4A6A3,C5B3AE,CBB3AD,CCB4AB,C8B6AC,BFA290,BFAB9E,CCB8A8,C3A38C,D3B198,CFB298,D7BEA3,CFBAA5,C7B29A,D4BFA5,C7BBA8,C5B39A,D4BE97,CCBEA2,B3AE97,C1C0A4,A3A98E,A2B090,A5B39D,A8BAA6,ACB7AD,97B4A8,8EAEAC,8AA8AC,9DB5BB,99B1BB,9BB0BC,9BABB6,93A1A8,9CA7B7,B2B7C4,BABBCA,9F9DAB,BEBAC5,C1B8C0,A49CA7,DEDBD9,DFDBD8,E2DCD6,DDD4C2,DFDCD3,DDE2E2,D7DCE1,DADBD9,C9C2C4,D9CFD1,CDC1C2,D9CFCD,DBCDCA,CAC0BE,D8CDC9,DDCEC9,DCCEC8,DCD0C8,D7C5B8,D2C3B9,DED0C5,D3BAA8,E5D2C3,E3D2C3,DEC8B2,D8C6B4,D7C8B5,DCC9B3,DED3C3,D5C8B5,E0CFB0,DFD3BC,CBC6B2,D7D6BD,C6CBB5,CAD7C2,BEC9B7,C5D4C3,C8D1C9,B4CCC2,BCD6D3,B1CBCE,B9C7CC,B5C9D0,B6C8D1,BAC5CD,BAC4C9,BBC2CE,CED1DA,D1D1D9,B9B7C1,D4D1D8,D7D0D5,BCB6BD,ECE9E7,EBE9E6,EEE9E3,EBEAE6,ECEAE1,E8EBE8,E9EAEB,E9E9E6,D5CFD0,E3DBDC,D7CDCC,E4DCDA,E6DCD8,D8CECB,E3DAD7,E6DAD6,E7DCD7,E7DCD5,E3D1C5,DBCFC7,E9DDD3,E2D1C4,EFDDCF,EADDD1,E4D3C0,E0D1C2,DED2C3,E2D3C1,E2D8CA,DED7CD,EDE0CA,E9DEC8,D7D2BF,E2E1C7,DDE0CD,DBE3CD,CBD5C4,D3DFD1,D6DED6,D3DFD9,CEE0DE,C1D5D6,CCD9DD,C9DAE0,C4D2DA,C9D1D7,C8D1D5,C9CFD9,DCDDE1,DDDDE0,D2D0D7,DEDCDF,E0DCDF,D3D0D4,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,E0DCDB,EDE8E7,E3DBDA,ECE9E6,EEE9E6,E3DBD8,ECE8E5,EEE8E4,EDE9E5,EEE8E3,F0E8E1,E5DCD5,F2E9E1,F0E9E2,F2E8DE,F0E9E1,EBDDCF,E7DCD1,EFE9E1,E6DDD2,EAE4D9,E7E3DB,F1E9DB,F0E9DC,EEEAD9,EDEBD4,E8EBDA,E7EBDD,D8E0D1,E3ECE0,E5ECE4,E5EBE5,E2ECE9,D1DFDF,D3DEE0,D1DEE3,D1DEE4,D9DDDF,D5DDE0,D9DCE0,E9EAEA,EAE9E9,DEDCE0,EAE9E8,EDEAEA,DFDCDE,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF,FFFFFF', 32511 names: 'Russian Velvet 1, Summer Surprise 1, Moroccan Velvet 1, Garnet Symphony 1, Ruby Fountain 1, Red Stallion 1, Fire Cracker 1, Volcanic Splash 1, Hot Paprika 1, African Adventure 1, Flame Frenzy 1, Auburn Falls 1, Mexican Mosaic 1, Ginger Glow 1, Roasted Pumpkin 1, Spicy Sandalwood 1, Nigerian Sands 1, Rich Havana 1, Golden Bark 1, Earth Glaze 1, Sunflower Symphony 1, Sulphur Springs 1, Delhi Bazaar 1, Sunny Savannah 1, Tarragon Glory 1, Indian Ivy 1, Woodland Fern 1, Dublin Bay 1, Paradise Green 1, Emerald Delight 1, Jade Cluster 1, Minted Glory 1, Blue Diamond 1, Hawaiian Blue 1, Azure Fusion 1, Celestial Blue 1, Venetian Crystal 1, Royal Regatta 1, Sapphire Springs 1, Martian Skies 1, Plush Velvet 1, Amethyst Falls 1, Purple Infusion 1, Velvet Ribbon 1, Pamplona Purple 1, Damson Dream 1, Maraschino Mocha 1, Cocoa Blush 1, Bitter Chocolate 1, Exotic Spice 1, Velvet Truffle 1, Rich Praline 1, Rum Caramel 1, Night Jewels 1, Russian Velvet 2, Summer Surprise 2, Moroccan Velvet 2, Garnet Symphony 2, Ruby Fountain 2, Red Stallion 2, Fire Cracker 2, Volcanic Splash 2, Hot Paprika 2, African Adventure 2, Flame Frenzy 2, Auburn Falls 2, Mexican Mosaic 2, Ginger Glow 2, Roasted Pumpkin 2, Spicy Sandalwood 2, Nigerian Sands 2, Rich Havana 2, Golden Bark 2, Earth Glaze 2, Sunflower Symphony 2, Sulphur Springs 2, Delhi Bazaar 2, Sunny Savannah 2, Tarragon Glory 2, Indian Ivy 2, Woodland Fern 2, Dublin Bay 2, Paradise Green 2, Emerald Delight 2, Jade Cluster 2, Minted Glory 2, Blue Diamond 2, Hawaiian Blue 2, Azure Fusion 2, Celestial Blue 2, Venetian Crystal 2, Royal Regatta 2, Sapphire Springs 2, Martian Skies 2, Plush Velvet 2, Amethyst Falls 2, Purple Infusion 2, Velvet Ribbon 2, Pamplona Purple 2, Damson Dream 2, Maraschino Mocha 2, Cocoa Blush 2, Bitter Chocolate 2, Exotic Spice 2, Velvet Truffle 2, Rich Praline 2, Rum Caramel 2, Night Jewels 2, Russian Velvet 3, Summer Surprise 3, Moroccan Velvet 3, Garnet Symphony 3, Ruby Fountain 3, Red Stallion 3, Fire Cracker 3, Volcanic Splash 3, Hot Paprika 3, African Adventure 3, Flame Frenzy 3, Auburn Falls 3, Mexican Mosaic 3, Ginger Glow 3, Roasted Pumpkin 3, Spicy Sandalwood 3, Nigerian Sands 3, Rich Havana 3, Golden Bark 3, Earth Glaze 3, Sunflower Symphony 3, Sulphur Springs 3, Delhi Bazaar 3, Sunny Savannah 3, Tarragon Glory 3, Indian Ivy 3, Woodland Fern 3, Dublin Bay 3, Paradise Green 3, Emerald Delight 3, Jade Cluster 3, Minted Glory 3, Blue Diamond 3, Hawaiian Blue 3, Azure Fusion 3, Celestial Blue 3, Venetian Crystal 3, Royal Regatta 3, Sapphire Springs 3, Martian Skies 3, Plush Velvet 3, Amethyst Falls 3, Purple Infusion 3, Velvet Ribbon 3, Pamplona Purple 3, Damson Dream 3, Maraschino Mocha 3, Cocoa Blush 3, Bitter Chocolate 3, Exotic Spice 3, Velvet Truffle 3, Rich Praline 3, Rum
32511Caramel 3, Night Jewels 3, Russian Velvet 4, Summer Surprise 4, Moroccan Velvet 4, Garnet Symphony 4, Ruby Fountain 4, Red Stallion 4, Fire Cracker 4, Volcanic Splash 4, Hot Paprika 4, African Adventure 4, Flame Frenzy 4, Auburn Falls 4, Mexican Mosaic 4, Ginger Glow 4, Roasted Pumpkin 4, Spicy Sandalwood 5, Nigerian Sands 4, Rich Havana 4, Golden Bark 4, Earth Glaze 4, Sunflower Symphony 4, Sulphur Springs 4, Delhi Bazaar 4, Sunny Savannah 4, Tarragon Glory 4, Indian Ivy 4, Woodland Fern 4, Dublin Bay 4, Paradise Green 4, Emerald Delight 4, Jade Cluster 4, Minted Glory 4, Blue Diamond 4, Hawaiian Blue 4, Azure Fusion 4, Celestial Blue 4, Venetian Crystal 4, Royal Regatta 4, Sapphire Springs 4, Martian Skies 4, Plush Velvet 4, Amethyst Falls 4, Purple Infusion 4, Velvet Ribbon 4, Pamplona Purple 4, Damson Dream 4, Maraschino Mocha 4, Cocoa Blush 4, Bitter Chocolate 4, Exotic Spice 4, Velvet Truffle 4, Rich Praline 4, Rum Caramel 4, Night Jewels 4, Russian Velvet 5, Summer Surprise 5, Moroccan Velvet 5, Garnet Symphony 5, Ruby Fountain 5, Red Stallion 5, Fire Cracker 5, Volcanic Splash 5, Hot Paprika 5, African Adventure 5, Flame Frenzy 5, Auburn Falls 5, Mexican Mosaic 5, Ginger Glow 5, Roasted Pumpkin 5, Spicy Sandalwood 4, Nigerian Sands 5, Rich Havana 5, Golden Bark 5, Earth Glaze 5, Sunflower Symphony 5, Sulphur Springs 5, Delhi Bazaar 5, Sunny Savannah 5, Tarragon Glory 5, Indian Ivy 5, Woodland Fern 5, Dublin Bay 5, Paradise Green 5, Emerald Delight 5, Jade Cluster 5, Minted Glory 5, Blue Diamond 5, Hawaiian Blue 5, Azure Fusion 5, Celestial Blue 5, Venetian Crystal 5, Royal Regatta 5, Sapphire Springs 5, Martian Skies 5, Plush Velvet 5, Amethyst Falls 5, Purple Infusion 5, Velvet Ribbon 5, Pamplona Purple 5, Damson Dream 5, Maraschino Mocha 5, Cocoa Blush 5, Bitter Chocolate 5, Exotic Spice 5, Velvet Truffle 5, Rich Praline 5, Rum Caramel 5, Night Jewels 5, Russian Velvet 6, Summer Surprise 6, Moroccan Velvet 6, Garnet Symphony 6, Ruby Fountain 6, Red Stallion 6, Fire Cracker 6, Volcanic Splash 6, Hot Paprika 6, African Adventure 6, Flame Frenzy 6, Auburn Falls 6, Mexican Mosaic 6, Ginger Glow 6, Roasted Pumpkin 6, Spicy Sandalwood 6, Nigerian Sands 6, Rich Havana 6, Golden Bark 6, Earth Glaze 6, Sunflower Symphony 6, Sulphur Springs 6, Delhi Bazaar 6, Sunny Savannah 6, Tarragon Glory 6, Indian Ivy 6, Woodland Fern 6, Dublin Bay 6, Paradise Green 6, Emerald Delight 6, Jade Cluster 6, Minted Glory 6, Blue Diamond 6, Hawaiian Blue 6, Azure Fusion 6, Celestial Blue 6, Venetian Crystal 6, Royal Regatta 6, Sapphire Springs 6, Martian Skies 6, Plush Velvet 6, Amethyst Falls 6, Purple Infusion 6, Velvet Ribbon 6, Pamplona Purple 6, Damson Dream 6, Maraschino Mocha 6, Cocoa Blush 6, Bitter Chocolate 6, Exotic Spice 6, Velvet Truffle 6, Rich Praline 6, Rum Caramel 6, Night Jewels 6, Flamingo Fun 1, Rock Candy 1, Sweet Sundae 1, Fuchsia Falls 1, Rose Trellis 1, Party Surprise 1, Silken Sunrise 1, Caribbean Dawn 1, Bongo Jazz 1, Mango Melody 1, Coral Canyon 1, Amber Queen 1, Harvest Fruits 1, Moroccan Sands 1, Polar Flame 1, Golden Rambler 1, Californian Sands 1, Pharaohs Gold 1, Golden Fern 1, Buttercup Fool 1, Banana Dream 1, Lemon Chiffon 1, Easter Morn 1, Spring Breeze 1, Lime Zest 1, Kiwi Burst 1, Willow Creek 1, Green Parrot 1, Grecian Garland 1, Granada Green 1, Forest Falls 1, Grecian Spa 1, Crystal Surprise 1, Javan Dawn 1, Summer Medley 1, Azure Sky 1, Holiday Blues 1, Blue Seduction 1, Mystic Mauve 1, Fragrant Cloud 1, Lilac Heather 1, Amethyst Showers 1, Lavender Blossom 1, Lilac Spring 1, Violet Verona 1, Orchid Opera 1, Chalky White 1, Java Cream 1, Golden Jasmine 1, Subtle Ivory 1, Vanilla Mist 1, Cameo Silk 1, Chiffon White 1, Clouded Pearl 1, Flamingo Fun 2, Rock Candy 2, Sweet Sundae 2, Fuchsia Falls 2, Rose Trellis 2, Party Surprise 2, Silken Sunrise 2, Caribbean Dawn 2, Bongo Jazz 2, Mango Melody 2, Coral Canyon 2, Amber Queen 2, Harvest Fruits 2, Moroccan Sands 2, Polar Flame 2, Golden Rambler 2, Californian Sands 2, Pharaohs Gold 2, Golden Fern 2, Buttercup Fool 2, Banana Dream 2, Lemon Chiffon 2, Easter Morn 2, Spring Breeze 2, Lime Zest 2, Kiwi Burst 2, Willow Creek 2, Green Parrot 2, Grecian Garland 2, Granada Green 2, Forest Falls 2, Grecian Spa 2, Crystal Surprise 2, Javan Dawn 2, Summer Medley 2, Azure Sky 2, Holiday Blues 2, Blue Seduction 2, Mystic Mauve 2, Fragrant Cloud 2, Lilac Heather 2, Amethyst Showers 2, Lavender Blossom 2, Lilac Spring 2, Violet Verona 2, Orchid Opera 2, Chalky White 2, Java Cream 2, Golden Jasmine 2, Subtle Ivory 2, Vanilla Mist 2, Cameo Silk 2, Chiffon White 2, Clouded Pearl 2, Flamingo Fun 3, Rock Candy 3, Sweet Sundae 3, Fuchsia Falls 3, Rose Trellis 3, Party Surprise 3, Silken Sunrise 3, Caribbean Dawn 3, Bongo Jazz 3, Mango Melody 3, Coral Canyon 3, Amber Queen 3, Harvest Fruits 3, Moroccan Sands 3, Polar Flame 3, Golden Rambler 3, Californian Sands 3, Pharaohs Gold 3, Golden Fern 3, Buttercup Fool 3, Banana Dream 3, Lemon Chiffon 3, Easter Morn 3, Spring Breeze 3, Lime Zest 3, Kiwi Burst 3, Willow Creek 3, Green Parrot 3, Grecian Garland 3, Granada Green 3, Forest Falls 3, Grecian Spa 3, Crystal Surprise 3, Javan Dawn 3, Summer Medley 3, Azure Sky 3, Holiday Blues 3, Blue Seduction 3, Mystic Mauve 3, Fragrant Cloud 3, Lilac Heather 3, Amethyst Showers 3, Lavender Blossom 3, Lilac Spring 3, Violet Verona 3, Orchid Opera 3, Chalky White 3, Java Cream 3, Golden Jasmine 3, Subtle Ivory 3, Vanilla Mist 3, Cameo Silk 3, Chiffon White 3, Clouded Pearl 3, Flamingo Fun 4, Rock Candy 4, Sweet Sundae 4, Fuchsia Falls 4, Rose Trellis 4, Party Surprise 4, Silken Sunrise 4, Caribbean Dawn 4, Bongo Jazz 4, Mango Melody 4, Coral Canyon 4, Amber Queen 4, Harvest Fruits 4, Moroccan Sands 4, Polar Flame 4, Golden Rambler 4, Californian Sands 4, Pharaohs Gold 4, Golden Fern 4, Buttercup Fool 4, Banana Dream 4, Lemon Chiffon 4, Easter Morn 4, Spring Breeze 4, Lime Zest 4, Kiwi Burst 4, Willow Creek 4, Green Parrot 4, Grecian Garland 4, Granada Green 4, Forest Falls 4, Grecian Spa 4, Crystal Surprise 4, Javan Dawn 4, Summer Medley 4, Azure Sky 4, Holiday Blues 4, Blue Seduction 4, Mystic Mauve 4, Fragrant Cloud 4, Lilac Heather 4, Amethyst Showers 4, Lavender Blossom 4, Lilac Spring 4, Violet Verona 4, Orchid Opera 4, Chalky White 4, Java Cream 4, Golden Jasmine 4, Subtle Ivory 4, Vanilla Mist 4, C
32511ameo Silk 4, Chiffon White 4, Clouded Pearl 4, Flamingo Fun 5, Rock Candy 5, Sweet Sundae 5, Fuchsia Falls 5, Rose Trellis 5, Party Surprise 5, Silken Sunrise 5, Caribbean Dawn 5, Bongo Jazz 5, Mango Melody 5, Coral Canyon 5, Amber Queen 5, Harvest Fruits 5, Moroccan Sands 5, Polar Flame 5, Golden Rambler 5, Californian Sands 5, Pharaohs Gold 5, Golden Fern 5, Buttercup Fool 5, Banana Dream 5, Lemon Chiffon 5, Easter Morn 5, Spring Breeze 5, Lime Zest 5, Kiwi Burst 5, Willow Creek 5, Green Parrot 5, Grecian Garland 5, Granada Green 5, Forest Falls 5, Grecian Spa 5, Crystal Surprise 5, Javan Dawn 5, Summer Medley 5, Azure Sky 5, Holiday Blues 5, Blue Seduction 5, Mystic Mauve 5, Fragrant Cloud 5, Lilac Heather 5, Amethyst Showers 5, Lavender Blossom 5, Lilac Spring 5, Violet Verona 5, Orchid Opera 5, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Flamingo Fun 6, Rock Candy 6, Sweet Sundae 6, Fuchsia Falls 6, Rose Trellis 6, Party Surprise 6, Silken Sunrise 6, Caribbean Dawn 6, Bongo Jazz 6, Mango Melody 6, Coral Canyon 6, Amber Queen 6, Harvest Fruits 6, Moroccan Sands 6, Polar Flame 6, Golden Rambler 6, Californian Sands 6, Pharaohs Gold 6, Golden Fern 6, Buttercup Fool 6, Banana Dream 6, Lemon Chiffon 6, Easter Morn 6, Spring Breeze 6, Lime Zest 6, Kiwi Burst 6, Willow Creek 6, Green Parrot 6, Grecian Garland 6, Granada Green 6, Forest Falls 6, Grecian Spa 6, Crystal Surprise 6, Javan Dawn 6, Summer Medley 6, Azure Sky 6, Holiday Blues 6, Blue Seduction 6, Mystic Mauve 6, Fragrant Cloud 6, Lilac Heather 6, Amethyst Showers 6, Lavender Blossom 6, Lilac Spring 6, Violet Verona 6, Orchid Opera 6, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Emporium Rose 1, Waterlily Blush 1, Blush Rambler 1, Candy Love 1, Adobe Pink 1, Pink Nevada 1, Burnt Autumn 1, Gipsy Bloom 1, Thai Magic 1, Sicilian Summer 1, Jungle Ginger 1, Autumn Fern 1, Sumatran Melody 1, Nectar Jewels 1, Wholemeal Honey 1, Eastern Spice 1, Jamaican Bronze 1, Dutch Gold 1, Honey Drizzle 1, Tuscan Treasure 1, Golden Umber 1, Sundrenched Saffron 1, Desert Island 1, Sun Dust 1, Forest Lake 1, Soft Moss 1, Celtic Moor 1, Moorland Magic 1, Japanese Maze 1, Amazon Jungle 1, Crushed Pine 1, Peppermint Beach 1, Inky Pool 1, Velvet Touch 1, Liberty Blue 1, Luna Landscape 1, Amazon Beat 1, Atlantic Surf 1, Wild Water 1, Morning Jewel 1, Starlit Night 1, Marble Swirl 1, Purple Polka 1, Purple Sage 1, Himalayan Musk 1, Heather Bloom 1, Crushed Cotton 1, Cracked Clay 1, Frayed Hessian 1, Crumpled Linen 1, Earthen Cream 1, Gentle Gold 1, Quilted Calico 1, Twisted Bamboo 1, Emporium Rose 2, Waterlily Blush 2, Blush Rambler 2, Candy Love 2, Adobe Pink 2, Pink Nevada 2, Burnt Autumn 2, Gipsy Bloom 2, Thai Magic 2, Sicilian Summer 2, Jungle Ginger 2, Autumn Fern 2, Sumatran Melody 2, Nectar Jewels 2, Wholemeal Honey 2, Eastern Spice 2, Jamaican Bronze 2, Dutch Gold 2, Honey Drizzle 2, Tuscan Treasure 2, Golden Umber 2, Sundrenched Saffron 2, Desert Island 2, Sun Dust 2, Forest Lake 2, Soft Moss 2, Celtic Moor 2, Moorland Magic 2, Japanese Maze 2, Amazon Jungle 2, Crushed Pine 2, Peppermint Beach 2, Inky Pool 2, Velvet Touch 2, Liberty Blue 2, Luna Landscape 2, Amazon Beat 2, Atlantic Surf 2, Wild Water 2, Morning Jewel 2, Starlit Night 2, Marble Swirl 2, Purple Polka 2, Purple Sage 2, Himalayan Musk 2, Heather Bloom 2, Crushed Cotton 2, Cracked Clay 2, Frayed Hessian 2, Crumpled Linen 2, Earthen Cream 2, Gentle Gold 2, Quilted Calico 2, Twisted Bamboo 2, Emporium Rose 3, Waterlily Blush 3, Blush Rambler 3, Candy Love 3, Adobe Pink 3, Pink Nevada 3, Burnt Autumn 3, Gipsy Bloom 3, Thai Magic 3, Sicilian Summer 3, Jungle Ginger 3, Autumn Fern 3, Sumatran Melody 3, Nectar Jewels 3, Wholemeal Honey 3, Eastern Spice 3, Jamaican Bronze 3, Dutch Gold 3, Honey Drizzle 3, Tuscan Treasure 3, Golden Umber 3, Sundrenched Saffron 3, Desert Island 3, Sun Dust 3, Forest Lake 3, Soft Moss 3, Celtic Moor 3, Moorland Magic 3, Japanese Maze 3, Amazon Jungle 3, Crushed Pine 3, Peppermint Beach 3, Inky Pool 3, Velvet Touch 3, Liberty Blue 3, Luna Landscape 3, Amazon Beat 3, Atlantic Surf 3, Wild Water 3, Morning Jewel 3, Starlit Night 3, Marble Swirl 3, Purple Polka 3, Purple Sage 3, Himalayan Musk 3, Heather Bloom 3, Crushed Cotton 3, Cracked Clay 3, Frayed Hessian 3, Crumpled Linen 3, Earthen Cream 3, Gentle Gold 3, Quilted Calico 3, Twisted Bamboo 3, Emporium Rose 4, Waterlily Blush 4, Blush Rambler 4, Candy Love 4, Adobe Pink 4, Pink Nevada 4, Burnt Autumn 4, Gipsy Bloom 4, Thai Magic 4, Sicilian Summer 4, Jungle Ginger 4, Autumn Fern 4, Sumatran Melody 4, Nectar Jewels 4, Wholemeal Honey 4, Eastern Spice 4, Jamaican Bronze 4, Dutch Gold 4, Honey Drizzle 4, Tuscan Treasure 4, Golden Umber 4, Sundrenched S
32511affron 4, Desert Island 4, Sun Dust 4, Forest Lake 4, Soft Moss 4, Celtic Moor 4, Moorland Magic 4, Japanese Maze 4, Amazon Jungle 4, Crushed Pine 4, Peppermint Beach 4, Inky Pool 4, Velvet Touch 4, Liberty Blue 4, Luna Landscape 4, Amazon Beat 4, Atlantic Surf 4, Wild Water 4, Morning Jewel 4, Starlit Night 4, Marble Swirl 4, Purple Polka 4, Purple Sage 4, Himalayan Musk 4, Heather Bloom 4, Crushed Cotton 4, Cracked Clay 4, Frayed Hessian 4, Crumpled Linen 4, Earthen Cream 4, Gentle Gold 4, Quilted Calico 4, Twisted Bamboo 4, Emporium Rose 5, Waterlily Blush 5, Blush Rambler 5, Candy Love 5, Adobe Pink 5, Pink Nevada 5, Burnt Autumn 5, Gipsy Bloom 5, Thai Magic 5, Sicilian Summer 5, Jungle Ginger 5, Autumn Fern 5, Sumatran Melody 5, Nectar Jewels 5, Wholemeal Honey 5, Eastern Spice 5, Jamaican Bronze 5, Dutch Gold 5, Honey Drizzle 5, Tuscan Treasure 5, Golden Umber 5, Sundrenched Saffron 5, Desert Island 5, Sun Dust 5, Forest Lake 5, Soft Moss 5, Celtic Moor 5, Moorland Magic 5, Japanese Maze 5, Amazon Jungle 5, Crushed Pine 5, Peppermint Beach 5, Inky Pool 5, Velvet Touch 5, Liberty Blue 5, Luna Landscape 5, Amazon Beat 5, Atlantic Surf 5, Wild Water 5, Morning Jewel 5, Starlit Night 5, Marble Swirl 5, Purple Polka 5, Purple Sage 5, Himalayan Musk 5, Heather Bloom 5, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Emporium Rose 6, Waterlily Blush 6, Blush Rambler 6, Candy Love 6, Adobe Pink 6, Pink Nevada 6, Burnt Autumn 6, Gipsy Bloom 6, Thai Magic 6, Sicilian Summer 6, Jungle Ginger 6, Autumn Fern 6, Sumatran Melody 6, Nectar Jewels 6, Wholemeal Honey 6, Eastern Spice 6, Jamaican Bronze 6, Dutch Gold 6, Honey Drizzle 6, Tuscan Treasure 6, Golden Umber 6, Sundrenched Saffron 6, Desert Island 6, Sun Dust 6, Forest Lake 6, Soft Moss 6, Celtic Moor 6, Moorland Magic 6, Japanese Maze 6, Amazon Jungle 6, Crushed Pine 6, Peppermint Beach 6, Inky Pool 6, Velvet Touch 6, Liberty Blue 6, Luna Landscape 6, Amazon Beat 6, Atlantic Surf 6, Wild Water 6, Morning Jewel 6, Starlit Night 6, Marble Swirl 6, Purple Polka 6, Purple Sage 6, Himalayan Musk 6, Heather Bloom 6, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Mercury Shower 1, Soft Montelimar 1, Soft Cinnebar 1, Oyster Cove 1, Blush Noisette 1, Nordic Sails 1, Wild Mushroom 1, Nomadic Glow 1, Nutmeg Cluster 1, Sultana Spice 1, Cappuccino Candy 1, Expresso Delight 1, Roman Stone 1, Soft Almond 1, Treacle Tart 1, Summer Pecan 1, Caramel Blush 1, Soft Maplewood 1, Caramel Sand 1, Labrador Sands 1, Chalky Downs 1, Khaki Mists 1, Bracken Salts 1, Salisbury Stones 1, Gooseberry Fool 1, Celtic Forest 1, English Mist 1, Jungle Fever 1, Woodland Pearl 1, Tuscan Glade 1, Highland Falls 1, Soft Fauna 1, Sea Urchin 1, Jewelled Creek 1, Celestial Cloud 1, Winter Teal 1, Pebble Drift 1, Niagara Blues 1, Steel Symphony 1, Moon Waves 1, Imperial Mauve 1, Midnight Iris 1, Brooklyn Nights 1, Violet Surprise 1, Mellow Heather 1, Twilight Cinders 1, Potters Clay 1, Chalk Blush 1, Natural Taupe 1, Bleached Lichen 1, Dusted Moss 1, Quartz Flint 1, Mineral Haze 1, Grey Steel 1, Mercury Shower 2, Soft Montelimar 2, Soft Cinnebar 2, Oyster Cove 2, Blush Noisette 2, Nordic Sails 2, Wild Mushroom 2, Nomadic Glow 2, Nutmeg Cluster 2, Sultana Spice 2, Cappuccino Candy 2, Expresso Delight 2, Roman Stone 2, Soft Almond 2, Treacle Tart 2, Summer Pecan 2, Caramel Blush 2, Soft Maplewood 2, Caramel Sand 2, Labrador Sands 2, Chalky Downs 2, Khaki Mists 2, Bracken Salts 2, Salisbury Stones 2, Gooseberry Fool 2, Celtic Forest 2, English Mist 2, Jungle Fever 2, Woodland Pearl 2, Tuscan Glade 2, Highland Falls 2, Soft Fauna 2, Sea Urchin 2, Jewelled Creek 2, Celestial Cloud 2, Winter Teal 2, Pebble Drift 2, Niagara Blues 2, Steel Symphony 2, Moon Waves 2, Imperial Mauve 2, Midnight Iris 2, Brooklyn Nights 2, Violet Surprise 2, Mellow Heather 2, Twilight Cinders 2, Potters Clay 2, Chalk Blush 2, Natural Taupe 2, Bleached Lichen 2, Dusted Moss 2, Quartz Flint 2, Mineral Haze 2, Grey Steel 2, Mercury Shower 3, Soft Montelimar 3, Soft Cinnebar 3, Oyster Cove 3, Blush Noisette 3, Nordic Sails 3, Wild Mushroom 3, Nomadic Glow 3, Nutmeg Cluster 3, Sultana Spice 3, Cappuccino Candy 3, Expresso Delight 3, Roman Stone 3, Soft Almond 3, Treacle Tart 3, Summer Pecan 3, Caramel Blush 3, Soft Maplewood 3, Caramel Sand 3, Labrador Sands 3, Chalky Downs 3, Khaki Mists 3, Bracken Salts 3, Salisbury Stones 3, Gooseberry Fool 3, Celtic Forest 3, English Mist 3, Jungle Fever 3, Woodland Pearl 3, Tuscan Glade 3, Highland Falls 3, Soft Fauna 3, Sea Urchin 3, Jewelled Creek 3, Celestial Cloud 3, Winter Teal 3, Pebble Drift 3, Niagara Blues 3, Steel Symphony 3, Moon Waves 3, Imperial Mauve 3, Midnight Iris 3, Brooklyn Nights 3, Violet Surprise 3, Mellow Heather 3, Twilight Cinders 3, Potters Clay 3, Chalk Blush 3, Natural Taupe 3, Bleached Lichen 3, Dusted Moss 3, Quartz Flint 3, Mineral Haze 3, Grey Steel 3, Mercury Shower 4, Soft Montelimar 4, Soft Cinnebar 4, Oyster Cove 4, Blush Noisette 4, Nordic Sails 4, Wild Mushroom 4, Nomadic Glow 4, Nutmeg Cluster 4, Sultana Spice 4, Cappuccino Candy 4, Expresso Delight 4, Roman Stone 4, Soft Almond 4, Treacle Tart 4, Summer Pecan 4, Caramel Blush 4, Soft Maplewood 4, Caramel Sand 4, Labrador Sands 4, Chalky Downs 4, Khaki Mists 4, Bracken Salts 4, Salisbury Stones 4, Gooseberry Fool 4, Celtic Forest 4, English Mist 4, Jungle Fever 4, Woodland Pearl 4, Tuscan Glade 4, Highland Falls 4, Soft Fauna 4, Sea Urchin 4, Jewelled Creek 4, Celestial Cloud 4, Winter Teal 4, Pebble Drift 4, Niagara Blues 4, Steel Symphony 4, Moon Waves 4, Imperial Mauve 4, Midnight Iris 4, Brooklyn Nights 4, Violet Surprise 4, Mellow Heather 4, Twilight Cinders 4, Potters Clay 4, Chalk Blush 4, Natural Taupe 4, Bleached Lichen 4, Dusted Moss 4, Quartz Flint 4, Mineral Haze 4, Grey Steel 4, Mercury Shower 5, Soft Montelimar 5, Soft Cinnebar 5, Oyster Cove 5, Blush Noisette 5, Nordic Sails 5, Wild Mushroom 5, Nomadic Glow 5, Nutmeg Cluster 5, Sultana Spice 5, Cappuccino Candy 5, Expresso Delight 5, Roman Stone 5, Soft Almond 5, Treacle Tart 5, Summer Pecan 5, Caramel Blush 5, Soft Maplewood 5, Caramel Sand 5, Labrador Sands 5, Chalky Downs 5, Khaki Mists 5, Bracken Salts 5, Salisbury Stones 5, Gooseberry Fool 5, Celtic Forest 5, English Mist 5, Jungle Fever 5, Woodland Pearl 5, Tuscan Glade 5, Highland Falls 5, Soft Fauna 5, Sea Urchin 5, Jewelled Creek 5, Celestial Cloud 5, Winter Teal 5, Pebble Drift 5, Niagara Blues 5, Steel Symphony 5, Moon Waves 5, Imperial Mauve 5, Midnight Iris 5, Brooklyn Nights 5, Violet Surprise 5, Mellow Heather 5, Twilight Cinders 5, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Mercury Shower 6, Soft Montelimar 6, Soft Cinnebar 6, Oyster Cove 6, Blush Noisette 6, Nordic Sails 6, Wild Mushroom 6, Nomadic Glow 6, Nutmeg Cluster 6, Sultana Spice 6, Cappuccino Candy 6, Expresso Delight 6, Roman Stone 6, Soft Almond 6, Treacle Tart 6, Summer Pecan 6, Caramel Blush 6, Soft Maplewood 6, Caramel Sand 6, Labrador Sands 6, Chalky Downs 6, Khaki Mists 6, Bracken Salts 6, Salisbury Stones 6, Gooseberry Fool 6, Celtic Forest 6, English Mist 6, Jungle Fever 6, Woodland Pearl 6, Tuscan Glade 6, Highland Falls 6, Soft Fauna 6, Sea Urchin 6, Jewelled Creek 6, Celestial Cloud 6, Winter Teal 6, Pebble Drift 6, Niagara Blues 6, Steel Symphony 6, Moon Waves 6, Imperial Mauve 6, Midnight Iris 6, Brooklyn Nights 6, Violet Surprise 6, Mellow Heather 6, Twilight Cinders 6, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White, Pure Brilliant White', 32512 32513 // number of columns 32514 cols: 54, 32515 32516 // number of rows 32517 rows: 24, 32518 32519 // grid cell size including spacing 32520 size: 17, 32521 32522 // cells spacing 32523 spacing: 1, 32524 32525 // small image (scroller) 32526 sImage: { 32527 width: 260, 32528 height: 116, 32529 url: '/assets/austin/dulux-small.png' 32530 }, 32531 32532 // big image size (palette) 32533 bImage: { 32534 width: 916, 32535 height: 406, 32536 url: '/assets/austin/dulux-big.png' 32537 }, 32538 32539 init: function() { 32540 this.colors = this.colors.split(','); 32541 this.names = this.names.split(','); 32542 return this; 32543 }, 32544 32545 getName: function(color) { 32546 // at this moment colors is massive 32547 var index = this.getIndex(color); 32548 32549 if (myDeco.settings.DULUX_BRANDING && index !== -1) { 32550 color = this.names[index]; 32551 if (myDeco.settings.WL && myDeco.settings.WL.WL_PARTNER_NAME.toLowerCase() == 'dulux') { 32552 color = color + '™'; 32553 } 32554 } else {
32555 color = '#' + color; 32556 } 32557 return color; 32558 }, 32559 32560 getUrl: function(color) { 32561 var idx = this.getIndex(color); 32562 if (idx !== -1) { 32563 return 'http://ad-emea.doubleclick.net/clk;242773234;65674453;n?' + 32564 'http://www.dulux.co.uk/servlet/ShoppingBasketHandler?' + 32565 'ItemType=sku&ns_campaign=content&ns_mchannel=content&' + 32566 'ns_source=mydeco&ns_linkname=basket&action=add&ItemID=' + 32567 $.trim(this.names[idx]).toLowerCase().replace(/\s/g, '_'); 32568 } 32569 return false; 32570 } 32571}; 32572/*globals Class, $ */ 32573/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 32574 sloppy: true, white: true */ 32575/** 32576 * @class SearchModelFacet 32577 * @classDescription Abstract class for plain facets like supplier or color 32578 */ 32579var SearchModelFacet = Class.extend({ 32580 /** 32581 * List of values 32582 * @type {Object} 32583 * @private 32584 */ 32585 _values: null, 32586 32587 /** 32588 * Initial fill 32589 * @type {Boolean} 32590 * @private 32591 */ 32592 _initial: false, 32593 32594 /** 32595 * Default list of values, filled with append if initial is set to true 32596 * Useful for facets with default values like colour facet 32597 * @type {Object} 32598 * @private 32599 */ 32600 _default: {}, 32601 32602 /** 32603 * List of selected values, will be glued with facet=...& on refine 32604 * @type {Array} 32605 * @public 32606 */ 32607 selected: null, 32608 32609 /** 32610 * List of refiners which should updated with the current one 32611 * @type {Array} 32612 * @private 32613 */ 32614 _dependant: [], 32615 32616 /** 32617 * Will update self from returned values on self refine 32618 * @type {Boolean} 32619 * @private 32620 */ 32621 _updateOnRefine: true, 32622 32623 /** 32624 * Name of parameter for request 32625 * @type {String} 32626 * @public 32627 */ 32628 paramName: 'facet', 32629 32630 /** 32631 * Result sorting values 32632 * @type {Array} 32633 * @public 32634 */ 32635 sort: [], 32636 32637 /** 32638 * Skip request on facet value 32639 * @type {Array} 32640 * @private 32641 */ 32642 _skip: [], 32643 32644 /** 32645 * Init function 32646 * @param {Object} values Default values list 32647 * @public 32648 */ 32649 init: function(values) { 32650 this.selected = {}; 32651 this._values = values || {}; 32652 }, 32653 32654 /** 32655 * Function for parsing values list fetched from WS 32656 * @param {Array} values List of facets 32657 * @param {Object} appendTo Object to set facet values to, defaults to this._values 32658 * @return this 32659 * @public 32660 */ 32661 append: function(values, appendTo) { 32662 appendTo = appendTo || this._values; 32663 32664 var self = this, 32665 p; 32666 32667 // clear values on given level 32668 for (p in appendTo) { 32669 delete appendTo[p]; 32670 } 32671 32672 // process with structure like [name, count, [facet]] 32673 // where facet is "facetname,id"
32674 // TODO: check for correct count value 32675 $.each(values, function(idx) { 32676 var facet = this[2][0], 32677 id = facet.split(',').pop(); 32678 appendTo[id] = { 32679 id: id, 32680 value: facet, 32681 count: this[1], 32682 name: this[0], 32683 values: {}, 32684 order_val: idx 32685 }; 32686 32687 // fill defaults if needed 32688 if (self._initial) { 32689 self._default[id] = $.extend({}, appendTo[id]); 32690 } 32691 32692 }); 32693 32694 this._initial = false; 32695 32696 return this; 32697 }, 32698 32699 /** 32700 * Refine current facet with the new value 32701 * Clear current refiners if no value given 32702 * @param {String} value Refiner value 32703 * @return this 32704 * @public 32705 */ 32706 refine: function(values) { 32707 32708 if (values) { 32709 var id = values.shift(); 32710 32711 // toggle value 32712 if (typeof this.selected[id] !== 'undefined') { 32713 delete this.selected[id]; 32714 } else { 32715 this.selected[id] = values.join(':'); 32716 } 32717 } else { 32718 this.reset(); 32719 } 32720 32721 return this; 32722 }, 32723 32724 /** 32725 * Reset refiner to its original state 32726 * @public 32727 */ 32728 reset: function() { 32729 this._values = $.extend(true, {}, this._default); 32730 this.selected = {}; 32731 }, 32732 32733 /** 32734 * Return selected facets as list of refiners 32735 * @public 32736 */ 32737 getSelected: function() { 32738 var selected = {}; 32739 selected[this.paramName] = $.values(this.selected); 32740 if (selected[this.paramName].length > 0 && this.sort.length > 0) { 32741 selected.sort = this.sort; 32742 } 32743 return selected; 32744 }, 32745 32746 /** 32747 * Determine is current facet have some selected values 32748 * @public 32749 */ 32750 isSelected: function() { 32751 return $.keys(this.selected).length !== 0; 32752 }, 32753 32754 /** 32755 * Check is facet need to request for given value 32756 * @param {Any} value Value to check 32757 * @public 32758 */ 32759 skip: function(value) { 32760 if (typeof value === 'undefined') { 32761 value = $.keys(this.selected || {}).shift(); 32762 } 32763 return value && $.inArray(value, this._skip) !== -1; 32764 } 32765}); 32766 32767/** 32768 * List of facets by its name, should extend {SearchModelFacet} 32769 */ 32770var SearchModelFacets = {}; 32771/*globals SearchModelFacets, SearchModelFacet, $ */ 32772/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 32773 sloppy: true, white: true */ 32774/** 32775 * Category refiner facet class 32776 */ 32777SearchModelFacets.category = SearchModelFacet.extend({ 32778 /** 32779 * Facet name 32780 * @type {String} 32781 * @public 32782 * @static 32783 */ 32784 $name: 'category', 32785 32786 /** 32787 * Current selected path 32788 * @type {Array} 32789 * @public 32790 */ 32791 path: [], 32792 32793 /** 32794 * Facets which depend from this 32795 * @type {Array} 32796 * @private 32797 */ 32798 _dependant: ['image_colour'], 32799 32800 /** 32801 * Depth limit 32802 * @type {Number} 32803 * @public 32804 */ 32805 limit: 4, 32806 32807 /** 32808 * Append new values from WS 32809 * @param {Array} values Array of given values from WS 32810 * @public 32811 */ 32812 append: function(values) { 32813 if (this.path.length < this.limit) { 32814 var appendTo = this._values, 32815 pathId; 32816 32817 // find last element in tree relative to the last element in path 32818 $.each(this.path, function() { 32819 appendTo = appendTo[this.id].values; 32820 }); 32821 32822 // protect of circular references 32823 pathId = $.map(this.path, function(item) { return item.id; }); 32824 32825 values = $.grep(values, function(item) { 32826 return $.inArray(item[2][0].split(',').pop(), pathId) < 0; 32827 }); 32828 32829 this._super(values, appendTo); 32830 } 32831 return this; 32832 }, 32833 32834 /** 32835 * Refine search by category, only one category selected 32836 * @param {String} value Refiner value 32837 * @public 32838 */ 32839 refine: function(values) { 32840 // reset facet 32841 if (!values || values[0] === false) { 32842 this.reset(); 32843 } 32844 32845 if (values && values[0] === false) { 32846 values.shift(); 32847 } 32848 32849 if (values) { 32850 var depth = values.pop(), 32851 32852 // name of element in path 32853 id = values[0], 32854 v; 32855 32856 // fill selected values 32857 this.selected = {}; 32858 32859 // check for refining with previous selection 32860 if (depth < this.path.length) { 32861 // store path to appendTo 32862 // cut path 32863 this.path.splice(depth + 1, this.path.length - depth); 32864 32865 // set selected to previous to this one 32866 this.selected[id] = this.path[depth].facet; 32867 32868 } else { 32869 v = values.pop(); 32870 32871 this.path.push({id: id, facet: v}); 32872 this.selected[id] = v; 32873 } 32874 } 32875 32876 return this; 32877 }, 32878 32879 /** 32880 * Reset refiner to its original state 32881 * @public 32882 */ 32883 reset: function() { 32884 this.path = []; 32885 this.selected = {}; 32886 } 32887}); 32888/*globals SearchModelFacets, SearchModelFacet */ 32889/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 32890 sloppy: true, white: true */ 32891/** 32892 * Image colour facet 32893 */ 32894SearchModelFacets.image_colour = SearchModelFacet.extend({ 32895 /** 32896 * Facet name 32897 * @type {String} 32898 * @public 32899 * @static 32900 */ 32901 $name: 'image_colour', 32902 32903 /** 32904 * Do not update self on self refine 32905 * @type {Boolean} 32906 * @private 32907 */ 32908 _updateOnRefine: false, 32909 32910 /** 32911 * Init, use initial fill 32912 * @param {Object} values 32913 * @public 32914 */ 32915 init: function(values) { 32916 this._initial = true; 32917 this._super(values); 32918 }, 32919 32920 /* A HACK TO HAVE JUST ONE COLOR (OR NONE) SELECTED */ 32921 refine: function(values) { 32922 this.selected = {}; 32923 return this._super(values); 32924 } 32925}); 32926/*globals SearchModelFacets, SearchModelFacet, $ */ 32927/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 32928 sloppy: true, white: true */ 32929/** 32930 * Item dimensions facet, simulated 32931 */ 32932SearchModelFacets.dimension = SearchModelFacet.extend({ 32933 /** 32934 * Facet name 32935 * @type {String} 32936 * @public 32937 * @static 32938 */ 32939 $name: 'dimension', 32940 32941 /** 32942 * Shorthand to facet name converting matrix 32943 * @type {Object} 32944 * @private 32945 */ 32946 _types: { 32947 w: '3dwidth', 32948 h: '3dheight', 32949 d: '3ddepth' 32950 }, 32951 32952 /** 32953 * Set param name for 'ranges' 32954 * @type {String} 32955 * @public 32956 */ 32957 paramName: 'ranges', 32958 32959 /** 32960 * Sort by area size desc 32961 * @type {Array} 32962 */ 32963 /* sort: ['-3darea'], */ 32964 32965 /** 32966 * Refine facet method 32967 * @type {String} value Value refine with 32968 * @public 32969 */ 32970 refine: function(values) { 32971 this.selected = {}; 32972 32973 if (!values) { 32974 return this; 32975 } 32976 32977 var self = this, 32978 name; 32979 32980 for (name in values) { 32981 if (values[name]) { 32982 self.selected[name] = self._types[name] + ',0,' + values[name]; 32983 } 32984 } 32985 32986 return this; 32987 }, 32988 32989 /** 32990 * Return all currently selected values 32991 */ 32992 getSelected: function() { 32993 var selected = this._super(); 32994 // Hide all on-wall products from search by dimension 32995 if (selected[this.paramName].length > 0) { 32996 selected.filter = ['wall_mounted,0']; 32997 } 32998 return selected; 32999 } 33000}); 33001 33002/*globals Class, $ */ 33003/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 33004 sloppy: true, white: true */ 33005/** 33006 * @class ItemsListModel 33007 * @classDescription Abstract class used as model in misc compnent list based models 33008 */ 33009var ItemsListModel = Class.extend({ 33010 /** 33011 * Model type 33012 * @type {String} 33013 * @public 33014 * @static 33015 */ 33016 $type: 'items-list', 33017 33018 /** 33019 * Fetched items list 33020 * @type {Array} 33021 * @public 33022 */ 33023 items: [], 33024 33025 /** 33026 * Index of fetched items 33027 * @type {Object} 33028 * @public 33029 */ 33030 itemsIndex: { 33031 'scrapbook': {}, 33032 'component': {}, 33033 'material': {}, 33034 'product': {} 33035 }, 33036 33037 /** 33038 * Callbacks list which will be run after initial loading is done 33039 * @type {Array} 33040 * @private 33041 */ 33042 _onInit: [], 33043 33044 /** 33045 * Loaded flag 33046 * @type {Boolean} 33047 * @private 33048 */ 33049 loaded: false, 33050 33051 /** 33052 * Estimate number of search result 33053 * @type {Number} 33054 * @public 33055 */ 33056 found: 0, 33057 33058 /** 33059 * Items type, component, material etc 33060 * @type {String} 33061 */ 33062 type: 'component', 33063 33064 /** 33065 * Main init function, no parameters yet 33066 * @public 33067 */ 33068 init: function() { 33069 var self = this; 33070
33071 // set loaded flag 33072 this.loaded = true; 33073 33074 // run loading callbacks 33075 $.each(this._onInit, function() { 33076 this.call(null, self); 33077 }); 33078 33079 }, 33080 /** 33081 * Append loading callback which will run on init 33082 * @type {Function} callback 33083 * @public 33084 */ 33085 onInit: function(callback) { 33086 if (!this.loaded) { 33087 this._onInit.push(callback); 33088 } else { 33089 callback(this); 33090 } 33091 }, 33092 33093 /** 33094 * Reset all data 33095 */ 33096 reset: function() { 33097 var key; 33098 // update items list 33099 for (key in this.itemsIndex) { 33100 this.itemsIndex[key] = {}; 33101 } 33102 this.items = []; 33103 33104 // possible items count 33105 this.found = 0; 33106 }, 33107 33108 /** 33109 * Fetch additional items from WS, stub 33110 * @param {Number} offset Items result starting from 33111 * @param {Function} callback Callback run after successful request 33112 */ 33113 fetchMore: function() { 33114 }, 33115 33116 /** 33117 * Fetch additional component info from WS with syncronous request by list element mousedown event 33118 * @param {String} id Item ID 33119 * @param {Function} callback Function called when componen information arrived 33120 * @public 33121 */ 33122 requestComponentInfo: function(id, callback) { 33123 if ($.isFunction(callback)) { 33124 callback(this.itemsIndex[this.type][id]); 33125 } 33126 return this.itemsIndex[this.type][id]; 33127 } 33128}); 33129/*globals ItemsListModel, $, SearchModelFacets, myDeco */ 33130/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 33131 sloppy: true, white: true */ 33132/** 33133 * @class SearchModel 33134 * @classDescription Class used as model in MVC search representation 33135 */ 33136var SearchModel = ItemsListModel.extend({ 33137 /** 33138 * Model type 33139 * @type {String} 33140 * @public 33141 * @static 33142 */ 33143 $type: 'search', 33144 33145 /** 33146 * Items type 33147 * @type {String} 33148 */ 33149 type: 'component', 33150 33151 /** 33152 * Facets available 33153 * @type {Object} 33154 * @public 33155 */ 33156 facets: {}, 33157 33158 /** 33159 * List of used filters 33160 * @type {Object} 33161 * @public 33162 */ 33163 filters: {}, 33164 33165 /** 33166 * Query value 33167 * @type {String} 33168 * @public 33169 */ 33170 query: '', 33171 33172 /** 33173 * Facets used in search 33174 * @type {Array} 33175 * @private 33176 */ 33177 _facetsUsed: ['category', 'manual_colour', 'dimension'], 33178 33179 /** 33180 * Flag for request status 33181 * @type {Boolean} 33182 * @private 33183 */ 33184 _requestActive: false, 33185 33186 /** 33187 * Default facet used for refine if no facet specified 33188 * @type {String} 33189 * @public 33190 */ 33191 defaultFacet: '', 33192 33193 /** 33194 * Default WS request options 33195 * @private 33196 */ 33197 _default: { 33198 // database to request 33199 db: 'component', 33200 33201 // items count 33202 count: 30, 33203 33204 // list of facets 33205 requirefacet: ['category', 'manual_colour'], 33206 33207 api_version: 2, 33208 33209 locale: 'en-gb' /*myDeco.settings.LOCALE*/ 33210 }, 33211 33212 /** 33213 * number of items fetched for the initial request 33214 * @type {Number} 33215 */ 33216 _initialCount: 0, 33217 33218 /** 33219 * Additional options passed to backed 33220 * @type {Object} 33221 */ 33222 additionalOptions: {}, 33223 33224 /** 33225 * Options excluded from general and additional options 33226 * @type {Object} 33227 */ 33228 excludedOptions: {}, 33229 33230 /** 33231 * Main init function, no parameters yet 33232 * @param {Object} options Misc init options 33233 * @param {Object} additionalOptions which can be passed at init state 33234 * @public 33235 */ 33236 init: function(options, additionalOptions) { 33237 options = options || {}; 33238 33239 this.defaultFacet = options.defaultFacet || this._facetsUsed[0]; 33240 33241 var self = this, 33242 i, ilen, facetName, ClassName; 33243 33244 // extend options 33245 $.each(additionalOptions || {}, function(key, value) { 33246 if ($.isArray(value)) { 33247 self.additionalOptions[key] = $.merge( 33248 self.additionalOptions[key] || [], 33249 value 33250 ); 33251 } else { 33252 self.additionalOptions[key] = $.extend( 33253 true, 33254 self.additionalOptions[key] || {}, 33255 value 33256 ); 33257 } 33258 }); 33259 33260 // exclude options 33261 $.each(this.excludedOptions, function(key, value) { 33262 if (self.additionalOptions[key]) { 33263 if ($.isArray(value)) { 33264 $.each(value, function() { 33265 var param = this; 33266 self.additionalOptions[key] = $.grep(self.additionalOptions[key], 33267 function(param1) { 33268 return param1.indexOf(param) !== 0; 33269 }); 33270 }); 33271 } 33272 } 33273 }); 33274 33275 // init facets list 33276 for (i = 0, ilen = this._facetsUsed.length; i < ilen; i++) { 33277 facetName = this._facetsUsed[i]; 33278 ClassName = SearchModelFacets[facetName]; 33279 33280 // use class if it available 33281 if (ClassName) { 33282 this.facets[facetName] = new ClassName(); 33283 } 33284 } 33285 33286 var init_facets = $.proxy(function() { 33287 // initial fill of default facets 33288 this._request({count: this._initialCount}, function() { 33289 // we can't execute parent constructior in the delayed mode 33290 // so copy init code from parent 33291 self.loaded = true; 33292 33293 // run loading callbacks 33294 $.each(self._onInit, function() { 33295 this.call(null, self); 33296 }); 33297 }); 33298 }, this); 33299 33300 if (this.$type == 'search') { 33301 myDeco.new_search.get_used_categories( function(keys) { 33302 var result = []; 33303 var copy_used = function(source, dest) { 33304 source.forEach( function(category) { 33305 if (keys.indexOf(category.id) > -1) { 33306 clone = {name: category.name, id: category.id, children: []}; 33307 dest.push(clone); 33308 copy_used(category.children, clone.children); 33309 } } ) 33310 } 33311 copy_used(myDeco.settings.ITEM_CATEGORIES, result); 33312 myDeco.settings.ITEM_CATEGORIES = result; 33313 init_facets(); }) 33314 } else { 33315 init_facets(); 33316 } 33317 }, 33318 33319 /** 33320 * Check for request availability 33321 * @return {Boolean} 33322 * @public 33323 */
33324 searchAllowed: function() { 33325 var name; 33326 33327 if (this._requestActive) { 33328 return false; 33329 } 33330 33331 for (name in this.facets) { 33332 if (this.facets[name].skip()) { 33333 return false; 33334 } 33335 } 33336 33337 return true; 33338 }, 33339 33340 /** 33341 * Refine search with given facet 33342 * @type {String} facetName Facet name to refine with 33343 * @type {String} facetValue Passed value to facet refined 33344 * @type {Function} callback Optional callback for running after refine is done (refine view for example) 33345 * @public 33346 */ 33347 refine: function(facetName, facetValue, callback) { 33348 var name = facetName || this.defaultFacet, 33349 options = {}, 33350 facet = this.facets[name], 33351 33352 // list of facets which will refine after loading 33353 refineFacets = this.facets[name]._dependant; 33354 33355 if (facet._updateOnRefine) { 33356 refineFacets.push(name); 33357 } 33358 33359 if (facetName) { 33360 facet.refine(facetValue); 33361 } 33362 33363 this.reset(); 33364 this._request(options, callback, refineFacets); 33365 }, 33366 33367 /** 33368 * Fetch additional items from WS 33369 * @param {Number} offset Items result starting from 33370 * @param {Function} callback Callback run after successful request 33371 */ 33372 fetchMore: function(offset, callback) { 33373 this._request({startrank: offset}, callback); 33374 }, 33375 33376 /** 33377 * Append/remove filter to query 33378 * @param {String|Object} name Name of filter or object with filters 33379 * @param {String|Function} value Filter value or Callback in case of first 33380 * first value is object. Will remove from 33381 * list if omitted or undefined 33382 * @param {Function} callback Callback run after successful request 33383 */ 33384 filter: function(name, value, callback) { 33385 if (typeof name === 'object') { 33386 this.filters = name; 33387 callback = value; 33388 } else { 33389 if (typeof value === 'undefined' && this.filters[name]) { 33390 delete this.filters[name]; 33391 } else { 33392 this.filters[name] = value; 33393 } 33394 } 33395 33396 this.reset(); 33397 this._request({}, callback); 33398 }, 33399 33400 /** 33401 * Request search result from backend 33402 * @param {Object} options Options to request, will merge with default options and facets 33403 * @param {Function} callback Optional callback run after request 33404 * @private 33405 */ 33406 _request: function(options, callback, refineFacets) { 33407 var self = this; 33408 33409 if (!this.searchAllowed()) { 33410 return callback({}); 33411 } 33412 33413 refineFacets = refineFacets || this._facetsUsed; 33414 this._requestActive = true; 33415 33416 myDeco.body.trigger('model-request-sent', [this.$type]); 33417 33418 myDeco.App.ws.proxy.search(this._constructOptions(options), function(data, dictionary, list) { 33419 33420 var facet_data = data.results[self._default.db].facets || []; 33421 33422 // override categories for components 33423 if (self.$type == 'search') { 33424 var parent_cat = self.facets.category.getSelected().facet[0]; 33425 var cats = myDeco.settings.ITEM_CATEGORIES; 33426 33427 var recursive_find = function(items, id) { 33428 var found = null; 33429 var idx; 33430 for(idx = 0; idx < items.length; idx++) { 33431 var item = items[idx]; 33432 if (item.id == id) { 33433 found = item; 33434 break; 33435 } else { 33436 found = recursive_find(item.children, id); 33437 if (found) break; 33438 } 33439 } 33440 return found; 33441 }; 33442 33443 if (parent_cat) { 33444 var parent_id = parseInt(parent_cat.split(',').pop()); 33445 var cat = recursive_find(cats, parent_id); 33446 cats = cat ? cat.children : []; 33447 } 33448 33449 var formatted_cats = $.map(cats, function(cat) { 33450 return [[cat.name, 1, ["new," + cat.id.toString()]]]; 33451 }); 33452 facet_data = [["category", formatted_cats]]; 33453 } 33454 33455 // override categories for materials 33456 if (self.$type == 'materials-search') { 33457 var parent_cat = self.facets["class"].getSelected().facet[0]; 33458 var material_classes = (myDeco.settings.WL && myDeco.settings.WL.MATERIAL_CLASSES && JSON.parse(myDeco.settings.WL.MATERIAL_CLASSES)) || 33459 ["carpet", "paint", "stone", "tile", "wallpaper", "wood", "patio"]; 33460 33461 var cats = parent_cat ? [] : $.map(material_classes, function(c, i) { 33462 return [[c, i + 1, ["class," + c]]]; // for some reason, $.map flattens resulting array, so I have to nest it twice 33463 }); 33464 33465 facet_data = [["class", cats]]; 33466 } 33467 33468 // refine facets 33469 $.each(facet_data, function() { 33470 var facetName = this.shift(); 33471 33472 if ($.inArray(facetName, refineFacets) >= 0) { 33473 self.facets[facetName].append(this.shift()); 33474 } 33475 }); 33476 33477 // set possible number of items 33478 self.found = data.results[self._default.db].estmatches || 0; 33479 33480 $.extend(true, self.itemsIndex[self.type], dictionary); 33481 33482 $.each(list, function(i, id) { 33483 self.items.push(self.itemsIndex[self.type][id]); 33484 }); 33485 33486 $.each(list, function(i, id) { 33487 var item = self.itemsIndex[self.type][id]; 33488 // HACK for John Lewis. They wanted to show only their own wallpapers, but the search engine 33489 // doesn't support this filtering. So the filtering happens here... 33490 // NOTE it only filters the data that is coming in. If the data doesn't have John Lewis wallpapers 33491 // the sidebar will be empty... 33492/* if(self.facets.class && self.facets.class.selected.wallpaper && 33493 myDeco.settings.WL && myDeco.settings.WL.WL_MYDECO_COM_REPLACEMENT === 'John Lewis') { 33494 if(item.supplier === 'John Lewis') { 33495 self.items.push(item); 33496 } 33497 } else { 33498 self.items.push(item); 33499 } 33500*/ }); 33501 33502 self._requestActive = false;
33503 33504 if ($.isFunction(callback)) { 33505 callback(self); 33506 } 33507 }); 33508 }, 33509 33510 /** 33511 * Construct request parameters for search request 33512 * @param {Object} options Options URL constructed from 33513 * @private 33514 * @returns {Object} 33515 */ 33516 _constructOptions: function(options) { 33517 var facetName, name, facet, selected, filters, i; 33518 33519 // do not request any items if no facet selected and query is empty 33520 if (!this.query && typeof options.count === 'undefined' && $.keys(this.filters).length === 0 && 33521 !$.any($.values(this.facets), function(facet) { return facet.isSelected(); })) { 33522 options.count = this._initialCount; 33523 } 33524 33525 // extend with default options 33526 options = $.extend(true, {}, this._default, options); 33527 33528 // list of selected facets 33529 for (facetName in this.facets) { 33530 33531 facet = this.facets[facetName]; 33532 selected = facet.getSelected(); 33533 33534 for (name in selected) { 33535 options[name] = $.merge(options[name] || [], selected[name]); 33536 } 33537 } 33538 33539 // filters 33540 filters = $.extend({}, this.filters, options.filters || {}); 33541 delete options[filters]; 33542 33543 if ($.keys(filters).length > 0) { 33544 options.filter = []; 33545 for (name in filters) { 33546 options.filter.push([name + ',' + filters[name]]); 33547 } 33548 } 33549 33550 // store and add query only if it needed 33551 if (typeof options.query !== 'undefined') { 33552 this.query = options.query; 33553 } 33554 33555 if (this.query) { 33556 options.query = this.query; 33557 } else { 33558 delete options.query; 33559 } 33560 33561 if (options.sort && $.isArray(options.sort)) { 33562 options.sort = options.sort.join(','); 33563 } 33564 33565 // integrate additional options 33566 for (name in this.additionalOptions) { 33567 options[name] = $.merge(options[name] || [], this.additionalOptions[name]); 33568 } 33569 33570 // process ranges 33571 if ($.isArray(options.ranges)) { 33572 for (i = options.ranges.length - 1; i >= 0; i--) { 33573 if (options.ranges[i].indexOf('completed_date') === 0) { 33574 facet = options.ranges[i].split(','); 33575 // means that ranges provides only number of days from now 33576 if (facet.length === 2) { 33577 // now timestamp 33578 facet[2] = Math.round((new Date()).valueOf() / 1000); 33579 // now - N days timestamp 33580 facet[1] = facet[2] - (+facet[1]) * 24 * 60 * 60; 33581 options.ranges[i] = facet.join(','); 33582 // NOTE: remove this if you want to process another 33583 // ranges later 33584 break; 33585 } 33586 } 33587 } 33588 } 33589 return options; 33590 } 33591 33592}); 33593/*globals myDeco, AppController, $ */ 33594/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 33595 sloppy: true, white: true */ 33596/** 33597 * @class ItemsListController 33598 * @classDescription Abstract class for misc item list based controllers 33599 */ 33600var ItemsListController = AppController.extend({ 33601 /** 33602 * Controller type 33603 * @type {String} 33604 * @public 33605 * @static 33606 */ 33607 $type: 'items-list', 33608 33609 /** 33610 * Current position of fetchNext items 33611 * @type {Number} 33612 * @public 33613 */ 33614 position: -1, 33615 33616 /** 33617 * Flag indicated is drag operation executed 33618 * @type {Boolean} 33619 */ 33620 hasOperation: false, 33621 33622 /** 33623 * Array of types of undraggable elements 33624 * @type {Array} 33625 * @public 33626 * @static 33627 */ 33628 undraggableTypes: [], 33629 33630 /** 33631 * Register view for a given controller 33632 * @param {FpeView} view 33633 * @public 33634 */ 33635 registerView: function(view) { 33636 var self = this; 33637 33638 this._super(view); 33639 33640 this._view._canvas.mousedown(this.renderComponent.bind(this)); 33641 33642 // bind controllers to component drag start 33643 myDeco.body 33644 .bind('controller-fetch-next', this.fetchNext.bind(this)) 33645 .bind('controller-fetch-prev', this.fetchPrev.bind(this)) 33646 .bind('controller-component-info', this.fetchComponent.bind(this)) 33647 .bind('roomCleared', function() { 33648 self._view._canvas.find('li').removeClass(self.settings.inRoomClass); 33649 }) 33650 .bind('mouseup', this.operationReset.bind(this)); 33651 // hide/display component info 33652 this._view._canvas.delegate('li', 'hover', 33653 $.proxy(this._view.displayComponentInfo, this._view)); 33654 33655 this.bindRelatedEvents(); 33656 33657 }, 33658 33659 operationReset: function(e) { 33660 if (this.hasOperation) { 33661 this._super(e); 33662 this.hasOperation = false; 33663 } 33664 }, 33665 33666 /** 33667 * Listener for fetch next event 33668 * @param {Event} e 33669 * @param {String} type 33670 * @param {Function} callback 33671 * @param {Boolean} first Force to reset position to a first item 33672 */ 33673 fetchNext: function(e, type, callback, first) { 33674 if (type !== this.$type) { 33675 return; 33676 } 33677 33678 var self = this, 33679 ownCallback = function() { 33680 if (first) { 33681 self.position = -1; 33682 }
33683 if (self.position < self._model.found) { 33684 self.position = self.position + 1; 33685 } 33686 if ($.isFunction(callback)) { 33687 callback(self._model.items[self.position] ? self.fetchComponent(false, self.$type, self._model.items[self.position].component_id) : undefined, 33688 self.position, self._model.found); 33689 } 33690 }; 33691 33692 if (this.position < this._model.items.length - 1) { 33693 ownCallback(); 33694 } else { 33695 this.fetchMore(ownCallback); 33696 } 33697 }, 33698 33699 /** 33700 * Listener for fetch prev event 33701 * @param {Event} e 33702 * @param {String} type 33703 * @param {Function} callback 33704 */ 33705 fetchPrev: function(e, type, callback) { 33706 if (type !== this.$type) { 33707 return; 33708 } 33709 33710 if (this.position > 0) { 33711 this.position = this.position - 1; 33712 } 33713 if ($.isFunction(callback)) { 33714 callback(this.fetchComponent(false, this.$type, this._model.items[this.position].component_id), this.position, this._model.found); 33715 } 33716 }, 33717 33718 /** 33719 * Listener for fetchComponent event 33720 * Outline requested item if it exist 33721 * @param {Event} e 33722 * @param {String} type 33723 * @param {Number} id 33724 * @param {Function} callback 33725 */ 33726 fetchComponent: function(e, type, id, callback) { 33727 if (type !== this.$type || (!id && e.target.tagName !== 'LI')) { 33728 return false; 33729 } 33730 33731 this.hasOperation = true; 33732 id = id || $(e.target).closest('li').data('id'); 33733 this._view.selectComponent(id); 33734 33735 return this._model.requestComponentInfo(id, callback); 33736 }, 33737 33738 /** 33739 * Event listener to initiate drag-n-drop from components panel 33740 * @param {Event} e Mousedown event object 33741 * @param {Number} id 33742 * @public 33743 */ 33744 renderComponent: function(e, id) { 33745 var self = this, options, 33746 type = $(e.target).closest('.scroller-holder').data('type'); 33747 33748 if (e.isDefaultPrevented() || e.target.tagName !== 'LI') { 33749 return; 33750 } 33751 e.stop(); 33752 33753 // remove decor special items 33754 if ($(e.target).hasClass('no-decor')) { 33755 myDeco.App.model.removeDecor(type); 33756 } else { 33757 //all other items 33758 this.fetchComponent(e, this.$type, id, function (component) { 33759 33760 // remove component_id for paints 33761 if (component['class'] === 'color') { 33762 delete component.component_id; 33763 } 33764 33765 // store component in cache 33766 if (myDeco.App.ws.proxy.has(self._model.type) && 33767 self._model.itemsIndex[self._model.type][component.component_id]) { 33768 myDeco.App.ws.proxy.set(self._model.type, component.component_id, 33769 self._model.itemsIndex[self._model.type][component.component_id]); 33770 } 33771 33772 options = { 33773 kind: 'furniture', 33774 entity: component, 33775 visual: self._view.generateElementForDrag(component) 33776 }; 33777 33778 switch (self._model.type) { 33779 case 'material': 33780 case 'scrapbook': 33781 options.kind = 'decor'; 33782 options.ox = options.oy = -10; 33783 options.type = component['class']; 33784 break; 33785 } 33786 33787 if ($.inArray(type, self.undraggableTypes) == -1) { 33788 myDeco.body.trigger('entity-drag-started', [options, e]); 33789 } else { 33790 myDeco.App.model.addImage(options.entity, 0, 0, type); 33791 } 33792 33793 }); 33794 } 33795 }, 33796 33797 /** 33798 * Fetch one more set from backend 33799 * @public 33800 */ 33801 fetchMore: function(callback) { 33802 var len = this._model.items.length, 33803 self = this; 33804 33805 if (this._model.found - len > 0) { 33806 this._view.scrollerLoading(true); 33807 this._model.fetchMore(len, function() { 33808 self._view.appendItems(self._model.items.slice(len)); 33809 self._view.scrollerLoading(false); 33810 if ($.isFunction(callback)) { 33811 callback(); 33812 } 33813 }); 33814 } else if ($.isFunction(callback)) { 33815 callback(); 33816 } 33817 }, 33818 33819 /** 33820 * Run event listeners related to the model 33821 */ 33822 bindRelatedEvents: function() { 33823 var self = this; 33824 33825 myDeco.body.bind(this._model.type + 'Added ' + this._model.type + 33826 'Removed', function(e, component) { 33827 self._view._canvas.find('.' + self._model.type + '-' + 33828 (component.component_id || '').replace(/#/, '_')) 33829 .toggleClass(self._view.settings.inRoomClass, 33830 myDeco.App.model['is' + self._model.type.capitalize() + 33831 'InModel'].call(myDeco.App.model, component)); 33832 }); 33833 33834 } 33835 33836}); 33837/*globals ItemsListController, $, myDeco */ 33838/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 33839 sloppy: true, white: true */ 33840/** 33841 * Search controller base class 33842 * 33843 * Listen on body: 33844 * controller-search-facet-refine
33845 * {String} facet name 33846 * {Any} refine data 33847 * 33848 * Triggers on body: 33849 * controller-search-facet-toggle: 33850 * {String} facet name 33851 * {Boolean} state 33852 * controller-search-view-registered: 33853 * {jQset} canvas, 33854 * {Function} component info fetch function 33855 */ 33856var SearchController = ItemsListController.extend({ 33857 /** 33858 * Controller type 33859 * @type {String} 33860 * @public 33861 * @static 33862 */ 33863 $type: 'search', 33864 33865 /** 33866 * Main search form 33867 * @type {jQset} 33868 */ 33869 _form: false, 33870 33871 /** 33872 * Query field 33873 * @type {jQset} 33874 */ 33875 _query: false, 33876 33877 /** 33878 * Register view for a given controller 33879 * @param {FpeView} view 33880 * @public 33881 */ 33882 registerView: function(view) { 33883 this._super(view); 33884 33885 var self = this; 33886 this._form = this._view.canvasElement.find('form.search'); 33887 this._query = this._form.find('input[name=query]'); 33888 33889 // listen search bar for submit 33890 this._form.submit(function(e) { 33891 e.stop(); 33892 if (self._view.facets.category) { 33893 self._view.facets.category.toggle(false); 33894 self.search(self._query.val(), 'category'); 33895 } 33896 }); 33897 33898 this._view._queryHolder.find('a').click(function(e) { 33899 e.stop(); 33900 self._query.val(''); 33901 self._form.submit(); 33902 }); 33903 33904 // bind controllers to component drag start 33905 myDeco.body 33906 .bind('controller-facet-toggle', function(e, type, facetName, state) { 33907 if ((type === false || self.$type === type) && self._view.facets[facetName]) { 33908 self._view.facets[facetName].toggle(state); 33909 } 33910 }) 33911 .bind('controller-facet-refine', function(e, type, facetName, data, callback) { 33912 if (type === false || type === self.$type) { 33913 self.search(false, facetName, data, callback); 33914 } 33915 }); 33916 }, 33917 33918 /** 33919 * Register refiner from view 33920 * @param {jQset} element Element on which listener will applied 33921 * @param {Any} any Parameter additionally passed to the listener 33922 * ... 33923 * @public 33924 */ 33925 registerRefiner: function(element) { 33926 var parameters = Array.prototype.slice.call(arguments, [1]); 33927 33928 parameters.unshift(this); 33929 33930 if (element.get(0).tagName === 'A') { 33931 element.click(Function.prototype.bindAsEventListener.apply(this.refineByClick, parameters)); 33932 } else if (element.get(0).tagName === 'FORM') { 33933 element.submit(Function.prototype.bindAsEventListener.apply(this.refineBySubmit, parameters)); 33934 } 33935 return element; 33936 }, 33937 33938 /** 33939 * Register model filter 33940 * @param {jQset} elements 33941 * @public 33942 */ 33943 registerFilter: function(elements) { 33944 var self = this; 33945 33946 elements.change(function(e) { 33947 var element = $(e.target), 33948 name = element.attr('name'), 33949 value = element.val(); 33950 33951 if (e.target.tagName === 'SELECT') { 33952 element = element.children().eq(e.target.selectedIndex); 33953 name = element.attr('name') || name; 33954 } 33955 33956 value = element.data('json') || value; 33957 33958 if ((e.target.tagName === 'INPUT' && !element.is(':checked')) || 33959 (e.target.tagName === 'SELECT' && value === '')) { 33960 value = undefined; 33961 } 33962 33963 self._view.loading(true); 33964 if (typeof value === 'object') { 33965 self._model.filter(value, self._view.redraw.bind(self._view)); 33966 } else { 33967 self._model.filter(name, value, self._view.redraw.bind(self._view)); 33968 } 33969 }); 33970 }, 33971 33972 /** 33973 * Register refiner invoker to show/hide main refiner area 33974 * @param {jQset} element 33975 * @param {String} facetName 33976 * @public 33977 */ 33978 registerRefinerInvoker: function(element, facetName) { 33979 var self = this; 33980 33981 if (!element.data('registered')){ 33982 element.data('registered', 'true').click(function(e) { 33983 e.stop(); 33984 myDeco.body.trigger('controller-facet-toggle', 33985 [self.$type, facetName, self._view.facets[facetName].toggle()]); 33986 }); 33987 } 33988 33989 if(myDeco.settings.WL && myDeco.settings.WL.WL_DEFAULT_COMPONENT_CATEGORY) { 33990 if(this['$type'] === 'search') { 33991 this.search(false, "category", [myDeco.settings.WL.WL_DEFAULT_COMPONENT_CATEGORY, 33992 "cat1," + myDeco.settings.WL.WL_DEFAULT_COMPONENT_CATEGORY, 0]) 33993 } 33994 } 33995 }, 33996 33997 /** 33998 * Register resetter from view 33999 * @param {jQset} element Link on which reset will bind 34000 * @public 34001 */ 34002 registerResetter: function(element) { 34003 var self = this; 34004 34005 element.click(function(e) { 34006 e.stop(); 34007 34008 var facetName = $(e.target).data('filter'); 34009 34010 self._view.facets[facetName].toggle(false); 34011 self.search(false, facetName); 34012 }); 34013 34014 return element; 34015 }, 34016 34017 /** 34018 * Click-based refiner, get value from data storage 34019 * @param {Event} e 34020 * @public 34021 */ 34022 refineByClick: function(e) { 34023 e.stop(); 34024 34025 var target = $(e.target).closest('.filter-ref').data('filter'), 34026 facetName = target.shift(); 34027 34028 this.search(false, facetName, target); 34029 34030 this._view.facets[facetName].toggle(false); 34031 }, 34032 34033 /** 34034 * Submit-based refiner, get value as form serialize 34035 * @param {Event} e 34036 * @param {String} facetName Facet name, should passed from registerRefiner as additional parameter 34037 * @public 34038 */ 34039 refineBySubmit: function(e, facetName) { 34040 e.stop(); 34041 34042 if (!facetName) { 34043 throw "No facetName given"; 34044 } 34045 34046 this.search(false, facetName, $(e.target).serialize().toQueryParams()); 34047 }, 34048 34049 /** 34050 * Search method, pass updated values to model 34051 * @param {String} query Query therm. pass false if you do not want to change search query 34052 * @param {String} facetName 34053 * @param {String} facetValue 34054 * @public 34055 */
34056 search: function(query, facetName, facetValue, callback) { 34057 var self = this; 34058 34059 if (query !== false) { 34060 this._model.query = query; 34061 } 34062 34063 if (typeof facetName !== 'undefined') { 34064 myDeco.body.trigger('controller-facet-' + (facetValue ? 'refined' : 'cleared'), 34065 [this.$type, facetName, facetValue]); 34066 } 34067 34068 this._view.loading(true); 34069 34070 this.position = -1; 34071 this._model.refine(facetName, facetValue, function(data) { 34072 self._view.redraw(data); 34073 myDeco.body.trigger('controller-data-fetched', [self.$type]); 34074 if ($.isFunction(callback)) { 34075 callback(data); 34076 } 34077 }); 34078 34079 this._view.redrawFacets(facetName); 34080 } 34081}); 34082 34083myDeco.App.controllers.push(SearchController); 34084/*globals Class, $ */ 34085/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 34086 sloppy: true, white: true */ 34087/** 34088 * @class SearchViewFacet 34089 * @classDescription Abstract class for facet view classes 34090 */ 34091var SearchViewFacet = Class.extend({ 34092 /** 34093 * Parent view 34094 * @type {FpeView} 34095 * @private 34096 */ 34097 _view: false, 34098 34099 /** 34100 * Facet model 34101 * @type {SearchModelFacet} 34102 * @private 34103 */ 34104 _model: false, 34105 34106 /** 34107 * HTML element for operating with 34108 * @type {jQSet} 34109 * @private 34110 */ 34111 _canvas: false, 34112 34113 /** 34114 * HTML element on which facet will be shown 34115 * @type {jQset} 34116 * @private 34117 */ 34118 _invoker: false, 34119 34120 /** 34121 * Name of facet 34122 * @type {String} 34123 * @public 34124 * @static 34125 */ 34126 $name: 'facet', 34127 34128 /** 34129 * Facet constructor 34130 * @param {FpeView} view 34131 * @param {jQset} canvas HTML canvas for facet, defaults to #facet-$name 34132 * @public 34133 */ 34134 init: function(view, canvas, invoker) { 34135 this._view = view; 34136 34137 // fetch relevant model from model facets list 34138 this._model = this._view._model.facets[this.$name]; 34139 34140 // set up canvas 34141 this._canvas = canvas || this._view.canvasElement.find('.facet-' + this.$name); 34142 34143 // set up invoker 34144 this._invoker = invoker || this._view.canvasElement.find('.facet.' + this.$name); 34145 // prevent double register 34146 if (this._invoker && this._invoker.size() > 0 && !this._invoker.data('registered')) { 34147 this._view._controller.registerRefinerInvoker(this._invoker, this.$name); 34148 } 34149 }, 34150 34151 /** 34152 * Cleanup canvas 34153 * @public 34154 */ 34155 reset: function() { 34156 this._canvas.text(''); 34157 }, 34158 34159 /** 34160 * Draw facet from scratch 34161 * @public 34162 */ 34163 draw: function() { 34164 this.reset(); 34165 this.redraw(); 34166 }, 34167 34168 /** 34169 * Redraw facet 34170 * @public 34171 */ 34172 redraw: function() { 34173 }, 34174 34175 toggle: function(state) { 34176 state = typeof state !== 'undefined' ? state : !this._canvas.is(':visible'); 34177 this._canvas.toggle(state); 34178 this._invoker.toggleClass('active', state); 34179 34180 return state; 34181 }, 34182 34183 /** 34184 * Make string representation for given arguments 34185 * @param {Object} item Model item 34186 * @param {String} Additional link parameters 34187 * ... 34188 */ 34189 _makeValueList: function(item) { 34190 return $.merge([this.$name, item.id, item.value], Array.prototype.slice.call(arguments, 1)); 34191 }, 34192 34193 /** 34194 * Make link for facet refiner 34195 * @param {Object} item Model item 34196 * @param {String} Additional link parameters 34197 * @private 34198 */ 34199 _makeListItem: function(item) { 34200 var filter = this._makeValueList.apply(this, arguments); 34201 34202 return $('<li></li>') 34203 .toggleClass('selected', typeof this._model.selected[item.id] !== 'undefined') 34204 .append(this._view._controller.registerRefiner($('<a href="" class="filter-ref" id="' + item.value.replace(',', '-') + '"/>').data('filter', filter).html( 34205 '<span class="prev"> </span>' + gettext(item.name.capitalize()) + 34206 // temporarily disabled 34207// '<span> (' + item.count + ')</span>' + 34208 (typeof this._model.selected[item.id] !== 'undefined' ? '<span class="expand"> </span>' : '') + '</a>'))); 34209 }, 34210 34211 _makeResetLink: function(text) { 34212 text = text || (myDeco.settings.WL && myDeco.settings.WL.WL_SEARCH_RESET_LINK ? 34213 myDeco.settings.WL.WL_SEARCH_RESET_LINK : gettext('Back to any type')); 34214 34215 return this._view._controller.registerResetter($('<a href="" class="back filter-ref" />').data('filter', this.$name).html(text)); 34216 } 34217}); 34218 34219/** 34220 * List of facet views used in search 34221 */ 34222var SearchViewFacets = {}; 34223/*globals SearchViewFacets, SearchViewFacet, $ */ 34224/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 34225 sloppy: true, white: true */ 34226/** 34227 * Category facet refiner 34228 */
34229SearchViewFacets.category = SearchViewFacet.extend({ 34230 /** 34231 * Refiner name 34232 * @type {String} 34233 * @public 34234 * @static 34235 */ 34236 $name: 'category', 34237 34238 /** 34239 * Redraw refiner 34240 * @public 34241 */ 34242 redraw: function() { 34243 this.reset(); 34244 34245 if (this._model.path.length > 0) { 34246 this._canvas.append(this._makeResetLink()); 34247 } else { 34248 this._canvas.append('<h1>' + 34249 (myDeco.settings.WL && myDeco.settings.WL.WL_SEARCH_CATEGORY_HEADER ? 34250 myDeco.settings.WL.WL_SEARCH_CATEGORY_HEADER : gettext('Browse category')) 34251 + '</h1>'); 34252 } 34253 34254 var list = $('<ul></ul>').toggleClass('selected', this._model.path.length === 0), item, p, plen = this._model.path.length, 34255 listSource = this._model._values, 34256 root = list, ilen, 34257 ul, ids; 34258 34259 for (p = 0; p < plen; p++) { 34260 item = listSource[this._model.path[p].id]; 34261 ul = $('<ul></ul>').toggleClass('selected', typeof this._model.selected[item.id] !== 'undefined'); 34262 root.append(this._makeListItem(item, p).append(ul)); 34263 listSource = item.values; 34264 root = ul; 34265 } 34266 34267 var values = $.values(listSource); 34268 plen = values.length; 34269 values.sort(function(a,b) { return a.order_val - b.order_val; }); 34270 values.forEach(function(v) { 34271 root.append(this._makeListItem(v, plen)); 34272 }, this); 34273 34274 this._canvas.append(list); 34275 }, 34276 34277 /** 34278 * Dummy function to prevent of facet hiding 34279 * @public 34280 */ 34281 toggle: function() { 34282 } 34283 34284}); 34285/*globals SearchViewFacets, SearchViewFacet, $, myDeco */ 34286/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 34287 sloppy: true, white: true */ 34288/** 34289 * Image colour refiner 34290 */ 34291SearchViewFacets.image_colour = SearchViewFacet.extend({ 34292 /** 34293 * Refiner name 34294 * @type {String} 34295 * @public 34296 * @static 34297 */ 34298 $name: 'image_colour', 34299 34300 init: function(view, canvas, invoker) { 34301 this._super(view, canvas, invoker); 34302 34303 this._content = this._canvas.find('.content'); 34304 34305 this._canvas.find('.clear').replaceWith(this._makeResetLink(gettext('Clear all')).addClass('clear')); 34306 }, 34307 34308 reset: function() { 34309 this._content.text(''); 34310 }, 34311 34312 /** 34313 * Redraw refiner 34314 * @public 34315 */ 34316 redraw: function() { 34317 this.reset(); 34318 34319 var p = 0, p16, key, selected, hsl, rgb, hex_color, 34320 canvas = $('<ul />').appendTo(this._canvas.find('.content')); 34321 34322 // plus one row is grayscale 34323 var rows = myDeco.settings.PALETTE_ROWS; 34324 var cols = myDeco.settings.PALETTE_COLS; 34325 34326 for (p = 0; p < (rows + 1) * cols; p++) { 34327 p16 = p.toString(16); 34328 hsl = pal2hsl(p16, rows, cols); 34329 rgb = hsl2rgb(hsl[0], hsl[1], hsl[2]); 34330 hex_color = diffuse2Hex(rgb); 34331 canvas.append(this._makeListItem({id: p16, name: p16, value: "manual_colour," + p16}). 34332 css('backgroundColor', '#' + hex_color)); 34333 } 34334 34335 if (this._invoker && this._invoker.size() > 0) { 34336 selected = this._invoker.find('.facet-invoker .selected').text('') 34337 .toggleClass('no-colour', $.keys(this._model.selected).length === 0); 34338 for (key in this._model.selected) { 34339 hsl = pal2hsl(key, rows, cols); 34340 rgb = hsl2rgb(hsl[0], hsl[1], hsl[2]); 34341 hex_color = diffuse2Hex(rgb); 34342 selected.append($('<span> </span>').css('backgroundColor', '#' + hex_color)); 34343 } 34344 } 34345 34346 } 34347}); 34348/*globals SearchViewFacets, SearchViewFacet, $, toUnits */ 34349/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 34350 sloppy: true, white: true */ 34351SearchViewFacets.dimension = SearchViewFacet.extend({ 34352 /** 34353 * Refiner name 34354 * @type {String} 34355 * @public 34356 * @static 34357 */ 34358 $name: 'dimension', 34359 34360 /** 34361 * Init dimension facet view 34362 * @param {AppView} view 34363 * @param {jQset} canvas 34364 * @param {jQset} invoker 34365 * @public 34366 */ 34367 init: function(view, canvas, invoker) { 34368 this._super(view, canvas, invoker); 34369 34370 this._content = this._invoker.find('.facet-invoker .selected'); 34371 34372 this._canvas.find('.clear').replaceWith(this._makeResetLink(gettext('Clear all')).addClass('clear')); 34373 }, 34374 34375 /** 34376 * Draw refiner 34377 * @public 34378 */ 34379 draw: function() { 34380 }, 34381 34382 /** 34383 * Reset facet view 34384 * @public 34385 */ 34386 reset: function() { 34387 this._content.text(''); 34388 }, 34389 34390 /** 34391 * Redraw refiner 34392 * @public 34393 */ 34394 redraw: function() { 34395 this._content.text(''); 34396 34397 if ($.keys(this._model.selected).length === 0) { 34398 return; 34399 } 34400 34401 // display size 34402 this._content.html([this._model.selected.w.split(',').pop(), this._model.selected.d.split(',').pop()].toUnits()); 34403 34404 this.showClear(true); 34405 }, 34406 34407 /** 34408 * Show clear link if data present 34409 * @param {Boolean} state 34410 */ 34411 showClear: function(state) { 34412 this._canvas.toggle($.keys(this._view._model.facets[this.$name].selected).length > 0 ? state : false); 34413 }, 34414 34415 /** 34416 * Toggle facet view 34417 * @param {Boolean} state 34418 * @public 34419 */ 34420 toggle: function(state) { 34421 state = typeof state !== 'undefined' ? state : !this._invoker.hasClass('active'); 34422 34423 // will show popup only if some selected values is present 34424 this.showClear(state); 34425 this._invoker.toggleClass('active', state); 34426 34427 return state; 34428 } 34429}); 34430/*globals AppView, $, generateElementForDragMixin, buildThumbnailLink, material2Style, tmpl, myDeco, window */ 34431/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 34432 sloppy: true, white: true */ 34433/** 34434 * @class ItemsListView 34435 * @classDescription Abstract class for items list based views 34436 */ 34437var ItemsListView = AppView.extend($.extend(true, {}, generateElementForDragMixin, { 34438 /** 34439 * Class type 34440 * @type {String} 34441 * @public 34442 * @static 34443 */ 34444 $type: 'items-list', 34445 34446 /** 34447 * Items holder 34448 * @type {jQset} 34449 * @private 34450 */ 34451 _canvas: null, 34452 34453 /** 34454 * Items counter 34455 * @type {jQset} 34456 * @private 34457 */ 34458 _counter: null, 34459 34460 /** 34461 * Empty results loopback 34462 * @type {jQset} 34463 * @private 34464 */ 34465 _empty: null, 34466 34467 /** 34468 * Current items count displayed on pad 34469 * @type {Number} 34470 * @public 34471 */ 34472 itemsCount: 0, 34473 34474 /** 34475 * Loading WoD for scroller 34476 * @type {jQset} 34477 * @private 34478 */
34479 _scrollerLoading: null, 34480 34481 /** 34482 * Loading layer 34483 * @type {jQset} 34484 */ 34485 _loading: null, 34486 34487 /** 34488 * Flag used to decide should we draw in-room corner or not 34489 * @type {Boolean} 34490 */ 34491 _setRoomMarker: true, 34492 34493 /** 34494 * View settings 34495 * @type {Object} 34496 * @public 34497 */ 34498 settings: { 34499 // thumbnail sizes for modes 34500 thumbnailSize: { 34501 small: { 34502 width: 110, 34503 height: 110 34504 }, 34505 normal: { 34506 width: 110, 34507 height: 110 34508 } 34509 }, 34510 selectedClass: 'selected', 34511 inRoomClass: 'in-room' 34512 }, 34513 34514 selected: false, 34515 34516 /** 34517 * List mode, normal of small 34518 * @type {String} 34519 * @public 34520 */ 34521 mode: 'normal', 34522 34523 /** 34524 * View constructor 34525 * @param {Object} options 34526 * @public 34527 */ 34528 init: function(options) { 34529 this._super(options); 34530 this._canvas = this.canvasElement.find('.items'); 34531 34532 var self = this; 34533 34534 this._counter = this.canvasElement.find('.counter'); 34535 this._loading = this.canvasElement.find('.wait'); 34536 this._empty = this.canvasElement.find('.empty'); 34537 34538 this._scrollerLoading = self.canvasElement.find('.scroller-notify .loading'); 34539 this._scrollerHolder = self.canvasElement.find('.scroller-holder') 34540 .scrollLoader({ 34541 showControls: function(controls) { 34542 if (self._model.items.length !== 0) { 34543 controls.fadeIn(); 34544 } 34545 } 34546 }) 34547 .bind('load', function() { 34548 self._controller.fetchMore(); 34549 }); 34550 34551 if (this._model.onInit) { 34552 this._model.onInit(this.draw.bind(this)); 34553 } 34554 }, 34555 34556 /** 34557 * Redraw view, used for items drawing from scratch 34558 * @public 34559 */ 34560 redraw: function() { 34561 this.drawItems(); 34562 this.loading(false); 34563 }, 34564 34565 /** 34566 * Draw view, used for partial items drawing 34567 * @public 34568 */ 34569 draw: function() { 34570 this.drawItems(); 34571 this.loading(false); 34572 }, 34573 34574 /** 34575 * Draw items 34576 * @public 34577 */ 34578 drawItems: function() { 34579 // direct type casting because toggleClass wants exactly Boolean type 34580 this._canvas.text('').toggleClass('small', this.mode === 'small').scrollTop(0); 34581 this.itemsCount = this._model.items.length; 34582 this._empty.toggle(this._model.found === 0 && ($.isFunction(this._model.searchAllowed) ? 34583 this._model.searchAllowed() : true)); 34584 this._counter.toggle(this._model.items.length !== 0).filter('.scrolling-item').hide(); 34585 this._scrollerHolder.trigger('rewind'); 34586 34587 if (this.itemsCount !== 0) { 34588 this.appendItems(this._model.items); 34589 } 34590 }, 34591 34592 /** 34593 * Append new set of items to already exist 34594 * @param {Array} items Items to add 34595 * @public 34596 */ 34597 appendItems: function(items) { 34598 var self = this, 34599 type = this._model.type, 34600 sizes = this.settings.thumbnailSize[this.mode], 34601 existent = [], element, holder; 34602 34603 if ((myDeco.App.model.uniqueComponentIds && myDeco.App.model.uniqueMaterialIds) || myDeco.App.model.uniqueProductIds) { 34604 switch (type) { 34605 case 'component': 34606 existent = myDeco.App.model.uniqueComponentIds(); 34607 break; 34608 34609 case 'material': 34610 existent = myDeco.App.model.uniqueMaterialIds(); 34611 break; 34612 34613 case 'product': 34614 existent = myDeco.App.model.uniqueProductIds(); 34615 break; 34616 } 34617 } 34618 34619 this._counter.find('.items-counter').html('1 – ' + this._model.items.length + ' of ' + this._model.found); 34620 34621 $.each(items, function() { 34622 var item = this; 34623 element = $('<li><b></b></li>') 34624 .attr('class', type + '-' + item.component_id.replace(/#/, '_')) 34625 .data('type', type) 34626 .data('id', item.component_id); 34627 switch (type) { 34628 case 'component': 34629 if (item.componentData) { 34630 if (item.componentData.labels.length !== 0 || 34631 item.componentData.locale !== 34632 myDeco.settings.BASE_LOCALE) { 34633 holder = $('<div />'); 34634 $.map(item.componentData.labels, function(label) { 34635 holder.append($('<label />').text(label)).append('<i />'); 34636 }); 34637 element.append(holder); 34638 } 34639 } 34640 34641 element.css('background-image', ['url(', (item.thumbnail || item.render_path)].join('')); 34642 break; 34643 case 'scrapbook': 34644 element.css('background-image', ['url(', (item.thumbnail || buildThumbnailLink(item.render_path, sizes)), ')'].join('')); 34645 break; 34646 case 'product': 34647 element.css('background-image', ['url(', item.thumbnail, ')'].join('')); 34648 break; 34649 case 'material': 34650 element.css(material2Style(item)); 34651 break; 34652 } 34653 34654 if (self._setRoomMarker && existent.indexOf(item.component_id) !== -1) { 34655 element.addClass(self.settings.inRoomClass); 34656 } 34657 34658 self._canvas.append(element); 34659 }); 34660 }, 34661 34662 /** 34663 * Show/hide wait screen 34664 * @param {Boolean} state hide/show flag, default show 34665 * @public 34666 */ 34667 loading: function(state) { 34668 this._loading.toggle(typeof state !== 'undefined' ? state : true); 34669 }, 34670 34671 /** 34672 * Show/hide bottom panel scroller WoD 34673 * @param {Boolean} state 34674 */
34675 scrollerLoading: function(state) { 34676 this._scrollerLoading.css('display', state ? 'inline-block' : 'none'); 34677 }, 34678 34679 /** 34680 * Display component info notify for search result 34681 * @param {Event} e Mouseover/out event 34682 */ 34683 displayComponentInfo: function(e) { 34684 var self = this, 34685 target = $(e.target), 34686 component_id = target.data('id'), 34687 34688 // get component from storage 34689 component = this._model.requestComponentInfo(component_id), 34690 34691 // hide popup function 34692 hidePopup = function() { 34693 window.clearTimeout(ItemsListView.openTimeout); 34694 ItemsListView.popup.animate({opacity: 0}, 0, function() { 34695 ItemsListView.content.html(''); 34696 ItemsListView.popup.hide(); 34697 }); 34698 }, 34699 34700 // show popup function 34701 showPopup = function(data) { 34702 // shorthands 34703 var popup = ItemsListView.popup, 34704 content = ItemsListView.content, 34705 offset = target.offset(), 34706 34707 // popup position 34708 top = offset.top + target.outerHeight() + 14, 34709 34710 left = self._canvas.offset().left, 34711 height; 34712 34713 // fill data to container 34714 content.html('').append(data); 34715 34716 ItemsListView.arrow 34717 .removeClass('bottom') 34718 .toggleClass('right', offset.left - left > 50); 34719 34720 // position popup 34721 popup 34722 .css({ 34723 top: top, 34724 left: left, 34725 opacity: 0.01 34726 }) 34727 .show(); 34728 34729 height = popup.outerHeight(); 34730 if (top + height > 34731 self._scrollerHolder.offset().top + 34732 self._scrollerHolder.height()) { 34733 popup.css('top', offset.top - height - 14); 34734 ItemsListView.arrow.addClass('bottom'); 34735 } 34736 34737 // show it finally 34738 popup.animate({opacity: 1}, 100, function() { 34739 popup.css('opacity', 'auto'); 34740 }); 34741 }, 34742 34743 // data compose function 34744 messageCompose = function(wishlisted) { 34745 var data = component.componentData || component, 34746 content = $(tmpl('component-info-popup-template', { 34747 component: component, 34748 data: data, 34749 wishlisted: wishlisted 34750 })), 34751 links, 34752 label; 34753 34754 content.find('.gts').data('gts', { 34755 guid: component.product_id || component.component_id, 34756 category: data.category, 34757 price: data.price 34758 }); 34759 34760 if (myDeco.username && data.available && typeof wishlisted !== 'undefined') { 34761 // wishlisting 34762 links = content.find('.wishlist'); 34763 wishlisted = links.filter('.wishlisted'); 34764 34765 links.click(function(e) { 34766 e.stop(); 34767 34768 if (target.get(0) === wishlisted.get(0)) { 34769 return; 34770 } 34771 34772 myDeco.body.trigger('component.wishlist', [component.component_id, function() { 34773 links.hide(); 34774 wishlisted.show(); 34775 }, wishlisted, $('#sidebar .shopping')]); 34776 }); 34777 } 34778 34779 // show popup with a timeout 34780 // ItemsListView.openTimeout = window.setTimeout(function() { 34781 showPopup(content); 34782 // }, 500); 34783 }; 34784 34785 // dont want to operate if any operation is in progress 34786 if (this._controller.hasOperation) { 34787 return; 34788 } 34789 34790 // create popup if not created already 34791 if (!ItemsListView.popup) { 34792 ItemsListView.popup = $('<div class="popup-info" style="display:block; opacity:0"><div class="ar" /><div class="close" /><div class="popup-content" /></div>').appendTo(myDeco.body); 34793 ItemsListView.content = ItemsListView.popup.find('.popup-content'); 34794 ItemsListView.arrow = ItemsListView.popup.find('.ar'); 34795 34796 // left coordinate right where items start 34797 34798 34799 // click observer 34800 myDeco.body.mousedown(function(e) { 34801 if (e.target.tagName === 'A') { 34802 return; 34803 } 34804 hidePopup(); 34805 }); 34806 34807 // mouseover observers 34808 ItemsListView.popup.hover(function() { 34809 window.clearTimeout(ItemsListView.closeTimeout); 34810 }, function() { 34811 ItemsListView.closeTimeout = window.setTimeout(hidePopup, 0); 34812 }); 34813 } 34814 34815 if (e.type === 'mouseenter') { 34816 window.clearTimeout(ItemsListView.closeTimeout); 34817 window.clearTimeout(ItemsListView.showTimeout); 34818 ItemsListView.showTimeout = window.setTimeout(function() { 34819 if (self._model.type !== 'scrapbook' && component_id) { 34820 // if component is furniture check for wishlisting 34821 if (component.componentData && component.componentData.available && 34822 !myDeco.settings.WL) { 34823 myDeco.body.trigger('component.wishlisted', [component_id, messageCompose]); 34824 } else { 34825 messageCompose(); 34826 } 34827 } 34828 }, /*100*/10); 34829 } else { 34830 window.clearTimeout(ItemsListView.showTimeout); 34831 ItemsListView.closeTimeout = window.setTimeout(hidePopup, 100); 34832 } 34833 }, 34834 34835 /** 34836 * Add selected class to a clicked item 34837 * @param {String} id Component ID 34838 */ 34839 selectComponent: function(id) { 34840 if (this.selected) { 34841 this.selected.removeClass(this.settings.selectedClass); 34842 } 34843 34844 this.selected = this.canvasElement.find('.' + this._model.type + '-' + id.replace(/#/, '_')).addClass(this.settings.selectedClass); 34845 } 34846})); 34847/*globals ItemsListView, SearchViewFacets, $, myDeco */ 34848/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 34849 sloppy: true, white: true */ 34850/** 34851 * @class SearchView 34852 * @classDescription Base class for search view 34853 */ 34854var SearchView = ItemsListView.extend({ 34855 /** 34856 * Class type 34857 * @type {String} 34858 * @public 34859 * @static 34860 */
34861 $type: 'search', 34862 34863 /** 34864 * List of used refiners in search 34865 * @type {Object} 34866 * @public 34867 */ 34868 facets: {}, 34869 34870 _queryHolder: false, 34871 34872 /** 34873 * View constructor 34874 * @param {Object} options 34875 * @public 34876 */ 34877 init: function(options) { 34878 this._super(options); 34879 34880 var self = this; 34881 34882 this._queryHolder = this.canvasElement.find('.query-data'); 34883 34884 if (this._model.onInit) { 34885 // init view refiners on model loading 34886 this._model.onInit(function() { 34887 var name; 34888 for (name in self._model.facets) { 34889 self.facets[name] = new SearchViewFacets[name](self); 34890 } 34891 }); 34892 34893 this._model.onInit(this.draw.bind(this)); 34894 } 34895 34896 if (this._controller) { 34897 this._controller.registerFilter(this.canvasElement.find('input.filter, select.filter')); 34898 34899 } 34900 }, 34901 34902 /** 34903 * Redraw view 34904 * @public 34905 */ 34906 redraw: function() { 34907 this._scrollerHolder.trigger('rewind'); 34908 this.redrawFacets(); 34909 this._super(); 34910 this._queryHolder 34911 .toggle(!!this._model.query && this._model.searchAllowed()) 34912 .find('span').text(this._model.query || ''); 34913 }, 34914 34915 /** 34916 * Function for invoke redraw facets only 34917 * @param {String} facetName Facet name to redraw; will redraw all registered facets if omitted 34918 * @public 34919 */ 34920 redrawFacets: function(facetName) { 34921 if (facetName) { 34922 this.facets[facetName].redraw(); 34923 } else { 34924 $.each(this.facets, function() { 34925 this.redraw(); 34926 }); 34927 } 34928 }, 34929 34930 /** 34931 * Draw view 34932 * @public 34933 */ 34934 draw: function() { 34935 $.each(this.facets, function() { 34936 this.draw(); 34937 }); 34938 34939 this._super(); 34940 } 34941}); 34942 34943myDeco.App.views.push(SearchView); 34944/*globals AppView, $, generateElementForDragMixin, myDeco, updateMaterialInfo */ 34945/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 34946 sloppy: true, white: true */ 34947/** 34948 * @fileOverview 34949 * Color picker view 34950 */ 34951var ColorPickerView = AppView.extend($.extend(true, {}, generateElementForDragMixin, { 34952 34953 /** 34954 * View type 34955 * @type {String} 34956 * @public 34957 * @static 34958 */ 34959 $type: 'color-picker', 34960 34961 /** 34962 * Reference to palette object 34963 * @type {Object} 34964 */ 34965 _palette: {}, 34966 34967 /** 34968 * Scroller element 34969 * @type {jQset} 34970 * @private 34971 */ 34972 _scroller: null, 34973 34974 /** 34975 * Scroller holder 34976 * @type {jQset} 34977 * @private 34978 */ 34979 _scrollerHolder: null, 34980 34981 /** 34982 * Scroller width, calculated from visible/full ratio of palette 34983 * @type {Number} 34984 * @private 34985 */ 34986 _scrollerWidth: 0, 34987 34988 /** 34989 * Palette container 34990 * @type {jQset} 34991 * @private 34992 */ 34993 _container: null, 34994 34995 /** 34996 * Palette container width 34997 * @type {Number} 34998 * @private 34999 */ 35000 _containerWidth: 0, 35001 35002 /** 35003 * Palette block 35004 * @type {jQset} 35005 * @private 35006 */ 35007 _paletteHolder: null, 35008 35009 /** 35010 * Palette block width, used for correct scrolling 35011 * @type {Number} 35012 * @private 35013 */ 35014 _paletteWidth: 0, 35015 35016 /** 35017 * Picked color selector 35018 * @type {jQset} 35019 * @private 35020 */ 35021 _selector: null, 35022 35023 /** 35024 * Internal variable for drag support 35025 * @type {Number} 35026 * @private 35027 */ 35028 _offsetX: 0, 35029 35030 /** 35031 * Info pane 35032 * @type {jQuery} 35033 */ 35034 _info: false, 35035 35036 /** 35037 * Current colour 35038 * @type {String} 35039 */ 35040 _currentColour: null, 35041 35042 /** 35043 * View init 35044 * @param {Object} options 35045 * @public 35046 */ 35047 init: function(options) { 35048 var self = this; 35049 35050 this._super(options); 35051 this._palette = options.palette || myDeco.App.palette; 35052 35053 // set parameters to scroller 35054 this.canvasElement.find('.small') 35055 .css('background-image', 'url(' + this._palette.sImage.url + ')') 35056 .width(this._palette.sImage.width) 35057 .height(this._palette.sImage.height); 35058 35059 // define controls 35060 this._selector = this.canvasElement.find('.selector') 35061 .width(this._palette.size - this._palette.spacing) 35062 .height(this._palette.size - this._palette.spacing); 35063 35064 // big palette container 35065 this._container = this.canvasElement.find('.palette-holder').height(this._palette.bImage.height); 35066 this._containerWidth = parseInt(this._container.css('width'), 10); 35067 35068 // set parameters to big palette 35069 this._paletteHolder = this._container.find('.palette') 35070 .css('background-image', 'url(' + this._palette.bImage.url + ')') 35071 .width(this._palette.bImage.width) 35072 .height(this._palette.bImage.height); 35073 35074 // scroller values 35075 this._scrollerWidth = Math.round(this._containerWidth * this._palette.sImage.height / this._palette.bImage.height); 35076 this._scroller = this.canvasElement.find('.scroller') 35077 // set calculated scroller width 35078 .width(this._scrollerWidth) 35079 .height(this._palette.sImage.height); 35080 35081 // scroller holder values 35082 this._scrollerHolder = this.canvasElement.find('.scroller-holder') 35083 .width(this._palette.sImage.width + 35084 (parseInt(this._scroller.css('border-left-width'), 10) || 0) + 35085 (parseInt(this._scroller.css('border-right-width'), 10) || 0)) 35086 .height(this._palette.sImage.height + 35087 (parseInt(this._scroller.css('border-top-width'), 10) || 0) + 35088 (parseInt(this._scroller.css('border-bottom-width'), 10) || 0)) 35089 .mousedown(this.startPan.bind(this)); 35090 35091 // color info pane 35092 this._info = this.canvasElement.find('.palette-info').hide(); 35093 // bind listener for color drag 35094 this._container.mousedown(this.renderComponent.bind(this)); 35095 35096 if (this.settings.mousemove) { 35097 this._container.mousemove(function(e) { 35098 self.settings.mousemove(self.getPaletteData(e)); 35099 }); 35100 } 35101 35102 if (this.settings.mouseleave) { 35103 this._container.mouseleave(self.settings.mouseleave); 35104 } 35105 35106 // shift palette to its starting position 35107 this.pan(false, 0); 35108 35109 // system wide listener fo selecting color on palette
35110 myDeco.body.bind('selectColor', function(e, color) { 35111 var index = self._palette.getIndex(color); 35112 if (index !== -1) { 35113 self.getMaterial(false, index); 35114 self.pan(false, Math.round( 35115 ((index % self._palette.cols) / self._palette.cols) * 35116 self._palette.sImage.width - self._scrollerWidth / 2)); 35117 } 35118 }); 35119 }, 35120 35121 /** 35122 * Scroller pan start listener 35123 * @param {Event} e 35124 * @public 35125 */ 35126 startPan: function(e) { 35127 if (e.isDefaultPrevented()) { 35128 return; 35129 } 35130 e.preventDefault(); 35131 35132 // cache listener for correct unbind 35133 var move = this.pan.bind(this), 35134 offset = this._scroller.offset(), 35135 left = parseInt(this._scroller.css('left'), 10); 35136 35137 // not moved to init due to some problems with detection of invisible DOM elements 35138 this._paletteWidth = this._paletteHolder.outerWidth(); 35139 35140 // check for bounds 35141 if (e.pageX < offset.left || e.pageX > offset.left + this._scrollerWidth) { 35142 left = Math.max(0, Math.min(this._palette.sImage.width - this._scrollerWidth, 35143 // value of left without bounds 35144 e.pageX - this._scrollerHolder.offset().left - Math.round(this._scrollerWidth / 2))); 35145 this._scroller.css('left', left + 'px'); 35146 } 35147 35148 // store initial offset 35149 this._offsetX = e.pageX - left; 35150 35151 myDeco.body 35152 .bind('mousemove', move) 35153 .one('mouseup', function() { 35154 myDeco.body.unbind('mousemove', move); 35155 }); 35156 // move palette in case of click somewhere out of scroller 35157 this.pan(e); 35158 }, 35159 35160 /** 35161 * Pan mouse move listener 35162 * @param {Event} e 35163 * @public 35164 */ 35165 pan: function(e, offset) { 35166 offset = typeof offset !== 'undefined' ? offset : e.pageX - this._offsetX; 35167 35168 offset = Math.round(Math.max(0, Math.min(offset, 35169 // offset without bound checking 35170 this._palette.sImage.width - this._scrollerWidth))); 35171 35172 this._scroller.css('left', offset + 'px'); 35173 35174 offset = offset === this._palette.sImage.width - this._scrollerWidth ? 35175 this._paletteWidth - this._containerWidth : 35176 offset * this._paletteWidth / this._palette.sImage.width; 35177 35178 // append('') is a fix for IE which doesn't refresh inner content shifted with scrollLeft without DOM modifications 35179 this._container.scrollLeft(offset).append(''); 35180 }, 35181 35182 getPaletteData: function(e, index) { 35183 var offset, row = 0, column = 0; 35184 35185 // calculate row/column for color 35186 if (typeof index !== 'undefined') { 35187 row = Math.floor(index / this._palette.cols); 35188 column = index % this._palette.cols; 35189 } else if (e) { 35190 offset = this._container.offset(); 35191 column = Math.floor((e.pageX - offset.left + 35192 this._container.scrollLeft()) / this._palette.size); 35193 row = Math.floor((e.pageY - offset.top) / this._palette.size); 35194 } 35195 35196 // set the current color from palette 35197 this._currentColour = this._palette.colors[row * this._palette.cols + column]; 35198 35199 return [column, row, this._currentColour]; 35200 }, 35201 35202 /** 35203 * update info pane, select color on palette and return material for event/index 35204 * @param {Event} e Click event 35205 * @param {Number} index Color index in palette 35206 */ 35207 getMaterial: function(e, index) { 35208 var data = this.getPaletteData(e, index), 35209 material, 35210 self = this; 35211 35212 if (data[2]) { 35213 //update the palette holder and scroller width 35214 this._container.width(this._containerWidth - this._info.width()); 35215 this._containerWidth = parseInt(this._container.css('width'), 10); 35216 this._scrollerWidth = Math.round(this._containerWidth * 35217 this._palette.sImage.height / this._palette.bImage.height); 35218 this._scroller.width(this._scrollerWidth); 35219 35220 // retrieve material 35221 material = myDeco.App.palette.getMaterial('#' + data[2]); 35222 35223 // update material info 35224 updateMaterialInfo(material, this._info).show(); 35225 35226 // bind listener for color drag from info panel 35227 if (!myDeco.browser.isMobileSafari) { 35228 this._info.find('.texture').mousedown(function(e) { 35229 self.renderComponent(e, self._currentColour); 35230 }); 35231 } 35232 35233 // position selector 35234 this._selector 35235 .css({ 35236 left: this._palette.size * data[0] + 'px', 35237 top: this._palette.size * data[1] + 'px' 35238 }) 35239 .toggleClass('light', (Math.max.apply(null, material.diffuse) + 35240 Math.min.apply(null, material.diffuse)) / 2 < 0.5); 35241 35242 // enable info pane and position selector 35243 this.canvasElement.addClass('selected'); 35244 } 35245 35246 return material; 35247 }, 35248 35249 /** 35250 * Color palette mousedown listener 35251 * @param {Event} e 35252 * @param {Boolean} current - optional 35253 * @public 35254 */
35255 renderComponent: function(e, current) { 35256 if (e.isDefaultPrevented()) { 35257 return; 35258 } 35259 e.preventDefault(); 35260 35261 // container offset relative to document 35262 var material = current ? myDeco.App.palette.getMaterial("#" + current) : this.getMaterial(e); 35263 35264 // palette can have holes, so check for color there 35265 if (material) { 35266 myDeco.body.trigger('entity-drag-started', [{ 35267 entity: material, 35268 kind: 'decor', 35269 type: 'color', 35270 ox: -10, 35271 oy: -10, 35272 visual: this.generateElementForDrag(material, 'material') 35273 }, e]); 35274 } 35275 35276 } 35277})); 35278 35279myDeco.App.views.push(ColorPickerView); 35280/*globals SearchModel */ 35281/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35282 sloppy: true, white: true */ 35283/** 35284 * @fileOverview Materials search model 35285 */ 35286var MaterialsSearchModel = SearchModel.extend({ 35287 /** 35288 * Model type 35289 * @type {String} 35290 * @public 35291 * @static 35292 */ 35293 $type: 'materials-search', 35294 35295 /** 35296 * Items type 35297 * @type {String} 35298 */ 35299 type: 'material', 35300 35301 /** 35302 * Facets used 35303 * @type {Array} 35304 * @private 35305 */ 35306 _facetsUsed: ['class'],//, 'colour' 35307 35308 /** 35309 * Default parameters 35310 * @type {Object} 35311 * @private 35312 */ 35313 _default: { 35314 // database to request 35315 db: 'materials', 35316 35317 // number of result items by default 35318 count: 30, 35319 35320 // list of facets 35321 requirefacet: ['class'],//'colour' 35322 35323 // current api version 35324 api_version: 2, 35325 35326 // the number of materials to look thru 35327 checkatleast: -1, 35328 35329 // locale 35330 locale: 'en-gb' /*myDeco.settings.LOCALE*/ 35331 } 35332}); 35333/*globals SearchModelFacets */ 35334/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35335 sloppy: true, white: true */ 35336/** 35337 * @fileOverview Class facet from models database 35338 * Fully inherit category facet 35339 */ 35340SearchModelFacets['class'] = SearchModelFacets.category.extend({ 35341 /** 35342 * Facet name 35343 * @type {String} 35344 * @public 35345 * @static 35346 */ 35347 $name: 'class', 35348 35349 /** 35350 * Skip facet request on paint value, 35351 * will replace with custom color picker 35352 * @type {Array} 35353 * @private 35354 */ 35355 _skip: ['paint'] 35356}); 35357/*globals SearchModelFacets */ 35358/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35359 sloppy: true, white: true */ 35360/** 35361 * @fileOverview Colour facet, fully inherited from image_colour facet 35362 */ 35363SearchModelFacets.colour = SearchModelFacets.image_colour.extend({ 35364 /** 35365 * Facet name 35366 * @type {String} 35367 * @public 35368 * @static 35369 */ 35370 $name: 'colour' 35371}); 35372/*globals SearchModelFacets, SearchModelFacet */ 35373/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35374 sloppy: true, white: true */ 35375/** 35376 * Image colour facet 35377 */ 35378SearchModelFacets.manual_colour = SearchModelFacets.image_colour.extend({ 35379 /** 35380 * Facet name 35381 * @type {String} 35382 * @public 35383 * @static 35384 */ 35385 $name: 'manual_colour' 35386}); 35387/*globals SearchModel, myDeco */ 35388/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35389 sloppy: true, white: true */ 35390/** 35391 * @fileOverview Special search model 35392 */ 35393var SpecialSearchModel = SearchModel.extend({ 35394 /** 35395 * Model type 35396 * @type {String} 35397 * @public 35398 * @static 35399 */ 35400 $type: 'special-search', 35401 35402 _facetsUsed: ['manual_colour', 'dimension'], 35403 35404 _default: { 35405 // database to request 35406 db: 'component', 35407 35408 // items count 35409 count: 30, 35410 35411 // list of facets 35412 requirefacet: ['manual_colour'], 35413 35414 api_version: 2 35415 }, 35416 35417 additionalOptions: $.extend(myDeco.settings.SPECIAL_SEARCH ? 35418 myDeco.settings.SPECIAL_SEARCH.options : {}, {skip_wl_filters: true}), 35419 35420 _initialCount: 30 35421 35422}); 35423/*globals SearchModel, myDeco */ 35424/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35425 sloppy: true, white: true */ 35426/** 35427 * @fileOverview Architecture search model 35428 */ 35429 35430var arch_id = (myDeco.settings.WL && myDeco.settings.WL.ARCH_CATEGORY_ID) || 2035; 35431 35432var ArchitectureSearchModel = SearchModel.extend({ 35433 /** 35434 * Model type 35435 * @type {String} 35436 * @public 35437 * @static 35438 */ 35439 $type: 'architecture-search', 35440 35441 _facetsUsed: [], 35442 35443 _default: { 35444 // database to request 35445 db: 'component', 35446 35447 // items count 35448 count: 30, 35449 35450 // list of facets 35451 requirefacet: [], 35452 35453 api_version: 2 35454 }, 35455 35456 _initialCount: 30, 35457 35458 additionalOptions: {facet: ['new,' + arch_id.toString()], skip_wl_filters: true}, 35459 35460 excludedOptions: {facet: ['supplier_id']} 35461 35462}); 35463/*globals SearchModel, myDeco */ 35464/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35465 sloppy: true, white: true */ 35466/** 35467 * @fileOverview Garden search model 35468 */ 35469var GardenSearchModel = SearchModel.extend({ 35470 /** 35471 * Model type 35472 * @type {String} 35473 * @public 35474 * @static 35475 */ 35476 $type: 'garden-search', 35477 35478 _facetsUsed: [], 35479 35480 _default: { 35481 // database to request 35482 db: 'component', 35483 35484 // items count 35485 count: 30, 35486 35487 // list of facets 35488 requirefacet: [], 35489 35490 api_version: 2 35491 }, 35492 35493 _initialCount: 30, 35494 35495 additionalOptions: {facet: ['new,1376'], skip_wl_filters: true}, 35496 35497 excludedOptions: {facet: ['supplier_id']} 35498}); 35499/*globals SearchController, myDeco */ 35500/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35501 sloppy: true, white: true */ 35502/** 35503 * @fileOverview Materials search controller 35504 */ 35505var MaterialsSearchController = SearchController.extend({ 35506 /** 35507 * Controller type 35508 * @type {String} 35509 * @public 35510 * @static 35511 */ 35512 $type: 'materials-search', 35513 35514 // add another form submit handler to make it possible to 35515 // name-search materials 35516 registerView: function(view) { 35517 this._super(view); 35518 var self = this; 35519 35520 // listen search bar for submit 35521 this._form.submit(function(e) { 35522 e.stop(); 35523 self.search(self._query.val()); 35524 }); 35525 } 35526}); 35527 35528myDeco.App.controllers.push(MaterialsSearchController); 35529/*globals SearchController, myDeco */ 35530/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35531 sloppy: true, white: true */ 35532/** 35533 * @fileOverview Special search controller 35534 */ 35535var SpecialSearchController = SearchController.extend({ 35536 /** 35537 * Controller type 35538 * @type {String} 35539 * @public 35540 * @static 35541 */
35542 $type: 'special-search' 35543}); 35544 35545if (myDeco.settings.SPECIAL_SEARCH) { 35546 myDeco.App.controllers.push(SpecialSearchController); 35547}; 35548/*globals SearchController, myDeco */ 35549/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35550 sloppy: true, white: true */ 35551/** 35552 * @fileOverview Special search controller 35553 */ 35554var ArchitectureSearchController = SearchController.extend({ 35555 /** 35556 * Controller type 35557 * @type {String} 35558 * @public 35559 * @static 35560 */ 35561 $type: 'architecture-search' 35562}); 35563 35564myDeco.App.controllers.push(ArchitectureSearchController); 35565 35566/*globals SearchController, myDeco */ 35567/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35568 sloppy: true, white: true */ 35569/** 35570 * @fileOverview Special search controller 35571 */ 35572var GardenSearchController = SearchController.extend({ 35573 /** 35574 * Controller type 35575 * @type {String} 35576 * @public 35577 * @static 35578 */ 35579 $type: 'garden-search' 35580}); 35581 35582myDeco.App.controllers.push(GardenSearchController); 35583 35584/*globals SearchView, myDeco */ 35585/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35586 sloppy: true, white: true */ 35587/** 35588 * @fileOverview Materials search view 35589 */ 35590var MaterialsSearchView = SearchView.extend({ 35591 /** 35592 * View type 35593 * @type {String} 35594 * @public 35595 * @static 35596 */ 35597 $type: 'materials-search', 35598 35599 /** 35600 * View constructor 35601 * @param {Object} options 35602 * @public 35603 */ 35604 init: function(options) { 35605 this._super(options); 35606 this.canvasElement.find('.controls').hide() 35607 } 35608 35609}); 35610 35611 35612myDeco.App.views.push(MaterialsSearchView); 35613/*globals SearchViewFacets */ 35614/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35615 sloppy: true, white: true */ 35616/** 35617 * @fileOverview 'class' view facet 35618 */ 35619SearchViewFacets['class'] = SearchViewFacets.category.extend({ 35620 /** 35621 * Facet name 35622 * @type {String} 35623 * @public 35624 * @static 35625 */ 35626 $name: 'class', 35627 35628 /** 35629 * Comparison function used for making Paint section at the top of the list 35630 * @param {String} a 35631 * @param {String} b 35632 * @returns {Number} 35633 */ 35634 sort: function(a, b) { 35635 if (a === 'paint') { 35636 return -1; 35637 } else if (b === 'paint') { 35638 return 1; 35639 } 35640 return 0; 35641 } 35642}); 35643/*globals SearchViewFacets */ 35644/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35645 sloppy: true, white: true */ 35646/** 35647 * @fileOverview Colour refiner 35648 */ 35649SearchViewFacets.colour = SearchViewFacets.image_colour.extend({ 35650 /** 35651 * Refiner name 35652 * @type {String} 35653 * @public 35654 * @static 35655 */ 35656 $name: 'colour' 35657}); 35658/*globals SearchViewFacets, SearchViewFacet, $, myDeco */ 35659/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35660 sloppy: true, white: true */ 35661/** 35662 * Image colour refiner 35663 */ 35664SearchViewFacets.manual_colour = SearchViewFacets.image_colour.extend({ 35665 /** 35666 * Refiner name 35667 * @type {String} 35668 * @public 35669 * @static 35670 */ 35671 $name: 'manual_colour' 35672}); 35673/*globals SearchView, myDeco */ 35674/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35675 sloppy: true, white: true */ 35676/** 35677 * @fileOverview Special search view 35678 */ 35679var SpecialSearchView = SearchView.extend({ 35680 /** 35681 * View type 35682 * @type {String} 35683 * @public 35684 * @static 35685 */ 35686 $type: 'special-search' 35687}); 35688 35689if (myDeco.settings.SPECIAL_SEARCH) { 35690 myDeco.App.views.push(SpecialSearchView); 35691}; 35692/*globals SearchView, myDeco */ 35693/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35694 sloppy: true, white: true */ 35695/** 35696 * @fileOverview Special search view 35697 */ 35698var ArchitectureSearchView = SearchView.extend({ 35699 /** 35700 * View type 35701 * @type {String} 35702 * @public 35703 * @static 35704 */ 35705 $type: 'architecture-search', 35706 35707 /** 35708 * View constructor 35709 * @param {Object} options 35710 * @public 35711 */ 35712 init: function(options) { 35713 this._super(options); 35714 this.canvasElement.find('.controls').hide() 35715 } 35716}); 35717 35718myDeco.App.views.push(ArchitectureSearchView); 35719/*globals SearchView, myDeco */ 35720/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35721 sloppy: true, white: true */ 35722/** 35723 * @fileOverview Special search view 35724 */ 35725var GardenSearchView = SearchView.extend({ 35726 /** 35727 * View type 35728 * @type {String} 35729 * @public 35730 * @static 35731 */ 35732 $type: 'garden-search', 35733 35734 /** 35735 * View constructor 35736 * @param {Object} options 35737 * @public 35738 */ 35739 init: function(options) { 35740 this._super(options); 35741 this.canvasElement.find('.controls').hide() 35742 } 35743}); 35744 35745myDeco.App.views.push(GardenSearchView); 35746/*globals myDeco, $, FpeController */ 35747/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35748 sloppy: true, white: true */ 35749/** 35750 * @fileOverview FPE room controller 35751 */ 35752 35753var IsometricController = FpeController.extend({ 35754 /** 35755 * Controller type 35756 * @type {String} 35757 * @static 35758 */ 35759 $type: 'isometric', 35760 35761 /** 35762 * Register view 35763 * @param {Object} view FPE view 35764 */ 35765 registerView: function(view) { 35766 var self = this; 35767 this._super(view); 35768 35769 this._view.viewHolder.find('.rotating-arrows a').click(function(e) { 35770 e.stop(); 35771 self._view.rotate($(e.target).hasClass('right')); 35772 }); 35773 }, 35774 35775 /** 35776 * Mouse move event listener 35777 * @param {Event} e Mousemove event 35778 */ 35779 mouseMove: function(e) { 35780 if (this._operation.type === 'drag') { 35781 var self = this; 35782 35783 $.each(this._model.selected(), function() { 35784 var p = self._view.toModelPosition( 35785 e.pageX - self._operation.data.d[this.ID].x, 35786 e.pageY - self._operation.data.d[this.ID].y, 35787 self._operation.data.d[this.ID].z, 35788 this.parent); 35789 35790 // special cases when walls are perpendicular to view 35791 if (p) { 35792 self._model.move( 35793 p.x - this.position.x, 35794 p.y - this.position.y, 35795 this.parent ? 35796 p.z - this.position.z : undefined, 35797 // force restrictions for on-wall 35798 // components in 3d view 35799 this, this.parent); 35800 } 35801 }); 35802 35803 e.stop(); 35804 } else { 35805 this._super(e); 35806 35807 if (this._operation.type === 'pan') { 35808 this._view.updateShapes(); 35809 } 35810 } 35811 }, 35812 35813 /** 35814 * Visual element drag observer 35815 * @param {Event} e Mousedown event 35816 * @param {Object} entity operating on 35817 */ 35818 dragStart: function(e, visual) { 35819 var self = this, 35820 transform = this._view.transforms[this._view.angle], 35821 offset = this._view.getCanvasOffset(), 35822 sceneX = (e.pageX - offset.left - this._view._offset.x) / 35823 this._view.zoom, 35824 sceneY = (e.pageY - offset.top - this._view._offset.y) / 35825 this._view.zoom; 35826 35827 this._super(e, visual); 35828
35829 // Storing distance between point of drag and center of door/window 35830 $.each(this._model.selected(), function() { 35831 var p = this.position 35832 .applyTransformation(transform, 'z' + self._view.angle); 35833 35834 if (!self._operation.data.d[this.ID]) { 35835 self._operation.data.d[this.ID] = { 35836 x: (sceneX - p.x) * self._view.zoom, 35837 y: (sceneY - p.y) * self._view.zoom, 35838 z: this.position.z 35839 }; 35840 } 35841 }); 35842 }, 35843 35844 /** 35845 * End operation handler, used for redrawing after some kind of changes 35846 * @param {Array} data Custom event data 35847 */ 35848 operationEnd: function(e, data) { 35849 data = this._super(e, data); 35850 35851 switch(data[0]) { 35852 case 'wallHeight': 35853 this._view.calculateTransforms(); 35854 this._view._cloud.findByField('type', ['IsoWall']) 35855 .batch('draw'); 35856 break; 35857 case 'drag': 35858 this._view.sortCloud(); 35859 break; 35860 } 35861 }, 35862 35863 /** 35864 * Move items by keypress 35865 * @param {Event} e 35866 */ 35867 moveByKey: function() { 35868 // don't want to move in 3D 35869 } 35870 35871}); 35872 35873myDeco.App.controllers.push(IsometricController); 35874/*globals myDeco, $, FpeView, T, Transform, IsoFloor, IsoWall, IsoWindow, 35875 IsoComponent, Point, getY, getIntersection */ 35876/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 35877 sloppy: true, white: true */ 35878/** 35879 * @fileOverview Isometric view 35880 */ 35881var IsometricView = FpeView.extend({ 35882 /** 35883 * View type, should be static 35884 * @type {String} 35885 * @static 35886 */ 35887 $type: 'isometric', 35888 35889 /** 35890 * App DOM element 35891 * @type {Object} 35892 */ 35893 app: false, 35894 35895 /** 35896 * Initial angle shift 35897 * @type {Number} 35898 */ 35899 angleShift: 45, 35900 35901 /* 35902 * List of precalculated transformations for different angles of view 35903 * @type {Hash of Transform} 35904 */ 35905 transforms: {}, 35906 35907 /* 35908 * Current angle of view 35909 * @type {Int} 35910 */ 35911 angle: 0, 35912 35913 /* 35914 * Angle of isometry 35915 * @type {Int} 35916 */ 35917 isoAngle: 27, 35918 35919 /** 35920 * Active shape for working on 35921 * @type {Object} 35922 */ 35923 activeShape: false, 35924 35925 /** 35926 * Rotation step, degrees 35927 * @type {Number} 35928 */ 35929 step: 45, 35930 35931 /** 35932 * Render revision for avoid of caching 35933 * @type {Number} 35934 */ 35935 renderRevision: 1, 35936 35937 /** 35938 * Init method 35939 */ 35940 init: function(options) { 35941 this._isoCos = T.cos(this.isoAngle); 35942 this._isoSin = T.sin(this.isoAngle); 35943 this._updateAngle(); 35944 35945 this._super(options); 35946 35947 this.calculateTransforms(); 35948 35949 }, 35950 35951 /** 35952 * Pre-calculate transformation matrices for 4 viewing angles (floor and 35953 * ceiling) 35954 */ 35955 calculateTransforms: function() { 35956 var i, t1, t2; 35957 35958 for (i = 0; i < parseInt(360 / this.step, 10); i++) { 35959 t1 = new Transform() 35960 .isometric(this.isoAngle, i * this.step); 35961 this.transforms[(i * this.step)] = t1; 35962 35963 t2 = new Transform() 35964 .translate(0, 0, this._model.ceilingHeight) 35965 .isometric(this.isoAngle, i * this.step); 35966 35967 this.transforms[(i * this.step) + 'c'] = t2; 35968 35969 this.transforms[(i * this.step) + 'c'].foo = 'bar'; 35970 } 35971 }, 35972 35973 /** 35974 * Update precalculated values for sin/cos of the view rotation angle 35975 * @param {Number} angle 35976 */ 35977 _updateAngle: function(angle) { 35978 if (typeof angle !== 'undefined') { 35979 this.angle = angle; 35980 } else if(this._model) { 35981 this.angle = (180 + Math.round((-this._model.camera.getAngle() + 35982 360 + this.angleShift) / this.step) * this.step) % 360; 35983 } 35984 this._rotCos = parseFloat(T.cos(this.angle - this.angleShift) 35985 .toFixed(5)); 35986 this._rotSin = parseFloat(T.sin(this.angle - this.angleShift) 35987 .toFixed(5)); 35988 }, 35989 35990 /** 35991 * Rotate view content CW and CCW. 35992 * @param {Boolean} backward rotate CCW. 35993 */ 35994 rotate: function(backward) { 35995 var offset = this.getCanvasOffset(), 35996 x = offset.left + this.canvasElement.width() / 2, 35997 y = offset.top + this.canvasElement.height() / 2, 35998 // rotate around a half wall height 35999 p = this.toModelPosition(x, y, this._model.ceilingHeight / 2); 36000 36001 this._updateAngle((this.angle + 36002 (backward ? -this.step : this.step) + 360) % 360); 36003 36004 p = p.applyTransformation(this.transforms[this.angle], this.angle); 36005 x -= p.x * this.zoom + this._offset.x + offset.left; 36006 y -= p.y * this.zoom + this._offset.y + offset.top; 36007 this.pan(x, y); 36008 36009 this.activeShape = false;
36010 this._cloud.batch('draw'); 36011 this.sortCloud(true); 36012 if (this._model.shapes.length === 1) { 36013 this.updateShapes(); 36014 } else { 36015 this.updateWalls(); 36016 } 36017 this.updateMaterial(); 36018 }, 36019 36020 /** 36021 * Load from model and draw isometry representation of the room on canvas 36022 * @param {Boolean} sameRoom Flag for calls for room load from undo/redo 36023 * routines. Disables room auto positioning 36024 */ 36025 load: function(sameRoom) { 36026 this._super(sameRoom); 36027 this._updateAngle(); 36028 this.calculateTransforms(); 36029 36030 this.activeShape = false; 36031 36032 // init and push components to a cloud 36033 this.populateCloud([ 36034 [this._model.shapes, IsoFloor, this._droppablesObserver], 36035 [this._model.walls, IsoWall, this._droppablesObserver], 36036 [this._model.windows, IsoWindow, this._draggablesObserver], 36037 [this._model.doors, IsoWindow, this._draggablesObserver], 36038 [this._model.components, IsoComponent, this._draggablesObserver] 36039 ], $.proxy(function() { 36040 this.sortCloud(true); 36041 36042 if (this._model.shapes.length === 1) { 36043 this.updateShapes(); 36044 } else { 36045 this.updateWalls(); 36046 } 36047 36048 this.updateMaterial(); 36049 }, this)); 36050 }, 36051 36052 /** 36053 * Sort cloud and reposition components by Z index on view 36054 * @param {Boolean} dropCache Drop cached median points values 36055 */ 36056 sortCloud: function(dropCache) { 36057 var transform = this.transforms[this.angle], 36058 angle = this.angle, 36059 self = this, 36060 stack = [], 36061 updateMiddlePoints = function(component) { 36062 var points, i, idx = 0; 36063 if (!component._mp || dropCache || 36064 $.inArray(component._model, self._model.selected()) !== -1) { 36065 if (component.type === 'IsoComponent' || 36066 component.type === 'IsoWindow') { 36067 points = $.map(component._model._bbox, function(point, i) { 36068 if (i % 2 === 0) { 36069 return point.applyTransformation(transform, angle); 36070 } 36071 }); 36072 36073 // normalize order of points 36074 points.push(points.splice(2, 1).shift()); 36075 36076 // find left and right points 36077 for (i = 0; i < 4; i++) { 36078 if (points[i].x < points[idx].x) { 36079 idx = i; 36080 } 36081 } 36082 36083 // get opposite points 36084 component._mp = [points[idx], points[(idx + 2) % 4]]; 36085 } else if (component.type === 'IsoWall') { 36086 component._mp = $.map(component._model, function(point) { 36087 var p = point.applyTransformation(transform, angle); 36088 p._model = point; 36089 return p; 36090 }); 36091 36092 } 36093 36094 // reverse middle points if first x greater than second or 36095 // if x equals and y coordinaes are reversed 36096 if (component._mp && 36097 (component._mp[0].x > component._mp[1].x || 36098 (component._mp[0].x === component._mp[1].x && 36099 component._mp[0].y < component._mp[1].y))) { 36100 component._mp.reverse(); 36101 } 36102 } 36103 36104 return component; 36105 }, 36106 36107 // sorting components and walls based on medians and distance between centers of intersection 36108 medianSort = function(a, b) { 36109 var median = getIntersection(a._mp, b._mp), 36110 ay = typeof median !== 'undefined' ? getY(a._mp, median) : 36111 (a._mp[0].y + a._mp[1].y) / 2, 36112 by = typeof median !== 'undefined' ? getY(b._mp, median) : 36113 (b._mp[0].y + b._mp[1].y) / 2; 36114 36115 return ay > by ? 1 : -1; 36116 }, 36117 onWallComponents = [], 36118 secondLevelComponents = [], 36119 zeroLevelComponents = [], 36120 shapes = [], 36121 toSort = $.grep(this._cloud, function(visual) { 36122 var parents; 36123 36124 if (visual.type === 'IsoFloor') { 36125 shapes.push(visual); 36126 36127 // on wall components detected by level ID 36128 } else if (visual._model.level === -1) { 36129 updateMiddlePoints(visual); 36130 36131 onWallComponents.push(visual); 36132 36133 // second level components which isn't over other components 36134 } else if (visual._model.level === 2) { 36135 updateMiddlePoints(visual); 36136 // find "parents" for these components 36137 parents = $.grep(self._model.components, 36138 function(component) { 36139 return (component.level === 1) && 36140 component._surface[0].z - 36141 component._surface.zDiff < 36142 visual._model._surface[0].z && 36143 visual._model.position 36144 .inPoly(component._surface); 36145 }); 36146 36147 // if no parents force it as ordinal first-level component 36148 if (parents.length === 0) { 36149 return true; 36150 } 36151 36152 // sort parents by z coordinate and fetch first 36153 parents.sort(function(a, b) { 36154 return b.position.z - a.position.z; 36155 }); 36156 36157 visual.parent = parents[0]; 36158 36159 secondLevelComponents.push(visual); 36160 // carpets and other coverings 36161 } else if (visual._model.level === 0) { 36162 zeroLevelComponents.push(visual); 36163 } else { 36164 updateMiddlePoints(visual); 36165 return true; 36166 } 36167 return false;
36168 }); 36169 36170 // put floor to the bottom 36171 $.merge(stack, shapes); 36172 36173 // sort carpets by height and put to stack 36174 zeroLevelComponents.sort(function(a, b) { 36175 return b._model.height - a._model.height; 36176 }); 36177 $.merge(stack, zeroLevelComponents); 36178 36179 // sort oll other elements 36180 toSort.sort(medianSort); 36181 36182 36183 36184 36185 // sort second level components respecting to each other 36186 secondLevelComponents.sort(function(a, b) { 36187 return -medianSort(a, b); 36188 }); 36189 36190 // sort second level components respecting to its parent and height 36191 // in accending order to satisfy height requirements 36192 secondLevelComponents.sort(function(a, b) { 36193 if (a.parent.ID === b.parent.ID) { 36194 return 0; 36195 } else { 36196 return a.parent._surface[0].z - b.parent._surface[0].z; 36197 } 36198 }); 36199 36200 // add onWall elements to before walls 36201 $.each(onWallComponents, function() { 36202 var idx = 0; 36203 while (toSort[idx]._model !== this._model.parent) { 36204 idx++; 36205 } 36206 36207 toSort.splice(idx + 1, 0, this); 36208 }); 36209 36210 // insert all second level components under its parent 36211 $.each(secondLevelComponents, function() { 36212 var idx = 0; 36213 while (toSort[idx]._model !== this.parent) { 36214 idx++; 36215 } 36216 36217 toSort.splice(idx + 1, 0, this); 36218 delete this.parent; 36219 }); 36220 36221 $.merge(stack, toSort); 36222 36223 $.each(stack, function() { 36224 this.element.toFront(); 36225 if ($.isFunction(this.sort)) { 36226 this.sort(); 36227 } 36228 }); 36229 }, 36230 36231 /** 36232 * Auto scale/pan current room to fit view area 36233 */
36234 showAll: function() { 36235 this._super(); 36236 36237 var settings = this.settings.offset || {}, 36238 top = settings.top || 0, 36239 bottom = settings.bottom || 0, 36240 transform = this.transforms[this.angle], 36241 bb = this._model.getBBox(transform, this.angle), 36242 wzoom = (Math.abs(bb.p2.x - bb.p1.x) || 500) / this.canvasWidth, 36243 hzoom = (Math.abs(bb.p2.y - bb.p1.y) || 500) / 36244 (this.canvasHeight - top - bottom), 36245 36246 // set some space around model 36247 zoom = 0.90 / Math.max(wzoom, hzoom), 36248 cx = this.canvasWidth / 2, 36249 cy = (this.canvasHeight + top - bottom) / 2; 36250 36251 this.pan(cx - Math.abs(bb.p2.x + bb.p1.x) / 2 * zoom, 36252 cy + Math.abs(bb.p2.y + bb.p1.y) / 2 * zoom, true); 36253 this.scale(zoom, true); 36254 }, 36255 36256 /** 36257 * Add component to cloud and canvas 36258 * 36259 * @param {Event} e Event 36260 * @param {Object} entity Canvas entity 36261 * @param {String} type Component type 36262 */ 36263 addComponent: function(entity) { 36264 switch(entity.type) { 36265 case 'component': 36266 this._cloud.append([entity], IsoComponent, 36267 this._draggablesObserver); 36268 break; 36269 case 'door': 36270 case 'window': 36271 this._cloud.append([entity], IsoWindow, 36272 this._draggablesObserver); 36273 break; 36274 } 36275 }, 36276 36277 /** 36278 * Add room 36279 * @param {Object} shape 36280 */ 36281 addShape: function(shape) { 36282 this._cloud.append([shape], IsoFloor, 36283 this.droppablesObserver, '_model'); 36284 }, 36285 36286 /** 36287 * Add wall to view 36288 * @param {Wall} wall 36289 */ 36290 addWall: function(wall) { 36291 this._cloud.append([wall], IsoWall, this.droppablesObserver, '_model'); 36292 }, 36293 36294 /** 36295 * Generate draggable HTML element for display while dragging 36296 * 36297 * @param {Object} component Display component 36298 * @param {String} kind Kind of entity: furniture, decor, build element 36299 */ 36300 generateElementForDrag: function(component, kind) { 36301 var result = {}, 36302 self = this, bb; 36303 36304 function getOnScreenBounds(bb, onWall) { 36305 var q1, q2, q3, xmin, ymin, point, 36306 i = 0, 36307 xs = [], 36308 ys = []; 36309 36310 for (q1 = 0; q1 <= 1; q1++) { 36311 for (q2 = 0; q2 <= 1; q2++) { 36312 for (q3 = 0; q3 <= 1; q3++) { 36313 point = new Point( bb[q1][0], 36314 onWall ? bb[q2][2] : bb[q2][1], 36315 onWall ? bb[q2][1] : bb[q2][2]); 36316 36317 point.applyTransformation(self.transforms[self.angle]); 36318 i++; 36319 xs.push(point.x); 36320 ys.push(point.y); 36321 } 36322 } 36323 } 36324 36325 xmin = Math.min.apply(Math, xs); 36326 ymin = Math.min.apply(Math, ys); 36327 36328 return { 36329 xmin: Math.round(xmin * self.zoom), 36330 ymin: Math.round(ymin * self.zoom), 36331 w: Math.round((Math.max.apply(Math, xs) - xmin) * self.zoom), 36332 h: Math.round((Math.max.apply(Math, ys) - ymin) * self.zoom) 36333 }; 36334 } 36335 36336 switch (kind) { 36337 case 'furniture': 36338 case 'element': 36339 if (component.type !== 'room') { 36340 bb = getOnScreenBounds(component.bounding_box, component.level === -1); 36341 result = { 36342 'ox': - bb.xmin, 36343 'oy': - bb.ymin, 36344 'visual': 36345 $('<img src="' + 36346 // In some rare cases render_perspective can be empty (should be fixed by SRCH-1228) 36347 (component.componentData['3d'].render_perspective || '').replace(/^(http:\/\/([^\/]+)).*$/, 36348 '$1/cdb/render/' + component.component_id + '_' + (component.revision || '00') + 36349 '_vir' + ((360 - this.angle) % 360) + (component.level === -1 ? 'w1' : '') + '.png') + 36350 '" style="width:' + bb.w + 'px;height:' + bb.h + 'px; display: none" class="isometric" />') 36351 }; 36352 } else { 36353 bb = getOnScreenBounds([[- component.width / 2, - component.depth / 2, 0], 36354 [component.width / 2, component.depth / 2, this._model.ceilingHeight]]); 36355 36356 result = { 36357 'ox': - bb.xmin, 36358 'oy': - bb.ymin, 36359 'visual': $('<img class="isometric" style="width:' + bb.w + 'px;height:' + (bb.h * 1.15) + 'px;display:none" src="assets/austin/room3d.png"/>') 36360 }; 36361 } 36362 break; 36363 case 'decor': 36364 result = { 36365 'ox': -10, 36366 'oy': -10, 36367 'visual': false 36368 }; 36369 break; 36370 } 36371 36372 return result; 36373 }, 36374 36375 /** 36376 * Calculate real model coordinates from given screen coordinates 36377 * @param {Float} screenX X screen coord 36378 * @param {Float} screenY Y screen coord 36379 * @param {Float} sceneZ height of the plane, parallel to the floor, on which coords should be projected (used for isometry) 36380 * @param {Wall} wall Parent wall to which screen coordinates should be projected (used for isometry) 36381 * @returns {Object} in format {x: ..., y: ...} 36382 */ 36383 toModelPosition: function(screenX, screenY, sceneZ, wall) { 36384 var offset = this.getCanvasOffset(), 36385 sceneX = (screenX - offset.left - this._offset.x) / this.zoom, 36386 sceneY = (screenY - offset.top - this._offset.y) / this.zoom, 36387 x, y, z = sceneZ || 0, l, t; 36388 36389 if (typeof wall === 'undefined') { 36390 // special case when angle is 90 or 270 deg 36391 if (this._rotCos === 0) { 36392 y = sceneX / this._rotSin; 36393 x = sceneY * this._rotSin / this._isoSin + (z * this._isoCos / this._isoSin) * this._rotSin; 36394 } else { 36395 y = - (sceneY * this._rotCos - this._isoSin * this._rotSin * sceneX + this._isoCos * this._isoCos * z * this._rotCos) / 36396 (this._isoSin * (this._rotSin * this._rotSin + this._rotCos * this._rotCos)); 36397 x = (sceneX - this._rotSin * y) / this._rotCos; 36398 } 36399 } else { 36400 l = wall.getCoeffs(); 36401 t = this._rotSin * l.a - l.b * this._rotCos; 36402 if (t === 0) { 36403 // wall is perpendicular to current view 36404 return false;
36405 } else { 36406 y = (l.c * this._rotCos + sceneX * l.a) / t; 36407 if (this._rotCos === 0) { 36408 x = (wall[0].y - y) * l.b / l.a + wall[0].x; 36409 } else { 36410 x = sceneX / this._rotCos - this._rotSin / this._rotCos * y; 36411 } 36412 z = (this._isoSin * this._rotSin * x - this._isoSin * this._rotCos * y - sceneY) / 36413 (this._isoCos * this._isoCos); 36414 } 36415 } 36416 return new Point(x, y, z); 36417 }, 36418 36419 /** 36420 * Do not change cursor for isometric view at all 36421 */ 36422 cursor: function() { 36423 }, 36424 36425 /** 36426 * Convert center of viewport to a model coordinate 36427 * @param {Number} height Height for which calculation will be 36428 * @returns {Point} 36429 */ 36430 viewCenterToModel: function(height) { 36431 height = height || 0; 36432 36433 var offset = this.getCanvasOffset(); 36434 return this.toModelPosition(offset.left + this.canvasElement.width() / 2, 36435 offset.top + this.canvasElement.height() / 2, height); 36436 }, 36437 36438 /** 36439 * Calculate new active shape 36440 */ 36441 updateShapes: function() { 36442 // return; 36443 var p, shape = false; 36444 // detect main shape 36445 if (this._model.shapes.length === 1) { 36446 shape = this._model.shapes[0]; 36447 } else if (this._model.shapes.length > 1) { 36448 p = this.viewCenterToModel(150); 36449 shape = $.first(this._model.shapes, function(s) { 36450 return p.inPoly(s); 36451 }); 36452 shape = shape || false; 36453 } 36454 36455 if ((shape && shape !== this.activeShape) || (!shape && this.activeShape)) { 36456 this.activeShape = shape; 36457 this.updateWalls(); 36458 } 36459 }, 36460 36461 /** 36462 * Update walls visibility based on current activeShape 36463 */ 36464 updateWalls: function() { 36465 var self = this, 36466 angle = this.angle, 36467 transform = this.transforms[angle], 36468 // grep walls for further actions, active shape or all 36469 walls = this._cloud.grep(function(visual) { 36470 return visual.type === 'IsoWall' &&
36471 ((!self.activeShape && visual.toggle()) || 36472 (self.activeShape && !visual.toggle(visual._model._shapes.indexOf(self.activeShape) === -1))); 36473 }), 36474 mpoints, vpoints, wall, modelWalls, 36475 point, lP, rP, lW; 36476 36477 if (this.activeShape) { 36478 modelWalls = this.activeShape.getWalls(); 36479 lP = rP = walls[0]._mp[0]; 36480 36481 // get left and right points from iso projection 36482 $.each(walls, function() { 36483 if (this._mp[0].x < lP.x || (this._mp[0].x === lP.x && this._mp[0].y > lP.y)) { 36484 lP = this._mp[0]; 36485 } 36486 36487 if (this._mp[1].x > rP.x || (this._mp[1].x === rP.x && this._mp[1].y > rP.y)) { 36488 rP = this._mp[1]; 36489 } 36490 }); 36491 36492 lP = lP._model; 36493 rP = rP._model; 36494 mpoints = this.activeShape.getNeighbors(lP); 36495 vpoints = $.map(mpoints, function(point) { 36496 return point.applyTransformation(transform, angle); 36497 }); 36498 36499 point = lP.applyTransformation(transform, angle); 36500 36501 lW = point.angleTo(vpoints[0]) < point.angleTo(vpoints[1]) ? 36502 [lP, mpoints[0]] : [lP, mpoints[1]]; 36503 36504 // walk thru shape points until max right point isn't reached 36505 for(;;) { 36506 wall = this._model._findWall(lW[0], lW[1], modelWalls); 36507 if (wall) { 36508 walls.findByField('_model', [wall]).batch('toggle', true); 36509 } 36510 36511 if (lP === rP) { 36512 break; 36513 } 36514 36515 lP = this.activeShape.getOpposite(lW[1], lW[0]); 36516 lW.push(lP); lW.shift(); 36517 } 36518 36519 // enable wall joints 36520 // find hidden walls 36521 walls = this._cloud.grep(function(c) { 36522 return c.type === 'IsoWall' && c.state === false; 36523 }); 36524 36525 // find wall joints which should be enabled 36526 $.each(walls, function(i, wall) { 36527 // find walls which connected to disabled wall 36528 for (i = 0; i < 2; i++) { 36529 walls = self._cloud 36530 .findByField('_model', 36531 wall._model[i].getWalls(wall._model)) 36532 .findByField('state', [true]) 36533 .batch('showJoint', i, wall._model); 36534 } 36535 }); 36536 } 36537 }, 36538 36539 /** 36540 * Update materials on all decorating items 36541 */ 36542 updateMaterial: function() { 36543 this._cloud.findByField('type', ['IsoWall', 'IsoFloor']).batch('updateMaterial'); 36544 this.activeShape = false;
36545 } 36546}); 36547 36548// register view in dispatched view list 36549myDeco.App.views.push(IsometricView); 36550/*globals myDeco, $, Drawable, window, diffuse2Hex, textureFill */ 36551/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 36552 sloppy: true, white: true */ 36553/** 36554 * @fileOverview Isometric representation of the floor 36555 */ 36556var IsoFloor = Drawable.extend({ 36557 /** 36558 * Intsance type 36559 * @type {String} 36560 */ 36561 type: 'IsoFloor', 36562 36563 /** 36564 * Init function 36565 * @param {Object} view FPE view 36566 * @param {Object} model Model entity 36567 */ 36568 init: function(view, model) { 36569 this._super(view, model); 36570 36571 var self = this; 36572 36573 this._model.attachEvent('onMaterial', function() { 36574 self.updateMaterial(); 36575 }, this._view.$type); 36576 }, 36577 36578 /** 36579 * Floor drawing function 36580 */ 36581 draw: function() { 36582 var self = this, 36583 points = $.map(this._model, function(point) { 36584 return point.applyTransformation( 36585 self._view.transforms[self._view.angle], self._view.angle); 36586 }); 36587 36588 if (!this.element) { 36589 this.element = this._view.canvas.path({ 36590 'stroke-opacity': 0, 36591 'stroke-width': 0 36592 }).hold().poly(points, true).render(); 36593 } else { 36594 this.element.hold().update(points, true).render(); 36595 } 36596 36597 this._super(); 36598 }, 36599 36600 /** 36601 * Update floor material 36602 */ 36603 updateMaterial: function() { 36604 this.element.attr({ 36605 fill: textureFill(this._model.floorMaterial, $.extend(true, { 36606 'filled_rotation': 36607 (this._view.angleShift - this._view.angle) % 360, 36608 'y_scale': Math.round(this._view._isoSin * 100) 36609 }, this._settings.options)) 36610 }); 36611 } 36612}); 36613/*globals myDeco, $, Drawable, window, diffuse2Hex, textureFill, 36614 getIntersection, getY */ 36615/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 36616 sloppy: true, white: true */ 36617/** 36618 * @fileOverview Isometric representation of the wall 36619 */ 36620var IsoWall = Drawable.extend({ 36621 /** 36622 * Intsance type 36623 * @type {String} 36624 */ 36625 type: 'IsoWall', 36626 36627 /** 36628 * Texture shear angle 36629 * @type {Number} 36630 */ 36631 _shearAngle: 0, 36632 36633 _top: false, 36634 36635 _order: [], 36636 36637 /** 36638 * Init function 36639 * @param {Object} view FPE view 36640 * @param {Object} model Model entity 36641 */ 36642 init: function(view, model) { 36643 this._super(view, model); 36644 36645 var self = this; 36646 this._model 36647 .attachEvent('onMaterial', function() { 36648 self.updateMaterial(); 36649 }, this._view.$type) 36650 .attachEvent('onThickness', function() { 36651 // update attached points on the attached walls 36652 self._view._cloud.findByField('_model', self._model.getWalls()) 36653 .batch('draw'); 36654 }, this._view.$type); 36655 }, 36656 36657 /** 36658 * Floor drawing function 36659 */ 36660 draw: function() { 36661 var i, j, 36662 settings = this._settings.visual, 36663 floor = [], ceiling = [], walls = [], points, 36664 yCoords = [], 36665 joints = this._model.getJoints(), 36666 tFloor = this._view.transforms[this._view.angle], 36667 tCeiling = this._view.transforms[this._view.angle + 'c'], 36668 noStroke = $.extend({}, settings, { 36669 'stroke-width': 0, 36670 'stroke-opacity': 0, 36671 'stroke': settings.fill 36672 }); 36673 36674 this._order = [0, 1, 2, 3, 4, 5]; 36675 36676 for (i = 0; i < 6; i++) { 36677 floor.push(joints[i] 36678 .applyTransformation(tFloor, this._view.angle)); 36679 ceiling.push(joints[i] 36680 .applyTransformation(tCeiling, this._view.angle + 'c', true)); 36681 } 36682 36683 for (i = 0; i < 6; i++) { 36684 walls.push([floor[i], floor[(i + 1) % 6]]); 36685 yCoords.push((walls[i][0].y + walls[i][1].y) / 2); 36686 } 36687 36688 this._shearAngle = -Math.round(floor[1].angleTo(floor[4], true)); 36689 36690 if (!this.element) { 36691 this.element = this._view.canvas.set(); 36692 } 36693 36694 for (i = 0; i < 6; i++) { 36695 j = (i + 1) % 6; 36696 points = [floor[i], ceiling[i], ceiling[j], floor[j]]; 36697 if (!this._top) { 36698 this.element.push(this._view.canvas.path(noStroke).hold() 36699 .poly(points, true).render()); 36700 } else { 36701 this.element[i].hold().update(points, true).render(); 36702 } 36703 } 36704 36705 this._order.sort(function(a, b) { 36706 var x = getIntersection(walls[a], walls[b]), 36707 ay = typeof x !== 'undefined' ? getY(walls[a], x) : yCoords[a], 36708 by = typeof x !== 'undefined' ? getY(walls[b], x) : yCoords[b]; 36709 36710 return ay > by ? 1 : -1; 36711 }); 36712 36713 if (!this._top) { 36714 this._top = this._view.canvas.path(settings).hold() 36715 .poly(ceiling, true).render(); 36716 this.element.push(this._top); 36717 this._super(); 36718 } else { 36719 this._top.hold().update(ceiling, true).render(); 36720 } 36721 36722 // add top side to sorting array 36723 this._order.push(6); 36724 36725 this.updateMaterial(); 36726 }, 36727 36728 /** 36729 * Sort wall after scene sorting 36730 */ 36731 sort: function() { 36732 var i; 36733 for (i = 1; i < 7; i++) { 36734 this.element[this._order[i]] 36735 .insertAfter(this.element[this._order[i - 1]]); 36736 } 36737 }, 36738 36739 /** 36740 * Update wall material 36741 */ 36742 updateMaterial: function() { 36743 var attr = { 36744 fill: textureFill(this._model.material, $.extend(true, { 36745 'y_wrapped_shear': this._shearAngle 36746 }, this._settings.options)) 36747 }; 36748 this.element[2].attr(attr); 36749 this.element[5].attr(attr); 36750 }, 36751 36752 /** 36753 * Toggle wall visibility with all components on it 36754 * @param {Boolean} state 36755 */ 36756 toggle: function(state) { 36757 var ret, self = this, 36758 x = Math.min(this._order.indexOf(2), this._order.indexOf(5)); 36759 36760 this._view._cloud.findByField('_model', this._model.components) 36761 .batch('toggle', state); 36762 36763 ret = this._super(state); 36764 $.each([0, 1, 3, 4], function(i, id) { 36765 self.element[id].hide(); 36766 }); 36767 36768 this.element[this._order[x]].hide(); 36769 36770 return ret; 36771 }, 36772 36773 /** 36774 * Show joint wall pieces for given wall and corner index in given wall 36775 * @param {Number} i 36776 * @param {Object} wall 36777 */ 36778 showJoint: function(i, wall) { 36779 // convert foreign wall point index to our model point index 36780 i = this._model.indexOf(wall[i]); 36781 36782 var idx = 3 * i, 36783 joints = this._model[i].getJointWalls(this._model), 36784 ilen = joints.length; 36785 36786 for (i = 0; i < ilen; i++) { 36787 if (joints[i] === wall) { 36788 this.element[i + idx].show(); 36789 } 36790 } 36791 } 36792}); 36793/*globals Draggable, IsoWindow, Point, T, buildThumbnailLink, myDeco */ 36794/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 36795 sloppy: true, white: true */ 36796/** 36797 * @fileOverview Isometric representation of the window 36798 */ 36799var IsoWindow = Draggable.extend({ 36800 /** 36801 * Intsance type 36802 * @type {String} 36803 */ 36804 type: 'IsoWindow', 36805 36806 /** 36807 * Init function 36808 * @param {Object} view FPE view 36809 * @param {Object} model Model entity 36810 */ 36811 init: function(view, model) { 36812 var self = this; 36813 // Little trick to full parent's constructor 36814 this.move = this.draw; 36815 this._super(view, model); 36816 36817 this._model.attachEvent('onMaterial', function() { 36818 self.updateMaterial(); 36819 }, this._view.$type); 36820 36821 this._model.attachEvent('onAttributeChange', this.draw.bind(this)); 36822 }, 36823 36824 /** 36825 * Shear angle for door/window texture 36826 * @type {Number} 36827 */ 36828 _shearAngle: 0, 36829 36830 /** 36831 * Floor drawing function 36832 */ 36833 draw: function() { 36834 var p, x = [], y = [], x1, x2, y1, y2, c, 36835 tFloor = this._view.transforms[this._view.angle], 36836 points = [], angleFix = 90, 36837 position = this._model.position.clone() 36838 .translate(this._model.parent.thickness / 2, 0), 36839 indexes = [[0, 0], [0, 1], [1, 1], [1, 0]], 36840 doorway = '48d83ae6e14995f52feb252631f9eac1da8105da'; 36841 36842 y1 = this._model.position.clone().translate(100, 0) 36843 .rotate(this._model.position, 36844 this._model.parent.getAngle() + angleFix) 36845 .applyTransformation(tFloor, this._view.angle).y; 36846 36847 y2 = this._model.position.clone().translate(100, 0) 36848 .rotate(this._model.position, 36849 this._model.parent.getAngle() - angleFix) 36850 .applyTransformation(tFloor, this._view.angle).y; 36851 36852 if (y1 < y2) { 36853 angleFix = -90; 36854 } 36855 36856 position.rotate(this._model.position, 36857 this._model.parent.getAngle() + angleFix); 36858 36859 x1 = y1 = Number.MAX_VALUE; 36860 x2 = y2 = -Number.MAX_VALUE; 36861 36862 for (c = 0; c < 4; c++) {
36863 p = new Point(this._model.bounding_box[indexes[c][0]].x, 36864 this._model.bounding_box[indexes[c][1]].y, 0) 36865 .translate(position.x, position.y, position.z) 36866 .rotate(position, -this._model.rotation[2], 36867 this._model.rotation[0], this._model.rotation[1]) 36868 .applyTransformation(tFloor, this._view.angle); 36869 points.push(p); 36870 x.push(p.x); 36871 y.push(p.y); 36872 } 36873 36874 if (this._model.component_id === doorway) { 36875 if (!this.element) { 36876 this.element = this._view.canvas.path({ 36877 'fill': '#fff', 36878 'stroke-width': 0.5, 36879 'stroke': '#ccc' 36880 }).poly(points, true); 36881 } else { 36882 this.element.update(points, true); 36883 } 36884 } else { 36885 x1 = Math.min.apply(Math, x); 36886 x2 = Math.max.apply(Math, x); 36887 y1 = Math.min.apply(Math, y); 36888 y2 = Math.max.apply(Math, y); 36889 36890 if (!this.element) { 36891 this.element = this._view.canvas.image( 36892 myDeco.settings.STATIC_URL + 36893 'blank.gif', x1, y1, x2 - x1, y2 - y1); 36894 } else { 36895 this.element.attr({ 36896 src: myDeco.settings.STATIC_URL + 'blank.gif', 36897 x: x1, y: y1, width: x2 - x1, height: y2 - y1 36898 }); 36899 } 36900 36901 } 36902 36903 this._shearAngle = Math.round(T.atan( 36904 ((x2 - x1) / ((points[1].y - points[0].y) * this._model.width / 36905 this._model.height)) * (points[1].y - points[2].y) / 36906 (points[2].x - points[1].x))); 36907 36908 this._super(); 36909 }, 36910 36911 /** 36912 * Update component image 36913 */ 36914 updateMaterial: function() { 36915 if (!isNaN(this._shearAngle)) { 36916 this.element.attr({src: buildThumbnailLink( 36917 this._model.componentData['3d'].render_top, { 36918 mirror: +(this._model.open_side === 'l'), 36919 shear: this._shearAngle 36920 })}); 36921 } 36922 } 36923}); 36924/*globals myDeco, $, Draggable, extractRootUrl */ 36925/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 36926 sloppy: true, white: true */ 36927/** 36928 * @fileOverview Isometric representation of the component 36929 */ 36930var IsoComponent = Draggable.extend({ 36931 /** 36932 * Intsance type 36933 * @type {String} 36934 */ 36935 type: 'IsoComponent', 36936 36937 /** 36938 * Init function 36939 * @param {Object} view FPE view 36940 * @param {Object} model Model entity 36941 */ 36942 init: function(view, model) { 36943 // Little trick to fool parent's constructor 36944 this.move = this.draw; 36945 36946 this._super(view, model); 36947 this._model.attachEvent('onRotate', this.draw.bind(this), 36948 this._view.$type); 36949 }, 36950 36951 /** 36952 * Component drawing function 36953 */ 36954 draw: function() { 36955 var p, q1, q2, q3, 36956 bbox = {}, i = 0, 36957 tFloor = this._view.transforms[this._view.angle], 36958 x1 = Number.MAX_VALUE, y1 = Number.MAX_VALUE, 36959 x2 = -Number.MAX_VALUE, y2 = -Number.MAX_VALUE, 36960 angleFix = 90; 36961 36962 if (this._model.parent) { 36963 q1 = this._model.position.clone().translate(100, 0) 36964 .rotate(this._model.position, 36965 this._model.parent.getAngle() + angleFix) 36966 .applyTransformation(tFloor, this._view.angle).y; 36967 36968 q2 = this._model.position.clone().translate(100, 0) 36969 .rotate(this._model.position, 36970 this._model.parent.getAngle() - angleFix) 36971 .applyTransformation(tFloor, this._view.angle).y; 36972 if (q1 < q2) { 36973 angleFix = -90; 36974 } 36975 } 36976 36977 for (q1 = 0; q1 <= 1; q1++) { 36978 for (q2 = 0; q2 <= 1; q2++) { 36979 for (q3 = 0; q3 <= 1; q3++) { 36980 p = this._model._bbox[i]; 36981 36982 if (this._model.parent) { 36983 p = p.clone() 36984 .translate(this._model.parent.thickness / 2, 0) 36985 .rotate(this._model._bbox[i], 36986 this._model.parent.getAngle() + angleFix); 36987 } 36988 36989 p = bbox[String(q1) + String(q2) + String(q3)] = p 36990 .applyTransformation(tFloor, this._view.angle); 36991 36992 x1 = Math.min(x1, p.x); 36993 y1 = Math.min(y1, p.y); 36994 x2 = Math.max(x2, p.x); 36995 y2 = Math.max(y2, p.y); 36996 i++; 36997 } 36998 } 36999 } 37000 37001 if (typeof this._hover === 'undefined') { 37002 this._hover = this._view.canvas.path( 37003 this._view.settings.IsoComponent.visual.inactive).hold(); 37004 this._base = this._view.canvas.image( 37005 myDeco.settings.STATIC_URL + 'blank.gif', 37006 x1, y1, x2 - x1, y2 - y1); 37007 } else { 37008 this._hover.hold().clear(); 37009 // Point image src to small blank image to avoid skewing of 37010 // old image, while new is loading 37011 this._base.attr({src: myDeco.settings.STATIC_URL + 'blank.gif', 37012 width: x2 - x1, height: y2 - y1, x: x1, y: y1}); 37013 } 37014 37015 this.updateRender(); 37016 37017 this._hover 37018 .moveTo(bbox['000'].x, bbox['000'].y) 37019 .lineTo(bbox['100'].x, bbox['100'].y) 37020 .lineTo(bbox['110'].x, bbox['110'].y) 37021 .lineTo(bbox['010'].x, bbox['010'].y) 37022 .lineTo(bbox['000'].x, bbox['000'].y) 37023 .lineTo(bbox['001'].x, bbox['001'].y) 37024 .lineTo(bbox['101'].x, bbox['101'].y) 37025 .lineTo(bbox['111'].x, bbox['111'].y) 37026 .lineTo(bbox['011'].x, bbox['011'].y) 37027 .lineTo(bbox['001'].x, bbox['001'].y) 37028 .moveTo(bbox['100'].x, bbox['100'].y) 37029 .lineTo(bbox['101'].x, bbox['101'].y) 37030 .moveTo(bbox['110'].x, bbox['110'].y) 37031 .lineTo(bbox['111'].x, bbox['111'].y) 37032 .moveTo(bbox['010'].x, bbox['010'].y) 37033 .lineTo(bbox['011'].x, bbox['011'].y) 37034 .render(); 37035 37036 this.element = this._view.canvas.set([this._hover, this._base]); 37037 37038 this._super(); 37039 }, 37040 37041 /** 37042 * Update render view on canvas 37043 */ 37044 updateRender: function() { 37045 if (this._model.componentData) { 37046 this._base 37047 // clear previous image 37048 .attr({'src': myDeco.settings.STATIC_URL + 'blank.gif'}) 37049 // set new image 37050 .attr({'src': extractRootUrl( 37051 this._model.componentData['3d'].render_perspective) + 37052 'cdb/render/r' + this._view.renderRevision + '/' + 37053 this._model.component_id + '_' + 37054 (this._model.revision || '00') + 37055 '_vir' + ((this._view.angleShift - this._view.angle - 37056 this._model.rotation[2] + 360).toFixed() % 360) + 37057 (this._model.level === -1 ? 'w1' : '') + '.png'}); 37058 } 37059 }, 37060 37061 /** 37062 * Decorate component depend on event type 37063 * @param {String} type Event type 37064 * @returns {Object} Event settings 37065 */ 37066 _decorate: function(type) { 37067 switch(type) { 37068 case 'selected': 37069 case 'highlighted': 37070 this._hover 37071 .attr(this._view.settings.IsoComponent.visual.active); 37072 break; 37073 case 'normal': 37074 this._hover 37075 .attr(this._view.settings.IsoComponent.visual.inactive); 37076 break; 37077 } 37078 37079 return {}; 37080 } 37081}); 37082/*globals myDeco, $, AppController, window, document, KEY */ 37083/*jslint sloppy: true, white: true, nomen: true, plusplus: true */ 37084/** 37085 * @fileOverview Common Layout controller 37086 */ 37087 37088var LayoutController = AppController.extend({ 37089 /** 37090 * Controller type 37091 * @type {String} 37092 * @static 37093 */ 37094 $type: 'layout', 37095 37096 /** 37097 * The list of elements which should not loose focus 37098 * @type {String} 37099 */ 37100 tagNames: ' INPUT TEXTAREA SELECT OPTION EMBED ', 37101 37102 /** 37103 * Active action name 37104 * @type {String} 37105 */ 37106 action: false, 37107 37108 /** 37109 * Status message element and data 37110 * @type {Object} 37111 */ 37112 _status: {}, 37113 37114 /** 37115 * Layout controller init 37116 * @param {Object} model FPE model 37117 * @param {Object} settings Controller settings 37118 */ 37119 init: function (model, settings) { 37120 this._super(model, settings); 37121 37122 var self = this, 37123 sidebar = $('#sidebar'), 37124 leftLimit = parseInt(sidebar.css('left'), 10), 37125 sidebarTabs = sidebar.find('.sidebar-buttons ul.tabs'), 37126 fakeControl = $('#fakeControl input'), 37127 preventReset; 37128 37129 /* Montage tabs */ 37130 if (sidebarTabs.size() === 0) { 37131 sidebarTabs = sidebar.find('ul.sidebar-buttons'); 37132 } 37133 37134 sidebar.delegate('dt a', 'click', function (e) { 37135 e.stop(); 37136 $(this).closest('dl').toggleClass('collapsed'); 37137 }); 37138 37139 myDeco.tabSwitcher.initTabSwitcher(sidebarTabs, { 37140 onShow: function (tab, callback) { 37141 callback(tab); 37142 sidebar.animate({left: 0}, 'fast', 'swing', function() { 37143 sidebar.removeClass('minimized').trigger('maximized'); 37144 }); 37145 }, 37146 37147 onHide: function (tab, callback) { 37148 sidebar.animate({left: leftLimit}, 'fast', 'swing', function() { 37149 callback(tab); 37150 sidebar.addClass('minimized').trigger('minimized'); 37151 }); 37152 } 37153 }); 37154 37155 myDeco.body 37156 .bind('preventOperationReset', function() { 37157 preventReset = true; 37158 }) 37159 .mouseup(function() { 37160 if (preventReset) { 37161 preventReset = false;
37162 } else { 37163 myDeco.App.model.operationReset(); 37164 } 37165 }) 37166 .mousemove(this.globalMouseMove.bind(this)) 37167 // To avoid uneccessary scrolling on iPad. 37168 .bind('touchmove', function (e) { 37169 if (e.changedTouches.length === 2) { 37170 e.preventDefault(); 37171 } 37172 }) 37173 .bind('toggleSidebar', function (e, tabClass) { 37174 if (tabClass) { 37175 sidebarTabs.trigger('show', [tabClass]); 37176 } else { 37177 sidebarTabs.trigger('hide'); 37178 } 37179 }) 37180 .bind('entity-drag-started', this.entityDrag.bind(this)) 37181 .bind('status roomLoading roomLoaded roomLoadError roomSaving ' + 37182 'roomSaved roomSavedError', 37183 function (e, data1, data2, data3) { 37184 var text, timeout, 37185 roomName = (myDeco.settings.WL && myDeco.settings.WL.WL_ROOM_NAME) ? myDeco.settings.WL.WL_ROOM_NAME : 'room'; 37186 37187 switch (e.type) { 37188 case 'status': 37189 text = data1 || ''; 37190 timeout = data2; 37191 break; 37192 37193 case 'roomLoading': 37194 if (typeof data1 === 'string') { 37195 text = roomName.capitalize() + ' is loading…'; 37196 timeout = 0; 37197 } 37198 break; 37199 37200 case 'roomLoaded': 37201 if (!data1 && self._model._ws_url) { 37202 text = roomName.capitalize() + ' loaded successfully'; 37203 } 37204 break; 37205 37206 case 'roomLoadError': 37207 text = 'Error while '+ roomName +' loading, please try later'; 37208 timeout = 30; 37209 break; 37210 37211 case 'roomSaving': 37212 text = 'Saving your '+ roomName +'…'; 37213 timeout = 0; 37214 break; 37215 37216 case 'roomSaved': 37217 text = $('<span>Your ' + (roomName === 'room' ? 'design' : roomName) + ' has been saved successfully. </span>'); 37218 timeout = 30; 37219 break; 37220 37221 case 'roomSavingError': 37222 text = 'Error while '+ roomName +' saving, please try later'; 37223 timeout = 30; 37224 break; 37225 } 37226 37227 if (text !== undefined) { 37228 self.status(text, timeout); 37229 } 37230 }); 37231 37232 if (!myDeco.browser.isMobileSafari) { 37233 myDeco.body.bind('mousedown selectstart dragstart', 37234 function (e) { 37235 if (self.tagNames.indexOf(' ' + e.target.tagName + ' ') === -1) { 37236 e.preventDefault(); 37237 // undefine text cursor position in the document 37238 fakeControl.focus().blur(); 37239 } 37240 }); 37241 } else { 37242 // longpress emulation for iPad (doubleclick replacement) 37243 myDeco.body.bind('mousedown', function (e) { 37244 var timeout = window.setTimeout(function() { 37245 var event = new $.Event(e); 37246 event.type = 'dblclick'; 37247 $(e.target).trigger(event); 37248 }, 1000); 37249 37250 myDeco.body.one('mouseup', function() { 37251 window.clearTimeout(timeout); 37252 }); 37253 }); 37254 } 37255 37256 37257 this._status.element = $('#messages'); 37258 this._status.id = 0; 37259 this._status.top = parseInt(this._status.element.css('top'), 10); 37260 this._status.height = this._status.element.outerHeight(true); 37261 this._status.element.click(function (e) { 37262 e.preventDefault(); 37263 self.status(); 37264 }); 37265 37266 $(document) 37267 .keydown(this.keyDown.bind(this)) 37268 .keypress(this.keyPress.bind(this)) 37269 .keyup(this.keyUp.bind(this)); 37270 }, 37271 37272 /** 37273 * Register view 37274 * @param {Object} view View associated to controller 37275 */ 37276 registerView: function (view) { 37277 this._super(view); 37278 var self = this; 37279 37280 view.app 37281 .mouseout(function (e) { 37282 var offset = self._view.getAppOffset(), 37283 dimensions = self._view.getAppDimensions(); 37284 37285 // reset operation if pointer is out of app 37286 if (e.pageX < offset.left || e.pageX > offset.left + dimensions.width || 37287 e.pageY < offset.top || e.pageY > offset.top + dimensions.height) { 37288 self._model.operationReset(); 37289 } 37290 }); 37291 37292 }, 37293 37294 /** 37295 * Handler for custom entity-drag-started event. Creates a DOM wrapper for 37296 * draggable element and does some visual machinery. 37297 * @param {Event} e entity-drag-started event 37298 * @param {Object} draggable Info about draggable entity and it's visual representation 37299 * @param {Event} originalEvent Original event which caused start of drag-n-drop 37300 */ 37301 entityDrag: function (e, draggable, originalEvent) { 37302 this._view.app 37303 .append(draggable.visual); 37304 37305 this._operation = { type: 'add', 37306 data: { 37307 entity: draggable.entity, 37308 visual: draggable.visual, 37309 ox: draggable.ox || draggable.visual.width() / 2, 37310 oy: draggable.oy || draggable.visual.height() / 2 37311 } 37312 }; 37313 37314 draggable.visual 37315 .css({ 37316 left: (originalEvent.pageX || 0) - this._operation.data.ox + 'px', 37317 top: (originalEvent.pageY || 0) - this._operation.data.oy + 'px', 37318 position: 'absolute' 37319 }); 37320 }, 37321 37322 /** 37323 * Global mouse move event handler for serving drag-n-drop between sidebar and canvases 37324 * @param {Event} e Mousemove event 37325 */ 37326 globalMouseMove: function (e) { 37327 if (this._operation.type === 'add') { 37328 e.preventDefault(); 37329 var position = this._operation.data.visual.data('position') || 37330 {x: this._operation.data.ox, y: this._operation.data.oy}; 37331 37332 this._operation.data.visual.css({ 37333 left: e.pageX - position.x + 'px', 37334 top: e.pageY - position.y + 'px' 37335 }); 37336 } 37337 }, 37338 37339 /** 37340 * Callback for resetting current operation state 37341 */ 37342 operationReset: function() { 37343 if (this._operation.type === 'add' && this._operation.data.visual) { 37344 this._operation.data.visual.remove(); 37345 } 37346 37347 this._operation = {type: '', data: {}}; 37348 }, 37349 37350 /** 37351 * Show status message in app
37352 * @param {jQset|String} message Message to show, will close if omitted 37353 * @param {Number} timeout Timeout to hide message, will show endless 37354 * message if less or equals to zero and show WoD in 37355 * case of equals to zero. 37356 */ 37357 status: function (message, timeout) { 37358 var self = this, 37359 type = typeof message; 37360 37361 if (message && type !== 'number') { 37362 if (type === 'string') { 37363 message = $(document.createTextNode(message)); 37364 } 37365 37366 timeout = typeof timeout === 'undefined' ? 5 : timeout; 37367 37368 this._status.element 37369 .toggleClass('waiting', timeout === 0) 37370 .children('.message').html('').append(message).end() 37371 .animate({top: this._status.top + this._status.height}); 37372 37373 if (timeout > 0) { 37374 window.setTimeout(function() { 37375 self.status(++self._status.id); 37376 }, timeout * 1000); 37377 } 37378 } else if (!message || 37379 (type === 'number' && message === this._status.id)) { 37380 this._status.element.animate({top: this._status.top}); 37381 } 37382 }, 37383 37384 /** 37385 * Global document observer, used to prevent default browser behavior on backspace. 37386 * @param {Event} e 37387 */ 37388 keyDown: function (e) { 37389 // prevent to stop animation etc 37390 if (typeof KEY !== 'undefined' && e.keyCode === KEY.Esc) { 37391 e.preventDefault(); 37392 } 37393 37394 // prevent to deletion if key pressed in input fields 37395 // pass through any special events with modifiers 37396 if (!e.ctrlKey && !e.altKey && !e.metaKey && 37397 this.tagNames.indexOf(' ' + e.target.tagName + ' ') !== -1) { 37398 return; 37399 } 37400 37401 // under win repeated event is keydown 37402 var kdr = window.navigator.platform.indexOf('Win') !== -1 && 37403 ($.browser.msie || $.browser.mozilla), 37404 action; 37405 37406 37407 if (!this.action) { 37408 action = $.first((this.settings.shortcuts || []), function (cmd) { 37409 return (e.keyCode === cmd[1] && e.shiftKey === cmd[2] && 37410 (e.ctrlKey === cmd[3] || e.metaKey === cmd[3]) && e.altKey === cmd[4]); 37411 }); 37412 } 37413 37414 // prevent default behavior of some keys to 37415 // disable default browser reaction 37416 // eg. scrolling of focused layers or move back in browser history 37417 if (this.action || action || 37418 (typeof KEY !== 'undefined' && 37419 (e.keyCode === KEY.Up || e.keyCode === KEY.Down || 37420 e.keyCode === KEY.BackSpace))) { 37421 e.preventDefault(); 37422 } 37423 37424 if (action || this.action) { 37425 if (!this.action || (this.action[5] && kdr)) { 37426 myDeco.body.trigger('actionRequest', [this.action || action]); 37427 } 37428 this.action = this.action || action; 37429 } 37430 }, 37431 37432 /** 37433 * Keypress listener 37434 * @param {Event} e 37435 */ 37436 keyPress: function() { 37437 if (this.action && this.action[5]) { 37438 myDeco.body.trigger('actionRequest', [this.action]); 37439 } 37440 }, 37441 37442 /** 37443 * Keyup listener 37444 * @param {Event} e 37445 */ 37446 keyUp: function() { 37447 this.action = false; 37448 } 37449}); 37450 37451(function() { 37452var _obj = { 37453 width: { 37454 "name":"width", 37455 "text":gettext("Width"), 37456 "type":"length", 37457 "detail":0, 37458 "default":60, 37459 "minimum":20, 37460 "maximum":500 37461 }, 37462 height: {"name":"height", 37463 "text":gettext("Height"), 37464 "type":"length", 37465 "detail":0, 37466 "default":180, 37467 "minimum":50, 37468 "maximum":500 37469 }, 37470 depth: { 37471 "name":"depth", 37472 "text":gettext("Depth"), 37473 "type":"length", 37474 "detail":99, 37475 "default":20, 37476 "minimum":0, 37477 "maximum":100 37478 }, 37479 open_side: { 37480 "name":"open_side", 37481 "text":gettext("Open Side"), 37482 "type":"enumeration", 37483 "detail":0, 37484 "default":"l", 37485 "values":[["l", gettext("Left")], ["r", gettext("Right")]] 37486 }, 37487 open_direction: { 37488 "name":"open_direction", 37489 "text":gettext("Open Direction"), 37490 "type":"enumeration", 37491 "detail":0, 37492 "default":"i", 37493 "values":[["i", gettext("In")], ["o", gettext("Out")]] 37494 }, 37495 open: { 37496 "name":"open", 37497 "text":gettext("Open"), 37498 "type":"fraction", 37499 "detail":0, 37500 "default":"0" 37501 }, 37502 finish: { 37503 "name":"finish", 37504 "text":gettext("Colour"), 37505 "type":"material", 37506 "detail":0, 37507 "default":"white" 37508 }, 37509 37510 panel_h: { 37511 "name":"panels_h", 37512 "text":"Panels Across", 37513 "type":"count", 37514 "detail":0, 37515 "default":3, 37516 "minimum":1, 37517 "maximum":6 37518 }, 37519 panel_v: { 37520 "name":"panels_v", 37521 "text":"Panels Down", 37522 "type":"count", 37523 "detail":0, 37524 "default":6, 37525 "minimum":1, 37526 "maximum":8 37527 } 37528 }; 37529myDeco.App.ws.components = { 37530 //door 37531 "f75c8910592b2bd613dcd1d2b60d5f237bb27a08": { 37532 "name": gettext("Plain Door"), 37533 "level": -1, 37534 "renders": { 37535 "back": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/e7/e79b4c034a30d52dac5d8b2cd460a2dddf711874_07.back.png", 37536 "bottom": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/e7/e79b4c034a30d52dac5d8b2cd460a2dddf711874_07.bottom.png", 37537 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/e7/e79b4c034a30d52dac5d8b2cd460a2dddf711874_07.front.png", 37538 "left": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/e7/e79b4c034a30d52dac5d8b2cd460a2dddf711874_07.left.png", 37539 "perspective": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/e7/e79b4c034a30d52dac5d8b2cd460a2dddf711874_07.perspective.png", 37540 "right": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/e7/e79b4c034a30d52dac5d8b2cd460a2dddf711874_07.right.png", 37541 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/e7/e79b4c034a30d52dac5d8b2cd460a2dddf711874_07.top.png" 37542 }, 37543 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/e7/e79b4c034a30d52dac5d8b2cd460a2dddf711874_07.perspective.png"], 37544 "type": "Door", 37545 "aperture": "rectangle", 37546 "configuration": [ 37547 _obj.width, 37548 _obj.height, 37549 _obj.depth, 37550 _obj.open_side, 37551 _obj.open_direction, 37552 _obj.open, 37553 _obj.finish 37554 ] 37555 }, 37556 //panel door 37557 "cf28e9e5b7659cbc88e3ed735bdfc827606628ff": { 37558 "name": gettext("Panel Door"), 37559 "level": -1, 37560 "renders": { 37561 "back": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/59/59e1639b55
37561d9b3c7b9c138f5370aa5d4cd23fae7_07.back.png", 37562 "bottom": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/59/59e1639b55d9b3c7b9c138f5370aa5d4cd23fae7_07.bottom.png", 37563 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/59/59e1639b55d9b3c7b9c138f5370aa5d4cd23fae7_07.front.png", 37564 "left": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/59/59e1639b55d9b3c7b9c138f5370aa5d4cd23fae7_07.png", 37565 "perspective": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/59/59e1639b55d9b3c7b9c138f5370aa5d4cd23fae7_07.perspective.png", 37566 "right": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/59/59e1639b55d9b3c7b9c138f5370aa5d4cd23fae7_07.right.png", 37567 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/59/59e1639b55d9b3c7b9c138f5370aa5d4cd23fae7_07.top.png" 37568 }, 37569 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/59/59e1639b55d9b3c7b9c138f5370aa5d4cd23fae7_07.perspective.png"], 37570 "type": "Door", 37571 "aperture": "rectangle", 37572 "configuration": [ 37573 _obj.width, 37574 _obj.height, 37575 _obj.depth, 37576 _obj.open_side, 37577 _obj.open_direction, 37578 _obj.open, 37579 _obj.finish, 37580 $.extend({}, _obj.panel_h, 37581 { 37582 "default":2, 37583 "maximum":5 37584 } 37585 ), 37586 $.extend({}, _obj.panel_v, 37587 { 37588 "default":2, 37589 "maximum":5 37590 } 37591 ) 37592 ] 37593 }, 37594 //door 37595 "899f1d4c7b441166851b9e38eb28f0ade2837687": { 37596 "name": gettext("Sliding Door"), 37597 "level": -1, 37598 "renders": { 37599 "back": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/c0/c0f1132733e116371860ad3e8bf2e7005d0853b8_01.back.png", 37600 "bottom": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/c0/c0f1132733e116371860ad3e8bf2e7005d0853b8_01.bottom.png", 37601 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/c0/c0f1132733e116371860ad3e8bf2e7005d0853b8_01.front.png", 37602 "left": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/c0/c0f1132733e116371860ad3e8bf2e7005d0853b8_01.left.png", 37603 "perspective": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/c0/c0f1132733e116371860ad3e8bf2e7005d0853b8_01.perspective.png", 37604 "right": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/c0/c0f1132733e116371860ad3e8bf2e7005d0853b8_01.right.png", 37605 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/c0/c0f1132733e116371860ad3e8bf2e7005d0853b8_01.top.png" 37606 }, 37607 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/c0/c0f1132733e116371860ad3e8bf2e7005d0853b8_01.perspective.png"], 37608 "type": "Door", 37609 "aperture": "rectangle", 37610 "configuration": [ 37611 _obj.width, 37612 _obj.height, 37613 _obj.depth, 37614 _obj.open_side, 37615 _obj.open, 37616 _obj.finish 37617 ] 37618 }, 37619 //Patio door 37620 "ed4398ca15141c396325b0903ac23fbacc960479": { 37621 "name": gettext("Patio Door"), 37622 "level": -1, 37623 "renders": { 37624 "back": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/77/7713d0f323bc68e2be45e287dcd8bf28d6568bec_04.back.png", 37625 "bottom": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/77/7713d0f323bc68e2be45e287dcd8bf28d6568bec_04.bottom.png", 37626 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/77/7713d0f323bc68e2be45e287dcd8bf28d6568bec_04.front.png", 37627 "left": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/77/7713d0f323bc68e2be45e287dcd8bf28d6568bec_04.png", 37628 "perspective": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/77/7713d0f323bc68e2be45e287dcd8bf28d6568bec_04.perspective.png", 37629 "right": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/77/7713d0f323bc68e2be45e287dcd8bf28d6568bec_04.right.png", 37630 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/77/7713d0f323bc68e2be45e287dcd8bf28d6568bec_04.top.png" 37631 }, 37632 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/77/7713d0f323bc68e2be45e287dcd8bf28d6568bec_04.perspective.png"], 37633 "type": "Door", 37634 "aperture": "rectangle", 37635 "configuration": [ 37636 _obj.width, 37637 _obj.height, 37638 _obj.depth, 37639 _obj.open_side, 37640 _obj.open, 37641 _obj.finish 37642 ] 37643 }, 37644 "5dd92a4fd20cc337ef9cd009d620ef39ac051e54": { 37645 "name": gettext("Folding Door"), 37646 "level": -1, 37647 "renders": { 37648 "back": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/3e/3e6a6
3764873e4ad12f17a08f1f2304e5dc47e95ab2d6_04.back.png", 37649 "bottom": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/3e/3e6a673e4ad12f17a08f1f2304e5dc47e95ab2d6_04.bottom.png", 37650 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/3e/3e6a673e4ad12f17a08f1f2304e5dc47e95ab2d6_04.front.png", 37651 "left": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/3e/3e6a673e4ad12f17a08f1f2304e5dc47e95ab2d6_04.png", 37652 "perspective": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/3e/3e6a673e4ad12f17a08f1f2304e5dc47e95ab2d6_04.perspective.png", 37653 "right": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/3e/3e6a673e4ad12f17a08f1f2304e5dc47e95ab2d6_04.right.png", 37654 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/3e/3e6a673e4ad12f17a08f1f2304e5dc47e95ab2d6_04.top.png" 37655 }, 37656 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/3e/3e6a673e4ad12f17a08f1f2304e5dc47e95ab2d6_04.perspective.png"], 37657 "type": "Door", 37658 "aperture": "rectangle", 37659 "configuration": [ 37660 _obj.width, 37661 _obj.height, 37662 _obj.depth, 37663 _obj.open_side, 37664 _obj.open, 37665 _obj.finish, 37666 { 37667 "name":"folds", 37668 "text":gettext("Folds Across"), 37669 "type":"count", 37670 "detail":0, 37671 "default":2, 37672 "minimum":1, 37673 "maximum":6 37674 } 37675 ] 37676 }, 37677 //door 37678 "fa310873ea1f5a6816728bfc50d96dd0d1c619ec": { 37679 "name": gettext("Glazed Double French"), 37680 "level": -1, 37681 "renders": { 37682 "back": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/79/79cbd33997154d30c4d7057a73f403dbbf673a89_06.back.png", 37683 "bottom": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/79/79cbd33997154d30c4d7057a73f403dbbf673a89_06.bottom.png", 37684 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/79/79cbd33997154d30c4d7057a73f403dbbf673a89_06.front.png", 37685 "left": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/79/79cbd33997154d30c4d7057a73f403dbbf673a89_06.png", 37686 "perspective": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/79/79cbd33997154d30c4d7057a73f403dbbf673a89_06.perspective.png", 37687 "right": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/79/79cbd33997154d30c4d7057a73f403dbbf673a89_06.right.png", 37688 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/79/79cbd33997154d30c4d7057a73f403dbbf673a89_06.top.png" 37689 }, 37690 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/79/79cbd33997154d30c4d7057a73f403dbbf673a89_06.perspective.png"], 37691 "type": "Door", 37692 "aperture": "rectangle", 37693 "configuration": [ 37694 _obj.width, 37695 _obj.height, 37696 _obj.depth, 37697 _obj.open_side, 37698 _obj.open, 37699 _obj.finish 37700 ] 37701 }, 37702 //door 37703 "1d07f9d6ba47a92d3bdb428f656ead081851f22f": { 37704 "name": gettext("Glazed Panel Door"), 37705 "level": -1, 37706 "renders": { 37707 "back": "https://fp-media-img-cdn.floorplanner.com/cdb/renders83/83282e79a02ebfa3c4aa50c7665538bf922e83a3_04.back.png", 37708 "bottom": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/83/83282e79a02ebfa3c4aa50c7665538bf922e83a3_04.bottom.png", 37709 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/83/83282e79a02ebfa3c4aa50c7665538bf922e83a3_04.front.png", 37710 "left": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/83/83282e79a02ebfa3c4aa50c7665538bf922e83a3_04.png", 37711 "perspective": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/83/83282e79a02ebfa3c4aa50c7665538bf922e83a3_04.perspective.png", 37712 "right": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/83/83282e79a02ebfa3c4aa50c7665538bf922e83a3_04.right.png", 37713 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/83/83282e79a02ebfa3c4aa50c7665538bf922e83a3_04.top.png" 37714 }, 37715 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/83/83282e79a02ebfa3c4aa50c7665538bf922e83a3_04.perspective.png"], 37716 "type": "Door", 37717 "aperture": "rectangle", 37718 "configuration": [ 37719 _obj.width, 37720 _obj.height, 37721 _obj.depth, 37722 _obj.open_side, 37723 _obj.open_direction, 37724 _obj.open, 37725 _obj.finish, 37726 _obj.panel_h, 37727 _obj.panel_v 37728 ] 37729 }, 37730 //door 37731 "0143d8e0e3c2de799c8661d5d97130bd248af342": { 37732 "name": gettext("Glazed Door"), 37733 "level": -1, 37734 "renders": { 37735 "back": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/93/93c95ea3540158b52ae6ec3c5af682d1ccb57867_05.back.png", 37736 "bottom": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/93/93c95ea3540158b52ae6ec3c5af682d1ccb57867_05.bottom.png", 37737 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/93/93c95ea3540158b52ae6ec3c5af682d1ccb57867_05.front.png", 37738 "left": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/93/93c95ea3540158b52ae6ec3c5af682d1ccb57867_05.png", 37739 "perspective": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/93/93c95ea3540158b52ae6ec3c5af682d1ccb57867_05.perspective.png", 37740 "right": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/93/93c95ea3540158b52ae6ec3c5af682d1ccb57867_05.right.png", 37741 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/93/93c95ea3540158b52ae6ec3c5af682d1ccb57867_05.top.png" 37742 }, 37743 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/93/93c95ea3540158b52ae6ec3c5af682d1ccb57867_05.perspective.png"], 37744 "type": "Door", 37745 "aperture": "rectangle", 37746 "configuration": [ 37747 _obj.width, 37748 _obj.height, 37749 _obj.depth, 37750 _obj.open_side, 37751 _obj.open_direction, 37752 _obj.open, 37753 _obj.finish, 37754 $.extend({}, _obj.panel_h, 37755 { 37756 "default":2, 37757 "maximum":5 37758 } 37759 ), 37760 $.extend({}, _obj.panel_v, 37761 { 37762 "default":2, 37763 "maximum":5 37764 } 37765 ) 37766 ] 37767 }, 37768 //door:FIXME: have no renders becuase of error on qa/prod 37769 "56e44065a5645a008497882a7901c06811fe6298": { 37770 "name": gettext("Opening"), 37771 "level": -1, 37772 "renders": { 37773 "back": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/83/83282e79a02ebfa3c4aa50c766
377735538bf922e83a3_04.back.png", 37774 "bottom": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/83/83282e79a02ebfa3c4aa50c7665538bf922e83a3_04.bottom.png", 37775 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/29/2968716ba07d5794311e0058b68e7c7f4ec6bfe9_03.front.png", 37776 "left": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/83/83282e79a02ebfa3c4aa50c7665538bf922e83a3_04.left.png", 37777 "perspective": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/83/83282e79a02ebfa3c4aa50c7665538bf922e83a3_04.perspective.png", 37778 "right": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/83/83282e79a02ebfa3c4aa50c7665538bf922e83a3_04.right.png", 37779 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/83/83282e79a02ebfa3c4aa50c7665538bf922e83a3_04.top.png" 37780 }, 37781 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/83/83282e79a02ebfa3c4aa50c7665538bf922e83a3_04.perspective.png"], 37782 "type": "Door", 37783 "aperture": "rectangle", 37784 "configuration": [ 37785 _obj.width, 37786 _obj.height, 37787 _obj.depth, 37788 _obj.finish 37789 ] 37790 }, 37791 // rectangular opening 37792 // NB! using renders and render_path of a different item 37793 "482e0a08d4a4666fdad20c0bc8194bbebf223077": { 37794 name: gettext("Rectangular Opening"), 37795 level: -1, 37796 type: "Door", 37797 aperture: "rectangle", 37798 "renders": { 37799 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/29/2968716ba07d5794311e0058b68e7c7f4ec6bfe9_03.front.png", 37800 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/scaled/renders/29/2968716ba07d5794311e0058b68e7c7f4ec6bfe9_03.top.png" 37801 }, 37802 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/29/2968716ba07d5794311e0058b68e7c7f4ec6bfe9_03.front.png"], 37803 "configuration": [ 37804 _obj.width, 37805 {"name": "height", 37806 "text": gettext("Height"), 37807 "type": "length", 37808 "detail": 0, 37809 "default": 180, 37810 "minimum": 20, 37811 "maximum": 500 37812 }, 37813 _obj.depth, 37814 _obj.finish 37815 ] 37816 }, 37817 37818 // circular opening 37819 // NB! using renders and render_path of a different item 37820 "22b0042cf9b5710f2fc20a407af244e3e8a98147": { 37821 name: gettext("Round Opening"), 37822 level: -1, 37823 type: "Door", 37824 aperture: "round", 37825 "renders": { 37826 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/29/2968716ba07d5794311e0058b68e7c7f4ec6bfe9_03.front.png", 37827 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/scaled/renders/5f/5ff1e7646a74fcfc81695a682db7a60c2577a6e8_00.top.png" 37828 }, 37829 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/29/2968716ba07d5794311e0058b68e7c7f4ec6bfe9_03.front.png"], 37830 "configuration": [ 37831 _obj.width, 37832 _obj.height, 37833 _obj.depth, 37834 _obj.finish 37835 ] 37836 }, 37837 37838 // arched opening 37839 // NB! using renders and render_path of a different item 37840 "1b4768a1cb1ffcacdc3a37de6eb3678a125ae34f": { 37841 name: gettext("Arched Opening"), 37842 level: -1, 37843 type: "Door", 37844 aperture: "arched", 37845 "renders": { 37846 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/29/2968716ba07d5794311e0058b68e7c7f4ec6bfe9_03.front.png", 37847 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/scaled/renders/91/91ebc7872da6ff77157f26b77a4bbdace2c93bb4_00.top.png" 37848 }, 37849 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/29/2968716ba07d5794311e0058b68e7c7f4ec6bfe9_03.front.png"], 37850 "configuration": [ 37851 _obj.width, 37852 _obj.height, 37853 _obj.depth, 37854 _obj.finish 37855 ] 37856 }, 37857 37858 // half-round opening 37859 // NB! using renders and render_path of a different item 37860 "14f0695b5b699d2cde9ac56b72d064c714e440a9": { 37861 name: gettext("Half Round Opening"), 37862 level: -1, 37863 type: "Door", 37864 aperture: "half-round", 37865 "renders": { 37866 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/29/2968716ba07d5794311e0058b68e7c7f4ec6bfe9_03.front.png", 37867 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/scaled/renders/42/42919437e4daa8c94e51acdf9bfbd76080d323bd_00.top.png" 37868 }, 37869 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/29/2968716ba07d5794311e0058b68e7c7f4ec6bfe9_03.front.png"], 37870 "configuration": [ 37871 _obj.width, 37872 _obj.height, 37873 _obj.depth, 37874 _obj.finish 37875 ] 37876 }, 37877 37878 //Sash window 37879 "3dfe203d609c4e61ed5d5ed34144421a176cfeed": { 37880 "name": gettext("Sash Window"), 37881 "level": -1, 37882 "renders": { 37883 "back": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/95/9569fbcfae9444412504bbebfc2cc988f031ea99_03.back.png", 37884 "bottom": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/95/9569fbcfae9444412504bbebfc2cc988f031ea99_03.bottom.png", 37885 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/95/9569fbcfae9444412504bbebfc2cc988f031ea99_03.front.png", 37886 "left": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/95/9569fbcfae9444412504bbebfc2cc988f031ea99_03.left.png", 37887 "perspective": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/95/9569fbcfae9444412504bbebfc2cc988f031ea99_03.perspective.png", 37888 "right": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/95/9569fbcfae9444412504bbebfc2cc988f031ea99_034.right.png", 37889 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/95/9569fbcfae9444412504bbebfc2cc988f031ea99_03.top.png" 37890 }, 37891 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/95/9569fbcfae9444412504bbebfc2cc988f031ea99_03.perspective.png"], 37892 "type": "Window", 37893 "aperture": "rectangle", 37894 "configuration": [ 37895 _obj.width, 37896 _obj.height, 37897 _obj.depth, 37898 _obj.open, 37899 _obj.finish 37900 ] 37901 }, 37902 //Picture window 37903 "23f3f34205d4709811052a6709914697600302c6": { 37904 "name": gettext("Picture Window"), 37905 "level": -1, 37906 "renders": { 37907 "back": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/29/2968716ba07d5794311e0058b68e7c7f4ec6bfe9_03.back.png", 37908 "bottom": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/29/2968716ba07d5794311e0058b68e7c7f4ec6bfe9_03.bottom.png", 37909 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/29/2968716ba07d5794311e0058b68e7c7f4ec6bfe9_03.front.png", 37910 "left": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/29/2968716ba07d5794311e0058b68e7c7f4ec6bfe9_034.left.png", 37911 "perspective": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/29/2968716ba07d5794311e0058b68e7c7f4ec6bfe9_03.perspective.png", 37912 "right": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/29/2968716ba07d5794311e0058b68e7c7f4ec6bfe9_03.right.png", 37913 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/29/2968716ba07d5794311e0058b68e7c7f4ec6bfe9_03.top.png" 37914 }, 37915 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/29/2968716ba07d5794311e0058b68e7c7f4ec6bfe9_03.perspective.png"], 37916 "type": "Window", 37917 "aperture": "rectangle", 37918 "configuration": [ 37919 _obj.width, 37920 _obj.height, 37921 _obj.depth, 37922 _obj.finish 37923 ] 37924 }, 37925 //Case window 37926 "6d48b971d09f830198aad21b2b9fcb2c5b9d36af": { 37927 "name": gettext("Casement Window"), 37928 "level": -1, 37929 "renders": { 37930 "back": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/9f/9ff3d15ce477fa98bb0f64d29df5042648ebfc0a_03.back.png", 37931 "bottom": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/9f/9ff3d15ce477fa98bb0f64d29df5042648ebfc0a_03.bottom.png", 37932 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/9f/9ff3d15ce477fa98bb0f64d29df5042648ebfc0a_03.front.png", 37933 "left": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/9f/9ff3d15ce477fa98bb0f64d29df5042648ebfc0a_03.left.png", 37934 "perspective": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/9f/9ff3d15ce477fa98bb0f64d29df5042648ebfc0a_03.perspective.png", 37935 "right": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/9f/9ff3d15ce477fa98bb0f64d29df5042648ebfc0a_03.right.png", 37936 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/9f/9ff3d15ce477fa98bb0f64d29df5042648ebfc0a_03.top.png" 37937 }, 37938 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/9f/9ff3d15ce477fa98bb0f64d29df5042648ebfc0a_03.perspective.png"], 37939 "type": "Window", 37940 "aperture": "rectangle", 37941 "configuration": [ 37942 _obj.width, 37943 _obj.height, 37944 _obj.depth, 37945 _obj.open_side, 37946 _obj.open_direction, 37947 _obj.open, 37948 _obj.finish 37949 ] 37950 }, 37951 //Arched window 37952 "6c705d137661ddf3de8ce3e159cc2446562912cf": { 37953 "name": gettext("Arched Window"), 37954 "level": -1, 37955 "renders": { 37956 "back": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/91/91ebc7872da6ff77157f26b77a4bbdace2c93bb4_00.back.png", 37957 "bottom": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/91/91ebc7872da6ff77157f26b77a4bbdace2c93bb4_00.bottom.png", 37958 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/91/91ebc7872da6ff77157f26b77a4bbdace2c93bb4_00.front.png", 37959 "left": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/91/91ebc7872da6ff77157f26b77a4bbdace2c93bb4_00.left.png", 37960 "perspective": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/91/91ebc7872da6ff77157f26b77a4bbdace2c93bb4_00.perspective.png", 37961 "right": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/91/91ebc7872da6ff77157f26b77a4bbdace2c93bb4_00.right.png", 37962 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/91/91ebc7872da6ff77157f26b77a4bbdace2c93bb4_00.top.png" 37963 }, 37964 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/91/91ebc7872da6ff77157f26b77a4bbdace2c93bb4_00.perspective.png"], 37965 "type": "Window", 37966 "aperture": "arched", 37967 "configuration": [ 37968 _obj.width, 37969 _obj.height, 37970 _obj.depth, 37971 _obj.open_side, 37972 _obj.open_direction, 37973 _obj.open, 37974 _obj.finish 37975 ] 37976 }, 37977 //Half-round window 37978 "b83b277ce9f79359fe2fef82c8ab7e1f7f83495b": { 37979 "name": gettext("Half Round Window"), 37980 "level": -1, 37981 "renders": { 37982 "back": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/42/42919437e4daa8c94e51acdf9bfbd76080d323bd_00.back.png", 37983 "bottom": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/42/42919437e4daa8c94e51acdf9bfbd76080d323bd_00.bottom.png", 37984 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/42/42919437e4daa8c94e51acdf9bfbd76080d323bd_00.front.png", 37985 "left": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/42/42919437e4daa8c94e51acdf9bfbd76080d323bd_00.left.png", 37986 "perspective": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/42/42919437e4daa8c94e51acdf9bfbd76080d323bd_00.perspective.png", 37987 "right": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/42/42919437e4daa8c94e51acdf9bfbd76080d323bd_00.right.png", 37988 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/42/42919437e4daa8c94e51acdf9bfbd76080d323bd_00.top.png" 37989 }, 37990 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/42/42919437e4daa8c94e51acdf9bfbd76080d323bd_00.perspective.png"], 37991 "type": "Window", 37992 "aperture": "half-round", 37993 "configuration": [ 37994 _obj.width, 37995 _obj.height, 37996 _obj.depth, 37997 _obj.finish 37998 ] 37999 }, 38000 //Round window 38001 "2bf5ae66583be1729caabb74f64f44fe5057c0df": { 38002 "name": gettext("Round Window"), 38003 "level": -1, 38004 "renders": { 38005 "back": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/5f/5ff1e764
380056a74fcfc81695a682db7a60c2577a6e8_00.back.png", 38006 "bottom": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/5f/5ff1e7646a74fcfc81695a682db7a60c2577a6e8_00.bottom.png", 38007 "front": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/5f/5ff1e7646a74fcfc81695a682db7a60c2577a6e8_00.front.png", 38008 "left": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/5f/5ff1e7646a74fcfc81695a682db7a60c2577a6e8_00.left.png", 38009 "perspective": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/5f/5ff1e7646a74fcfc81695a682db7a60c2577a6e8_00.perspective.png", 38010 "right": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/5f/5ff1e7646a74fcfc81695a682db7a60c2577a6e8_00.right.png", 38011 "top": "https://fp-media-img-cdn.floorplanner.com/cdb/renders/5f/5ff1e7646a74fcfc81695a682db7a60c2577a6e8_00.top.png" 38012 }, 38013 "render_path": ["https://fp-media-img-cdn.floorplanner.com/cdb/renders/5f/5ff1e7646a74fcfc81695a682db7a60c2577a6e8_00.perspective.png"], 38014 "type": "Window", 38015 "aperture": "round", 38016 "configuration": [ 38017 _obj.width, 38018 _obj.height, 38019 _obj.depth, 38020 _obj.finish 38021 ] 38022 }, 38023 //molding 38024 "c903ee5c4d0d7c194964a77e8412bcbdb2fba8e6": { 38025 "name": "Molding", 38026 "level": -1, 38027 "renders": { 38028 "back": "", 38029 "bottom": "", 38030 "front": "", 38031 "left": "", 38032 "perspective": "", 38033 "right": "", 38034 "top": "" 38035 }, 38036 "render_path": [], 38037 "type": "Window", 38038 "aperture": "rectangle", 38039 "configuration": [ 38040 { 38041 "name":"width", 38042 "text":"Width", 38043 "type":"length", 38044 "detail":0, 38045 "default":100, 38046 "minimum":0, 38047 "maximum":10000 38048 }, 38049 { 38050 "name":"cut_angle_l", 38051 "text":"Left cut angle", 38052 "type":"angle", 38053 "detail":99, 38054 "default":90, 38055 "minimum":0, 38056 "maximum":180 38057 }, 38058 { 38059 "name":"cut_angle_r", 38060 "text":"Right cut angle", 38061 "type":"angle", 38062 "detail":99, 38063 "default":90, 38064 "minimum":0, 38065 "maximum":180 38066 }, 38067 { 38068 "name":"profile", 38069 "text":"Profile", 38070 "type":"enumeration", 38071 "detail":0, 38072 "default":"simple", 38073 "values":[["cove","Cove"], ["ogee", "Ogee"]] 38074 }, 38075 { 38076 "name":"finish", 38077 "text":"Colour", 38078 "type":"material", 38079 "detail":0, 38080 "default":"white" 38081 } 38082 ] 38083 }, 38084 //molding 38085 "86403415a0c5756ba1f07997805256a1a88b8268": { 38086 "name": "Picture Rail", 38087 "level": -1, 38088 "renders": { 38089 "back": "", 38090 "bottom": "", 38091 "front": "", 38092 "left": "", 38093 "perspective": "", 38094 "right": "", 38095 "top": "" 38096 }, 38097 "render_path": [], 38098 "type": "Molding", 38099 "aperture": "rectangle", 38100 "configuration": [ 38101 { 38102 "name":"width", 38103 "text":"Width", 38104 "type":"length", 38105 "detail":0, 38106 "default":100, 38107 "minimum":0, 38108 "maximum":10000 38109 }, 38110 { 38111 "name":"cut_angle_l", 38112 "text":"Left cut angle", 38113 "type":"angle", 38114 "detail":99, 38115 "default":90, 38116 "minimum":0, 38117 "maximum":180 38118 }, 38119 { 38120 "name":"cut_angle_r", 38121 "text":"Right cut angle", 38122 "type":"angle", 38123 "detail":99, 38124 "default":90, 38125 "minimum":0, 38126 "maximum":180 38127 }, 38128 { 38129 "name":"finish", 38130 "text":"Colour", 38131 "type":"material", 38132 "detail":0, 38133 "default":"white" 38134 } 38135 ] 38136 }, 38137 //molding 38138 "9da8ad612b335b4a8dbcb8c2e4b58bd68b2eb4b2": { 38139 "name": "Dado Rail", 38140 "level": -1, 38141 "renders": { 38142 "back": "", 38143 "bottom": "", 38144 "front": "", 38145 "left": "", 38146 "perspective": "", 38147 "right": "", 38148 "top": "" 38149 }, 38150 "render_path": [], 38151 "type": "MoldingS", 38152 "aperture": "rectangle", 38153 "configuration": [ 38154 { 38155 "name":"width", 38156 "text":"Width", 38157 "type":"length", 38158 "detail":0, 38159 "default":100, 38160 "minimum":0, 38161 "maximum":10000 38162 }, 38163 { 38164 "name":"cut_angle_l", 38165 "text":"Left cut angle", 38166 "type":"angle", 38167 "detail":99, 38168 "default":90, 38169 "minimum":0, 38170 "maximum":180 38171 }, 38172 { 38173 "name":"cut_angle_r", 38174 "text":"Right cut angle", 38175 "type":"angle", 38176 "detail":99, 38177 "default":90, 38178 "minimum":0, 38179 "maximum":180 38180 }, 38181 { 38182 "name":"finish", 38183 "text":"Colour", 38184 "type":"material", 38185 "detail":0, 38186 "default":"white" 38187 } 38188 ] 38189 }, 38190 //molding 38191 "b8aa8b9573ed536e0bbbc820255f46ea07b67933": { 38192 "name": "Skirting Board", 38193 "level": -1, 38194 "renders": { 38195 "back": "", 38196 "bottom": "", 38197 "front": "", 38198 "left": "", 38199 "perspective": "", 38200 "right": "", 38201 "top": "" 38202 }, 38203 "render_path": [], 38204 "type": "Molding", 38205 "aperture": "rectangle", 38206 "configuration": [ 38207 { 38208 "name":"width", 38209 "text":"Width", 38210 "type":"length", 38211 "detail":0, 38212 "default":100, 38213 "minimum":0, 38214 "maximum":10000 38215 }, 38216 { 38217 "name":"cut_angle_l", 38218 "text":"Left cut angle", 38219 "type":"angle", 38220 "detail":99, 38221 "default":90, 38222 "minimum":0, 38223 "maximum":180 38224 }, 38225 { 38226 "name":"cut_angle_r", 38227 "text":"Right cut angle", 38228 "type":"angle", 38229 "detail":99, 38230 "default":90, 38231 "minimum":0, 38232 "maximum":180 38233 }, 38234 { 38235 "name":"profile", 38236 "text":"Profile", 38237 "type":"enumeration", 38238 "detail":0, 38239 "default":"square", 38240 "values":[["square","Square Edge"], ["bevelled", "Bevelled"], ["bullnose", "Bullnose"], ["victorian", "Victorian"]] 38241 }, 38242 { 38243 "name":"finish", 38244 "text":"Colour", 38245 "type":"material", 38246 "detail":0, 38247 "default":"white" 38248 } 38249 ] 38250 } 38251}; 38252})(); 38253/*globals */ 38254/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 38255 sloppy: true, white: true */ 38256// http://mydeco.com/rooms/planner/ 38257var demoRoom1 = 38258{ 38259 "category": "", 38260 "view_count": 0, 38261 "original_image": null, 38262 "last_updated": "2009-07-23 16:22:27", 38263 "name": "A Room", 38264 "author": "mydeco", 38265 "rating": "0", 38266 "image": "http://cdn.mydeco.com/media/data/rooms/images/408759.jpeg", 38267 "children": [], 38268 "background_image": null, 38269 "original_author": null, 38270 // original value is 408759 38271 "id": 0, 38272 "thumbnail": "http://cdn.mydeco.com/media/data/rooms/images/_thumbs/408759.jpeg_169x127_q85.jpg", 38273 "comment_count": 0, 38274 "products": ["0108bfd5b4a7971afc69ad6d97c75e3ff0fbb5a8", "000c3ff0e44a2ace51095c7084bc6f7243e5d35e", "2c87ac734f537b095acb386a264a0f16238994f8", "4571adbcf5fbe5cd8261b0f0025407b3c4e46cd0", "5b0eed690b9d285448591c6becd8e2a52cf69aeb", "743129b2201d36c493818831460f561b44237e33", "752ff2646297f0c52fdf3925b6e5ba93e0724085", "c1b1f9d7ee04fe16e4c786a0180b8e63f6caa4a8", "cf2e4cf4cc979fc09e88b6842f28867de707f91a", "cf5abb1ea271b0ac4c7789f19a853eb13072470a"], 38275 "model": { 38276 "geometry": { 38277 "points": [[-245.02500000000001, 255.75, 0], [335.6875, 255.77500000000001, 0], [335.6875, 255.77500000000001, 310], [-245.02500000000001, 255.75, 310], [-117.98674984868759, 255.75546906785999, 77.087000000000003], [-30.550749929712353, 255.75923323687303, 77.087000000000003], [-30.550749929712353, 255.75923323687303, 232.91300000000001], [-117.98674984868759, 255.75546906785999, 232.91300000000001], [104.81324994451236, 255.76506073349293, 77.087000000000003], [192.24924986348762, 255.76882490250597, 77.087000000000003], [192.24924986348762, 255.76882490250597, 232.91300000000001], [104.81324994451236, 255.76506073349293, 232.91300000000001], [335.6875, 255.77500000000001, 0], [335.71249999999998, -277.27499999999998, 0], [335.71249999999998, -277.27499999999998, 310], [335.6875, 255.77500000000001, 310], [335.71249999999998, -277.27499999999998, 0], [-245, -277.30000000000001, 0], [-245, -277.30000000000001, 310], [335.71249999999998, -277.27499999999998, 310], [-245, -277.30000000000001, 0], [-245.02500000000001, 255.75, 0], [-245.02500000000001, 255.75, 310], [-245, -277.30000000000001, 310], [-244.99793630991635, 141.79800004839342, 1], [-245.00206369008257, 229.80199995160655, 1], [-245.00206369008257, 229.80199995160655, 207], [-244.99793630991635, 141.79800004839342, 207], [-244.99794962949289, -55.617999951918897, 77.087000000000003], [-245.00205037050708, 31.817999951918907, 77.087000000000003], [-245.00205037050708, 31.817999951918907, 232.91300000000001], [-244.99794962949289, -55.617999951918897, 232.91300000000001], [-245.02500000000001, 255.75, 0], [-245, -277.30000000000001, 0], [335.71249999999998, -277.27499999999998, 0], [335.6875, 255.77500000000001, 0], [-245.02500000000001, 255.75, 0], [-245.02500000000001, 255.75, 310], [335.6875, 255.77500000000001, 310], [335.71249999999998, -277.27499999999998, 310], [-245, -277.30000000000001, 310], [-245.02500000000001, 255.75, 310]], 38278 "polygons": [{ 38279 "indices": [0, 1, 2, 3, 0, 4, 5, 6, 7, 4, 8, 9, 10, 11], 38280 "material": "materialp578", 38281 "ID": "wall0" 38282 }, 38283 { 38284 "indices": [12, 13, 14, 15], 38285 "material": "materialp233", 38286 "ID": "wall1" 38287 }, 38288 { 38289 "indices": [16, 17, 18, 19], 38290 "material": "materialp578", 38291 "ID": "wall2" 38292 }, 38293 { 38294 "indices": [20, 21, 22, 23, 20, 24, 25, 26, 27, 24, 28, 29, 30, 31], 38295 "material": "materialp578", 38296 "ID": "wall3" 38297 }, 38298 { 38299 "indices": [32, 33, 34, 35, 36], 38300 "material": "material105", 38301 "ID": "Floor" 38302 }, 38303 { 38304 "indices": [37, 38, 39, 40, 41], 38305 "material": "materialpsdpfbf7ee", 38306 "ID": "Ceiling" 38307 }] 38308 }, 38309 "camera": { 38310 "position": [190.44864468037053, -70.11369249303513, 112.833061446], 38311 "focal_point": [-88.03980515482976, 85.10937790691267, 112.833061446] 38312 }, 38313 "lights": [{ 38314 "position": [10, 0, 500], 38315 "direction": [0, 0, -500], 38316 "type": "sun" 38317 }], 38318 "materials": [{ 38319 "shader": "combi_material", 38320 "diffuse": [1, 0.97999999999999998, 0.96799999999999997], 38321 "use_diffuse_map": false, 38322 "ambient": [0, 0, 0], 38323 "shiny": 0, 38324 "ID": "materialp578" 38325 }, 38326 { 38327 "shader": "combi_material", 38328 "diffuse": [0.98399999999999999, 0.84699999999999998, 0.439], 38329 "use_diffuse_map": false, 38330 "ambient": [0, 0, 0], 38331 "shiny": 0, 38332 "ID": "materialp233" 38333 }, 38334 { 38335 "use_ambientmap": "on", 38336 "diffuse_texture": "floor_and_wall/original/T_al_Ukr0008WideBoardAsClas.jpg", 38337 "ambient_texture": "floor_and_wall/original/T_al_Ukr0008WideBoardAsClas.jpg", 38338 "shader": "combi_material", 38339 "diffuse": [1, 1, 1], 38340 "texture_width": 150, 38341 "ambient": [0.050000000000000003, 0.050000000000000003, 0.050000000000000003], 38342 "use_diffusemap": "on", 38343 "shiny": 0.20000000000000001, 38344 "ID": "material105" 38345 }, 38346 { 38347 "shader": "combi_material", 38348 "diffuse": [0.98399999999999999, 0.96899999999999997, 0.93300000000000005], 38349 "use_diffuse_map": false, 38350 "ambient": [0, 0, 0], 38351 "shiny": 0, 38352 "ID": "materialpsdpfbf7ee" 38353 }], 38354 "room_id": 0, 38355 "components": [{ 38356 "category": null, 38357 "component_id": "000c3ff0e44a2ace51095c7084bc6f7243e5d35e", 38358 "hingeSide": null, 38359 "ID": "4", 38360 "bounding_box": [ 38361 [-73.650000000000006, - 99.25, 0.0], 38362 [73.650000000000006, 99.25, 119.0] 38363 ], 38364 "parentID": null, 38365 "position": [209.09999999999999, -25.550000000000001, 0], 38366 "model": "models/00/000c3ff0e44a2ace51095c7084bc6f7243e5d35e_03.mi", 38367 "type": "Component", 38368 "rotation": [0, 0, 89.908747671100002], 38369 "openDirection": null 38370 }, 38371 { 38372 "category": null, 38373 "component_id": "2c87ac734f537b095acb386a264a0f16238994f8", 38374 "hingeSide": null, 38375 "ID": "5", 38376 "bounding_box": [[-74.703000000000003, 25.449000000000002, 0], [74.703000000000003, -25.449000000000002, 62.792000000000002]], 38377 "parentID": null, 38378 "position": [303.25, 156.44999999999999, 0], 38379 "model": "models/2c/2c87ac734f537b095acb386a264a0f16238994f8_01.mi", 38380 "type": "Component", 38381 "rotation": [0, 0, 89.908747671100002], 38382 "openDirection": null 38383 }, 38384 { 38385 "category": null, 38386 "component_id": "4571adbcf5fbe5cd8261b0f0025407b3c4e46cd0", 38387 "hingeSide": null, 38388 "ID": "6", 38389 "bounding_box": [[-35, 35, 0], [35, -35, 70]], 38390 "parentID": null, 38391 "position": [293.69999999999999, 185.30000000000001, 63], 38392 "model": "models/45/4571adbcf5fbe5cd8261b0f0025407b3c4e46cd0_02.mi", 38393 "type": "Component", 38394 "rotation": [0, 0, 89.908747671078501], 38395 "openDirection": null 38396 }, 38397 { 38398 "category": "Door", 38399 "component_id": "e79b4c034a30d52dac5d8b2cd460a2dddf711874", 38400 "hingeSide": "right", 38401 "ID": "8", 38402 "bounding_box": [[-45.009, 103.952, -14.529], [44.994999999999997, -104.048, 1.2330000000000001]], 38403 "parentID": "3", 38404 "position": [-245, 185.80000000000001, 104], 38405 "model": "models/e7/e79b4c034a30d52dac5d8b2cd460a2dddf711874_02.mi", 38406 "type": "ArchitecturalComponent", 38407 "rotation": [-89.954373835539243, 0, 269.99731283300002], 38408 "openDirection": "in" 38409 }, 38410 { 38411 "category": null, 38412 "component_id": "5b0eed690b9d285448591c6becd8e2a52cf69aeb", 38413 "hingeSide": null, 38414 "ID": "9", 38415 "bounding_box": [[-17, 17, 0], [17, -17, 5]], 38416 "parentID": null, 38417 "position": [306.77692307699999, 119.97307692299999, 63], 38418 "model": "models/5b/5b0eed690b9d285448591c6becd8e2a52cf69aeb_02.mi", 38419 "type": "Component", 38420 "rotation": [0, 0, 0], 38421 "openDirection": null 38422 }, 38423 { 38424 "category": null, 38425 "component_id": "cf2e4cf4cc979fc09e88b6842f28867de707f91a", 38426 "hingeSide": null, 38427 "ID": "10", 38428 "bounding_box": [[-11.089, 11.109, 0.050000000000000003], [11.31, -11.291, 33.049999999999997]], 38429 "parentID": null, 38430 "position": [306.89999999999998, 119.7, 63], 38431 "model": "models/cf/cf2e4cf4cc979fc09e88b6842f28867de707f91a_02.mi", 38432 "type": "Component", 38433 "rotation": [0, 0, 0], 38434 "openDirection": null 38435 }, 38436 { 38437 "category": "Window", 38438 "component_id": "97e2f2b80d14b085d626c9a3c1b77a8a86f557b7", 38439 "hingeSide": null, 38440 "ID": "12",
38441 "bounding_box": [[-44.865000000000002, 78.853999999999999, -6.2859999999999996], [44.570999999999998, -78.971999999999994, 0.84099999999999997]], 38442 "parentID": "3", 38443 "position": [-245, -11.9, 155], 38444 "model": "models/97/97e2f2b80d14b085d626c9a3c1b77a8a86f557b7_02.mi", 38445 "type": "ArchitecturalComponent", 38446 "rotation": [-89.954373835539243, 0, 269.99731283300002], 38447 "openDirection": null 38448 }, 38449 { 38450 "category": "Window", 38451 "component_id": "97e2f2b80d14b085d626c9a3c1b77a8a86f557b7", 38452 "hingeSide": null, 38453 "ID": "13", 38454 "bounding_box": [[-44.865000000000002, 78.853999999999999, -6.2859999999999996], [44.570999999999998, -78.971999999999994, 0.84099999999999997]], 38455 "parentID": "0", 38456 "position": [-74.268749889199995, 255.757351153, 155], 38457 "model": "models/97/97e2f2b80d14b085d626c9a3c1b77a8a86f557b7_02.mi", 38458 "type": "ArchitecturalComponent", 38459 "rotation": [-89.954373835539243, 0, 359.99753338400001], 38460 "openDirection": null 38461 }, 38462 { 38463 "category": "Window", 38464 "component_id": "97e2f2b80d14b085d626c9a3c1b77a8a86f557b7", 38465 "hingeSide": null, 38466 "ID": "14", 38467 "bounding_box": [[-44.865000000000002, 78.853999999999999, -6.2859999999999996], [44.570999999999998, -78.971999999999994, 0.84099999999999997]], 38468 "parentID": "0", 38469 "position": [148.53124990399999, 255.76694281799999, 155], 38470 "model": "models/97/97e2f2b80d14b085d626c9a3c1b77a8a86f557b7_02.mi", 38471 "type": "ArchitecturalComponent", 38472 "rotation": [-89.954373835539243, 0, 359.99753338400001], 38473 "openDirection": null 38474 }, 38475 { 38476 "category": null, 38477 "component_id": "752ff2646297f0c52fdf3925b6e5ba93e0724085", 38478 "hingeSide": null, 38479 "ID": "15", 38480 "bounding_box": [[-35, 35, 0], [35, -35, 0.20000000000000001]], 38481 "parentID": "1", 38482 "position": [335.69999999999999, -19.300000000000001, 155], 38483 "model": "models/75/752ff2646297f0c52fdf3925b6e5ba93e0724085_01.mi", 38484 "type": "Component", 38485 "rotation": [-89.954373835539243, 0, 89.997312832800006], 38486 "openDirection": null 38487 }, 38488 { 38489 "category": null, 38490 "component_id": "c1b1f9d7ee04fe16e4c786a0180b8e63f6caa4a8", 38491 "hingeSide": null, 38492 "ID": "16", 38493 "bounding_box": [[-37.5, 82.5, 0], [37.5, -82.5, 1.3999999999999999]], 38494 "parentID": "2", 38495 "position": [42.8812500023, -277.28760655000002, 155], 38496 "model": "models/c1/c1b1f9d7ee04fe16e4c786a0180b8e63f6caa4a8_03.mi", 38497 "type": "Component", 38498 "rotation": [-89.954373835539243, 0, 179.99753338400001], 38499 "openDirection": null 38500 }, 38501 { 38502 "category": null, 38503 "component_id": "cf5abb1ea271b0ac4c7789f19a853eb13072470a", 38504 "hingeSide": null, 38505 "ID": "17", 38506 "bounding_box": [[-35.317999999999998, 10.946, -0.126], [35.432000000000002, -9.1509999999999998, 59.695]], 38507 "parentID": null, 38508 "position": [41.299999999999997, -11.5, 36], 38509 "model": "models/cf/cf5abb1ea271b0ac4c7789f19a853eb13072470a_02.mi", 38510 "type": "Component", 38511 "rotation": [0, 0, 269.81749534199997], 38512 "openDirection": null 38513 }, 38514 { 38515 "category": null, 38516 "component_id": "743129b2201d36c493818831460f561b44237e33", 38517 "hingeSide": null, 38518 "ID": "18", 38519 "bounding_box": [[-90, 28, 0], [90, -28, 36]], 38520 "parentID": null, 38521 "position": [41.299999999999997, -18.449999999999999, 0], 38522 "model": "models/74/743129b2201d36c493818831460f561b44237e33_03.mi", 38523 "type": "Component", 38524 "rotation": [0, 0, -90], 38525 "openDirection": null 38526 }, 38527 { 38528 "category": null, 38529 "component_id": "0108bfd5b4a7971afc69ad6d97c75e3ff0fbb5a8", 38530 "hingeSide": null, 38531 "ID": "19", 38532 "bounding_box": [[-69, 31.305, 0], [69, -31.305, 220]], 38533 "parentID": null, 38534 "position": [230.75, -236.90000000000001, 0], 38535 "model": "models/01/0108bfd5b4a7971afc69ad6d97c75e3ff0fbb5a8_05.mi", 38536 "type": "Component", 38537 "rotation": [0, 0, 179.86312150699999], 38538 "openDirection": null 38539 }] 38540 }, 38541 "type": "3d", 38542 "public": 0, 38543 "description": "" 38544}; 38545 38546// This is the default room model used when 38547// opening roomstyler.com/3dplanner (used in _view_room_management.erb) 38548var emptyRoom = 38549{ 38550 "category": "", 38551 "original_image": null, 38552 "name": "", 38553 "rating": "0", 38554 "children": [], 38555 "background_image": null, 38556 "author": null, 38557 "original_author": null, 38558 "comment_count": 0, 38559 "products": [], 38560 "image": "", 38561 "thumbnail": "", 38562 "model": { 38563 "camera": { 38564 "position": [0, 0, 112.833061446], 38565 "focal_point": [-278.48980515482975, 155.21937790691265, 112.833061446] 38566 }, 38567 "lights": [{ 38568 "position": [10, 0, 500], 38569 "direction": [0, 0, - 500], 38570 "type": "sun" 38571 }], 38572 "materials": [{ 38573 "shader": "combi_material", 38574 "diffuse": [1, 0.98, 0.968], 38575 "use_diffuse_map": false, 38576 "ambient": [0, 0, 0], 38577 "shiny": 0, 38578 "ID": "default" 38579 }], 38580 "room_id": 0, 38581 "components": [], 38582 "plan": { 38583 "height": 310, 38584 "points": [], 38585 "areas": {}, 38586 "walls": {} 38587 }, 38588 "version": 2 38589 }, 38590 "type": "3d", 38591 "public": 0, 38592 "description": "" 38593}; 38594/*globals myDeco, jQuery, window */ 38595/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 38596 sloppy: true, white: true */ 38597 38598/** 38599 * @fileOverview 38600 * Units selector jquery plugin, special for austin, uses mydeco namespace a lot 38601 */ 38602jQuery(function() { 38603 'use strict'; 38604 38605 var $ = jQuery, 38606 units = $.cookie('units'), 38607 updateUnits = function(holder) { 38608 holder 38609 .find('.unit').text(units).end() 38610 .find('.button') 38611 .removeClass('active') 38612 .filter('.' + units) 38613 .addClass('active'); 38614 }; 38615
38616 // if valid units set in cookie then override default setting 38617 // otherwise use default 38618 if (units && myDeco.settings.UNITS_LIST[units]) { 38619 myDeco.settings.UNITS = units; 38620 } else { 38621 units = myDeco.settings.UNITS; 38622 } 38623 38624 $.fn.unitsSelector = function() { 38625 this.each(function() { 38626 var unit, 38627 holder = $(this).append($('<span class="unit">').text(units)), 38628 options = $('<span class="holder" />').appendTo(holder); 38629 38630 for (unit in myDeco.settings.UNITS_LIST) { 38631 var name = myDeco.settings.UNITS_LIST[unit][1] === 'Feet' ? 'ft' : 'mtr'; 38632 38633 options.append($('<a class="button" href="" />') 38634 .addClass(unit) 38635 .toggleClass('active', unit === units) 38636 .append($('<span class="u" />') 38637 .data('unit', unit) 38638 .text(holder.hasClass('fullname') ? 38639 name : unit))); 38640 } 38641 38642 myDeco.body.bind('unitsChanged', function() { 38643 updateUnits(holder); 38644 }); 38645 }) 38646 .click(function(e) { 38647 e.stop(); 38648 38649 var unit = $(e.target), 38650 holder; 38651 38652 if (unit.hasClass('u')) { 38653 38654 holder = unit.closest('.units'); 38655 units = myDeco.settings.UNITS = unit.data('unit'); 38656 38657 $.cookie('units', units, { 38658 expires: 365, 38659 domain: window.location.hostname, 38660 path: window.location.pathname 38661 }); 38662 38663 myDeco.body.trigger('unitsChanged', [units]); 38664 } 38665 }); 38666 return this; 38667 }; 38668}); 38669/*globals $, myDeco, AppView, window, buildThumbnailLink, tmpl */ 38670/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 38671 sloppy: true, white: true */ 38672/** 38673 * @fileOverview 38674 * Build view 38675 */ 38676var BuildView = AppView.extend({ 38677 38678 /** 38679 * View type 38680 * @type {String} 38681 * @public 38682 * @static 38683 */ 38684 $type: 'build', 38685 38686 /** 38687 * Settings 38688 * @type {Object} 38689 */ 38690 settings: { 38691 // available 38692 types: ['doors', 'windows'], 38693 38694 // prefix for images 38695 prefix: '/assets/austin/', 38696 38697 listingThumbnail: {width: 70, height: 70, maxWidth: 110, maxHeight: 110} 38698 }, 38699 38700 /** 38701 * View init 38702 * @param {Object} options 38703 * @public 38704 */ 38705 init: function(options) { 38706 this._super(options); 38707 38708 var self = this, ids = [], t, tlen, type, 38709 typesCreation = function(mtype, m) { 38710 ids.push(mtype.component_id); 38711 38712 return { 38713 type: mtype.type, 38714 name: mtype.name, 38715 sizes: $.map(mtype.sizes, function(size) { 38716 return { 38717 width: size[0], 38718 height: size[1], 38719 subtype: size[2] 38720 }; 38721 }) 38722 }; 38723 }, 38724 masking = this.canvasElement.find('.setscale a') 38725 .click(function(e) { 38726 e.stop(); 38727 myDeco.body.trigger('setscaleStart'); 38728 }) 38729 .closest('.masking'); 38730 38731 this.canvasElement 38732 .find('.setscale a').click(function(e) { 38733 e.stop(); 38734 myDeco.body.trigger('setscaleStart'); 38735 }).end() 38736 .find('.adjusting').delegate('li', 'click', function(e) { 38737 e.stop(); 38738 myDeco.body.trigger('backgroundChange', 38739 [$(this).attr('className'), e.ctrlKey ? 10 : e.shiftKey ? 0.1 : 1]); 38740 }).end() 38741 .find('.build-walls-container').html( 38742 tmpl('view-build-af-template', { 38743 type: 'shapes', 38744 types: $.keys(this._model.settings.shapes) 38745 }) 38746 ) 38747 .find('.own-walls a').click(function(e) { 38748 e.stop(); 38749 myDeco.body.trigger('drawStart'); 38750 }); 38751 38752 $('#view-units').find('.draw-walls').click(function(e) { 38753 e.stop(); 38754 myDeco.body.trigger('drawStart'); 38755 }); 38756 38757 // windows and doors 38758 for (t = 0, tlen = this.settings.types.length; t < tlen; t++) { 38759 type = this.settings.types[t]; 38760 var items = this._model.settings[type], custom_list; 38761 if (myDeco.settings.WL && (custom_list = myDeco.settings.WL[type.toUpperCase() + '_LIST'])) { 38762 items = $.grep(items, function(item) { return custom_list.indexOf(item.component_id) > -1 }); 38763 this._model.settings[type] = items; 38764 } 38765 this.canvasElement.find('.build-' + type + '-container').html( 38766 tmpl('view-build-af-template', { 38767 type: type, 38768 types: $.map(items, typesCreation) 38769 }) 38770 ); 38771 } 38772 38773 // force loading components to local cache 38774 myDeco.App.ws.proxy.fetch('component', ids, $.noop, true); 38775 38776 this.canvasElement.find('.build-variants').delegate('a', 'click', 38777 function(e) { 38778 e.preventDefault(); 38779 38780 var element = $(this), 38781 root = element.closest('.build-variants'), 38782 sections = root.children().removeClass('selected'), 38783 section = element.closest('li').addClass('selected'), 38784 i = sections.index(section.get(0)); 38785 38786 if (i === 0) { 38787 root.siblings('.dimensions').show().children('ul').show(); 38788 } else { 38789 root.siblings('.dimensions').show().children('ul').hide() 38790 .eq(i - 1).show(); 38791 } 38792 } 38793 ); 38794 38795 this.canvasElement.delegate('.dimensions a', { 38796 click: function(e) { 38797 e.stop(); 38798 }, 38799 mousedown: function(e) { 38800 e.preventDefault(); 38801 38802 var path = $(this).data('path').split(':'), 38803 fireEvent = function(data) { 38804 myDeco.body.trigger('entity-drag-started', [{ 38805 entity: data, 38806 kind: 'element', 38807 visual: self.generateElementForDrag(data) 38808 }, e]); 38809 }; 38810 38811 switch (path[0]) { 38812 case 'doors': 38813 case 'windows': 38814 myDeco.App.ws.proxy.fetch('component', 38815 self._model.settings[path[0]][path[1]].component_id, 38816 function(data) { 38817 var size = self._model.settings[path[0]][path[1]].sizes[path[2]]; 38818 data = $.values(data).shift(); 38819 data.configuration.width = size[0]; 38820 data.configuration.height = size[1]; 38821 fireEvent(data); 38822 }); 38823 break; 38824 38825 default: 38826 fireEvent({ 38827 type: 'room', 38828 subtype: path[1], 38829 width: self._model.settings.shapes[path[1]].width, 38830 depth: self._model.settings.shapes[path[1]].height 38831 }); 38832 } 38833 } 38834 }); 38835 38836 myDeco.body 38837 .bind('view-' + this.$type + '-upload-response', 38838 this.uploadResponse.bind(this)) 38839 .bind('view-' + this.$type + '-upload',
38840 this.showFloorUpload.bind(this)) 38841 .bind('unitsChanged', function() { 38842 self.canvasElement.find('.component-dimensions') 38843 .each(function() { 38844 var element = $(this); 38845 element.html(element.data('dimensions') 38846 .split(':').toUnits()); 38847 }); 38848 }) 38849 .bind('roomLoaded', function() { 38850 masking.toggle(self._model._masking); 38851 }); 38852 38853 this.canvasElement.find('.build-floor-container .button.upload') 38854 .click(function(e) { 38855 e.stop(); 38856 myDeco.body.trigger('view-' + self.$type + '-upload'); 38857 }); 38858 }, 38859 38860 /** 38861 * Shows upload popin 38862 * @param {Event} e 38863 */ 38864 showFloorUpload: function(e) { 38865 if (e) { 38866 e.stop(); 38867 } 38868 38869 var self = this; 38870 38871 myDeco.user.loginRequired(function() { 38872 self._model.checkFloorPermissions(true, function() { 38873 myDeco.popin.open({ 38874 url: '#floor-plan-popin', 38875 width: 595, 38876 afterOpen: function (content) { 38877 myDeco.fileInputs.init(content); 38878 myDeco.buttons.initSubmitButtons( 38879 content.find('input:submit')); 38880 content.find('form').submit(function (e) { 38881 myDeco.buttons.disable($(e.target)); 38882 }).find('.form-submit-button').click(function (e) { 38883 e.stop(); 38884 $(e.target).closest('form').submit(); 38885 }); 38886 } 38887 }) 38888 }) 38889 }); 38890 }, 38891 38892 /** 38893 * Generate visual representation of given build entity for drag-n-drop 38894 * @param {Material} component Floor shape/window/door to render 38895 * @public 38896 */ 38897 generateElementForDrag: function(component) { 38898 var result; 38899 38900 if (component.type === 'ArchitecturalComponent') { 38901 result = $('<div id="new-component">' + 38902 '<img src="' + buildThumbnailLink(component.componentData['3d'].render_top, 38903 (component.model === 'doorway_door' ? null : this.settings.listingThumbnail)) + 38904 '" class="original" /></div>'); 38905 } else { 38906 result = $('<div id="new-room"><div class="original ' + 38907 component.subtype + ' room-icon"></div></div>'); 38908 } 38909 38910 return result; 38911 }, 38912 38913 /** 38914 * Listener for iframe response from WS 38915 * @param {Event} e 38916 * @param {Object} object Response from WS 38917 */ 38918 uploadResponse: function(e, object) { 38919 var form = myDeco.buttons.enable(myDeco.popin.content().find('form')), 38920 name, message, field; 38921 38922 if (object && object.error) { 38923 form.find('.error').hide(); 38924 38925 for (name in object.error.message) { 38926 message = object.error.message[name][0]; 38927 field = form.find('.error.' + (name === '__all__' ? 'image' : name)); 38928 if (message && !field.is(':visible')) { 38929 field.text(message).show(); 38930 } 38931 } 38932 38933 myDeco.popin.resize(); 38934 } else { 38935 // assign back to me 38936 if (form.find('input:checked').size() !== 0) { 38937 myDeco.popin.close(); 38938 myDeco.App.model.loadRoom(object.location); 38939 } else { 38940 myDeco.popin.open({ 38941 url: '#floor-plan-popin-success', 38942 width: 595, 38943 closeIn: 30, 38944 afterOpen: function(div) { 38945 div.find('.note a').one('click', function(e) { 38946 e.preventDefault(); 38947 myDeco.body.trigger('roomOpenRequest'); 38948 }); 38949 } 38950 }); 38951 } 38952 } 38953 } 38954}); 38955 38956/** 38957 * Callback for WS response from iframe proxy 38958 * @param {Object} object 38959 */ 38960function FPEfloorUploadResponse(object) { 38961 myDeco.body.trigger('view-build-upload-response', [object]); 38962} 38963 38964myDeco.App.views.push(BuildView); 38965/*globals myDeco, $, AppView, window, location, document, emptyRoom, tmpl */ 38966/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 38967 sloppy: true, white: true */ 38968/** 38969 * @fileOverview 38970 * Menu view 38971 */ 38972var MenuView = AppView.extend({ 38973 38974 /** 38975 * View type 38976 * @type {String} 38977 * @public 38978 * @static 38979 */ 38980 $type: 'menu', 38981 38982 /**
38983 * Used controls cache 38984 * @type {Object} 38985 */ 38986 _controls: {}, 38987 38988 /** 38989 * Help page 38990 * @type {jQset} 38991 */ 38992 _helpPage: $(), 38993 38994 /** 38995 * View init 38996 * @param {Object} options 38997 * @public 38998 */ 38999 init: function(options) { 39000 this._super(options); 39001 39002 var self = this, 39003 form = this.canvasElement.find('#search'), 39004 query = form.find('input[name=query]'), 39005 clear = form.find('.clear').hide().click(function(e) { 39006 e.stop(); 39007 clear.hide(); 39008 query.val(''); 39009 }); 39010 39011 this._controls.save = this.canvasElement.find('.save'); 39012 this._controls.hd = this.canvasElement.find('.save-hd'); 39013 this._controls.undo = this.canvasElement.find('.undo'); 39014 this._controls.redo = this.canvasElement.find('.redo'); 39015 this._controls.extra = this.canvasElement.find('.extra-buttons'); 39016 39017 this.canvasElement.find('#rm-invoker') 39018 .click(function(e) { 39019 e.stop(); 39020 myDeco.body.trigger('roomOpenRequest'); 39021 }); 39022 39023 // change/loading/saving 39024 myDeco.body 39025 .bind('roomLoaded roomSaved operationEnd roomLoadError', 39026 function(e, sameRoom) { 39027 switch (e.type) { 39028 case 'operationEnd': 39029 self._controls.undo.removeClass('disabled'); 39030 self._controls.redo.addClass('disabled'); 39031 break; 39032 39033 case 'roomLoaded': 39034 if (!sameRoom && self._model._ws_url) { 39035 self._controls.undo.addClass('disabled'); 39036 self._controls.redo.addClass('disabled'); 39037 } 39038 self._controls.extra.toggleClass('masking', 39039 self._model._masking); 39040 break; 39041 } 39042 39043 self._updateSaveButton(); 39044 } 39045 ) 39046 .bind('roomSaving', function() { 39047 self._controls.save.addClass('disabled'); 39048 }) 39049 .bind('roomSavingError', function() { 39050 self._updateSaveButton(); 39051 }) 39052 .bind('roomNewRequest', function() { 39053 self.showNewAction(); 39054 }) 39055 .bind('undo redo', function(e) { 39056 self._controls.undo.toggleClass('disabled', e.type === 'undo'); 39057 self._controls.redo.toggleClass('disabled', e.type !== 'undo'); 39058 self._updateSaveButton(); 39059 }) 39060 .bind('mydeco.login', this.updateUsername.bind(this)) 39061 .bind('actionRequest', function(e, action) { 39062 if (action[0] === 'help') { 39063 self.toggleShortcuts(); 39064 } 39065 }); 39066 39067 this._controls.undo.click(function(e) { 39068 e.stop(); 39069 if (self._controls.undo.hasClass('disabled')) { 39070 return; 39071 } 39072 39073 self._model.undo(); 39074 }); 39075 39076 this._controls.redo.click(function(e) { 39077 e.stop(); 39078 if (self._controls.redo.hasClass('disabled')) { 39079 return; 39080 } 39081 39082 self._model.redo(); 39083 }); 39084 39085 this.canvasElement.find('.menu-item').click(function(e) { 39086 e.preventDefault(); 39087 }) 39088 .filter('.file').mouseup(this.fileItem.bind(this)).end() 39089 .filter('.help').mouseup(this.helpItem.bind(this)).end() 39090 .filter('.profile, .newsfeed').mouseup(this.profileItem.bind(this)).end() 39091 .filter('.share').mouseup(function() { 39092 if (!self._model.isNew) { 39093 if (self._model._metadata['public']) { 39094 var url = myDeco.urls.ws.roomShare.replace("/0/", "/" + self._model._room_id + "/"); 39095 if (myDeco.settings.WL_PROFILE) { 39096 url += '?profile=' + myDeco.settings.WL_PROFILE; 39097 } 39098 myDeco.roomShare.share(url); 39099 } else { 39100 myDeco.popin.alert({ 39101 title: gettext('Room sharing'), 39102 message: gettext('Your design should be marked public.') 39103 }); 39104 } 39105 } else { 39106 myDeco.popin.alert({ 39107 title: gettext('Room sharing'), 39108 message: gettext('Please save your design before sharing.') 39109 }); 39110 } 39111 }); 39112 39113 // show new splash on startup if no parent action is given 39114 if (!myDeco.App.roomUrl && window.location.hash.indexOf('nosplash') === -1) { 39115 this.showNewAction(); 39116 } 39117 39118 form.submit(function(e) { 39119 e.stop(); 39120 39121 var tabs = $('#sidebar').find('.sidebar-buttons .tabs'); 39122 39123 if (tabs.find('.tab.decorate').hasClass('active')) { 39124 $('.sidebar-decorate-container form') 39125 .find('input').val(query.blur().val()).end() 39126 .submit(); 39127 } else { 39128 tabs.trigger('show', ['furnish']).end() 39129 .find('.sidebar-furnish-container form') 39130 .find('input').val(query.blur().val()).end() 39131 .submit(); 39132 } 39133 39134 // simulate mouseover to show clear button 39135 form.mouseover(); 39136 }).find('.submit').click(function(e) { 39137 e.stop(); 39138 form.submit(); 39139 }).end(); 39140 39141 form.hover(function(e) { 39142 clear.toggle(e.type === 'mouseenter' && query.val() !== ''); 39143 }); 39144 39145 var that = this; 39146 this._helpPage = $('#view-help').find('.close').click(function(){ 39147 that.toggleShortcuts(); 39148 }).end(); 39149 39150 this.updateUsername(); 39151 }, 39152 39153 /** 39154 * Update save button state method 39155 * @private 39156 */ 39157 _updateSaveButton: function() { 39158 this._controls.save.toggleClass('disabled', this._model.history.counter === 0); 39159 }, 39160 39161 /** 39162 * Process of file submenu items 39163 * @param {Event} e Click event 39164 */ 39165 fileItem: function(e) { 39166 var menu = $(e.target), 39167 saveCallback = function() { 39168 myDeco.body.trigger('roomSaveRequest', [{ 39169 forceNew: menu.hasClass('save-as') 39170 }
39170]); 39171 }; 39172 if (menu.closest('.disabled').size() === 0) { 39173 if (menu.hasClass('new')) { 39174 window.location.href = location.protocol + '//' + location.host + location.pathname; 39175 //this.showNewAction(); 39176 } else if (menu.hasClass('open')) { 39177 myDeco.body.trigger('roomOpenRequest'); 39178 } else if (menu.hasClass('save') || menu.hasClass('save-as')) { 39179 if (this._model._masking) { 39180 myDeco.popin.confirm({ 39181 message: gettext('Are you sure you want to send this floor-plan to the customer?'), 39182 yesButtonText: gettext('Confirm'), 39183 noButtonText: gettext('Not yet!'), 39184 yesButton: function(e) { 39185 e.stop(); 39186 saveCallback(); 39187 } 39188 }); 39189 } else { 39190 saveCallback(); 39191 } 39192 } else if (this._model._masking && menu.hasClass('reject')) { 39193 this.showRejectAction(); 39194 } 39195 } 39196 }, 39197 39198 /** 39199 * Support for help menu item 39200 * @param {Event} e Click event 39201 */ 39202 helpItem: function(e) { 39203 e.preventDefault(); 39204 switch ($.trim($(e.target).attr('class')).split(/\s+/).pop()) { 39205 case 'manual': 39206 var locale = myDeco.settings.I18N_LOCALE, 39207 wls = myDeco.settings.WL, 39208 manual_url = wls && (wls['MANUAL_URL_' + locale.toUpperCase()] || wls.MANUAL_URL); 39209 39210 // FIXME if we use a convention naming for manuals, we can make this logic a bit simpler 39211 if (!manual_url) { 39212 if (locale === 'de') { 39213 manual_url = "https://s3-eu-west-1.amazonaws.com/media.eu.mydeco3d.com/media/manual/manual-de-2014-v1.pdf"; 39214 } else { 39215 manual_url = "https://s3-eu-west-1.amazonaws.com/media.eu.mydeco3d.com/media/manual/manual-en-2013-v1.pdf"; 39216 } 39217 } 39218 window.open(manual_url); 39219 break; 39220 case 'users': 39221 window.open("https://roomstyler.com/forum/discussions/5083"); 39222 break; 39223 case 'forum': 39224 window.open("https://roomstyler.com/forum/"); 39225 break; 39226 case 'tutorial': 39227 window.open(myDeco.settings.WL && myDeco.settings.WL.WL_VIDEO_TUTORIAL_LINK ? 39228 myDeco.settings.WL.WL_VIDEO_TUTORIAL_LINK : myDeco.settings.AUSTIN_TUTORIAL_URL); 39229 break; 39230 case 'shortcuts': 39231 this.toggleShortcuts(); 39232 break; 39233 case 'forum': 39234 window.open(myDeco.settings.AUSTIN_FORUM_TOPIC_URL); 39235 break; 39236 case 'furniture': 39237 window.open(myDeco.settings.AUSTIN_REQUEST_FURNITURE_URL); 39238 break; 39239 case 'tips': 39240 this.showTips(); 39241 break; 39242 } 39243 }, 39244 39245 /** 39246 * Support for profile submenu 39247 * @param {Event} e Click event 39248 */ 39249 profileItem: function(e) { 39250 var elem = $(e.target); 39251 var isRoomstyler = location.host.indexOf('roomstyler') != -1; 39252 if(isRoomstyler) { 39253 if (elem.hasClass('profile')) { 39254 window.open('/users/' + myDeco.userid) 39255 } else if (elem.hasClass('newsfeed')) { 39256 window.open('/newsfeed'); 39257 } 39258 } else { 39259 if (elem.hasClass('profile') || elem.hasClass('newsfeed')) { 39260 window.open(elem.data('href') + (myDeco.settings.WL ? "?profile=" + myDeco.settings.WL_PROFILE : "")); 39261 } 39262 } 39263 }, 39264 39265 toggleShortcuts: function() { 39266 this._helpPage.toggle(); 39267 }, 39268 39269 /** 39270 * Show new splash 39271 */ 39272 showNewAction: function() { 39273 var self = this; 39274 39275 if (myDeco.settings.WL && myDeco.settings.WL.NOSPLASH) { 39276 // sidebar event handlers are not yet attached, so this new 'scratch' action should be called asyncronously, 39277 // after all syncronous stuff finishes running 39278 setTimeout(function() { 39279 self._model.operationStart(['startingchoice', 'scratch']); 39280 self.newAction('scratch'); 39281 }, 0); 39282 return; 39283 } 39284 39285 myDeco.popin.open({ 39286 template: tmpl('view-' + this.$type + '-new'), 39287 width: 694, 39288 className: 'view-' + this.$type + '-new', 39289 afterOpen: function(content) { 39290 content.find('li a:not(.youtube)').click(function(e) { 39291 e.stop(); 39292 39293 var type = $(e.target).closest('li').attr('className'); 39294 39295 // No upload for mobile safari. 39296 if (type === 'upload' && myDeco.browser.isMobileSafari) { 39297 myDeco.popin.alert({ 39298 width: '650', 39299 title: gettext('Oops, we cannot get images from this device'), 39300 message: interpolate( 39301 gettext('To upload a floor plan, please visit %s from a computer where the image is stored'), 39302 [location.hostname] 39303 ) 39304 }); 39305 return; 39306 } 39307 39308 self._model.operationStart(['startingchoice', type]); 39309 self.newAction(type); 39310 }); 39311 } 39312 }); 39313 }, 39314 39315 /** 39316 * Action invoker for new splash 39317 * @param {String} type Action type 39318 */ 39319 newAction: function(type) { 39320 var self = this; 39321 39322 myDeco.popin.close(); 39323 switch(type) { 39324 case 'saved': 39325 myDeco.body.trigger('roomOpenRequest'); 39326 break; 39327 case 'scratch': 39328 if (!$.cookie('hide-hints') && !(myDeco.settings.WL && myDeco.settings.WL.NOHINTS)) { 39329 // show hints after timeout 39330 myDeco.body.one('roomLoaded', function() { 39331 window.setTimeout(function() { 39332 self.showTips(); 39333 }, 1000); 39334 }); 39335 39336 // set session cookie 39337 $.cookie('hide-hints', '1', {path: document.location.pathname}); 39338 } 39339 39340 myDeco.body 39341 .trigger('toggleSidebar', ['build']) 39342 .find('#view-build .build-tabs').trigger('show', ['walls']).end() 39343 .trigger('viewToggle', ['floor']) 39344 .trigger('roomLoadRequest', [emptyRoom]); 39345 break; 39346 case 'wizard': 39347 myDeco.body.trigger('showWizard'); 39348 break; 39349 case 'furnished': 39350 myDeco.body.one('roomLoaded', function() { myDeco.body.trigger('roomMetadata', [{name: ''}]); }); 39351 myDeco.body.trigger('roomLoadRequest', [myDeco.settings.DEFAULT_ROOM_URL]); 39352 break; 39353 case 'upload': 39354 myDeco.body.trigger('view-build-upload'); 39355 break; 39356 } 39357 }, 39358 39359 showTips: function() { 39360 var bubbles = $('.bubble.init').filter('.help').rearrange({ 39361 anchor: $('#menu .help'), 39362 position: [0, 'bottom'] 39363 }).end() 39364 .filter('.camera-angle').rearrange({ 39365 anchor: $('#view-render'), 39366 position: ['right', 10] 39367 }).end() 39368 .filter('.camera').rearrange().end() 39369 .filter('.drag').rearrange({ 39370 position: [280, 620] 39371 }).end() 39372 .fadeIn(500), 39373 cancelBubbles = function() { 39374 bubbles.fadeOut(500); 39375 }; 39376 myDeco.body.one('mousedown', cancelBubbles); 39377 }, 39378 39379 /** 39380 * Update welcome text when user log in/out 39381 * @param {Event} e 39382 */ 39383 updateUsername: function() { 39384 this.canvasElement.find('#username-container') 39385 .html(myDeco.settings.T.welcome.replace('%s', myDeco.username || '')); 39386 }, 39387 39388 /** 39389 * Show popup for floorplan rejecting 39390 */ 39391 showRejectAction: function() { 39392 var self = this, 39393 callback = function(e) { 39394 if (e.type === 'roomDeleted') { 39395 myDeco.popin.close(); 39396 } else { 39397 myDeco.popin.alert({ 39398 message: gettext('Error in deletion!') 39399 }); 39400 } 39401 myDeco.body.unbind('roomDeleted roomDeletionError', callback); 39402 }; 39403 39404 myDeco.popin.open({ 39405 url: '#view-menu-reject', 39406 afterOpen: function(div) { 39407 myDeco.buttons.initSubmitButtons(div.find('form').submit(function(e) { 39408 e.stop(); 39409 myDeco.body.one('roomDeleted roomDeletionError', callback); 39410 self._model.deleteRoom({reason: $(this).find('textarea').val()}); 39411 }
39411).find('input:submit')); 39412 } 39413 }); 39414 } 39415 39416}); 39417 39418myDeco.App.views.push(MenuView); 39419/*globals myDeco, $, AppView, window, location, document, emptyRoom, 39420 numberInputKeyPress, tmpl, WizardColorPicker, diffuse2RGB, Point */ 39421/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 39422 sloppy: true, white: true */ 39423/** 39424 * @fileOverview 39425 * Wizard view 39426 */ 39427var WizardView = AppView.extend({ 39428 39429 /** 39430 * View type 39431 * @type {String} 39432 * @public 39433 * @static 39434 */ 39435 $type: 'wizard', 39436 39437 /** 39438 * Used controls cache 39439 * @type {jQset} 39440 */ 39441 _controls: false, 39442 39443 /** 39444 * Colour controls 39445 * @type {jQuery} 39446 */ 39447 _colours: false, 39448 39449 /** 39450 * New room settings 39451 * @type {Object} 39452 */ 39453 defaultRoomSettings: { 39454 width: 450, 39455 length: 450, 39456 height: 310, 39457 style: false, 39458 shape: false, 39459 colours: [] 39460 }, 39461 39462 /** 39463 * Current room settings 39464 * @type {Object} 39465 */ 39466 roomSettings: {}, 39467 39468 /** 39469 * Order of panes 39470 * @type {Array} 39471 */ 39472 _order: ['type', 'shape', 'color', 'style', 'last'], 39473 39474 /** 39475 * Current step of the wizard 39476 * @type {Number} 39477 */ 39478 _step: 'type', 39479 39480 /** 39481 * Default architecture elements 39482 * @type {Array} 39483 */ 39484 _components: [ 39485 '23f3f34205d4709811052a6709914697600302c6', 39486 'e79b4c034a30d52dac5d8b2cd460a2dddf711874' 39487 ], 39488 39489 /** 39490 * Current colour to select 39491 * @type {Number} 39492 */ 39493 _currentColour: false, 39494 39495 /** 39496 * Component added instead of style selection 39497 * @type {String} 39498 */ 39499 component_id: undefined, 39500 39501 /** 39502 * View init 39503 * @param {Object} options 39504 * @public 39505 */ 39506 init: function(options) { 39507 this._super(options); 39508 39509 var self = this; 39510 39511 myDeco.body 39512 .bind('showWizard', function(e, component_id) { 39513 myDeco.body.trigger('roomLoadRequest', 39514 [emptyRoom, function() { 39515 self.showWizard(component_id); 39516 }]); 39517 }); 39518 }, 39519 39520 /** 39521 * Show wizard popin 39522 * @param {String} component_id Component added instead of hardcoded styles 39523 * @public 39524 */ 39525 showWizard: function(component_id) { 39526 var self = this; 39527 39528 this.component_id = component_id; 39529 39530 myDeco.popin.open({ 39531 url: '#view-' + this.$type, 39532 width: 694, 39533 height: 386, 39534 className: 'view-' + this.$type, 39535 afterOpen: function(content) { 39536 // reset settings to default 39537 self.roomSettings = $.cloneObject(self.defaultRoomSettings); 39538 39539 // room dimensions 39540 var dimensions = content.find('.number-input input') 39541 .numberInput({ 39542 formatFunction: function() { 39543 return Array.prototype.slice.call(arguments).toUnits(); 39544 }, 39545 parseFunction: function(v) { 39546 var value = v.fromUnits().shift() || 0; 39547 if (value < 10) { 39548 value = 10; 39549 } 39550 return value; 39551 }, 39552 positive: true, 39553 frac: 2, 39554 increment: function(shift) { 39555 return myDeco.settings 39556 .UNITS_LIST[myDeco.settings.UNITS][+shift + 2] 39557 .fromViewUnits(); 39558 }, 39559 keyPress: numberInputKeyPress 39560 }) 39561 .each(function() { 39562 var input = $(this); 39563 input.trigger('setValue', self.roomSettings[input.attr('name')]); 39564 }) 39565 .change(function() { 39566 var input = $(this); 39567 self.roomSettings[input.attr('name')] = input.data('value'); 39568 }), 39569 palette = content.find('.color .palette-holder'), 39570 picker; 39571 39572 myDeco.body.bind('unitsChanged', function() { 39573 dimensions.each(function() { 39574 $(this).trigger('setValue'); 39575 }); 39576 }); 39577 39578 self._step = self._order[0]; 39579 self.steps = content.find('.wizard-step'); 39580 39581 // wizard types 39582 content.find('.types').html(tmpl('view-wizard-types-template', { 39583 types: $.keys(myDeco.settings.WIZARD_BUNDLES) 39584 })); 39585 39586 // wizard shapes 39587 content.find('.shapes').html(tmpl('view-wizard-shapes-template', { 39588 types: $.keys(self._model.settings.shapes) 39589 })); 39590 39591 picker = new WizardColorPicker({ 39592 canvas: self.steps.filter('.color'), 39593 settings: { 39594 mousemove: function(data) { 39595 if (self._currentColour !== false && data[2]) { 39596 self._colours.eq(self._currentColour) 39597 .css('background-color', '#' + data[2]); 39598 } 39599 }, 39600 mouseleave: function() { 39601 if (self._currentColour !== false) { 39602 self._colours.eq(self._currentColour) 39603 .css('background-color', 'transparent'); 39604 } 39605 }, 39606 select: function(material) { 39607 if (self._currentColour !== false) { 39608 self.roomSettings.colours[self._currentColour] = 39609 material; 39610 39611 self._colours.eq(self._currentColour) 39612 .css('background-color', 'rgb(' + 39613 diffuse2RGB(material.diffuse) + ')') 39614 .parent().removeClass('selected'); 39615 39616 palette.hide(); 39617 39618 self._currentColour = false;
39619 } 39620 39621 if (self.roomSettings.colours[0] && !self.roomSettings.colours[1]){ 39622 self._colours.eq(1).click(); 39623 } 39624 39625 if (self.roomSettings.colours[0] || 39626 self.roomSettings.colours[1]) { 39627 self._controls.filter('.next').removeClass('disabled'); 39628 } 39629 } 39630 }, 39631 palette: $.extend(true, {}, myDeco.App.palette, { 39632 size: 9, 39633 spacing: 1, 39634 bImage: { 39635 width: 484, 39636 height: 214, 39637 url: '/assets/austin/wizard.png' 39638 } 39639 }) 39640 }); 39641 39642 // controls 39643 self._controls = content.find('.button.next, .button.prev') 39644 .click(function(e) { 39645 e.stop(); 39646 39647 var button = $(this); 39648 if (!button.hasClass('disabled')) { 39649 self._switchStep(button.hasClass('next') ? 1 : -1); 39650 } 39651 }); 39652 39653 self._colours = content.find('.colour-block .colour') 39654 .click(function(e) { 39655 e.stop(); 39656 39657 self._colours.parent().removeClass('selected'); 39658 $(this).parent().addClass('selected'); 39659 palette.show(); 39660 self._currentColour = self._colours.index(this); 39661 }); 39662 39663 content.find('.units').unitsSelector(); 39664 39665 content.delegate('li.room-icon a', 'click', function(e) { 39666 e.preventDefault(); 39667 39668 var button = $(this); 39669 39670 button.closest('.wizard-step').find('.room-icon a') 39671 .removeClass('active'); 39672 39673 self.roomSettings[self._step] = button.addClass('active') 39674 .data(self._step); 39675 39676 self._controls.filter('.next').removeClass('disabled'); 39677 39678 if (self._step === 'type' || self._step === 'style') { 39679 self._switchStep(1); 39680 } 39681 }); 39682 39683 self._switchStep(self._order[0]); 39684 } 39685 }); 39686 }, 39687 39688 /** 39689 * Switch slides in the wizard 39690 * @param {String} step Step to switch 39691 * @private 39692 */ 39693 _switchStep: function(step) { 39694 var next = this._controls.filter('.next'), 39695 prev = this._controls.filter('.prev'), 39696 nextText = next.children(), 39697 pane, bubbles, 39698 cancelBubbles = function() { 39699 bubbles.fadeOut(500); 39700 }; 39701 39702 if (typeof step === 'number') { 39703 step = this._order[ 39704 Math.max(0, Math.min(this._order.length, 39705 this._order.indexOf(this._step) + step)) 39706 ]; 39707 } 39708 39709 // that means that we have all steps completed 39710 if (!step) { 39711 myDeco.popin.close(); 39712 if (this.roomSettings.style !== 'user') { 39713 bubbles = $('.bubble.drag-wizard').rearrange() 39714 .fadeIn(500); 39715 myDeco.body.one('mousedown', cancelBubbles); 39716 } 39717 return; 39718 } 39719 39720 // time to update controls and switch to next pane 39721 this._step = step; 39722 39723 pane = this.steps.filter('.' + this._step); 39724 39725 next.toggleClass('disabled', this._step !== 39726 this._order[this._order.length - 1]).hide(); 39727 prev.toggle(this._step !== this._order[0]); 39728 39729 switch (step) { 39730 //if component_id skip this step 39731 case 'style': 39732 if (!this.component_id) { 39733 pane.find('.styles').html(tmpl('view-wizard-styles-template', { 39734 types: myDeco.settings.WIZARD_BUNDLES[this.roomSettings.type] 39735 })); 39736 } else { 39737 this._switchStep(1); 39738 return; 39739 } 39740 break; 39741 39742 case 'last': 39743 this.buildRoom(); 39744 next.show(); 39745 nextText.text(gettext('See my room now')); 39746 //if component_id - prev button should switch 3 steps behind 39747 if (this.component_id) { 39748 self = this; 39749 prev.unbind().bind('click',function(e) { 39750 e.stop(); 39751 self._switchStep(-3); 39752 }); 39753 } 39754 break; 39755 39756 case 'color': 39757 // if component_id - skip this step and the colors are white by default 39758 if (!this.component_id) { 39759 this._colours.eq(0).click(); 39760 pane.find('.roomtype').html(this.roomSettings.type); 39761 next.show(); 39762 nextText.text(gettext('Next')); 39763 } else { 39764 this.roomSettings.colours[0] = myDeco.App.palette.getMaterial('#FFFFFF'); 39765 this._switchStep(2); 39766 return; 39767 } 39768 break; 39769 39770 case 'shape': 39771 pane.find('.roomtype').html(this.roomSettings.type); 39772 next.show(); 39773 nextText.text(gettext('Next')); 39774 break; 39775 39776 default: 39777 next.show(); 39778 nextText.text(gettext('Next')); 39779 } 39780 39781 this.steps.hide(); 39782 pane.show(); 39783 }, 39784 39785 /** 39786 * get 5 products for chosen room type and furniture style 39787 * @public 39788 */ 39789 buildRoom: function() { 39790 var self = this, 39791 wallsCount, data, walls, 39792 metadata = { 39793 category: this.roomSettings.type 39794 }, 39795 lastStep = function() { 39796 // show window tab 39797 myDeco.body 39798 .trigger('toggleSidebar', ['build']) 39799 .find('#view-build .build-tabs').trigger('show', ['windows']); 39800 39801 // update room metadata with selected category and style 39802 self._model.metadata(false, metadata); 39803 myDeco.body.trigger('roomLoaded', [true]); 39804 39805 }; 39806 39807 // add room 39808 this._model.loadRoom(emptyRoom); 39809 39810 this._model.setCeilingHeight(this.roomSettings.height); 39811 this._model.addRoom(this.roomSettings.shape, 0, 0, 39812 this.roomSettings.width, this.roomSettings.length); 39813 39814 wallsCount = this._model.walls.length; 39815
39816 // get walls and sort them by length 39817 walls = this._model.walls.slice(); 39818 walls.sort(function(a, b) { 39819 return a.getAttribute('width') - b.getAttribute('width'); 39820 }); 39821 39822 $.each(this._model.walls, function(i, wall) { 39823 var material = self.roomSettings.colours[Math.round(i / wallsCount)] || 39824 self.roomSettings.colours[0] || self.roomSettings.colours[1]; 39825 39826 self._model.assignMaterial(wall, material, 'wall'); 39827 }); 39828 39829 // fetch products 39830 if (this.roomSettings.style && this.roomSettings.style !== 'user') { 39831 metadata.style = self.roomSettings.style; 39832 data = { 39833 category: self.roomSettings.type, 39834 style: self.roomSettings.style 39835 }; 39836 if (myDeco.settings.WL_PROFILE) { 39837 data.profile = myDeco.settings.WL_PROFILE; 39838 } 39839 $.ajax({ 39840 url: myDeco.urls.wizardComponents, 39841 data: data, 39842 type: 'get', 39843 success: function(data) { 39844 self._model.dropComponents(self._components.concat(data), lastStep); 39845 } 39846 }); 39847 } else { 39848 data = this._components; 39849 if (this.component_id) { 39850 data = data.concat([this.component_id]); 39851 } 39852 this._model.dropComponents(data, lastStep); 39853 } 39854 } 39855 39856}); 39857 39858myDeco.App.views.push(WizardView); 39859/*globals ColorPickerView */ 39860/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 39861 sloppy: true, white: true */ 39862 39863var WizardColorPicker = ColorPickerView.extend({ 39864 39865 $type: 'wizard-color-picker', 39866 39867 renderComponent: function(e) { 39868 if (e.isDefaultPrevented()) { 39869 return; 39870 } 39871 e.preventDefault(); 39872 this.settings.select(this.getMaterial(e)); 39873 } 39874}); 39875/*globals myDeco, $, AppView, Raphael, T, window */ 39876/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 39877 sloppy: true, white: true */ 39878/** 39879 * @fileOverview Render view 39880 */ 39881var RenderView = AppView.extend({ 39882 39883 /** 39884 * View type 39885 * @type {String} 39886 * @public 39887 * @static 39888 */ 39889 $type: 'render', 39890 39891 /** 39892 * Render timeout holder 39893 * @type {Number} 39894 * @private 39895 */ 39896 _renderTimeout: 0, 39897 39898 /** 39899 * Active render process 39900 * @type {Boolean} 39901 * @private 39902 */ 39903 _renderInProgress: false, 39904 39905 /** 39906 * Was any render request while render is working 39907 * @type {Boolean} 39908 * @private 39909 */ 39910 _renderRequested: false, 39911 39912 /** 39913 * render() method with bind to current object cache 39914 * @type {Function} 39915 * @private 39916 */ 39917 _cacheRender: false, 39918 39919 /** 39920 * Render info overlay 39921 * @type {jQset} 39922 * @private 39923 */ 39924 _overlay: false, 39925 39926 /** 39927 * Control buttons 39928 * @type {Array} 39929 * @private 39930 */ 39931 _buttons: {}, 39932 39933 /** 39934 * Flag for availability HQ render 39935 * @type {Boolean} 39936 */ 39937 _hqRenderAvailable: false, 39938 39939 /** 39940 * HQ render request callback 39941 * @type {Function} 39942 */ 39943 _hqRenderCallback: $.noop, 39944 39945 /** 39946 * Default view settings 39947 * @type {Object} 39948 * @public 39949 */ 39950 settings: { 39951 enlargeClassName: 'big-render', 39952 39953 timeout: 1000, 39954 39955 camera: { 39956 // max camera top position in cm 39957 maxHeight: 700, 39958 39959 // rotating point X shift 39960 rotateX: 55, 39961 39962 // distance between rotating point and focal point on image 39963 rotateD: 70, 39964 39965 // max stroke of rotating point 39966 rotateH: 175, 39967 39968 // drag square coordinates (relative to rotateX and vertical center of canvas) and sizes : [x, y, w, h] 39969 drag: [-4, -19, 48, 38], 39970 39971 // arrow space: 39972 dragPaddingTop: 105, 39973 39974 // rotate circle (relative to rotateX - rotateD and vertical center of canvas) and radius: [x, y, r] 39975 rotate: [-9, 0, 7], 39976 39977 image: ['/vertical-camera.png', -4, -21, 79, 42], 39978 39979 // main drag path image: 39980 dragPath: '/vertical-camera-path.png' 39981 }, 39982 // render timeout before next request after error in seconds 39983 renderErrorTimeout: 15, 39984 39985 // max render requests after error response 39986 renderErrorCount: 3, 39987 39988 // big render size 39989 bigRender: [1024, 576, 'normal'] 39990 }, 39991 39992 /** 39993 * View init 39994 * @param {Object} options View options 39995 * @public 39996 */ 39997 init: function(options) { 39998 if (window.location.hash.indexOf('norender') !== -1) { 39999 return; 40000 } 40001 40002 var self = this, 40003 setDimensions = function() { 40004 self.canvasWidth = self.canvas.innerWidth(); 40005 self.canvasHeight = self.canvas.innerHeight(); 40006 }; 40007 40008 this._super(options); 40009 this.canvas = this.canvasElement.find('.render');
40010 this._overlay = this.canvasElement.find('.overlay'); 40011 this.fileMenuItem = $('#view-menu').find('.save-hd'); 40012 40013 if (this.canvas.size() === 0) { 40014 this.canvas = this.canvasElement; 40015 } else { 40016 setDimensions(); 40017 } 40018 40019 this._cacheRender = this.render.bind(this); 40020 this._attachCamera($("#view-component-info").find('.verticalcamera')); 40021 40022 $('.camera-fov input').click(function() { 40023 var value = parseInt($('.camera-fov input:checked').val()); 40024 self._model.camera.setFov(value); 40025 self.processChanges(); 40026 }); 40027 40028 this._hqRenderCallback = function(e) { 40029 e.stop(); 40030 if (self._hqRenderAvailable) { 40031 myDeco.body.trigger('roomSaveRequest', [{ 40032 showProperties: true, 40033 hqRender: true 40034 }]); 40035 } 40036 }; 40037 40038 this.canvasElement 40039 .bind('restored maximized minimized', function(e) { 40040 setDimensions(); 40041 40042 // don't want to re-render in a worse quality 40043 if (e.type === 'maximized') { 40044 self.render(); 40045 } 40046 }); 40047 40048 this._buttons.preview = this.canvasElement 40049 .find('.preview').click(this.bigRender.bind(this)); 40050 40051 this._buttons.hd = $('#view-menu') 40052 .find('.hd').click(this._hqRenderCallback); 40053 40054 this.fileMenuItem.click(this._hqRenderCallback); 40055 40056 myDeco.body 40057 .bind('operationEnd roomLoaded roomSaved', this.processChanges.bind(this)) 40058 .bind('renderState', function(state) { 40059 if (state === 'session') { 40060 self.updateMessage(gettext('still rendering...')); 40061 } 40062 }) 40063 .bind('actionRequest', function(e, action) { 40064 if (action[0] === 'enlarge') { 40065 self._buttons.preview.click(); 40066 } 40067 }); 40068 40069 this.processChanges(); 40070 }, 40071 40072 /** 40073 * Camera drawing and support 40074 * @param {jQset} holder Camera holder 40075 */ 40076 _attachCamera: function(holder) { 40077 if (!holder || holder.size() === 0) { 40078 return; 40079 } 40080 40081 var self = this, 40082 // settings shortcut 40083 s = this.settings.camera, 40084 40085 // default attributes for rotating handlers 40086 attr = {opacity: 0.01, fill: '#fff', 'stroke-width': 0}, 40087 40088 // camera shortcut 40089 model = self._model.camera, 40090 40091 // canvas size 40092 canvasW = holder.innerWidth(), 40093 canvasH = holder.innerHeight(), 40094 40095 // rotating center X coordinate 40096 rotateX = s.rotateX, 40097 40098 // rotating center Y coordinate 40099 rotateY = Math.round(canvasH), 40100 40101 // distance between rotating center and rotating handler 40102 rotateD = s.rotateD, 40103 40104 // rotation angle 40105 rotateA = 0, 40106 40107 // calculating coefficient between px and cm 40108 coeff = s.rotateH / s.maxHeight, 40109 40110 // drag operations offset storage 40111 offsetY = 0, 40112 offsetX = 0, 40113 40114 // drag operation active entity 40115 activeEntity = null, 40116 40117 // canvas 40118 canvas = new Raphael(holder.get(0), canvasW, canvasH), 40119 40120 // camera entity 40121 camera = canvas.set(), 40122 40123 // Y axis movement holder entity 40124 drag = canvas.rect(rotateX + s.drag[0], rotateY + s.drag[1], s.drag[2], s.drag[3]).attr(attr), 40125 40126 // rotating holder entity 40127 rotate = canvas.set(), 40128 40129 // top offset of camera base 40130 offsetH = Math.round((canvasH - s.rotateH) / 2), 40131 40132 // mouse move listener 40133 mousemove = function(e) { 40134 var rA = 0, rY = 0, rpY, offset, result; 40135 40136 if (e) { 40137 // for dragging 40138 if (activeEntity === drag) { 40139 offset = (offsetY + rotateY - e.pageY) / coeff; 40140 result = model.position.z + offset; 40141 40142 // limit with 0 and max top 40143 if (result < 10 || result > (s.maxHeight - s.dragPaddingTop)) { 40144 return; 40145 } 40146 40147 model.move(0, 0, offset); 40148 40149 // for rotating 40150 } else { 40151 rA = T.atan2(offsetY - e.pageY, e.pageX - offsetX); 40152 40153 // limit angle with +90 / -90 degrees 40154 if (rA > 90 || rA < -90) { 40155 return; 40156 } 40157 } 40158 } 40159 40160 if (!e || activeEntity === drag) { 40161 rY = Math.round(s.rotateH + offsetH - coeff * model.position.z); 40162 } 40163 40164 if (!e) { 40165 rA = model.getVAngle(); 40166 } 40167 40168 // check for position of rotating circle 40169 rpY = (rY || rotateY) - rotateD * T.sin(rA || rotateA); 40170 if (rpY < s.rotate[2] || rpY > canvasH - s.rotate[2]) { 40171 // if rotating mode, do not rotate anything 40172 if (rA !== 0) { 40173 rA = 0; 40174 // else calcualte shifting angle 40175 } else { 40176 rA = rotateA + T.asin((rpY - (rpY < s.rotate[2] ? s.rotate[2] : canvasH - s.rotate[2])) / rotateD); 40177 } 40178 } 40179 40180 // Y axis movement if needed 40181 if (rY) { 40182 camera.translate(0, rY - rotateY); 40183 rotateY = rY; 40184 } 40185 40186 // rotating 40187 if (rA) { 40188 40189 // rotate camera relative to Z axis 40190 model.rotate(undefined, rA); 40191 40192 camera.rotate(-rA, rotateX, rotateY); 40193 rotateA = rA; 40194 } 40195 40196 }, 40197 40198 // mouse up listener 40199 mouseup = function() { 40200 myDeco.body 40201 .unbind('mouseup', mouseup) 40202 .unbind('mousemove', mousemove); 40203 self.processChanges(); 40204 }, 40205 40206 // mouse down listener 40207 mousedown = function(e, entity) { 40208 if (e.isDefaultPrevented()) { 40209 return; 40210 } 40211 e.preventDefault(); 40212 40213 if (entity === drag) { 40214 offsetY = e.pageY - rotateY; 40215 } else { 40216 var offset = holder.offset(); 40217 offsetX = offset.left + rotateX; 40218 offsetY = offset.top + rotateY; 40219 } 40220 activeEntity = entity; 40221 40222 // bind handlers 40223 myDeco.body 40224 .bind('mouseup', mouseup) 40225 .bind('mousemove', mousemove); 40226 }, 40227 40228 // mouse over listener 40229 mouseover = function(e, entity) { 40230 self.cursor(entity === drag ? 'move' : 'crosshair'); 40231 }, 40232 40233 // mouse out listener 40234 mouseout = function() { 40235 self.cursor(''); 40236 }; 40237 40238 rotate.push(canvas.circle(rotateX + s.rotate[0] + rotateD, rotateY + s.rotate[1], s.rotate[2]).attr(attr), 40239 // Hidden rect for easier rotation on ipad. 40240 canvas.rect(rotateX + s.rotate[0] + rotateD - s.rotate[2] * 1.5, 40241 rotateY + s.rotate[1] - s.rotate[2] * 3, 40242 s.rotate[2] * 3, 40243 s.rotate[2] * 6) 40244 .attr({fill: 'white', opacity: 0})); 40245 40246 // draw vertical line 40247 canvas.image(myDeco.settings.STATIC_URL + '..' + s.dragPath, 46, 0, 20, s.rotateH); 40248 40249 camera 40250 .push(canvas.image(myDeco.settings.STATIC_URL + '..' + s.image[0], rotateX + s.image[1], rotateY + s.image[2], s.image[3], s.image[4]) 40251 // prevent image drag if cursor leave control box 40252 .mousedown(function(e) { e.preventDefault(); }, true)) 40253 .push(drag.toFront() 40254 .mousedown(mousedown.bindAsEventListener(this, drag), true) 40255 .mouseover(mouseover.bindAsEventListener(this, drag)) 40256 .mouseout(mouseout.bindAsEventListener(this, drag))) 40257 .push(rotate.toFront() 40258 .mousedown(mousedown.bindAsEventListener(this, rotate), true) 40259 .mouseover(mouseover.bindAsEventListener(this, rotate)) 40260 .mouseout(mouseout.bindAsEventListener(this, rotate))); 40261 40262 // bind respositioning to room load 40263 myDeco.body.bind('roomLoaded', function() { 40264 model = self._model.camera; 40265 mousemove(); 40266 $('.camera-fov input[value=' + model.fieldofview + ']').attr('checked', true); 40267 }); 40268 40269 // redraw camera 40270 mousemove(); 40271 }, 40272 40273 /** 40274 * Schedule render for view update and enable/disable buttons 40275 * @public 40276 * @param {Event} e 40277 * @param {String} type 40278 * @param {Number} id 40279 * @param {Object} options Custom options 40280 */ 40281 processChanges: function(e, type, id, options) { 40282 options = options || {}; 40283 if (e && e.type === 'roomLoaded') { 40284 this._renderInProgress = false;
40285 } 40286 40287 this._hqRenderAvailable = this._model.history.position !== -1 && 40288 !options.hqRender && !options.forceNew; 40289 40290 this._buttons.hd.toggleClass('disabled', !this._hqRenderAvailable); 40291 this.fileMenuItem.toggleClass('disabled', !this._hqRenderAvailable); 40292 40293 if (e && e.type === 'roomSaved') { 40294 return; 40295 } 40296 40297 if (this._renderTimeout) { 40298 window.clearTimeout(this._renderTimeout); 40299 } 40300 if (!this.canvasElement.hasClass('minimized')) { 40301 this._renderTimeout = window.setTimeout(this._cacheRender, this.settings.timeout); 40302 } 40303 }, 40304 40305 /** 40306 * Render room 40307 * @public 40308 */ 40309 render: function() { 40310 RoomstylerLive.render(this._model.getModel()); 40311 40312 if (this._renderTimeout) { 40313 window.clearTimeout(this._renderTimeout); 40314 } 40315 40316 // schedule render if it already in progress 40317 if (this._renderInProgress) { 40318 this._renderRequested = true; 40319 return; 40320 } 40321 40322 this._renderInProgress = true; 40323 40324 var self = this, 40325 errors = 0, 40326 finishRender = function() { 40327 self._renderInProgress = false; 40328 if (self._renderRequested) { 40329 self._renderRequested = false; 40330 self.render(); 40331 } 40332 }, 40333 processRender, 40334 renderCountdown = function(timeLeft) { 40335 self.updateMessage( 40336 interpolate( 40337 ngettext('Next camera preview in %s second', 'Next camera preview in %s seconds', timeLeft), 40338 [timeLeft] 40339 ) 40340 ); 40341 if (timeLeft > 0) { 40342 window.setTimeout(function() { 40343 renderCountdown(--timeLeft); 40344 }, 1000); 40345 } else { 40346 processRender(); 40347 } 40348 }, 40349 renderRequest = function() { 40350 self._model.render(processRender, undefined, self.canvasWidth, self.canvasHeight); 40351 }; 40352 40353 processRender = function(img) { 40354 // initial state 40355 if (typeof img === 'undefined') { 40356 self.updateMessage(gettext('render...')).show(); 40357 renderRequest(); 40358 } else if (img) { 40359 self.canvas.find('img').remove(); 40360 self.canvas.append(img); 40361 self.updateMessage().hide(); 40362 finishRender(); 40363 } else if (typeof img !== 'undefined') { 40364 if (errors < self.settings.renderErrorCount) { 40365 renderCountdown(self.settings.renderErrorTimeout * (++errors)); 40366 } else { 40367 // remove message at all 40368 self.updateMessage(''); 40369 finishRender(); 40370 } 40371 } 40372 }; 40373 processRender(); 40374 }, 40375 40376 /** 40377 * Update render message 40378 * @param {String} text 40379 * @return {jQset} 40380 */ 40381 updateMessage: function(text) { 40382 text = text || ''; 40383 return this._overlay.html(text); 40384 }, 40385 40386 /** 40387 * Show big render with good quality 40388 * @param {Event} e Click event 40389 */
40390 bigRender: function(e) { 40391 if (e) { 40392 e.stop(); 40393 if ($(e.target).closest('.button').hasClass('disabled')) { 40394 return; 40395 } 40396 } 40397 40398 var self = this, 40399 info = self.settings.bigRender, 40400 addDisabled = true, // !!(myDeco.browser.isMobileSafari || myDeco.settings.WL_PROFILE || myDeco.settings.FACEBOOK_CANVAS), 40401 params = {}; 40402 40403 // Additional params for popin window with advertise 40404 if (addDisabled) { 40405 params = { 40406 width: info[0] + 20, 40407 holderClassName: 'add-disabled' 40408 }; 40409 } 40410 40411 myDeco.popin.open($.extend({ 40412 url: '#view-render-template', 40413 borders: 10, 40414 width: info[0] + 170, 40415 tracking: 'big-render', 40416 afterOpen: function(div) { 40417 var renderImage, 40418 holder = div.find('.render-image').css({ 40419 width: info[0], 40420 height: info[1] 40421 }); 40422 40423 myDeco.popin.contentHolder().addClass('big-render'); 40424 40425 div.find('.hd') 40426 .toggleClass('disabled', !self._hqRenderAvailable) 40427 .click(self._hqRenderCallback); 40428 40429 self._model.render(function(img) { 40430 renderImage = img.attr('src'); 40431 holder.append(img); 40432 }, undefined, info[0], info[1], info[2]); 40433 40434 if (!addDisabled) { 40435 div.find('.block-render-add') 40436 .append($('<iframe src="' + myDeco.urls.promotion_adhost + '" scrolling="no"' 40437 + 'marginwidth="0" marginheight="0" width="120" height="600">')); 40438 } 40439 } 40440 }, params)); 40441 } 40442}); 40443 40444myDeco.App.views.push(RenderView); 40445/*globals $, AppController, myDeco, numberInputKeyPress */ 40446 40447/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 40448 sloppy: true, white: true */ 40449var ComponentInfoController = AppController.extend({ 40450 40451 /** 40452 * Component type 40453 * @type {String} 40454 * @public 40455 * @static 40456 */ 40457 $type: 'component-info', 40458 40459 /** 40460 * Register view in controller 40461 * @param {FPEView} view 40462 */ 40463 registerView: function(view) { 40464 this._super(view); 40465 40466 var self = this, 40467 dimensions = view.pages.find('.number-input input'), 40468 model = myDeco.App.model, 40469 parseFunction = function(v) { 40470 return v.fromUnits().shift(); 40471 }, 40472 incrementFunction = function(shift) { 40473 var a = myDeco.settings 40474 .UNITS_LIST[myDeco.settings.UNITS][+shift + 2] 40475 .fromViewUnits(); 40476 return a; 40477 }, 40478 formatFunction = function() { 40479 return Array.prototype.slice.call(arguments).toUnits(); 40480 }; 40481 40482 myDeco.body 40483 .bind('roomLoaded roomCleared roomSaved materialAdded materialRemoved roomMetadata', function(e, sameRoom) { 40484 view.updateScenePage(); 40485 // for real rooms, not empty one 40486 if (e.type === 'roomLoaded' && !sameRoom && myDeco.App.model.walls.length !== 0) { 40487 myDeco.body.trigger('showInfo'); 40488 } 40489 }) 40490 .bind('unselectEnd operationEnd showInfo unitsChanged', function(e, entity) { 40491 var entities = model.selected(), 40492 destination = 'scene', 40493 page = destination; 40494 // skip on add operation 40495 if ($.isArray(entity) && (entity[0] === 'add' || 40496 (entity[0] === 'material' && entity[1] !== true))) { 40497 return; 40498 } 40499 40500 entity = $.isPlainObject(entity) ? entity : (entities.length === 1 ? entities[0] : false); 40501 40502 if (entity && entity.type) { 40503 switch(entity.type) { 40504 case 'wall': 40505 case 'shape': 40506 case 'camera': 40507 destination = entity.type; 40508 break; 40509 case 'door': 40510 case 'window': 40511 case 'component': 40512 destination = 'component'; 40513 break; 40514 } 40515 page = entity.type; 40516 } 40517 40518 self._view.pages.hide(); 40519 40520 if (destination) { 40521 self._view['update' + destination.capitalize() + 'Page'].call(self._view, entity); 40522 self._view.pages.filter('.' + page).show(); 40523 } 40524 40525 if (entity && ( 40526 (entity.type == 'wall' && !(myDeco.settings.WL && myDeco.settings.WL.NO_WALL_PROPERTIES)) || 40527 ($.inArray(entity.type, ['window', 'door', 'component', 'shape', 'camera']) !== -1))) { 40528 myDeco.body.trigger('toggleSidebar', ['info']); 40529 } 40530 }); 40531 40532 // scenery 40533 view.pages.filter('.scene').find('.button').click(function(e) { 40534 e.stop(); 40535 40536 model.removeEnvironment('image'); 40537 model.operationEnd(['material']); 40538 40539 self._view.updateScenePage(); 40540 }); 40541 40542 view.pages.filter('.shape') 40543 .find('.ceiling .change, .ceiling .delete, .floor .change, .floor .delete').click(function(e) { 40544 e.stop(); 40545 40546 var button = $(this), 40547 type = button.closest('.floor').size() !== 0 ? 'floor' : 'ceiling'; 40548 40549 if (button.hasClass('delete')) { 40550 model.assignMaterial(model.selected(true), model.settings.defaultMaterials[type], type); 40551 model.operationEnd(['material']); 40552 } else { 40553 myDeco.body.trigger('selectColor', [model.selected(true)[type + 'Material'].component_id]); 40554 } 40555 }); 40556 40557 // doors
40558 view.pages.filter('.door').find('input:radio').change(function() { 40559 var input = $(this); 40560 40561 if (!input.attr('checked')) { 40562 return; 40563 } 40564 40565 model.selected(true).setAttribute(input.attr('name'), input.val()); 40566 }); 40567 40568 // walls 40569 40570 function updateArtCoordinates(selector) { 40571 var entity = model.selected(true), 40572 wallWidth = entity.getAttribute('width'), 40573 40574 40575 holder = selector.closest('.wall'), 40576 coeff = wallWidth / holder.width(); 40577 40578 model.assignPhoto(entity, false, { 40579 x: (parseInt(selector.css('left'), 10) + parseInt(selector.css('border-left-width'), 10) || 0) * coeff, 40580 y: (parseInt(selector.css('top'), 10) + parseInt(selector.css('border-top-width'), 10) || 0) * coeff, 40581 width: selector.width() * coeff, 40582 height: selector.height() * coeff 40583 }); 40584 model.operationEnd(['material']); 40585 } 40586 40587 view.pages.filter('.wall') 40588 // remove material from wall 40589 .find('.material .change, .material .delete').click(function(e) { 40590 e.stop(); 40591 40592 if ($(this).hasClass('delete')) { 40593 model.assignMaterial(model.selected(true), model.settings.defaultMaterials.wall); 40594 model.operationEnd(['material']); 40595 } else { 40596 myDeco.body.trigger('selectColor', [model.selected(true).material.component_id]); 40597 } 40598 }).end() 40599 .find('.thickness').delegate('input', 'change', function() { 40600 view.updateWallPage(model.selected(true) 40601 .setAttribute('thickness', +$(this).val())); 40602 }).end() 40603 .find('.art') 40604 // remove art from wall 40605 .find('.button').click(function(e) { 40606 e.stop(); 40607 40608 model.removePhoto(model.selected(true)); 40609 model.operationEnd(['material']); 40610 40611 view.updateWallPage(model.selected(true)); 40612 }).end() 40613 .find('.selector') 40614 .mousedown(function(e) { 40615 if (e.isDefaultPrevented()) { 40616 return; 40617 } 40618 e.preventDefault(); 40619 40620 var selector = $(this), 40621 cx = parseInt(selector.css('left'), 10), 40622 cy = parseInt(selector.css('top'), 10), 40623 x = e.pageX, y = e.pageY, 40624 mousemove = function(e) { 40625 cx += e.pageX - x; 40626 cy += e.pageY - y; 40627 40628 x = e.pageX; 40629 y = e.pageY; 40630 selector.css({ 40631 'left': cx + 'px', 40632 'top': cy + 'px' 40633 }); 40634 }, 40635 holder = selector.closest('.wall'), 40636 endaction = function(e) { 40637 e.preventDefault(); 40638 myDeco.body 40639 .unbind('mousemove', mousemove) 40640 .unbind('mouseup', endaction); 40641 holder.unbind('mouseleave', endaction); 40642 updateArtCoordinates(selector); 40643 }; 40644 40645 holder.bind('mouseleave', endaction); 40646 myDeco.body 40647 .bind('mouseup', endaction) 40648 .bind('mousemove', mousemove); 40649 }) 40650 .find('.drag') 40651 .mousedown(function(e) { 40652 if (e.isDefaultPrevented()) { 40653 return; 40654 } 40655 e.preventDefault(); 40656 40657 var selector = $(this).parent(), 40658 w = selector.width(), h = selector.height(), 40659 x = e.pageX, y = e.pageY, 40660 mousemove = function(e) { 40661 w += e.pageX - x; 40662 h += e.pageY - y; 40663 x = e.pageX; 40664 y = e.pageY; 40665 selector.width(w).height(h); 40666 }; 40667 40668 myDeco.body 40669 .one('mouseup', function() { 40670 myDeco.body.unbind('mousemove', mousemove); 40671 updateArtCoordinates(selector); 40672 }) 40673 .bind('mousemove', mousemove); 40674 }) 40675 .end() 40676 .end() 40677 .find('input[type=checkbox]').change(function() { 40678 var input = $(this), 40679 options = {}; 40680 options[input.attr('name')] = input.is(':checked'); 40681 myDeco.App.model.assignPhoto(model.selected(true), false, options); 40682 myDeco.App.model.operationEnd(['material']); 40683 }); 40684 40685 myDeco.body.bind('unitsChanged', function() { 40686 view.pages.filter('.wall').find('.tickness .dimension') 40687 .each(function() { 40688 var element = $(this); 40689 element.html([element.data('dimension')].toUnits()); 40690 }); 40691 }); 40692 40693 view.pages.filter('.component') 40694 .find('.wishlist').click(function(e) { 40695 e.stop(); 40696 40697 var link = $(this); 40698 40699 myDeco.body.trigger('component.wishlist', [link.attr('rel'), function() { 40700 link.closest('.component').find('.wishlist, .wishlisted').hide().filter('.wishlisted').show(); 40701 }]); 40702 }); 40703 40704 view.pages.filter('.door, .window').find('.colours') 40705 .delegate('a', 'click', function(e) { 40706 e.stop(); 40707 40708 var element = $(this), 40709 id = element.data('id'); 40710 40711 element 40712 .closest('ul') 40713 .find('a').removeClass('current') 40714 .end() 40715 .end() 40716 .addClass('current'); 40717 40718 myDeco.App.ws.proxy.get('material', id, function(result) { 40719 if (result) { 40720 self._model.assignMaterial(self._model.selected(true), result); 40721 self._model.operationEnd(['material', true]); 40722 } 40723 }); 40724 }); 40725 40726 // wall dimensions 40727 dimensions.filter('*[name=width], *[name=height]').numberInput({ 40728 formatFunction: formatFunction, 40729 parseFunction: parseFunction, 40730 positive: true, 40731 increment: incrementFunction, 40732 keyPress: numberInputKeyPress 40733 }).change(function(e) { 40734 var input = $(this), 40735 value = input.data('value'), 40736 type = input.attr('name'), 40737 entity = model.selected(true), 40738 newValue = false;
40739 40740 newValue = entity.setAttribute(type, value).getAttribute(type); 40741 40742 if (newValue !== false && value !== newValue) { 40743 input.trigger('setValue', newValue); 40744 } 40745 }); 40746 40747 // window position 40748 dimensions.filter('*[name=distance]').numberInput({ 40749 formatFunction: formatFunction, 40750 parseFunction: parseFunction, 40751 positive: true, 40752 increment: incrementFunction, 40753 keyPress: numberInputKeyPress 40754 }).change(function(e) { 40755 var input = $(this), 40756 value = input.data('value'), 40757 entity = model.selected(true); 40758 40759 entity.setFloorDistance(value); 40760 }); 40761 40762 $('#drop-the-floor').click(function() { 40763 myDeco.body.trigger('actionRequest', [["remove", 46, true]]); 40764 }); 40765 40766 $('#drop-the-room').click(function() { 40767 myDeco.body.trigger('actionRequest', [["remove"]]); 40768 }); 40769 } 40770}); 40771 40772myDeco.App.controllers.push(ComponentInfoController); 40773/*globals $, AppView, material2Style, myDeco, appImage2Thumbnail, toSquareUnits, 40774 window, buildThumbnailLink, tmpl, 40775 toDimensionsString, updateMaterialInfo */ 40776/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 40777 sloppy: true, white: true */ 40778 40779var ComponentInfoView = AppView.extend({ 40780 40781 /** 40782 * Component type 40783 * @type {String} 40784 * @public 40785 * @static 40786 */ 40787 $type: 'component-info', 40788 40789 /** 40790 * Init view 40791 * @param {Object} options 40792 */ 40793 init: function(options) { 40794 var self = this; 40795 40796 this._super(options); 40797 40798 // cache door/window predefined materials 40799 myDeco.App.ws.proxy.fetch('material', this._model.settings.finishes, function(result) { 40800 self._model.settings.finishes = $.map(self._model.settings.finishes, 40801 function(id) { 40802 return result[id] || null; 40803 }); 40804 }, true); 40805 40806 this.pages = this.canvasElement.children(); 40807 40808 if ($('su\\:badge').size()) { 40809 $.ajax({dataType: "script", cache: true, 40810 url: '//platform.stumbleupon.com/1/widgets.js'}); 40811 } 40812 40813/* if (!myDeco.browser.isIE) { 40814 this._appendFacebookLikeButton("#recommend-mydeco-facebook-button"); 40815 this._appendFacebookLikeButton("div.dulux-facebook-button"); 40816 } 40817*/ }, 40818 40819 /** 40820 * Add social facebook like button to the info panel 40821 * 40822 * @param {String} container selector 40823 */ 40824 _appendFacebookLikeButton: function(container) { 40825 var facebookContainer = $(container), 40826 facebookPath, 40827 facebookLike; 40828 40829 if (facebookContainer.size()) { 40830 facebookLike = $(document.createElement("iframe")); 40831 40832 facebookPath = '//www.facebook.com/plugins/like.php?href=' 40833 + facebookContainer.data("likeurl") + '&layout=box_count&send_button=false'; 40834 40835 facebookLike 40836 .attr({'src' : facebookPath, 'scrolling' : 'no', 'frameborder' : 0}) 40837 .addClass("facebook-button") 40838 .appendTo(facebookContainer); 40839 } 40840 }, 40841 40842 /** 40843 * Update pane with the scene information 40844 */ 40845 updateScenePage: function() { 40846 var page = this.pages.filter('.scene'), 40847 model = myDeco.App.model, 40848 materialsCount = $.map(model.materials, function(material) { 40849 var id = material.ID.substring(material.ID.indexOf(':') + 1); 40850 if (id.match(/Default$/)) { 40851 return null; 40852 } 40853 return id; 40854 }).unique().length, 40855 scrapbooks = $.map(model.materials, function(material) { return material.decal_texture || null; }), 40856 scenery = page.find('.scenery').toggle(!!model.environment.image), 40857 original = page.find('.original').toggle(!!model._metadata.original_author); 40858 40859 if (model.environment.image) { 40860 scenery 40861 .find('.name').text(model.environment.image.replace(/^.*\/(.*)$/, '$1')).end() 40862 .find('.thumb').css('background-image', 'url(' + appImage2Thumbnail(model.environment.image, 70, 70) + ')'); 40863 scrapbooks.push(model.environment.image); 40864 } 40865 40866 if (model._metadata.original_author) { 40867 original.find('dd a').text(model._metadata.original_author).attr('href', myDeco.urls.publicUserPageStub.replace('username', model._metadata.original_author)); 40868 } 40869 40870 scrapbooks = scrapbooks.unique(); 40871 40872 // update stats 40873 page.find('.name').text(model._metadata.name).parent().toggle(!!model._metadata.name).end().end() 40874 .find('.description').text(model._metadata.description).parent().toggle(!!model._metadata.description).end().end() 40875 .find('.wallsCount').text(model.walls.length).end() 40876 .find('.doorsCount').text(model.doors.length).end() 40877 .find('.windowsCount').text(model.windows.length).end() 40878 .find('.componentsCount').text(model.components.length).end() 40879 .find('.productsCount').text(model.components.length).end() 40880 .find('.materialsCount').text(materialsCount).end() 40881 .find('.scrapbookCount').text(scrapbooks.length).end(); 40882 }, 40883 40884 /** 40885 * Update pane with the room information 40886 * @param {Object} shape Room shape entity 40887 */ 40888 updateShapePage: function(shape) { 40889 var page = this.pages.filter('.shape'), 40890 wallsCount = shape._walls.length, 40891 doorsCount = 0, windowsCount = 0, productsCount = 0, 40892 materials = [], scrapbook = [], x, y, pos, id, wall; 40893 40894 updateMaterialInfo(shape.ceilingMaterial, page.find('.ceiling')).toggle(shape.ceilingMaterial.ID.indexOf('Default') === -1); 40895 updateMaterialInfo(shape.floorMaterial, page.find('.floor')).toggle(shape.floorMaterial.ID.indexOf('Default') === -1); 40896 40897 for (x = wallsCount - 1; x >= 0; x--) { 40898 wall = shape._walls[x]; 40899 40900 for (y = wall.components.length - 1; y >= 0; y--) { 40901 switch (wall.components[y].category) { 40902 case 'Door': 40903 doorsCount++; 40904 break; 40905 case 'Window': 40906 windowsCount++; 40907 break; 40908 default: 40909 productsCount++; 40910 } 40911 } 40912 40913 pos = wall.material.ID.indexOf(':'); 40914 40915 if (pos === -1) { 40916 id = wall.material.ID; 40917 } else { 40918 id = wall.material.ID.substring(pos + 1); 40919 scrapbook.push(wall.material.ID.substring(0, pos)); 40920 } 40921 40922 if (!id.match(/Default$/)) { 40923 materials.push(id); 40924 } 40925 } 40926 40927 for (x = myDeco.App.model.components.length - 1; x >=0; x--) { 40928 if (!myDeco.App.model.components[x].parent && myDeco.App.model.components[x].position.inPoly(shape)) { 40929 productsCount++; 40930 } 40931 } 40932 40933 page.find('.data') 40934 .find('.area').text(Math.abs(shape.getArea()).toSquareUnits()).end() 40935 .find('.wallsCount').text(wallsCount).end() 40936 .find('.doorsCount').text(doorsCount).end() 40937 .find('.windowsCount').text(windowsCount).end() 40938 .find('.productsCount').text(productsCount).end() 40939 .find('.materialsCount').text(materials.unique().length).end() 40940 .find('.scrapbookCount').text(scrapbook.unique().length); 40941 }, 40942 40943 /** 40944 * Update pane with the wall info 40945 * @param {Object} wall Wall entity 40946 */ 40947 updateWallPage: function(wall) { 40948 var page = this.pages.filter('.wall'), 40949 thickness = myDeco.App.model.settings.wallThickness, 40950 materials = page.find('.materials'), 40951 material = materials.find('.material'), 40952 art = materials.find('.art'), 40953 materialCss = material2Style(wall.material), 40954 wallWidth = wall.getAttribute('width'), 40955 wallHeight = wall.getAttribute('height'), 40956 wallBox, selector, width, height, coeff, repeatX, repeatY, i, ilen; 40957 40958 // dimensions 40959 page.find('input[name=width]').trigger('setValue', [wallWidth]).end() 40960 .find('input[name=height]').trigger('setValue', [wallHeight]); 40961 40962 selector = page.find('.thickness').html(''); 40963 for (i = 0, ilen = thickness.length; i < ilen; i++) { 40964 $('<label />').append( 40965 $('<input type="radio" name="thickness" />') 40966 .val(thickness[i].value) 40967 .toggleAttr('checked', 'checked', 40968 thickness[i].value === wall.thickness) 40969 ) 40970 .append(thickness[i].name + ' (') 40971 .append($('<span class="dimension">' + 40972 [thickness[i].value].toUnits() + '</span>') 40973 .data('dimensions', thickness[i].value)) 40974 .append(')') 40975 .appendTo(selector); 40976 } 40977 40978 // material 40979 if (wall.material.ID.indexOf('materialWallDefault') === -1 || typeof wall.material.photo !== 'undefined') { 40980 // material 40981 updateMaterialInfo(wall.material, material).toggle(wall.material.ID.indexOf('materialWallDefault') === -1); 40982 40983 // wall art 40984 if (typeof wall.material.photo !== 'undefined') { 40985 wallBox = art.find('.wall').css(materialCss); 40986 selector = wallBox.find('.selector'); 40987 // it's hardcoded because width of hidden element can't be 40988 // properly calculated 40989 width = 235; 40990 height = width * wallHeight / wallWidth; 40991 coeff = width / wallWidth; 40992 40993 repeatX = art.find('input[name=repeatX]'); 40994 repeatY = art.find('input[name=repeatY]'); 40995 40996 if (wall.material.photo.repeatX) { 40997 repeatX.attr('checked', 'checked'); 40998 } else { 40999 repeatX.removeAttr('checked'); 41000 } 41001 41002 if (wall.material.photo.repeatY) { 41003 repeatY.attr('checked', 'checked'); 41004 } else { 41005 repeatY.removeAttr('checked'); 41006 } 41007 41008 wallBox.width(width).height(height); 41009 selector 41010 .width(wall.material.photo.width * coeff) 41011 .height(wall.material.photo.height * coeff) 41012 .css({ 41013 left: wall.material.photo.x * coeff - parseInt(selector.css('border-left-width'), 10), 41014 top: wall.material.photo.y * coeff - parseInt(selector.css('border-top-width'), 10) 41015 }) 41016 .find('img').attr('src', appImage2Thumbnail(wall.material.decal_texture)); 41017 41018 art.show(); 41019 } else { 41020 art.hide(); 41021 } 41022 41023 materials.show(); 41024 } else { 41025 materials.hide(); 41026 } 41027 }, 41028 41029 /** 41030 * Update component pane with its information 41031 * @param {Object} component Door/Window/Component entity 41032 * @param {String} ctype type of the component: 'door', 'window' or 'component' 41033 */ 41034 updateComponentPage: function(component) { 41035 var ctype = component.type, 41036 page = this.pages.hide().filter('.' + ctype), 41037 materialsHolder = page.find('.colours'), 41038 hinge = page.find('.hinge'), 41039 elevation = page.find('.elevation'), 41040 direction = page.find('.direction'), 41041 data, supplier, wishlist, gtsData; 41042 41043 if (ctype === 'door' || ctype === 'window') { 41044 page.find('.type span').text(component.componentData.name).end() 41045 .find('input[name=width]').trigger('setValue', [component.getAttribute('width')]).end() 41046 .find('input[name=height]').trigger('setValue', [component.getAttribute('height')]); 41047 41048 // check for finiahes have plain objects instead of Number or String 41049 // it's possible when initial loading of materials isn't done yet 41050 if ($.isPlainObject(this._model.settings.finishes[0])) { 41051 page.find('.colours ul').html(tmpl('view-component-info-colours', { 41052 finishes: this._model.settings.finishes, 41053 component: component 41054 })); 41055 } 41056 } 41057 41058 switch (ctype) { 41059 case 'window': 41060 //distance of the window 41061 page.find('input[name=distance]') 41062 .trigger('setValue', [component.getFloorDistance()]); 41063 break; 41064 case 'door': 41065 page.find('input[name=distance]') 41066 .trigger('setValue', [component.getFloorDistance()]); 41067 // hinge & opening of the door 41068 if (component.open_side === 'l' || component.open_side === 'r') { 41069 hinge.find('input').removeAttr('checked') 41070 .filter('[value=' + component.open_side + ']') 41071 .attr('checked', 'checked'); 41072 hinge.show(); 41073 } else { 41074 hinge.hide(); 41075 } 41076
41077 if (component.open_direction === 'i' || component.open_direction === 'o') { 41078 direction.find('input').removeAttr('checked') 41079 .filter('[value=' + component.open_direction + ']') 41080 .attr('checked', 'checked'); 41081 direction.show(); 41082 } else { 41083 direction.hide(); 41084 } 41085 break; 41086 default: 41087 if(component.level === -1) { 41088 // set the elevation 41089 page.find('input[name=distance]') 41090 .trigger('setValue', [component.getFloorDistance()]); 41091 elevation.show(); 41092 } else { 41093 elevation.hide(); 41094 } 41095 41096 data = component.componentData; 41097 gtsData = { 41098 guid: component.product_id || component.component_id, 41099 category: data.category, 41100 price: data.price 41101 }; 41102 41103 page.toggleClass('unavailable', !data.name).toggleClass('empty', !data.available); 41104 page 41105 .find('.thumbnail').css('background-image', 'url(' + component.render_path + ')').end() 41106 .find('.name').html(data.name).end() 41107 .find('.dimensions').html(toDimensionsString(component)); 41108 41109 supplier = page.find('.supplier').toggle(!!data.supplier).find('span, a').text(data.supplier).hide(); 41110 if (data.supplier_url) { 41111 supplier.filter('a').attr('href', data.supplier_url).show(); 41112 } else { 41113 supplier.filter('span').show(); 41114 } 41115 41116 if (myDeco.settings.component_page_hook) { 41117 myDeco.settings.component_page_hook(data, page); 41118 } 41119 41120 if(data.price && data.price > 0 && (!myDeco.settings.WL || data.supplier_id == myDeco.settings.WL.WL_SUPPLIERS)) { 41121 page.find('.price').text(myDeco.formatPrice(data.price || 0, data.locale)).end() 41122 } else { 41123 page.find('.price').html(" ").end(); 41124 } 41125 41126 if(data.url && (!myDeco.settings.WL || data.supplier_id == myDeco.settings.WL.WL_SUPPLIERS)) { 41127 page.find('.gts').data('gts', gtsData).attr('href', data.url).end() 41128 page.find('.gts').css('height', '34px'); 41129 } else { 41130 // a hack to hide the Buy button 41131 page.find('.gts').css('height', '0px'); 41132 } 41133 41134 if(data.out_of_stock) { 41135 page.find('.price').html(" ").end(); 41136 page.find('.gts').css('height', '0px'); 41137 } 41138 41139 if (myDeco.settings.WL && data.supplier_id == myDeco.settings.WL.WL_SUPPLIERS) { 41140 page.find('.description').toggle(!!data.description).html(data.description || ''); 41141 } 41142 41143 if (data.available) { 41144 page 41145 .find('.price').text(myDeco.formatPrice(data.price || 0, data.locale)).end() 41146 .find('.details').toggle(!!data.url).attr('href', data.url || '').toggleClass('gts', !!myDeco.settings.WL).end() 41147 .find('.gts').data('gts', gtsData) 41148 .not('.details') 41149 .attr('href', data.url).end() 41150 // don't show button and details link if WL_SUPPLIERS_BRANDED is set and current supplier isn't there 41151 .toggle(!myDeco.settings.WL || (!!myDeco.settings.WL.WL_SUPPLIERS_BRANDED === false || 41152 (!!myDeco.settings.WL.WL_SUPPLIERS_BRANDED && $.inArray(data.supplier_id, myDeco.settings.WL.WL_SUPPLIERS_BRANDED.split(',')) !== -1))) 41153 .end() 41154 .find('.description').toggle(!!data.description).html(data.description || ''); 41155 41156 wishlist = page.find('.wishlist, .wishlisted').hide().filter('.wishlist').attr('rel', component.component_id).end(); 41157 41158 myDeco.body.trigger('component.wishlisted', [component.component_id, function(result) { 41159 if (typeof result !== 'undefined') { 41160 wishlist.filter(result ? '.wishlisted' : '.wishlist').show(); 41161 } 41162 }]); 41163 } 41164 41165 } 41166 }, 41167 41168 /** 41169 * Update pane with the Camera info 41170 * @param {Object} win Camera entity 41171 */ 41172 updateCameraPage: function(entity) { 41173 // It's empty because we have no information to observe 41174 } 41175}); 41176 41177myDeco.App.views.push(ComponentInfoView); 41178/*globals ItemsListModel, $, myDeco, appImage2Thumbnail */ 41179/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 41180 sloppy: true, white: true */ 41181/** 41182 * @class ComponentListModel 41183 * @classDescription Model class for wishlisted and current room component fetching 41184 */ 41185var ComponentListModel = ItemsListModel.extend({ 41186 41187 /** 41188 * Model type 41189 * @type {String} 41190 * @public 41191 * @static 41192 */ 41193 $type: 'component-list', 41194 41195 /** 41196 * Model source, can be set to 'room' and 'wishlist' 41197 * @type {String} 41198 * @public 41199 */
41200 src: 'component', 41201 41202 /** 41203 * List of wishlisted _components_, not products 41204 * @type {Array} 41205 */ 41206 _wishlist: [], 41207 41208 /** 41209 * List of scrapbook images of owner 41210 * @type {Array} 41211 */ 41212 _scrapbook: [], 41213 41214 /** 41215 * Flag indicated wishlist fetching status 41216 * @type {Boolean} 41217 */ 41218 _wishlistLoaded: false, 41219 41220 /** 41221 * Flag indicated scrapbook fetching status 41222 * @type {Boolean} 41223 */ 41224 _scrapbookLoaded: false, 41225 41226 /** 41227 * Number of items fetched per request 41228 */ 41229 _scrapbookLimit: 60, 41230 41231 _scrapbookLoadingAvailable: true, 41232 41233 /** 41234 * Fetch scrapbook list 41235 * @param {Function} callback 41236 */ 41237 fetchScrapbook: function(callback, append) { 41238 var self = this; 41239 41240 if (!append) { 41241 this._scrapbookStart = 0; 41242 this._scrapbookLoaded = false; 41243 this._scrapbookLoadingAvailable = true; 41244 this._scrapbook = []; 41245 this.itemsIndex.scrapbook = {}; 41246 } 41247 41248 if (myDeco.username && this._scrapbookLoadingAvailable) { 41249 $.ajax({ 41250 url: myDeco.urls.ws.userRooms, 41251 data: { 41252 type: 'photo', 41253 api: 2, 41254 startrank: this._scrapbookStart, 41255 count: this._scrapbookLimit 41256 }, 41257 cache: false, 41258 success: function(data) { 41259 self._scrapbookLoadingAvailable = 41260 data.length === self._scrapbookLimit; 41261 self._scrapbookLoaded = true; 41262 self._scrapbookStart += data.length; 41263 41264 data = $.map(data, function(item) { 41265 var id = String(item[1].id); 41266 $.extend(item[1], { 41267 'component_id': id, 41268 'class': 'photo', 41269 'texture': item[1].image 41270 .replace(/^.*\/(rooms\/images\/.*)$/, '$1') 41271 .replace(/^.*\/(scrapbook_images\/.*)$/, '$1'), 41272 'thumbnail': item[1].thumbnail && item[1].thumbnail.replace('169x127', '70x70') || appImage2Thumbnail(item[1].image, 70, 70) 41273 }); 41274 self.itemsIndex.scrapbook[id] = item[1]; 41275 return item[1]; 41276 }); 41277 41278 self._scrapbook.push.apply(self._scrapbook, data); 41279 self.found = self._scrapbook.length; 41280 if (self._scrapbookLoadingAvailable) { 41281 self.found += '+'; 41282 } 41283 if ($.isFunction(callback)) { 41284 callback(data); 41285 } 41286 } 41287 }); 41288 } else { 41289 callback([]); 41290 } 41291 }, 41292 41293 /** 41294 * Fetch wishlist components 41295 * @param {Function} callback 41296 */ 41297 fetchWishlist: function(callback) { 41298 var self = this; 41299 if (myDeco.username) { 41300 this._wishlistLoaded = false; 41301 $.getJSON(myDeco.addCacheMissParam(myDeco.urls.ws.userShortlistedProducts), function(data) { 41302 // filter wishlist by components with 3d info 41303 self._wishlist = []; 41304 $.each($.values(data), function() { 41305 if (this && this['3d_component_id'] && self._wishlist.indexOf(this['3d_component_id']) === -1) { 41306 self._wishlist.push(this['3d_component_id']); 41307 } 41308 }); 41309 // request 3D info for specified components 41310 myDeco.App.ws.proxy.fetch('component', self._wishlist, function(data) { 41311 self._wishlistLoaded = true; 41312 $.extend(true, self.itemsIndex.component, data); 41313 if ($.isFunction(callback)) { 41314 callback(); 41315 } 41316 }); 41317 }); 41318 } 41319 }, 41320 41321 /** 41322 * Fetching wishlist from a backend for current user 41323 * @param {Function} callback Callback called after fetching 41324 */ 41325 fetchLists: function(callback) { 41326 var self = this, 41327 internalCallback = function() { 41328 if (self._wishlistLoaded && self._scrapbookLoaded && $.isFunction(callback)) { 41329 callback(); 41330 } 41331 }; 41332 41333 this.reset(); 41334 41335 if (myDeco.username) { 41336 this.fetchWishlist(internalCallback); 41337 this.fetchScrapbook(internalCallback); 41338 } else if ($.isFunction(callback)) { 41339 callback(); 41340 } 41341 }, 41342 41343 /** 41344 * Clear all internal owner-related storages 41345 */ 41346 reset: function() { 41347 this._wishlistLoaded = false; 41348 this._scrapbookLoaded = false; 41349 this._wishlist = []; 41350 this._scrapbook = []; 41351 this._super(); 41352 }, 41353 41354 /** 41355 * Set internal representation to a wishlisted products 41356 * @param {Function} callback Callback executed when done 41357 */ 41358 setWishlist: function(callback) { 41359 this.src = 'wishlist'; 41360 this.type = 'component'; 41361 this._setItems(this._wishlist, callback); 41362 }, 41363 41364 /** 41365 * Set internal representation to a current room components 41366 * @param {Function} callback Callback executed when done 41367 */ 41368 setComponent: function(callback) { 41369 this.type = this.src = 'component'; 41370 this._setItems(myDeco.App.model.uniqueComponentIds(), callback); 41371 }, 41372 41373 /** 41374 * Set internal representation to a scrapbook list 41375 * @param {Function} callback Callback executed when done 41376 */ 41377 setScrapbook: function(callback) { 41378 this.type = this.src = 'scrapbook'; 41379 this._setItems(this._scrapbook, callback); 41380 }, 41381 41382 /** 41383 * Set internal representation to a materials list 41384 * @param {Function} callback Callback executed when done 41385 */ 41386 setMaterial: function(callback) { 41387 this.type = this.src = 'material'; 41388 this._setItems(myDeco.App.model.uniqueMaterialIds(), callback); 41389 }, 41390 41391 /** 41392 * Fill internal structures and prepare variables with the given items list 41393 * @param {Array} items List of component ID 41394 * @param {Function} callback Callback executed when done 41395 * @private 41396 */ 41397 _setItems: function(items, callback) { 41398 callback = callback || function() {}; 41399 var self = this; 41400
41401 if (items.length > 0) { 41402 switch (this.type) { 41403 case 'scrapbook': 41404 this.items = this._scrapbook; 41405 this.found = this._scrapbook.length; 41406 if (this._scrapbookLoadingAvailable) { 41407 this.found += '+'; 41408 } 41409 callback(); 41410 break; 41411 case 'material': 41412 myDeco.App.ws.proxy.fetch(this.type, items, function(data) { 41413 $.extend(self.itemsIndex[self.type], data); 41414 self.items = $.map(items, function(e) { return self.itemsIndex[self.type][e]; }); 41415 self.found = self.items.length; 41416 callback(); 41417 }); 41418 break; 41419 // wihlist and room components 41420 case 'component': 41421 myDeco.App.ws.proxy.fetch(this.type, items, function(data) { 41422 $.extend(self.itemsIndex[self.type], data); 41423 self.items = $.map(items, function(e) { return self.itemsIndex[self.type][e]; }); 41424 self.found = self.items.length; 41425 callback(); 41426 }); 41427 break; 41428 } 41429 } else { 41430 this.items = []; 41431 this.found = 0; 41432 callback(); 41433 } 41434 }, 41435 41436 /** 41437 * Wishlist given component 41438 * @param {String} or {Array} guid Component GUID 41439 * @param {Function} callback Callback executed when done 41440 */ 41441 wishlist: function(guid, callback) { 41442 var self = this, 41443 guids = $.isArray(guid) ? guid : [guid], 41444 products = []; 41445 $.each(guids, function(i, guid) { 41446 if ($.inArray(guid, self._wishlist) === -1) { 41447 self._wishlist.unshift(guid); 41448 } 41449 41450 myDeco.App.ws.proxy.fetch('component', guid, function(data) { 41451 products.push(data[guid].product_id); 41452 }); 41453 }); 41454 41455 myDeco.shortlist.sendOne($(), JSON.stringify(products), callback); 41456 } 41457}); 41458/*globals ItemsListController, myDeco, $, */ 41459/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 41460 sloppy: true, white: true */ 41461/** 41462 * @class ComponentListController 41463 * @classDescription Component list controller manage current room component list and wishlisted components 41464 */ 41465var ComponentListController = ItemsListController.extend({ 41466 41467 /** 41468 * Controller type 41469 * @type {String} 41470 * @public 41471 * @static 41472 */ 41473 $type: 'component-list', 41474 41475 /** 41476 * Register given view 41477 * @param {Object} view 41478 */ 41479 registerView: function(view) { 41480 var self = this; 41481 41482 this._super(view); 41483 41484 // set loaded to view 41485 this._view.loading(true); 41486 41487 // bind wishlist component checking 41488 myDeco.body 41489 .bind('component.wishlisted', function(e, guid, callback) { 41490 var guids = $.isArray(guid) ? guid : [guid], 41491 results = $.intersect(self._model._wishlist, guids); 41492 callback(self._model._wishlistLoaded ? ($.isArray(guid) ? results : results.length !== 0) : undefined); 41493 }) 41494 .bind('scrapbookReload', function() { 41495 self._view.loading(true); 41496 self._model.fetchScrapbook(function() { 41497 self._view.loading(false); 41498 self.setBackend('scrapbook'); 41499 }); 41500 }); 41501 41502 this._view.canvasElement.find('.button.scrapbook').click(function(e) { 41503 e.stop(); 41504 41505 myDeco.user.loginRequired(self._view.showUpload.bind(self)); 41506 }); 41507 41508 // fetch lists 41509 this._model.fetchLists(function() { 41510 self._view.canvasElement.find('.sources a').click(self.switchSource.bind(self)); 41511 myDeco.body 41512 // update room components list on room change 41513 .bind('componentAdded componentRemoved materialAdded materialRemoved roomLoaded roomCleared loadRoom', function(e) { 41514 switch (self._model.src) { 41515 case 'component': 41516 case 'material': 41517 self.setBackend(self._model.src); 41518 break; 41519 } 41520 }) 41521 // update lists on login/logout 41522 .bind('mydeco.login', function(e, data) { 41523 function updateLists() { 41524 switch (self._model.src) { 41525 case 'wishlist': 41526 self.setBackend('wishlist'); 41527 break; 41528 case 'scrapbook': 41529 self.setBackend('scrapbook'); 41530 break; 41531 } 41532 } 41533 if ($.keys(data).length > 0) { 41534 self._view.loading(true); 41535 self._model.fetchLists(function() { 41536 self._view.loading(false); 41537 updateLists(); 41538 }); 41539 } else { 41540 self._model.reset(); 41541 updateLists(); 41542 } 41543 }) 41544 // wishlist component listener; guid can be either an array or single guid 41545 .bind('component.wishlist', function(e, guid, callback, start, finish) { 41546 self._model.wishlist(guid, function() { 41547 // avoid of switching if wishlisting has been made from room items tab 41548 if (self._model.src !== 'component') { 41549 self.setBackend('wishlist'); 41550 } 41551 if (callback) { 41552 callback(); 41553 } 41554 }, start, finish); 41555 }); 41556 41557 if (myDeco.App.model.loaded) { 41558 self.setBackend('component'); 41559 } 41560 41561 }); 41562 }, 41563
41564 fetchMore: function() { 41565 var self = this; 41566 if (this._model.type === 'scrapbook') { 41567 this._view.scrollerLoading(true); 41568 this._model.fetchScrapbook(function(data) { 41569 self._view.appendItems(data); 41570 self._view.scrollerLoading(false); 41571 }, true); 41572 } 41573 }, 41574 41575 /** 41576 * Switch list source 41577 * @param {Event} e 41578 */ 41579 switchSource: function(e) { 41580 e.stop(); 41581 41582 var link = $(e.target), 41583 element = link.closest('.source'), 41584 elements = element.siblings('.source'), 41585 type = link.attr('class'); 41586 41587 elements.removeClass('selected'); 41588 element.addClass('selected'); 41589 this.setBackend(type); 41590 }, 41591 41592 /** 41593 * Set backend type depending on storage 41594 * @type {String} type backend type 41595 */ 41596 setBackend: function(type) { 41597 // call model 41598 var self = this; 41599 41600 this._view._setRoomMarker = type === 'wishlist'; 41601 41602 this._model['set' + type.capitalize()].call(this._model, function() { 41603 self._view.draw(); 41604 self._view._scrollerHolder.removeClass('scrapbook wishlist component') 41605 .addClass(type); 41606 }); 41607 }, 41608 41609 /** 41610 * Bind to model events to reflect the changes on the pane 41611 */ 41612 bindRelatedEvents: function() { 41613 var self = this; 41614 // will update only the whishlist page if it already shown 41615 myDeco.body.bind('componentAdded componentRemoved', function(e, component) { 41616 var components = myDeco.App.model.components, 41617 totalCost = 0, 41618 i=0; 41619 for (i=0; i < components.length; i++) { 41620 totalCost += components[i].componentData.price; 41621 } 41622 self._view.canvasElement.find('.total-cost').text(myDeco.formatPrice(totalCost || 0)); 41623 41624 if (self._model.src === 'wishlist' && self._view._setRoomMarker) { 41625 self._view._canvas.find('.' + self._model.type + '-' + component.component_id) 41626 .toggleClass(self._view.settings.inRoomClass, myDeco.App.model.isComponentInModel(component)); 41627 } 41628 }); 41629 } 41630}); 41631 41632/** 41633 * Scrapbook upload response 41634 * @param {String} response HTML response 41635 */ 41636function FPEscrapbookUploadResponse(response) { 41637 if (typeof response.error !== 'undefined') { 41638 var form = myDeco.popin.content().find('form'), 41639 errors = form.find('div.error').hide(), 41640 name; 41641 41642 myDeco.buttons.enable(form); 41643 41644 for (name in response.error.message) { 41645 errors.filter('.' + name).text(response.error.message[name].shift()).show(); 41646 } 41647 41648 myDeco.popin.resize(); 41649 } else { 41650 myDeco.body.trigger('scrapbookReload'); 41651 myDeco.popin.close(); 41652 } 41653} 41654 41655myDeco.App.controllers.push(ComponentListController); 41656/*globals ItemsListView, myDeco */ 41657/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 41658 sloppy: true, white: true */ 41659/** 41660 * @class ComponentListView 41661 * @classDescription Component list view class, used for displaying wishlisted and components in a current room 41662 */ 41663var ComponentListView = ItemsListView.extend({ 41664 41665 /** 41666 * View type 41667 * @type {String} 41668 * @public 41669 * @static 41670 */ 41671 $type: 'component-list', 41672 41673 /** 41674 * Icons size 41675 * @type {String} 41676 */ 41677 mode: 'small', 41678 41679 draw: function() { 41680 if (!window._roomLoading) { 41681 this._super(); 41682 } 41683 }, 41684 41685 /** 41686 * Show upload popup 41687 */ 41688 showUpload: function() { 41689 myDeco.popin.open({ 41690 url: '#scrapbook-popin', 41691 afterOpen: function(div) { 41692 var form = div.find('form'); 41693 41694 myDeco.buttons.initSubmitButtons(form.find('input:submit').removeClass('no-decorate')); 41695 myDeco.fileInputs.init(form); 41696 41697 form.submit(function() { 41698 form.find('div.error').hide(); 41699 myDeco.buttons.disable(form); 41700 myDeco.popin.resize(); 41701 }); 41702 } 41703 }); 41704 } 41705 41706}); 41707 41708myDeco.App.views.push(ComponentListView); 41709/*globals myDeco, $, FpePlugin, window */ 41710/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 41711 sloppy: true, white: true */ 41712 41713var SnappingPlugin = FpePlugin.extend({ 41714 /** 41715 * Plugin type 41716 * @type {String} 41717 * @static 41718 */ 41719 $type: 'floor', 41720 41721 /** 41722 * Plugin name 41723 * @type {String} 41724 * @static 41725 */ 41726 $name: 'snapping', 41727 41728 _settings: { 41729 autostart: true, 41730 41731 /** 41732 * Default visual attributes 41733 */ 41734 attributes: { 41735 'stroke': '#777', 41736 'stroke-width': 1 41737 } 41738 }, 41739 41740 /** 41741 * Snapping lines 41742 * @type {Object} 41743 */ 41744 _snaps: { 41745 x: false, 41746 y: false 41747 }, 41748 41749 /** 41750 * Init function 41751 * @param {FpeView} view FPE view 41752 * @param {Object} options Plugin options 41753 */ 41754 init: function(view, options) { 41755 this._super(view, options); 41756 // bind to model events 41757 myDeco.body 41758 .bind('snappingOn', this.snapOn.bind(this))
41759 .bind('snappingOff', this.snapOff.bind(this)); 41760 }, 41761 41762 /** 41763 * Snap on listener 41764 * @param {Event} e Snap event 41765 * @param {String} type Snapping type 41766 * @param {Point} p1 Snapping points #1 41767 * @param {Point} p2 Snapping points #2 41768 */ 41769 snapOn: function(e, axis, value) { 41770 var i; 41771 if (!this._snaps[axis]) { 41772 this._snaps[axis] = this._view.canvas 41773 .path(this._settings.attributes) 41774 .moveTo(-10000, -10000) 41775 .lineTo(10000, 10000) 41776 .hide(); 41777 } 41778 41779 if (axis === 'y') { 41780 value = -value; 41781 } 41782 41783 this._snaps[axis].hold(); 41784 for (i = 0; i < 2; i++) { 41785 this._snaps[axis].pathPoints[i].dropCache(); 41786 this._snaps[axis].pathPoints[i][axis] = value; 41787 } 41788 this._snaps[axis].render().toFront().show(); 41789 }, 41790 41791 /** 41792 * Snap off listener 41793 * @param {Event} e Snap event 41794 * @param {String} axis Snapping type 41795 */ 41796 snapOff: function(e, axis) { 41797 var i, ilen, 41798 axes = axis ? [axis] : ['x', 'y']; 41799 41800 for (i = 0, ilen = axes.length; i < ilen; i++) { 41801 if (this._snaps[axes[i]]) { 41802 this._snaps[axes[i]].hide(); 41803 } 41804 } 41805 } 41806 41807}); 41808 41809myDeco.App.plugins.push(SnappingPlugin); 41810/*globals myDeco, $, FpePlugin, document, buildThumbnailLink */ 41811/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 41812 sloppy: true, white: true */ 41813/** 41814 * @fileOverview Dimension selector plugin with component scrolling 41815 */ 41816var DimensionSelectorPlugin = FpePlugin.extend({ 41817 /** 41818 * View type 41819 * @static 41820 */ 41821 $type: 'floor', 41822 41823 /** 41824 * Plugin name 41825 * @type {String} 41826 */ 41827 $name: 'dimension-selector', 41828 41829 /** 41830 * Selector left coordinate 41831 * @type {Number} 41832 */ 41833 x: undefined, 41834 41835 /** 41836 * Selector top coordinate 41837 * @type {Number} 41838 */ 41839 y: undefined, 41840 41841 /** 41842 * Selector width 41843 * @type {Number} 41844 */ 41845 width: undefined, 41846 41847 /** 41848 * Selector height 41849 * @type {Number} 41850 */ 41851 height: undefined, 41852 41853 /** 41854 * Default settings 41855 */ 41856 _settings: { 41857 /** 41858 * Value of width/height in px 41859 * @type {Number} 41860 */ 41861 'default': 100 41862 }, 41863 41864 /** 41865 * Parent view 41866 * @type {FPEView} 41867 */ 41868 view: null, 41869 41870 /** 41871 * Selector element 41872 * @type {jQset} 41873 */ 41874 selector: null, 41875 41876 /** 41877 * Flag for resize operation 41878 * @type {Boolean} 41879 */ 41880 resize: false, 41881 41882 /** 41883 * Selector offset helper values 41884 * @type {Number} 41885 */ 41886 offset: {x: 0, y: 0}, 41887 41888 /** 41889 * Flag for dimension selection state 41890 * @type {Number} 41891 */ 41892 set: false, 41893 41894 /** 41895 * Text info element 41896 * @type {jQset} 41897 */ 41898 info: null, 41899 41900 /** 41901 * Resulting image element 41902 * @type {jQset} 41903 */ 41904 image: null, 41905 41906 /** 41907 * Inner block of selector with loader etc 41908 * @type {jQset} 41909 */ 41910 inner: null, 41911 41912 /** 41913 * List of controls 41914 * @type {jQset} 41915 */ 41916 controls: null, 41917 41918 /** 41919 * Facet name events listen to 41920 * @type {String} 41921 */ 41922 facetName: 'dimension', 41923 41924 /** 41925 * Clear listener 41926 * @type {Function} 41927 * @private 41928 */ 41929 _clear: null, 41930 41931 /** 41932 * Scale listener 41933 * @type {Function} 41934 * @private 41935 */ 41936 _scale: null, 41937 41938 /** 41939 * Cached callback for updateComponent method 41940 * @type {Function} 41941 */ 41942 _updateComponent: null, 41943 41944 /** 41945 * Cached callback for updateByOtherFacet method 41946 * @type {Function} 41947 */ 41948 _updateSelectorByOther: null, 41949 41950 /** 41951 * Show hint flag 41952 * @type {Boolean} 41953 */ 41954 _showHint: true, 41955 41956 /** 41957 * Is help massage has been viewed or not 41958 * @type {Boolean} 41959 */ 41960 _helpMessageViewed: false, 41961 41962 /** 41963 * Move bound function cache 41964 * @type {Function} 41965 */ 41966 _moveCache: false, 41967 41968 /** 41969 * Type of controller/model which triggered opening 41970 * @type {String} 41971 */ 41972 _invokerType: false, 41973 41974 init: function(view, options) { 41975 this._super(view, options); 41976 var self = this; 41977 41978 // cache event listeners for proper unbind 41979 this._scale = this.update.bind(this); 41980 this._clear = this.clear.bind(this); 41981 this._updateComponent = this.updateComponent.bind(this); 41982 this._updateSelectorByOther = this.updateSelectorByOther.bind(this); 41983 this._showHint = !$.cookie('hint-dimension'); 41984 this._moveCache = this.move.bind(this); 41985 41986 // main selector 41987 this.selector = $('<div class="dimension-facet" style="display:none"><div class="inner"><span>' + 41988 this.getText() + '</span><div class="loader" /><img src="/assets/austin/blank.gif" style="display:none" class="item" />' + 41989 '<div class="controls"><a class="prev" /><a class="drop" /><a class="next" /></div>' + 41990 '</div><div class="resizer" /><a class="close" /></div>') 41991 .mousedown(this.start.bind(this)) 41992 .mouseup(this.stop.bind(this)) 41993 .appendTo(this._view.canvasElement); 41994 // and nested elements 41995 this.inner = this.selector.find('.inner'); 41996 this.info = this.inner.find('span'); 41997 this.image = this.inner.find('img.item'); 41998 this.controls = this.inner.find('.controls a') 41999 .mousedown(function(e) { e.preventDefault(); }) 42000 // bind events to listeners 42001 .filter('.drop').mouseup(this.dropItem.bind(this)).end()
42002 .filter('.next, .prev').mouseup(this.switchItem.bind(this)).end(); 42003 42004 this.selector.find('.close').mouseup(function(e) { 42005 e.preventDefault(); 42006 myDeco.body.trigger('controller-facet-toggle', [false, self.facetName, false]); 42007 }) 42008 .mousedown(function(e) { e.preventDefault(); }); 42009 myDeco.body 42010 .bind('controller-facet-toggle', this.toggle.bind(this)) 42011 .bind('roomCleared', function() { 42012 myDeco.body.trigger('controller-facet-toggle', [false, self.facetName, false]); 42013 }) 42014 .bind('unitsChanged', function() { 42015 self.info.html(self.getText()); 42016 }); 42017 }, 42018 42019 /** 42020 * Toggle selector visibility 42021 * @param {Event} e Event from search controller 42022 * @param {String} type Invoker type 42023 * @param {String} facetName 42024 * @param {Boolean} state Selector visibility 42025 */ 42026 toggle: function(e, type, facetName, state) { 42027 42028 facetName = facetName || this.facetName; 42029 state = state || false; 42030 42031 // do not service other facets 42032 if (facetName !== this.facetName) { 42033 return; 42034 } 42035 42036 var bubble; 42037 42038 myDeco.body 42039 .unbind('controller-facet-cleared', this._clear) 42040 .unbind('view-' + this._view.$type + '-scaled', this._scale) 42041 .unbind('controller-data-fetched', this._updateSelectorByOther); 42042 42043 // show selector 42044 if (state) { 42045 this._invokerType = type; 42046 this.x = this.y = undefined; 42047 if (!this.set) { 42048 this.width = this.height = undefined; 42049 } 42050 42051 this.update(); 42052 42053 // hide not necessary items 42054 this.state(this.set ? 'empty' : 'default'); 42055 42056 if (this.set) { 42057 myDeco.body.trigger('controller-fetch-next', [this._invokerType, this._updateComponent, true]); 42058 } 42059 42060 // listen to events 42061 myDeco.body 42062 .bind('controller-facet-cleared', this._clear) 42063 .bind('view-' + this._view.$type + '-scaled', this._scale) 42064 .bind('controller-data-fetched', this._updateSelectorByOther); 42065 42066 this.selector.fadeIn('fast'); 42067 42068 if (this._showHint) { 42069 bubble = $('.bubble.drag-dimension').rearrange({ 42070 anchor: this.selector.find('.resizer'), 42071 position: ['right', 'bottom'] 42072 }).fadeIn('fast'); 42073 $.cookie('hint-dimension', '1', {path: document.location.pathname}); 42074 this._showHint = false; 42075 myDeco.body.one('mousedown', function() { 42076 bubble.fadeOut('fast'); 42077 }); 42078 } 42079 } else if (type === false || type === this._invokerType) { 42080 this.selector.fadeOut('fast'); 42081 } 42082 }, 42083 42084 /** 42085 * Mouse move event listener 42086 * @param {Event} e Mousemove event 42087 */ 42088 move: function(e) { 42089 // calculate actual values 42090 var x = e.pageX - this.offset.x, 42091 y = e.pageY - this.offset.y; 42092 42093 if (this.resize) { 42094 this.width = x / this._view.zoom; 42095 this.height = y / this._view.zoom; 42096 this.info.html(this.getText()); 42097 this.set = true; 42098 } else { 42099 this.x = x; 42100 this.y = y; 42101 } 42102 this.update(); 42103 }, 42104 42105 /** 42106 * Update size and position of selector 42107 * @public 42108 */ 42109 update: function() { 42110 var zoom = this._view.zoom, 42111 width, height; 42112 42113 this.width = this.width || Math.round(this._settings['default'] / zoom); 42114 this.height = this.height || Math.round(this._settings['default'] / zoom); 42115 42116 width = this.width * zoom; 42117 height = this.height * zoom; 42118 42119 this.x = this.x || Math.ceil((this._view.canvasWidth - width) / 2); 42120 this.y = this.y || Math.ceil((this._view.canvasHeight - height) / 2); 42121 42122 if (this.selector) { 42123 this.selector.css({ 42124 left : this.x + 'px', 42125 top : this.y + 'px', 42126 width : width + 'px', 42127 height: height + 'px' 42128 }); 42129 } 42130 }, 42131 42132 /** 42133 * Start operation listener 42134 * @param {Event} e Mousedown event 42135 */ 42136 start: function(e) { 42137 if ($(e.target).closest('.controls').size() !== 0) { 42138 return; 42139 } 42140 42141 if (e.isDefaultPrevented()) { 42142 return; 42143 } 42144 e.preventDefault(); 42145 42146 // select operation type by event starter 42147 this.resize = $(e.target).hasClass('resizer'); 42148 42149 var o = this.selector.offset(), 42150 p = this.selector.offsetParent().offset(); 42151 this.offset = { 42152 x: e.pageX - o.left + p.left, 42153 y: e.pageY - o.top + p.top 42154 }; 42155 42156 if (this.resize) { 42157 this.state('resize'); 42158 p = this.selector.position(); 42159 this.offset = { 42160 x: this.offset.x - Math.round(this.width * this._view.zoom) + p.left, 42161 y: this.offset.y - (this.height * this._view.zoom) + p.top 42162 }; 42163 } 42164 this._view.canvasElement.bind('mousemove', this._moveCache); 42165 }, 42166 42167 /** 42168 * End of operation 42169 * @param {Event} e 42170 * @public 42171 */
42172 stop: function(e) { 42173 e.preventDefault(); 42174 42175 this._view.canvasElement.unbind('mousemove', this._moveCache); 42176 42177 // fire event if dimension is selected 42178 if (this.set && this.resize) { 42179 this.state('loading'); 42180 myDeco.body.trigger('controller-facet-refine', 42181 [this._invokerType, this.facetName, {w: this.width, d: this.height}]); 42182 this.resize = false; 42183 } 42184 }, 42185 42186 /** 42187 * Prepare text for info block 42188 * @param {Number} width Selector width, defaults to this.width 42189 * @param {Number} width Selector height, defaults to this.height 42190 */ 42191 getText: function() { 42192 return this.set ? [this.width || 0, this.height || 0].toUnits() : gettext('Dimension<br />cleared'); 42193 }, 42194 42195 /** 42196 * Clear current selection and hide selector 42197 * @param {Event} e 42198 * @param {String} facetName 42199 */ 42200 clear: function(e, type, facetName) { 42201 // do not service another facets 42202 if ((e && facetName !== this.facetName) || type !== this._invokerType) { 42203 return; 42204 } 42205 42206 this.x = undefined; 42207 this.y = undefined; 42208 this.width = undefined; 42209 this.height = undefined; 42210 this.set = false; 42211 this.info.html(this.getText()); 42212 this.state('default'); 42213 this.toggle(false, type); 42214 }, 42215 42216 /** 42217 * Method for updating selector block with result component 42218 * @param {Object} item Component object 42219 * @param {Number} position Current component position in stack 42220 * @param {Number} found Number of totally found items 42221 */ 42222 updateComponent: function(item, position, found) { 42223 var self = this, 42224 width, height; 42225 if (item) { 42226 // update controls according to item ID and item position in stack 42227 this.controls 42228 .filter('.prev').toggleClass('disabled', position <= 0).end() 42229 .filter('.next').toggleClass('disabled', position >= found - 1).end() 42230 .filter('.drop').data('component', item.component_id); 42231 42232 // update image attributes 42233 width = Math.round(item.width * this._view.zoom); 42234 height = Math.round(item.depth * this._view.zoom); 42235 this.image.css({ 42236 width: width + 'px', 42237 height: height + 'px', 42238 left: Math.round((this.width * this._view.zoom - width) / 2) + 'px', 42239 top: Math.round((this.height * this._view.zoom - height) / 2) + 'px' 42240 }).one('load', function() { 42241 self.state('active'); 42242 if (!self._helpMessageViewed) { 42243 myDeco.body.trigger('status', 42244 [gettext('Click arrows to preview furniture. Press «✓» to select.')]); 42245 self._helpMessageViewed = true; 42246 } 42247 }).attr({ 42248 src: buildThumbnailLink(item.componentData['3d'].render_top), 42249 alt: item.componentData.name 42250 }); 42251 42252 // hack for already cached images (chrome) 42253 if (myDeco.browser.isChrome && this.image.get(0).complete) { 42254 this.image.trigger('load'); 42255 } 42256 } else { 42257 self.state('empty'); 42258 } 42259 }, 42260 42261 /** 42262 * Listener for item dropping 42263 * @param {Event} e 42264 */ 42265 dropItem: function(e) { 42266 e.preventDefault(); 42267 42268 var self = this; 42269 42270 this.state('loading'); 42271 // fetch component info and drop component to canvas 42272 myDeco.body.trigger('controller-component-info', [this._invokerType, $(e.target).data('component'), function(component) { 42273 var offset = self.selector.offset(), 42274 pos = self._view.toModelPosition( 42275 offset.left + parseInt(self.selector.css('border-left-width'), 10) + (self.width / 2) * self._view.zoom, 42276 offset.top + parseInt(self.selector.css('border-top-width'), 10) + (self.height / 2) * self._view.zoom), 42277 entity = $.cloneObject(component); 42278 42279 delete entity.ID; 42280 42281 self._view._model.addComponent(entity, pos.x, pos.y); 42282 self._view._model.operationEnd(['add']); 42283 42284 // hide selector 42285 myDeco.body.trigger('controller-facet-toggle', [self._invokerType, self.facetName, false]); 42286 }]); 42287 }, 42288 42289 /** 42290 * Listener for component switching in the list 42291 * @param {Event} e 42292 */ 42293 switchItem: function(e) { 42294 e.preventDefault(); 42295 42296 var element = $(e.target), 42297 action = element.hasClass('prev') ? 'prev' : 'next'; 42298 42299 // do not serve disabled buttons 42300 if (element.hasClass('disabled')) { 42301 return; 42302 } 42303 42304 this.state('loading'); 42305 myDeco.body.trigger('controller-fetch-' + action, 42306 [this._invokerType, this._updateComponent]); 42307 }, 42308 42309 /** 42310 * Update selector with new set of items by events from search 42311 * @param {Event} e 42312 */ 42313 updateSelectorByOther: function(e, type) { 42314 if (this.set && type && type === this._invokerType) { 42315 this.state('loading'); 42316 // fetch first item 42317 myDeco.body.trigger('controller-fetch-next', [this._invokerType, this._updateComponent, true]); 42318 } 42319 }, 42320 42321 /** 42322 * Hide/show layers by setting combinations of classNames to selector element 42323 * @param {Boolean} state 42324 */ 42325 state: function(state) { 42326 switch (state) { 42327 case 'loading': 42328 this.selector.addClass('active loading').removeClass('resize empty'); 42329 this.image.hide(); 42330 break; 42331 case 'resize': 42332 this.selector.addClass('active resize').removeClass('empty loading'); 42333 this.image.hide(); 42334 break; 42335 case 'empty': 42336 this.selector.addClass('active empty').removeClass('resize loading'); 42337 this.image.hide(); 42338 break; 42339 case 'active': 42340 this.selector.addClass('active').removeClass('empty resize loading'); 42341 this.image.show(); 42342 break; 42343 default: 42344 this.image.hide(); 42345 this.selector.removeClass('active resize loading empty'); 42346 } 42347 } 42348}); 42349 42350myDeco.App.plugins.push(DimensionSelectorPlugin); 42351/*globals myDeco, $, KEY */ 42352/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 42353 sloppy: true, white: true */ 42354myDeco.App.settings = $.extend(true, myDeco.App.settings, { 42355 model: { 42356 minHeight: 200, 42357 maxHeight: 500, 42358 shapes: { 42359 square: { 42360 name: 'Square', 42361 shape: [[1, 1], [1, 0], [0, 0], [0, 1]], 42362 width: 450, 42363 height: 450 42364 }, 42365 lshaped1: { 42366 name: 'L Shaped 1', 42367 shape: [[1, 1], [1, 0.5], [0.5, 0.5], [0.5, 0], [0, 0], [0, 1]], 42368 width: 450, 42369 height: 450 42370 }, 42371 lshaped2: { 42372 name: 'L Shaped 2', 42373 shape: [[1, 0.5], [1, 0], [0, 0], [0, 1], [0.5, 1], [0.5, 0.5]], 42374 width: 450, 42375 height: 450 42376 }, 42377 lshaped3: { 42378 name: 'L Shaped 3', 42379 shape: [[1, 1], [1, 0], [0.5, 0], [0.5, 0.5], [0, 0.5], [0, 1]], 42380 width: 450, 42381 height: 450 42382 }, 42383 lshaped4: { 42384 name: 'L Shaped 4', 42385 shape: [[0.5, 1], [1, 1], [1, 0], [0, 0], [0, 0.5], [0.5, 0.5]], 42386 width: 450, 42387 height: 450 42388 }, 42389 rect1: { 42390 name: 'Rect 1', 42391 shape: [[1, 1], [1, 0], [0, 0], [0, 1]], 42392 width: 250, 42393 height: 450 42394 }, 42395 rect2: { 42396 name: 'Rect 2', 42397 shape: [[1, 1], [1, 0], [0, 0], [0, 1]], 42398 width: 450, 42399 height: 250 42400 }, 42401 alcove1: { 42402 name: 'Alcove 1', 42403 shape: [[1, 0.8], [1, 0], [0, 0], [0, 0.8], [0.25, 0.8], [0.25, 1], [0.75, 1], [0.75, 0.8]], 42404 width: 450, 42405 height: 450 42406 }, 42407 alcove2: { 42408 name: 'Alcove 2', 42409 shape: [[0.8, 1], [0.8, 0.75], [1, 0.75], [1, 0.25], [0.8, 0.25], [0.8, 0], [0, 0], [0, 1]], 42410 width: 450, 42411 height: 450 42412 }, 42413 alcove3: { 42414 name: 'Alcove 3', 42415 shape: [[1, 1], [1, 0.2], [0.75, 0.2], [0.75, 0], [0.25, 0], [0.25, 0.2], [0, 0.2], [0, 1]], 42416 width: 450, 42417 height: 450 42418 }, 42419 alcove4: { 42420 name: 'Alcove 4', 42421 shape: [[1, 1], [1, 0], [0.2, 0], [0.2, 0.25], [0, 0.25], [0, 0.75], [0.2, 0.75], [0.2, 1]], 42422 width: 450, 42423 height: 450 42424 } 42425 }, 42426 wallThickness: [ 42427 {name: gettext('Thick wall'), value: 30}, 42428 {name: gettext('Middle wall'), value: 17}, 42429 {name: gettext('Thin wall'), value: 10} 42430 ], 42431 // index from array above 42432 wallThicknessDefault: 1, 42433 finishes: [2649, 2652, 2653, 2657, 2658, 2660, 2661, 2662], 42434 doors: [ 42435 { 42436 type: "flush-plain", 42437 name: gettext("Plain Door"), 42438 component_id: "f75c8910592b2bd613dcd1d2b60d5f237bb27a08", 42439 sizes: [[82, 208]] 42440 }, 42441 { 42442 type: "moulded", 42443 name: gettext("Panel Door"), 42444 component_id: "cf28e9e5b7659cbc88e3ed735bdfc827606628ff", 42445 sizes: [[82, 208]] 42446 }, 42447 { 42448 type: "patio", 42449 name: gettext("Patio Door"), 42450 component_id: "ed4398ca15141c396325b0903ac23fbacc960479", 42451 sizes: [[173, 208], [365, 220]] 42452 }, 42453 { 42454 type: "folding", 42455 name: gettext("Folding Door"), 42456 component_id: "5dd92a4fd20cc337ef9cd009d620ef39ac051e54", 42457 sizes: [[240, 310]] 42458 }, 42459 { 42460 type: "double-french", 42461 name: gettext("Glazed Double French"), 42462 component_id: "fa310873ea1f5a6816728bfc50d96dd0d1c619ec", 42463 sizes: [[171, 208]] 42464 }, 42465 { 42466 type: "glazed", 42467 name: gettext("Glazed Door"), 42468 component_id: "0143d8e0e3c2de799c8661d5d97130bd248af342", 42469 sizes: [[82, 208]] 42470 }, 42471 { 42472 type: "patio", 42473 name: gettext("Sliding Door"), 42474 component_id: "899f1d4c7b441166851b9e38eb28f0ade2837687", 42475 sizes: [[400, 300]] 42476 }, 42477 { 42478 type: "doorway", 42479 name: gettext("Opening"), 42480 component_id: "56e44065a5645a008497882a7901c06811fe6298", 42481 sizes: [[82, 208]] 42482 }, 42483 { 42484 type: "paned", 42485 name: gettext("Glazed Panel Door"), 42486 component_id: "1d07f9d6ba47a92d3bdb428f656ead081851f22f", 42487 sizes: [[82, 208]] 42488 }, 42489 { 42490 type: "rectangular-opening", 42491 name: gettext("Rectangular Opening"), 42492 component_id: "482e0a08d4a4666fdad20c0bc8194bbebf223077", 42493 sizes: [[82, 208]] 42494 }, 42495 { 42496 type: "round-opening", 42497 name: gettext("Round Opening"), 42498 component_id: "22b0042cf9b5710f2fc20a407af244e3e8a98147", 42499 sizes: [[90, 90]] 42500 }, 42501 { 42502 type: "arched-opening", 42503 name: gettext("Arched Opening"), 42504 component_id: "1b4768a1cb1ffcacdc3a37de6eb3678a125ae34f", 42505 sizes: [[90, 160]] 42506 }, 42507 { 42508 type: "half-round-opening", 42509 name: gettext("Half Round Opening"), 42510 component_id: "14f0695b5b699d2cde9ac56b72d064c714e440a9", 42511 sizes: [[120, 60]] 42512 } 42513 ], 42514 windows: [ 42515 { 42516 type: "sash", 42517 name: gettext("Sash"), 42518 component_id: "3dfe203d609c4e61ed5d5ed34144421a176cfeed", 42519 sizes: [[92, 102]] 42520 }, 42521 { 42522 type: "picture", 42523 name: gettext("Picture"), 42524 component_id: "23f3f34205d4709811052a6709914697600302c6", 42525 sizes: [[90, 102]] 42526 }, 42527 { 42528 type: "standard", 42529 name: gettext("Casement"), 42530 component_id: "6d48b971d09f830198aad21b2b9fcb2c5b9d36af", 42531 sizes: [[92, 102]] 42532 }, 42533 { 42534 type: "arched", 42535 name: gettext("Arched"), 42536 component_id: "6c705d137661ddf3de8ce3e159cc2446562912cf", 42537 sizes: [[90, 160]] 42538 }, 42539 { 42540 type: "round", 42541 name: gettext("Half Round"), 42542 component_id: "b83b277ce9f79359fe2fef82c8ab7e1f7f83495b", 42543 sizes: [[120, 70]] 42544 }, 42545 { 42546 type: "circle", 42547 name: gettext("Round"), 42548 component_id: "2bf5ae66583be1729caabb74f64f44fe5057c0df", 42549 sizes: [[90, 90]] 42550 } 42551 ], 42552 replacements: { 42553 '3aa1d582ac225cb89ffccd90cd9a133226c40a25': '3e6a673e4ad12f17a08f1f2304e5dc47e95ab2d6', 42554 '2c515ee55bd217b573e05663f7ad0cf4f6458fa5': '98a6f80c8ac25cc7e03c5e301646463f50a8f973', 42555 'b303e1c2e108af3eed81f5d871ce51441d9ee728': 'f23b87d056907b297d89d9b3b70e19051d23b0d7', 42556 'f3e206bc66af47e2ef393aca58ca2422d6418bd1': '61b03b1e217f4164b27376f22068af2037307143' 42557 } 42558 }, 42559 views: { 42560 floor: { 42561 offset: { 42562 top: 56, 42563 bottom: 36 42564 }, 42565 library: { 42566 camera: { 42567 scaleFactor: 0.2 42568 } 42569 }, 42570 floor: { 42571 fillOpacity: 1 42572 }, 42573 measurer: { 42574 // distance between outer wall side and ruler 42575 distance: 20, 42576 text: { 42577 'font-size': 10, 42578 'color': '#f00' 42579 }, 42580 rulers: { 42581 'stroke-width': 1, 42582 'stroke': '#999' 42583 } 42584 }, 42585 keyboardShiftValue: 10, 42586 keyboardRotateValue: 15 42587 }, 42588 isometric: { 42589 linked: ['floor'], 42590 offset: { 42591 top: 56, 42592 bottom: 36 42593 }, 42594 IsoWall: { 42595 normal: { 42596 stroke: '#000000' 42597 }, 42598 highlighted: { 42599 stroke: '#67afdf' 42600 } 42601 } 42602 }, 42603 render: { 42604 timeout: myDeco.settings.AUSTIN_RENDER_TIMEOUT * 1000 42605 } 42606 }, 42607 controllers: { 42608 layout: { 42609 shortcuts: [ 42610 // action name, key code, shift, ctrl or meta, alt, repeated 42611 ['remove', KEY.Delete, false, false, false, false], 42612 ['remove', KEY.BackSpace, false, false, false, false], 42613 ['move', KEY.Left, false, false, false, true ], 42614 ['move', KEY.Left, true, false, false, true ], 42615 ['move', KEY.Right, false, false, false, true ], 42616 ['move', KEY.Right, true, false, false, true ], 42617 ['move', KEY.Up, false, false, false, true ], 42618 ['move', KEY.Up, true, false, false, true ], 42619 ['move', KEY.Down, false, false, false, true ], 42620 ['move', KEY.Down, true, false, false, true ], 42621 ['rotate', KEY.L, false, false, false, true ], 42622 ['rotate', KEY.L, true, false, false, true ], 42623 ['rotate', KEY.R, false, false, false, true ], 42624 ['rotate', KEY.R, true, false, false, true ], 42625 ['redo', KEY.Y, false, true, false, false], 42626 ['undo', KEY.Z, false, true, false, false], 42627 ['duplicate', KEY.D, false, false, false, false], 42628 ['split', KEY.S, false, false, false, false], 42629 ['info', KEY.I, false, false, false, false], 42630 ['help', KEY.H, false, false, false, false], 42631 ['open', KEY.O, false, true, false, false], 42632 ['save', KEY.S, false, true, false, false], 42633 ['saveas', KEY.S, true, true, false, false], 42634 ['enlarge', KEY.E, false, false, false, false], 42635 ['zoomin', KEY.PlusEq, true, false, false, false], 42636 ['zoomin', KEY.Plus, false, false, false, false], 42637 ['zoomout', KEY.Minus, false, false, false, false], 42638 ['draw', KEY.P, false, false, false, false], 42639 ['reset', KEY.Esc, false, false, false, false], 42640 ['find', KEY.F, false, true, false, false], 42641 ['setscale', KEY.D, false, true, false, false], 42642 ['debug', KEY.X, false, false, false, false] 42643 ] 42644 }, 42645 floor: { 42646 linked: ['isometric'] 42647 }, 42648 isometric: { 42649 linked: ['floor'] 42650 } 42651 } 42652}); 42653/*globals myDeco, $ */ 42654/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 42655 sloppy: true, white: true */ 42656if (myDeco.browser.isMobileSafari) { 42657 myDeco.App.settings = $.extend(true, myDeco.App.settings, { 42658 views: { 42659 'color-picker': { 42660 dragThumbnail: { 42661 width: 150, 42662 height: 150 42663 } 42664 }, 42665 'materials-search': { 42666 dragThumbnail: { 42667 width: 150, 42668 height: 150, 42669 postfix: '_50.jpg' 42670 } 42671 }, 42672 floor: { 42673 rotateArrow: { 42674 distance: 60, 42675 scale: 0.4 42676 }, 42677 library: { 42678 camera: { 42679 scaleFactor: 0.3, 42680 arrowDistance: 360 42681 }, 42682 splitButton: { 42683 size: 32, 42684 img: '/split-button.png' 42685 }, 42686 drawButton: { 42687 size: 32, 42688 img: '/draw-button.png' 42689 }, 42690 closeButton: { 42691 size: 32, 42692 img: '/delete-button.png' 42693 }, 42694 infoButton: { 42695 size: 32, 42696 img: '/default.png' 42697 }, 42698 duplicateButton: { 42699 size: 32, 42700 img: '/duplicate-button.png' 42701 } 42702 }, 42703 42704 Corner: { 42705 radius: 15 42706 }, 42707 42708 Wall: { 42709 thickness: 20 42710 }, 42711 42712 Door: { 42713 thickness: 20 42714 }, 42715 42716 Window: { 42717 thickness: 20 42718 }, 42719 42720 Drawable: { 42721 controlPanel: { 42722 distance: 50, 42723 padding: 1 42724 } 42725 }, 42726 42727 Component: { 42728 minsize: 8, 42729 hideOutline: true 42730 } 42731 } 42732 } 42733 }); 42734}; 42735/*globals myDeco, $ */ 42736/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 42737 sloppy: true, white: true */ 42738/** 42739 * Tracking module for 3D tool 42740 */ 42741 42742myDeco.App.tracking = $.extend({}, myDeco.tracking, { 42743 /** 42744 * Events namespace prefix 42745 * @type {String} 42746 */ 42747 prefix: 'austin.', 42748 42749 /** 42750 * Init tracking 42751 */
42752 watch: function() { 42753 var self = this; 42754 42755 if (!myDeco.settings.AUSTIN_GA_ACCOUNT) { 42756 return; 42757 } 42758 42759 this.setVar('Account', myDeco.settings.AUSTIN_GA_ACCOUNT); 42760 $.each(myDeco.settings.AUSTIN_GA_OPTIONS, this.setVar.bind(this)); 42761 42762 myDeco.body 42763 .bind('roomSaving', function(e, roomType, roomId, options) { 42764 self.trackEvent('austin-save', 'request', options.isNew ? 'new' : 'update'); 42765 }) 42766 .bind('roomSaved', function(e, roomType, roomId, options) { 42767 self.trackEvent('austin-save', 'success', options.isNew ? 'new' : 'update'); 42768 }) 42769 .bind('roomSavingError', function(e, code, status, message) { 42770 self.trackEvent('austin-save', 'error ' + code, message.toString()); 42771 }) 42772 .bind('roomLoadRequest', function() { 42773 self.trackEvent('app-openRoom', '3d'); 42774 }) 42775 .bind('operationStart', function(e, data) { 42776 switch (data[0]) { 42777 case 'draw': 42778 self.trackEvent('austin-actions', '3d', 'draw_walls'); 42779 break; 42780 42781 case 'startingchoice': 42782 self.trackEvent('startingchoice', '3d', data[1]); 42783 break; 42784 42785 case 'showdetails': 42786 self.trackEvent('GTS', data[1], data[2], data[3]); 42787 break; 42788 } 42789 }) 42790 .bind('entity-drag-started', function(e, data) { 42791 if (data.kind === 'decor' && data.type === 'color') { 42792 self.trackEvent('austin-actions', '3d', 'drag_paint', 1); 42793 } 42794 }) 42795 .bind('viewToggle', function(e, type) { 42796 self.trackEvent('austin-view-selection', '3d', type); 42797 }) 42798 .bind('controller-facet-refined', function(e, type, facetName, facetValue) { 42799 if (type === 'materials-search' && facetName === 'class') { 42800 if (facetValue && facetValue[0] === 'paint') { 42801 self.trackEvent('austin-section-selection', '3d', 42802 'decorate_paint', 1); 42803 if (myDeco.settings.DULUX_TRACKING) { 42804 // track DULUX code 42805 self.trackingImage(myDeco.settings.DULUX_BRANDING.tracking_image); 42806 } 42807 } 42808 } 42809 }) 42810 .bind('materialsError', function(materials) { 42811 self.trackEvent('austin-error-materials', '3d', materials); 42812 }) 42813 .bind('saveFormFriendInvite', function(e, prefix) { 42814 self.trackEvent('opinion', '3d', prefix + '_friend_invite'); 42815 }) 42816 .bind('selectedShareSubject', function(e, data) { 42817 var _val, i, trackingMap = { 42818 facebook: 'FbFriendsOpinionChoice', 42819 living: 'HomeFriendsOpinionChoice', 42820 email: 'EmailFriendsOpinionChoice', 42821 color: 'ColourOpinionChoice', 42822 furniture: 'FurnitureOpinionChoice', 42823 decoration: 'DecorationOpinionChoice' 42824 } 42825 function send(value) { 42826 self.trackEvent('opinion', '3d', trackingMap[value]); 42827 } 42828 42829 for (key in data) { 42830 _val = data[key]; 42831 if ($.isArray(_val)) { 42832 i = _val.length; 42833 for (i > -1; i--;) { 42834 send(_val[i]); 42835 } 42836 } else { 42837 send(_val); 42838 } 42839 } 42840 }); 42841 // global error tracking 42842 window.onerror = function(message, url, line) { 42843 self.trackEvent('austin-error', 'window', url + ':' + line + 42844 ':' + encodeURIComponent(message.substring(0, 100))); 42845 }; 42846 42847 // global ajax error tracking 42848 $(document).ajaxError(function(e, xhr, options, message) { 42849 self.trackEvent('austin-error', 'ajax', options.type + '=' + 42850 options.url + ':' + message.substring(0, 100)); 42851 }); 42852 } 42853}); 42854/*globals myDeco, $, SearchModel, MaterialsSearchModel, SpecialSearchModel, 42855 RoomManagementModel, ComponentListModel, emptyRoom, IsometricView, 42856 window, ArchitectureSearchModel, GardenSearchModel */ 42857/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 42858 sloppy: true, white: true */ 42859/** 42860 * @fileOverview Main FPE init function 42861 */ 42862myDeco.App.init = function() { 42863 var searchOptions = myDeco.settings.WL ? { 42864 facet: $.map(myDeco.settings.WL.WL_SUPPLIERS.split(','), function(supplier) { 42865 return supplier ? 'supplier_id,' + supplier : null; 42866 }), 42867 filter: myDeco.settings.WL.WL_SEARCH_EXTRA_FILTER ? [myDeco.settings.WL.WL_SEARCH_EXTRA_FILTER] : '' 42868 } : {}, 42869 search = new SearchModel(false, searchOptions), 42870 materialsSearch = new MaterialsSearchModel(), 42871 specialSearch = myDeco.settings.SPECIAL_SEARCH ? (new SpecialSearchModel(false, searchOptions)) : false, 42872 roomManagement = new RoomManagementModel('3d'), 42873 componentList = new ComponentListModel(), 42874 colorPicker = $('#view-color-picker'), 42875 settings = { 42876 model: emptyRoom, 42877 views: { 42878 search: { 42879 model: search 42880 }, 42881 'materials-search': { 42882 model: materialsSearch 42883 }, 42884 'special-search': { 42885 model: specialSearch 42886 }, 42887 'architecture-search': { 42888 model: new ArchitectureSearchModel(false, searchOptions) 42889 }, 42890 'garden-search': { 42891 model: new GardenSearchModel(false, searchOptions) 42892 }, 42893 'room-management': { 42894 model: roomManagement 42895 }, 42896 'component-list': { 42897 model: componentList 42898 }, 42899 floor: { 42900 canvasSelector: '#view-floor .view .canvas' 42901 }, 42902 layout: {}, 42903 isometric: { 42904 canvasSelector: '#view-isometric .view .canvas' 42905 } 42906 } 42907 }, 42908 action; 42909 42910 $('.scroller, .iscroll, #view-build, #view-component-info').iscroll(); 42911 42912 myDeco.App.tracking.watch(); 42913 42914 try { 42915 myDeco.App.roomUrl = myDeco.settings.OPEN_ROOM_URL; // window.location.search.getURIParam('room_url').pop() || * 42916 // uncomment one of the lines below to force loading a room (for debugging) 42917 // myDeco.App.roomUrl = '/ws/people/creo_forma/rooms/140298/265694/'; // version 0 42918 // myDeco.App.roomUrl = '/ws/people/timknip/rooms/4537186/'; // version 2 42919 // myDeco.App.roomUrl = '/Users/timk/Documents/floorplanner/floorpy/test/out/TestPlanExport_test_mydeco.json'; 42920 action = myDeco.App.roomUrl ? 'openRoom' : (window.opener ? window.opener.loadAction : ''); 42921 myDeco.App.roomUrl = myDeco.App.roomUrl || (window.opener ? window.opener.loadValue : ''); 42922 } catch(e) { } 42923 42924 myDeco.App.dispatcher.init(settings); 42925 42926 myDeco.body 42927 .bind('controller-facet-cleared', function(e, type, facetName) { 42928 if (type === 'materials-search' && facetName === 'class') { 42929 $('#view-color-picker').toggle(false); 42930 } 42931 }) 42932 .bind('controller-facet-refined', function(e, type, facetName, facetValue) { 42933 if (type === 'materials-search' && facetName === 'class') { 42934 colorPicker.toggle(facetValue && ((facetValue[0] === false && 42935 facetValue[1] === 'paint') || facetValue[0] === 'paint')); 42936 } 42937 }) 42938 .bind('model-request-sent', function(e, type) { 42939 if (type === 'materials-search') { 42940 colorPicker.hide(); 42941 } 42942 }) 42943 .trigger('viewToggle', ['floor']) 42944 .find('#view-layout').trigger('linkTo', [myDeco.body.find('#view-render')]); 42945 42946 // called by the parent page to load data, e.g. a room 42947 window.switchState = function(e, action, value) { 42948 if (action === 'openRoom') { 42949 if (value) { 42950 myDeco.body.trigger('roomLoadRequest', [value]); 42951 } else { 42952 myDeco.body.trigger('roomNewRequest'); 42953 } 42954 } else if (action === 'addComponent' && value) { 42955 if (myDeco.App.model._ws_url) { 42956 myDeco.App.model.dropComponents([value]); 42957 } else { 42958 myDeco.body.trigger('showWizard', [value]); 42959 } 42960 } 42961 }; 42962 42963 // load room or add component 42964 if (action && myDeco.App.roomUrl) { 42965 window.switchState(undefined, action, myDeco.App.roomUrl); 42966 } 42967 42968 // jquery has buggy support of this event in IE 42969 window.onbeforeunload = function() { 42970 if (myDeco.App.model.history.counter > 0) { 42971 return gettext('Have you saved your project? You may lose all your work!'); 42972 } 42973 }; 42974 42975 myDeco.popin.plugin('message', { 42976 template: '<div class="content"><h1 class="title">' + gettext('Alert') + '</h1><p class="message"></p></div></div>' 42977 }, function(div) { 42978 if (this.windowOptions.closeButtonText) { 42979 div.find('.close-button-text').html(this.windowOptions.closeButtonText); 42980 } 42981 if (this.windowOptions.title) { 42982 div.find('.title').html(this.windowOptions.title); 42983 } else { 42984 div.find('.title').remove(); 42985 } 42986 if (this.windowOptions.message) { 42987 div.find('.message').html(this.windowOptions.message); 42988 } else { 42989 div.find('.message').remove(); 42990 } 42991 }); 42992}; 42993 42994$(function() { 42995 myDeco.App.init(); 42996}); 42997myDeco.COUNTRIES = [ 42998 ["AB", "Abkhazia"], 42999 ["AF", "Afghanistan"], 43000 ["AL", "Albania"], 43001 ["DZ", "Algeria"], 43002 ["AS", "American Samoa"], 43003 ["AD", "Andorra"], 43004 ["AO", "Angola"], 43005 ["AI", "Anguilla"], 43006 ["AQ", "Antarctica"], 43007 ["AG", "Antigua and Barbuda"], 43008 ["AR", "Argentina"], 43009 ["AM", "Armenia"], 43010 ["AW", "Aruba"], 43011 ["AU", "Australia"], 43012 ["AT", "Austria"], 43013 ["AZ", "Azerbaijan"], 43014 ["BS", "Bahamas"], 43015 ["BH", "Bahrain"], 43016 ["BD", "Bangladesh"], 43017 ["BB", "Barbados"], 43018 ["BY", "Belarus"], 43019 ["BE", "Belgium"],
43020 ["BZ", "Belize"], 43021 ["BJ", "Benin"], 43022 ["BM", "Bermuda"], 43023 ["BT", "Bhutan"], 43024 ["BO", "Bolivia, plurinational state of"], 43025 ["BQ", "Bonaire, Sint Eustatius and Saba"], 43026 ["BA", "Bosnia and Herzegovina"], 43027 ["BW", "Botswana"], 43028 ["BV", "Bouvet Island"], 43029 ["BR", "Brazil"], 43030 ["IO", "British Indian Ocean Territory"], 43031 ["BN", "Brunei Darussalam"], 43032 ["BG", "Bulgaria"], 43033 ["BF", "Burkina Faso"], 43034 ["MM", "Burma"], 43035 ["BI", "Burundi"], 43036 ["KH", "Cambodia"], 43037 ["CM", "Cameroon"], 43038 ["CA", "Canada"], 43039 ["CV", "Cape Verde"], 43040 ["KY", "Cayman Islands"], 43041 ["CF", "Central African Republic"], 43042 ["TD", "Chad"], 43043 ["CL", "Chile"], 43044 ["CN", "China"], 43045 ["CX", "Christmas Island"], 43046 ["CC", "Cocos (Keeling) Islands"], 43047 ["CO", "Colombia"], 43048 ["KM", "Comoros"], 43049 ["CG", "Congo"], 43050 ["CD", "Congo, Democratic Republic of the"], 43051 ["CK", "Cook Islands"], 43052 ["CR", "Costa Rica"], 43053 ["CI", "Cote d'Ivoire"], 43054 ["HR", "Croatia"], 43055 ["CU", "Cuba"], 43056 ["CW", "Curaçao"], 43057 ["CY", "Cyprus"], 43058 ["CZ", "Czech Republic"], 43059 ["DK", "Denmark"], 43060 ["DJ", "Djibouti"], 43061 ["DM", "Dominica"], 43062 ["DO", "Dominican Republic"], 43063 ["EC", "Ecuador"], 43064 ["EG", "Egypt"], 43065 ["SV", "El Salvador"], 43066 ["GQ", "Equatorial Guinea"], 43067 ["ER", "Eritrea"], 43068 ["EE", "Estonia"], 43069 ["ET", "Ethiopia"], 43070 ["FK", "Falkland Islands (Malvinas)"], 43071 ["FO", "Faroe Islands"], 43072 ["FJ", "Fiji"], 43073 ["FI", "Finland"], 43074 ["FR", "France"], 43075 ["GF", "French Guiana"], 43076 ["PF", "French Polynesia"], 43077 ["TF", "French Southern Territories"], 43078 ["GA", "Gabon"], 43079 ["GM", "Gambia"], 43080 ["GE", "Georgia"], 43081 ["DE", "Germany"], 43082 ["GH", "Ghana"], 43083 ["GI", "Gibraltar"], 43084 ["GR", "Greece"], 43085 ["GL", "Greenland"], 43086 ["GD", "Grenada"], 43087 ["GP", "Guadeloupe"], 43088 ["GU", "Guam"], 43089 ["GT", "Guatemala"], 43090 ["GG", "Guernsey"], 43091 ["GN", "Guinea"], 43092 ["GW", "Guinea-Bissau"], 43093 ["GY", "Guyana"], 43094 ["HT", "Haiti"], 43095 ["HM", "Heard Island and McDonald Islands"], 43096 ["VA", "Holy See (Vatican City State)"], 43097 ["HN", "Honduras"], 43098 ["HK", "Hong Kong"], 43099 ["HU", "Hungary"], 43100 ["IS", "Iceland"], 43101 ["IN", "India"], 43102 ["ID", "Indonesia"],
43103 ["IR", "Iran, Islamic Republic of"], 43104 ["IQ", "Iraq"], 43105 ["IE", "Ireland"], 43106 ["IM", "Isle of Man"], 43107 ["IL", "Israel"], 43108 ["IT", "Italy"], 43109 ["JM", "Jamaica"], 43110 ["JP", "Japan"], 43111 ["JE", "Jersey"], 43112 ["JO", "Jordan"], 43113 ["KZ", "Kazakhstan"], 43114 ["KE", "Kenya"], 43115 ["KI", "Kiribati"], 43116 ["KP", "Korea, Democratic People's republic of"], 43117 ["KR", "Korea, Republic of"], 43118 ["KW", "Kuwait"], 43119 ["KG", "Kyrgyzstan"], 43120 ["LA", "Lao People's Democratic Republic"], 43121 ["LV", "Latvia"], 43122 ["LB", "Lebanon"], 43123 ["LS", "Lesotho"], 43124 ["LR", "Liberia"], 43125 ["LY", "Libyan Arab Jamahiriya"], 43126 ["LI", "Liechtenstein"], 43127 ["LT", "Lithuania"], 43128 ["LU", "Luxembourg"], 43129 ["MO", "Macao"], 43130 ["MK", "Macedonia, The Former Yugoslab Republic Of"], 43131 ["MG", "Madagascar"], 43132 ["MW", "Malawi"], 43133 ["MY", "Malaysia"], 43134 ["MV", "Maldives"], 43135 ["ML", "Mali"], 43136 ["MT", "Malta"], 43137 ["MH", "Marshall Islands"], 43138 ["MQ", "Martinique"], 43139 ["MR", "Mauritania"], 43140 ["MU", "Mauritius"], 43141 ["YT", "Mayotte"], 43142 ["MX", "Mexico"], 43143 ["FM", "Micronesia, Federated States of"], 43144 ["MD", "Moldova"], 43145 ["MC", "Monaco"], 43146 ["MN", "Mongolia"], 43147 ["ME", "Montenegro"], 43148 ["MS", "Montserrat"], 43149 ["MA", "Morocco"], 43150 ["MZ", "Mozambique"], 43151 ["NA", "Namibia"], 43152 ["NR", "Nauru"], 43153 ["NP", "Nepal"], 43154 ["NL", "Netherlands"], 43155 ["NC", "New Caledonia"], 43156 ["NZ", "New Zealand"], 43157 ["NI", "Nicaragua"], 43158 ["NE", "Niger"], 43159 ["NG", "Nigeria"], 43160 ["NU", "Niue"], 43161 ["NF", "Norfolk Island"], 43162 ["MP", "Northern Mariana Islands"], 43163 ["NO", "Norway"], 43164 ["OM", "Oman"], 43165 ["PK", "Pakistan"], 43166 ["PW", "Palau"], 43167 ["PS", "Palestinian Territory, Occupied"], 43168 ["PA", "Panama"], 43169 ["PG", "Papua New Guinea"], 43170 ["PY", "Paraguay"], 43171 ["PE", "Peru"], 43172 ["PH", "Philippines"], 43173 ["PN", "Pitcairn"], 43174 ["PL", "Poland"], 43175 ["PT", "Portugal"], 43176 ["PR", "Puerto Rico"], 43177 ["QA", "Qatar"], 43178 ["RE", "Reunion"], 43179 ["RO", "Romania"], 43180 ["RU", "Russian Federation"], 43181 ["RW", "Rwanda"], 43182 ["BL", "Saint Barthélemy"], 43183 ["SH", "Saint Helena, Ascension And Tristan Da Cunha"], 43184 ["KN", "Saint Kitts and Nevis"], 43185 ["LC", "Saint Lucia"], 43186 ["MF", "Saint Martin (French Part)"], 43187 ["PM", "Saint Pierre and Miquelon"], 43188 ["VC", "Saint Vincent and the Grenadines"], 43189 ["WS", "Samoa"], 43190 ["SM", "San Marino"], 43191 ["ST", "Sao Tome and Principe"], 43192 ["SA", "Saudi Arabia"], 43193 ["SN", "Senegal"], 43194 ["RS", "Serbia"], 43195 ["SC", "Seychelles"], 43196 ["SL", "Sierra Leone"], 43197 ["SG", "Singapore"], 43198 ["SX", "Sint Maarten"], 43199 ["SK", "Slovakia"], 43200 ["SI", "Slovenia"], 43201 ["SB", "Solomon Islands"], 43202 ["SO", "Somalia"], 43203 ["ZA", "South Africa"], 43204 ["GS", "South Georgia and the South Sandwich Islands"], 43205 ["OS", "South Ossetia"], 43206 ["SS", "South Sudan"], 43207 ["ES", "Spain"], 43208 ["LK", "Sri Lanka"], 43209 ["SD", "Sudan"], 43210 ["SR", "Suriname"], 43211 ["SJ", "Svalbard and Jan Mayen"], 43212 ["SZ", "Swaziland"], 43213 ["SE", "Sweden"], 43214 ["CH", "Switzerland"], 43215 ["SY", "Syrian Arab Republic"], 43216 ["TW", "Taiwan, Province of China"], 43217 ["TJ", "Tajikistan"], 43218 ["TZ", "Tanzania, United Republic Of"], 43219 ["TH", "Thailand"], 43220 ["TL", "Timor-Leste"], 43221 ["TG", "Togo"], 43222 ["TK", "Tokelau"], 43223 ["TO", "Tonga"], 43224 ["TT", "Trinidad and Tobago"], 43225 ["TN", "Tunisia"], 43226 ["TR", "Turkey"], 43227 ["TM", "Turkmenistan"], 43228 ["TC", "Turks and Caicos Islands"], 43229 ["TV", "Tuvalu"], 43230 ["UG", "Uganda"], 43231 ["UA", "Ukraine"], 43232 ["AE", "United Arab Emirates"], 43233 ["GB", "United Kingdom"], 43234 ["US", "United States"], 43235 ["UM", "United States Minor Outlying Islands"], 43236 ["UY", "Uruguay"], 43237 ["UZ", "Uzbekistan"], 43238 ["VU", "Vanuatu"], 43239 ["VE", "Venezuela"], 43240 ["VN", "Vietnam"], 43241 ["VG", "Virgin Islands, British"], 43242 ["VI", "Virgin Islands, U.S."], 43243 ["WF", "Wallis and Futuna"], 43244 ["EH", "Western Sahara"], 43245 ["YE", "Yemen"], 43246 ["ZM", "Zambia"], 43247 ["ZW", "Zimbabwe"], 43248 ["AX", "Åland Islands"] 43249]; 43250/*globals myDeco, $, LayoutController, window, document, KEY */ 43251/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 43252 sloppy: true, white: true */ 43253/** 43254 * @fileOverview FPE Layout controller 43255 */ 43256 43257var FpeLayoutController = LayoutController.extend({ 43258 /** 43259 * Controller type 43260 * @type {String} 43261 * @static 43262 */ 43263 $type: 'layout', 43264 43265 /** 43266 * Layout controller init 43267 * @param {Object} model FPE model 43268 * @param {Object} settings Controller settings 43269 */ 43270 init: function(model, settings) { 43271 this._super(model, settings); 43272 var self = this, 43273 saveTimestamp = 0; 43274 43275 if (this._model.checkFloorPermissions) { 43276 this._model.checkFloorPermissions(false); 43277 } 43278 43279 myDeco.body 43280 .bind('roomSaveRequest', function(e, options) { 43281 var time = $.now(), 43282 model = myDeco.App.model; 43283 }) 43284 .bind('selectColor', function() { 43285 $('#sidebar').find('.sidebar-buttons ul.tabs').trigger('show', ['decorate']); 43286 // false means reset facet before apply 43287 myDeco.body.trigger('controller-facet-refine', 43288 ['materials-search', 43289 'class', 43290 [false, "paint", "class,paint", 0] 43291 ]); 43292 }) 43293 .delegate('.gts', 'click', function() { 43294 var src = myDeco.settings.AUSTIN_GTS_SRC, 43295 element = $(this),
43296 data = element.data('gts'); 43297 43298 if (typeof data !== 'undefined') { 43299 if (myDeco.settings.WL) { 43300 src = myDeco.settings.WL.WL_MYDECO_COM_REPLACEMENT + src; 43301 } 43302 43303 if (element.hasClass('details')) { 43304 self._model.operationStart(['showdetails', data.category, src, data.price]); 43305 } else { 43306 myDeco.buynow.buynowClick(this, data.guid, data.price, data.category, src); 43307 } 43308 } 43309 }) 43310 .bind('status roomLoading roomLoaded roomLoadError roomSaving ' + 43311 'roomSaved roomSavingError', 43312 function(e, data1, data2, data3) { 43313 var text, timeout; 43314 43315 switch (e.type) { 43316 case 'status': 43317 text = data1 || ''; 43318 timeout = data2; 43319 break; 43320 43321 case 'roomLoading': 43322 if (typeof data1 === 'string') { 43323 text = gettext('Room is loading...'); 43324 timeout = 0; 43325 window._roomLoading = true; 43326 } 43327 break; 43328 43329 case 'roomLoaded': 43330 if (!data1 && self._model._ws_url) { 43331 text = gettext('Room loaded successfully'); 43332 if (self._model._masking) { 43333 myDeco.body 43334 .trigger('toggleSidebar', ['build']) 43335 .find('#view-build .build-tabs') 43336 .trigger('show', ['floor']); 43337 } 43338 window._roomLoading = false; 43339 } 43340 break; 43341 43342 case 'roomLoadError': 43343 text = gettext('Error while room loading, please try later'); 43344 timeout = 30; 43345 break; 43346 43347 case 'roomSaving': 43348 text = gettext('Saving your room...'); 43349 timeout = 0; 43350 break; 43351 43352 case 'roomSaved': 43353 text = $('<span>' + gettext('Your design has been saved successfully.') + '</span>'); 43354 if (!data3.hqRender) { 43355 text.append($('<a href="" />') 43356 .text(gettext('Get a photo-quality image of your room.')) 43357 .click(function(e) { 43358 e.stop(); 43359 43360 self.status(); 43361 43362 myDeco.body.trigger('roomSaveRequest', [{ 43363 showProperties: true, 43364 hqRender: true 43365 }]); 43366 }) 43367 ); 43368 } 43369 timeout = 30; 43370 break; 43371 43372 case 'roomSavingError': 43373 text = gettext('Error while room saving, please try later'); 43374 timeout = 30; 43375 break; 43376 } 43377 43378 if (text !== undefined) { 43379 self.status(text, timeout); 43380 } 43381 }); 43382 43383 $(document) 43384 .keydown(this.keyDown.bind(this)) 43385 .keypress(this.keyPress.bind(this)) 43386 .keyup(this.keyUp.bind(this)); 43387 43388 if (!myDeco.browser.isMobileSafari) { 43389 myDeco.body.bind('mousedown selectstart dragstart', 43390 function(e) { 43391 if (self.tagNames.indexOf(' ' + e.target.tagName + ' ') === -1) { 43392 e.preventDefault(); 43393 } 43394 }); 43395 } else { 43396 // longpress emulation for iPad (doubleclick replacement) 43397 myDeco.body.bind('mousedown', function(e) { 43398 var timeout = window.setTimeout(function() { 43399 var event = new $.Event(e); 43400 event.type = 'dblclick'; 43401 $(e.target).trigger(event); 43402 }, 1000); 43403 43404 myDeco.body.one('mouseup', function() { 43405 window.clearTimeout(timeout); 43406 }); 43407 }); 43408 } 43409 43410 43411 43412 this._status.element = $('#messages'); 43413 this._status.id = 0; 43414 this._status.top = parseInt(this._status.element.css('top'), 10); 43415 this._status.height = this._status.element.outerHeight(true); 43416 this._status.element.click(function(e) { 43417 e.preventDefault(); 43418 self.status(); 43419 }); 43420 43421 }, 43422 43423 /** 43424 * Register view 43425 * @param {Object} view View associated to controller 43426 */ 43427 registerView: function(view) { 43428 this._super(view); 43429 var self = this, 43430 wallHint = $.cookie('wall-hint'), 43431 wallHintFunction = function(e, event, visual) { 43432 if (visual._model.type === 'wall') { 43433 var bubble = $('.bubble.drag-wall').rearrange({position: [event.pageX, event.pageY]}).fadeIn(500); 43434 myDeco.body.one('mousedown', function() { 43435 bubble.fadeOut(500); 43436 }); 43437 myDeco.body.unbind('highlightOn', wallHintFunction); 43438 $.cookie('wall-hint', '1', {path: window.location.pathname}); 43439 } 43440 }, 43441 typeButtons = view.canvasElement.find('.type-buttons .button'), 43442 switchView = function(type) { 43443 type = type || (typeButtons.filter('.active') 43444 .hasClass('floor') ? 'isometric' : 'floor'); 43445 43446 myDeco.body.trigger('viewToggle', [type]); 43447 }, 43448 zoom = function(enlarge) { 43449 myDeco.body.trigger(enlarge ? 'zoomIn' : 'zoomOut'); 43450 }, 43451 lockButton = view.app.find('#view-units .lock-plan').click(function(e) { 43452 e.stop(); 43453 43454 var button = $(this); 43455 button.toggleClass('active'); 43456 43457 self._model.toggleLock(button.hasClass('active')); 43458 }), 43459 lockTimeout, 43460 controlsPane = view.app.find('#view-units'); 43461 43462 myDeco.body 43463 .bind('entity-drag-started', this.entityDrag.bind(this)) 43464 .bind('loadRoom', function() { 43465 myDeco.body.trigger('viewToggle', ['floor']); 43466 typeButtons.removeClass('active').filter('.floor').addClass('active'); 43467 }) 43468 .bind('actionRequest', function(e, action) { 43469 if (action[0] === 'switchview') { 43470 switchView(); 43471 } else if (action[0] === 'zoomin') { 43472 zoom(true); 43473 } else if (action[0] === 'zoomout') { 43474 zoom(false); 43475 } 43476 }).bind('locked-move', function() { 43477 var n = 6, 43478 toggleFunc; 43479 43480 if (lockTimeout) { 43481 window.clearTimeout(lockTimeout); 43482 lockButton.removeClass('attention'); 43483 } 43484 43485 toggleFunc = function() { 43486 lockButton.toggleClass('attention'); 43487 n = n - 1; 43488 if (n > 0 && lockButton.hasClass('active')) { 43489 lockTimeout = window.setTimeout(toggleFunc, 200); 43490 } else { 43491 lockButton.removeClass('attention'); 43492 } 43493 }; 43494 43495 toggleFunc(); 43496 }) 43497 .bind('view-toggle', function(e, view, state) { 43498 if (state) { 43499 typeButtons 43500 .removeClass('active') 43501 .filter('.' + view.$type) 43502 .addClass('active'); 43503 } 43504 43505 if (view.$type === 'floor') { 43506 controlsPane.toggle(state); 43507 } 43508 }); 43509 43510 if (!wallHint) { 43511 myDeco.body.bind('highlightOn', wallHintFunction); 43512 } 43513 43514 view.app 43515 .mouseout(function(e) { 43516 var offset = self._view.getAppOffset(), 43517 dimensions = self._view.getAppDimensions(); 43518 43519 // reset operation if pointer is out of app 43520 if (e.pageX < offset.left || e.pageX > offset.left + dimensions.width || 43521 e.pageY < offset.top || e.pageY > offset.top + dimensions.height) { 43522 self._model.operationReset(); 43523 } 43524 }); 43525 43526 view.canvasElement.find('.zoom-in, .zoom-out').bind('click', function(e) { 43527 e.stop(); 43528 zoom($(this).hasClass('zoom-in')); 43529 }); 43530 43531 typeButtons.click(function(e) { 43532 e.stop(); 43533 switchView($(this).hasClass('isometric') ? 'isometric' : 'floor'); 43534 }); 43535 }, 43536 43537 /** 43538 * Global mouse move event handler for serving drag-n-drop between sidebar and canvases 43539 * @param {Event} e Mousemove event 43540 */ 43541 globalMouseMove: function(e) { 43542 if (this._operation.type === 'add') { 43543 e.preventDefault(); 43544 var position = this._operation.data.visual.data('position') || 43545 {x: this._operation.data.ox, y: this._operation.data.oy}; 43546 43547 this._operation.data.visual.css({ 43548 left: e.pageX - position.x + 'px', 43549 top: e.pageY - position.y + 'px' 43550 }); 43551 } 43552 }, 43553 43554 /** 43555 * Callback for resetting current operation state 43556 */ 43557 operationReset: function() { 43558 if (this._operation.type === 'add' && this._operation.data.visual) { 43559 this._operation.data.visual.remove(); 43560 } 43561 43562 this._operation = {type: '', data: {}}; 43563 }, 43564 43565 /** 43566 * Handler for custom entity-drag-started event. Creates a DOM wrapper for 43567 * draggable element and does some visual machinery. 43568 * @param {Event} e entity-drag-started event 43569 * @param {Object} draggable Info about draggable entity and it's visual representation 43570 * @param {Event} originalEvent Original event which caused start of drag-n-drop 43571 */ 43572 entityDrag: function(e, draggable, originalEvent) { 43573 this._view.app
43574 .append(draggable.visual); 43575 43576 this._operation = { type: 'add', 43577 data: { 43578 entity: draggable.entity, 43579 visual: draggable.visual, 43580 ox: draggable.ox || draggable.visual.width() / 2, 43581 oy: draggable.oy || draggable.visual.height() / 2 43582 } 43583 }; 43584 43585 draggable.visual 43586 .css({ 43587 left: (originalEvent.pageX || 0) - this._operation.data.ox + 'px', 43588 top: (originalEvent.pageY || 0) - this._operation.data.oy + 'px', 43589 position: 'absolute' 43590 }); 43591 }, 43592 43593 /** 43594 * Send feedback to backend, assume all data is correct (validated with formValidation) 43595 * @param {Object} data Feedback data 43596 */ 43597 sendFeedback: function(data) { 43598 var feedbackData = { 43599 message: data.message + 43600 '\n\nOverall rating: ' + (data.rating || 'Not rated') + 43601 '\n\nAustin referrer: ' + window.document.referrer + 43602 '\n\nRoom stats' + 43603 '\nProducts (unique/total): ' + myDeco.App.model.uniqueComponentIds().length + ' / ' + 43604 myDeco.App.model.components.length + 43605 '\nMaterials (unique/total): ' + myDeco.App.model.uniqueMaterialIds().length + ' / ' + 43606 myDeco.App.model.materials.length + 43607 '\nRooms: ' + myDeco.App.model.shapes.length + 43608 '\nWalls: ' + myDeco.App.model.walls.length, 43609 context_data: { 43610 username: myDeco.username 43611 } 43612 }, 43613 inexistent; 43614 43615 if (data.email) { 43616 feedbackData.email = data.email; 43617 } 43618 43619 if (myDeco.settings.AUSTIN_FEEDBACK_CATEGORY) { 43620 feedbackData.category = myDeco.settings.AUSTIN_FEEDBACK_CATEGORY; 43621 } 43622 43623 feedbackData.message = feedbackData.message + 43624 '\nRoom url: ' + (myDeco.App.model._ws_url || 'room is not saved yet'); 43625 43626 inexistent = $.keys(myDeco.App.model._componentsFailed); 43627 if (inexistent.length > 20) { 43628 inexistent = inexistent.slice(0, 20); 43629 } 43630 $.extend(feedbackData.context_data, { 43631 model: myDeco.App.model.getModel(2), 43632 history: myDeco.App.model.changeLog(10), 43633 inexistentGUIDs: inexistent 43634 }); 43635 43636 feedbackData.context_data = JSON.stringify(feedbackData.context_data); 43637 43638 $.ajax({ 43639 url: myDeco.urls.feedback_post_ajax, 43640 type: 'post', 43641 dataType: 'json', 43642 data: feedbackData, 43643 success: this._view.feedbackSuccess.bind(this._view), 43644 error: function() { 43645 myDeco.popin.close(); 43646 } 43647 }); 43648 }, 43649 43650 /** 43651 * Global document observer, used to prevent default browser behavior on backspace. 43652 * @param {Event} e 43653 */ 43654 keyDown: function(e) { 43655 // prevent to stop animation etc 43656 if (e.keyCode === KEY.Esc) { 43657 e.preventDefault(); 43658 } 43659 43660 // prevent to deletion if key pressed in input fields 43661 // pass through any special events with modifiers 43662 if (!e.ctrlKey && !e.altKey && !e.metaKey && 43663 this.tagNames.indexOf(' ' + e.target.tagName + ' ') !== -1) { 43664 return; 43665 } 43666 43667 // under win repeated event is keydown 43668 var kdr = window.navigator.platform.indexOf('Win') !== -1 && 43669 ($.browser.msie || $.browser.mozilla), 43670 action; 43671 43672 43673 if (!this.action) { 43674 action = $.first((this.settings.shortcuts || []), function(cmd) { 43675 return (e.keyCode === cmd[1] && e.shiftKey === cmd[2] && 43676 (e.ctrlKey === cmd[3] || e.metaKey === cmd[3]) && e.altKey === cmd[4]); 43677 }); 43678 43679 if (action && myDeco.settings.WL && myDeco.settings.WL.WL_DISABLED_SHORTCUTS && 43680 JSON.parse(myDeco.settings.WL.WL_DISABLED_SHORTCUTS).indexOf(action[0]) > -1) { 43681 action = undefined; 43682 } 43683 } 43684 43685 // prevent default behavior of some keys to 43686 // disable default browser reaction 43687 // eg. scrolling of focused layers or move back in browser history 43688 if (this.action || action || 43689 e.keyCode === KEY.Up || e.keyCode === KEY.Down || 43690 e.keyCode === KEY.BackSpace) { 43691 e.preventDefault(); 43692 } 43693 43694 if (action || this.action) { 43695 if (!this.action || (this.action[5] && kdr)) { 43696 myDeco.body.trigger('actionRequest', [this.action || action]); 43697 } 43698 this.action = this.action || action; 43699 } 43700 }, 43701 43702 /** 43703 * Keypress listener 43704 * @param {Event} e 43705 */ 43706 keyPress: function() { 43707 if (this.action && this.action[5]) { 43708 myDeco.body.trigger('actionRequest', [this.action]); 43709 } 43710 }, 43711 43712 /** 43713 * Keyup listener 43714 * @param {Event} e 43715 */ 43716 keyUp: function() { 43717 this.action = false;
43718 }, 43719 43720 /** 43721 * Show status message in app 43722 * @param {jQset|String} message Message to show, will close if omitted 43723 * @param {Number} timeout Timeout to hide message, will show endless 43724 * message if less or equals to zero and show WoD in 43725 * case of equals to zero. 43726 */ 43727 status: function(message, timeout) { 43728 var self = this, 43729 type = typeof message; 43730 43731 if (message && type !== 'number') { 43732 if (type === 'string') { 43733 message = $(document.createTextNode(message)); 43734 } 43735 43736 timeout = typeof timeout === 'undefined' ? 5 : timeout; 43737 43738 this._status.element 43739 .toggleClass('waiting', timeout === 0) 43740 .children('.message').html('').append(message).end() 43741 .animate({top: this._status.top + this._status.height}); 43742 43743 if (timeout > 0) { 43744 window.setTimeout(function() { 43745 self.status(++self._status.id); 43746 }, timeout * 1000); 43747 } 43748 } else if (!message || 43749 (type === 'number' && message === this._status.id)) { 43750 this._status.element.animate({top: this._status.top}); 43751 } 43752 } 43753}); 43754 43755myDeco.App.controllers.push(FpeLayoutController); 43756/*globals myDeco, $, AppView */ 43757/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 43758 sloppy: true, white: true */ 43759/** 43760 * @fileOverview Layout View for FPE app 43761 */ 43762var LayoutView = AppView.extend({ 43763 /** 43764 * View type 43765 * @type {String} 43766 * @public 43767 * @static 43768 */ 43769 $type: 'layout', 43770 43771 /** 43772 * Init view 43773 * @param {Object} options View options 43774 */ 43775 init: function(options) { 43776 this._super(options); 43777 if($.fn.unitsSelector) { 43778 this.app.find('.units').unitsSelector(); 43779 } 43780 43781 // $('#app > .feedback').click(this.showFeedback.bind(this)); 43782 }, 43783 43784 showFeedback: function(e) { 43785 e.stop(); 43786 var self = this; 43787 myDeco.popin.open({ 43788 width: 760, 43789 url: '#view-' + this.$type + '-feedback', 43790 // enable template 43791 context: {}, 43792 afterOpen: function(content) { 43793 myDeco.formValidation.init(content); 43794 myDeco.buttons.initSubmitButtons(content.find('form').submit(function(e) { 43795 if (!e.isPropagationStopped()) { 43796 e.stop(); 43797 self._controller.sendFeedback({ 43798 rating: content.find('input[checked]').val() || '', 43799 email: content.find('[name=email]').val() || '', 43800 message: $.trim(content.find('textarea').val()) 43801 }); 43802 content.find('.loading').hide().show(); 43803 } 43804 }).find('input:submit')); 43805 } 43806 }); 43807 }, 43808 43809 feedbackSuccess: function(data) { 43810 myDeco.popin.content().find('.loading').hide(); 43811 myDeco.popin.close(); 43812 43813 if (data.success) { 43814 myDeco.popin.message({
43815 title: getttext('Thank you'), 43816 message: gettext('We appreciate your feedback.'), 43817 closeIn: 5 43818 }); 43819 } 43820 }, 43821 43822 feedbackError: function(text) { 43823 myDeco.popin.content().find('.loading').hide(); 43824 myDeco.popin.message({ 43825 title: 'Error', 43826 message: text 43827 }); 43828 } 43829}); 43830 43831myDeco.App.views.push(LayoutView); 43832/*globals Class, window, $, myDeco */ 43833/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 43834 sloppy: true, white: true */ 43835/** 43836 * @fileOverview Model class for Room management 43837 * Extendable with various set of backends 43838 */ 43839var RoomManagementModel = Class.extend({ 43840 /** 43841 * Component type 43842 * @type {String} 43843 */ 43844 $type: 'room-management', 43845 43846 /** 43847 * Type of rooms, defaults to '3d' 43848 * @type {String} 43849 */ 43850 _type: '3d', 43851 43852 /** 43853 * Storage for loaded rooms 43854 * @type {Array} 43855 */ 43856 rooms: [], 43857 43858 /** 43859 * List of room snbapshots, used for snapshots mode 43860 * @type {Array} 43861 */ 43862 snapshots: [], 43863 43864 /** 43865 * Start rank 43866 * @type {Number} 43867 */ 43868 start: 0, 43869 43870 /** 43871 * Default limit on loading 43872 * @type {Number} 43873 */ 43874 limit: 0, 43875 43876 /** 43877 * Flag for room loading availability 43878 * @type {Boolean} 43879 */ 43880 isLoadingAvailable: true, 43881 43882 /** 43883 * @constructor 43884 * @base {Class} 43885 * @param {String} type Room type 43886 * @public 43887 */ 43888 init: function (type) { 43889 if (type) { 43890 this._type = type; 43891 } 43892 }, 43893 43894 /** 43895 * Default errback for ajax operations 43896 * @type {Function} 43897 */ 43898 errback: function() { 43899 window.alert(gettext('Seems that something is happened with our service. Please try again later, sorry.')); 43900 }, 43901 43902 /** 43903 * Get rooms list from backend 43904 * @param {Function} callback 43905 * @param {Boolean} append Flag indicated append loading 43906 */ 43907 load: function(callback, append) { 43908 var self = this; 43909 43910 append = !!append; 43911 43912 if (!append) { 43913 this.reset(); 43914 } 43915 43916 if (!append || this.isLoadingAvailable) { 43917 $.ajax({ 43918 type: 'get', 43919 url: myDeco.urls.ws.userRooms, 43920 data: { 43921 api: 2, 43922 type: this._type, 43923 startrank: this.start, 43924 count: this.limit 43925 }, 43926 dataType: 'json', 43927 success: function(data) { 43928 var rooms = self.parse(data); 43929 self.rooms.push.apply(self.rooms, rooms); 43930 self.isLoadingAvailable = rooms.length === self.limit; 43931 self.start = self.rooms.length; 43932 callback.call(null, rooms); 43933 }, 43934 error: this.errback 43935 }); 43936 } else { 43937 callback.call(null, []); 43938 } 43939 }, 43940 43941 /** 43942 * Parse WS response (replace html entites etc) and return array of rooms 43943 * @param {Array} data Response from WS 43944 * @returns {Array} 43945 */ 43946 parse: function(data) { 43947 return $.map(data, function(room) { 43948 room[1].url = room[0]; 43949 room[1].name = room[1].name.fromHtml().replace(/^\s+/, ' '); 43950 room[1].category = room[1].category.replace(/'/g, "'"); 43951 43952 /** 43953 * Rooms and floor plans improvements it works only for 1 user @mask_monkey@ 43954 */ 43955 if (myDeco.username && myDeco.username === 'mask_monkey' && room[1].locale && room[1].locale != 'en-gb-x-retail') { 43956 room[1].name = room[1].locale.split('-').pop().toUpperCase() + ' - ' + room[1].name; 43957 room[1].isWL = true; 43958 } 43959 43960 return room[1]; 43961 }); 43962 }, 43963 43964 /** 43965 * Model reset to defaults 43966 */ 43967 reset: function() { 43968 this.isLoadingAvailable = true; 43969 this.rooms = []; 43970 this.start = 0; 43971 }, 43972 43973 /** 43974 * Fetch room snapshots 43975 * @param {Object} room 43976 * @param {Function} callback 43977 */ 43978 loadSnapshots: function(room, callback) { 43979 var self = this; 43980 43981 $.ajax({ 43982 type: 'get', 43983 url: myDeco.urls.ws.userRooms, 43984 data: {history: room.id, api: 2}, 43985 dataType: 'json', 43986 success: function(data) { 43987 self.snapshots = self.parse(data); 43988 callback(self.snapshots); 43989 } 43990 }); 43991 }, 43992 43993 /** 43994 * Save data for room 43995 * @param {Object} room 43996 * @param {Function} callback 43997 */ 43998 save: function(room, callback) { 43999 var url = room.url + 'attributes/', 44000 data = {}; 44001 44002 // denylisted attributes 44003 $.each(['category', 'style', 'name', 'public'], function(i, name) { 44004 data[name] = room[name]; 44005 }); 44006 44007 $.ajax({ 44008 type: 'put', 44009 contentType: 'application/json', 44010 data: JSON.stringify(data), 44011 dataType: 'json', 44012 url: url, 44013 success: function() { 44014 myDeco.body.trigger('roomMetadata', function(metadata, id) { 44015 if (room.id === id) { 44016 myDeco.body.trigger('roomMetadata', data); 44017 } 44018 }); 44019 callback.call(); 44020 }, 44021 error: this.errback 44022 }); 44023 }, 44024 44025 /** 44026 * Remove specified room by ID 44027 * @param {Object} room 44028 * @param {Function} callback 44029 * @param {Boolean} snapshot Snapshot mode flag 44030 */ 44031 remove: function(room, callback, snapshot) { 44032 var self = this, 44033 source = this[snapshot ? 'snapshots' : 'rooms'], 44034 index = source.indexOf(room); 44035 44036 $.ajax({ 44037 type: 'delete', 44038 data: '{}', 44039 dataType: 'json', 44040 url: room.url, 44041 success: function() { 44042 if (!snapshot) { 44043 self.start -= 1; 44044 } 44045 callback.call(null, index); 44046 source.splice(index, 1); 44047 }, 44048 error: this.errback 44049 }); 44050 } 44051}); 44052/*globals AppController, myDeco, $, window */ 44053/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 44054 sloppy: true, white: true */ 44055/** 44056 * @fileOverview Controller class for room management 44057 * Will bind to a 'roomOpenBrowser' event 44058 * 44059 * Triggers: 44060 * loadRoom - force ME model to load new room 44061 * {String} room url] 44062 * 44063 * Listens: 44064 * roomSaved - on ME model room loaded 44065 * roomOpenRequest - open room browser 44066 * roomSaveRequest - save currently opened room 44067 */ 44068var RoomManagementController = AppController.extend({ 44069 44070 /** 44071 * Component type 44072 * @type {String} 44073 * @public 44074 * @static 44075 */ 44076 $type: 'room-management', 44077 44078 /** 44079 * List type, default to normal 44080 * @type {String} 44081 */ 44082 type: 'normal', 44083 44084 /** 44085 * Operation mode, default or 'snapshots' 44086 * @type {String} 44087 */ 44088 mode: '', 44089 44090 /** 44091 * Options passed to save dispatcher 44092 * @type {Object} 44093 */ 44094 _options: {}, 44095 44096 /** 44097 * Storage for delayed action executed after successful save 44098 * @type {Function} 44099 */ 44100 _saveAction: false, 44101 44102 /** 44103 * Register view in controller 44104 * @param {AppView} view 44105 */ 44106 registerView: function(view) { 44107 var self = this; 44108 44109 this._super(view); 44110 44111 if (myDeco.settings.WL) { 44112 this.type = 'extended'; 44113 } 44114 44115 myDeco.body 44116 .bind('roomOpenRequest', function() { 44117 myDeco.user.loginRequired(function() { 44118 self._view.openRooms(); 44119 self.registerControls(); 44120 self.mode = ''; 44121 44122 // we need to use timeout here because right after show() 44123 // content is still invisible (fadeIn) 44124 window.setTimeout(function() { 44125 self.loadRooms(); 44126 }, 200); 44127 }); 44128 }) 44129 .bind('roomLoadRequest', function(e, url, callback, noClosePo
44129pup) { 44130 var loadRoom = function() { 44131 myDeco.body.trigger('loadRoom', [url]); 44132 if (callback) { 44133 callback.call(); 44134 } 44135 }; 44136 44137 if (myDeco.App.model.history.counter === 0 || noClosePopup) { 44138 loadRoom(); 44139 } else { 44140 // ask user about unsaved changes and run run loading 44141 myDeco.popin.confirm({ 44142 title: gettext('Unsaved project'), 44143 message: gettext('You have an unsaved project. Do you want to save it now?'), 44144 yesButton: function() { 44145 myDeco.body.trigger('roomSaveRequest', {callback: loadRoom}); 44146 }, 44147 noButton: loadRoom 44148 }); 44149 } 44150 }) 44151 .bind('roomSaveRequest', function(e, options) { 44152 var user_options = options.user_options, 44153 m = myDeco.App.model; 44154 44155 if (m._metadata.type != 'montage' && !m._masking && m.walls.length == 0 && m.components.length == 0) { 44156 myDeco.body.trigger('status', 'Cannot save an empty room', 3); 44157 var msg = {name: "error", code: 1, message: "room is empty"}; 44158 window.parent.postMessage(msg, "*"); 44159 window.parent.postMessage(JSON.stringify(msg), "*"); // IE9 44160 return; 44161 } 44162 44163 self._options = options || {}; 44164 self._options.forceNew = self._options.forceNew || myDeco.App.model.isNew; 44165 self._options.hqRender = self._options.hqRender || (user_options && user_options.hq_render) || false; 44166 self._saveAction = $.isFunction(self._options.callback) ? self._options.callback : false; 44167 44168 myDeco.user.loginRequired(function() { 44169 myDeco.body.trigger('roomMetadata', [function(room) { 44170 if (!room.category && myDeco.settings.WL && myDeco.settings.WL.DEFAULT_ROOM_CATEGORY) { 44171 room.category = myDeco.settings.WL.DEFAULT_ROOM_CATEGORY; 44172 } 44173 if (user_options && user_options.room_attributes) { 44174 $.extend(room, user_options.room_attributes); 44175 if (myDeco.settings.ROOM_CATEGORY_LIST.indexOf(room.category) == -1) { 44176 room.category = 'Other'; 44177 } 44178 } 44179 if ((self._options.forceNew || self._options.showProperties) && !(user_options && user_options.hide_dialog)) { 44180 self._view.save(room, [], self._options.forceNew); 44181 } else { 44182 self.saveRoom(room); 44183 } 44184 }]); 44185 }, 'register-mydeco'); 44186 }) 44187 .bind('actionRequest', function(e, action) { 44188 switch (action) { 44189 case 'open': 44190 myDeco.body.trigger('roomOpenRequest'); 44191 break; 44192 case 'save': 44193 case 'saveas': 44194 myDeco.body.trigger('roomSaveRequest', 44195 [{forceNew: action === 'saveas'}]); 44196 break; 44197 } 44198 }) 44199 .bind('roomSaving', function() { 44200 myDeco.App.model.getModel(); 44201 }); 44202 }, 44203 44204 /** 44205 * Register controls in open popup 44206 */ 44207 registerControls: function() { 44208 var self = this, 44209 getRoom = function(e) { 44210 var element = $(e.target).closest('li, .item'), 44211 index, source = 'rooms'; 44212 44213 if (element.get(0).tagName === 'LI') { 44214 source = self.mode ? 'snapshots' : 'rooms'; 44215 index = self._view[source].children().index(element); 44216 } else { 44217 index = parseInt(element.data('index'), 10); 44218 } 44219 return self._model[source][index]; 44220 }; 44221 44222 // back button 44223 this._view._canvas.find('.back').click(function(e) { 44224 e.stop(); 44225 44226 self._view._canvas.removeClass(self.mode); 44227 self.mode = ''; 44228 self._view.render(); 44229 }); 44230 44231 this._view._canvas.find('.list-type') 44232 .removeClass('selected') 44233 .click(function(e) { 44234 e.stop(); 44235 44236 var button = $(this), 44237 type = button.data('type'); 44238 44239 if (!button.hasClass('selected')) { 44240 self.type = type; 44241 if (self.mode) { 44242 self._view.render(); 44243 } else { 44244 self.loadRooms(); 44245 } 44246 button 44247 .addClass('selected') 44248 .siblings().removeClass('selected'); 44249 } 44250 }) 44251 .filter('.' + this.type) 44252 .addClass('selected'); 44253 44254 this._view._holder.bind('load', function() { 44255 self.loadRooms(true); 44256 }); 44257 44258 // load room click handler 44259 this._view._canvas 44260 .delegate('.load', 'click', function(e) { 44261 e.stop(); 44262 var room = getRoom(e), 44263 url = room.latest_snapshot || room.url; 44264 44265 // do not move close after trigger it will cause 44266 // problems with next popup opening 44267 self._view.close(); 44268 myDeco.body.trigger('roomLoadRequest', [url]); 44269 }) 44270 .delegate('.image', 'mouseenter mouseleave', function(e) { 44271 $(this).children('.hover').toggle(e.type === 'mouseenter'); 44272 }) 44273 .delegate('.snapshot', 'click', function(e) { 44274 e.stop(); 44275 44276 var room = getRoom(e); 44277 44278 self.mode = 'snapshots';
44279 self._view.openSnapshots(room); 44280 self._view.loading(true); 44281 self._model.loadSnapshots(room, function() { 44282 self._view.render(); 44283 self._view.loading(false); 44284 }); 44285 }) 44286 .delegate('.yoostyler', 'click', function(e) { 44287 e.stop(); 44288 var room = getRoom(e); 44289 //var socket = new WebSocket("ws://192.168.178.54:3000/ipad"); 44290 var socket = new WebSocket("ws://10.0.0.147:3000/ipad"); 44291 socket.onerror = function(e) { 44292 console.log("WebSocket error", e) 44293 } 44294 socket.onclose = function(e) { 44295 console.log("WebSocket closed", e) 44296 } 44297 socket.onopen = function(e) { 44298 socket.send( 44299 JSON.stringify( { 44300 kind: 'command', 44301 name: 'load room', 44302 extra: { room_id: room.id, user_id: myDeco.userid } 44303 } ) 44304 ); 44305 self._view.close(); 44306 } 44307 }) 44308 .delegate('.delete', 'click', function(e) { 44309 e.stop(); 44310 var room = getRoom(e), 44311 source = self.mode ? 'snapshots' : 'rooms'; 44312 44313 if (window.confirm('Do you really want to remove room «' + 44314 room.name.toHtml() + '»?')) { 44315 44316 self._view.loading(true); 44317 self._model.remove(room, function(index) { 44318 self._view[source].children().eq(index).remove(); 44319 if (self.mode) { 44320 self._view.loading(false); 44321 } else { 44322 self.loadRooms(true, 1); 44323 } 44324 }, self.mode); 44325 } 44326 }) 44327 .delegate('.visibility', 'click', function(e) { 44328 e.stop(); 44329 var link = $(this), 44330 room = getRoom(e); 44331 44332 room['public'] = link.hasClass('public') ? 0 : 1; 44333 self._model.save(room, function() { 44334 link.siblings('.visibility').andSelf().toggle(); 44335 if (self.mode) { 44336 self._view.replaceRoom(room); 44337 } 44338 }); 44339 }) 44340 .delegate('select.category, select.style', 'change', function(e) { 44341 var room = getRoom(e), 44342 selector = $(this).attr('disabled', 'disabled'), 44343 type = selector.attr('class'); 44344 44345 room[type] = selector.val(); 44346 self._model.save(room, function() { 44347 selector.removeAttr('disabled'); 44348 if (self.mode) { 44349 self._view.replaceRoom(room); 44350 } 44351 }); 44352 }); 44353 }, 44354 44355 /** 44356 * Load rooms list method 44357 * @param {Boolean} append Append flag 44358 * @param {Number} limit Number of items to fetch 44359 */ 44360 loadRooms: function(append, limit) { 44361 var self = this; 44362 44363 append = !!append; 44364 44365 this._model.limit = limit || 44366 this._view.getLimit() * (2 - +append); 44367 44368 this._view.loading(true); 44369 this._model.load(function(data) { 44370 self._view.render(data, append); 44371 self._view.loading(false); 44372 }, append); 44373 }, 44374 44375 /** 44376 * Save room 44377 * @param {Object} data Room metadata 44378 * @param {Array} share List of social networks room share to 44379 */ 44380 saveRoom: function(room, share) { 44381 room = room || {}; 44382 share = share || []; 44383 44384 var self = this, 44385 errors = $.map(['name', 'category'], function(field) { 44386 return room[field] ? null : field; 44387 }), 44388 saveSuccess, 44389 saveError = function(e, code, status, message) { 44390 myDeco.body.unbind('roomSaved', saveSuccess); 44391 self._view.showError(gettext('Please try again'), message); 44392 }; 44393 saveSuccess = function() { 44394 var i; 44395 44396 myDeco.body.unbind('roomSavingError', saveError); 44397 44398 // lets share something 44399 for (i = share.length - 1; i >= 0; i--) { 44400 myDeco.sharethis['open' + share[i].capitalize()]
44401 .call(myDeco.sharethis, myDeco.App.model.weburl); 44402 } 44403 44404 if ((self._options.forceNew || self._options.showProperties) && 44405 !(myDeco.settings.WL && myDeco.settings.WL.NO_SAVE_SUCCESS_POPUP)) { 44406 self._view.success(!room['public']); 44407 } 44408 44409 // execute planned action after saving 44410 if (self._saveAction) { 44411 self._saveAction(); 44412 self._saveAction = false; 44413 } 44414 }; 44415 44416 if (errors.length === 0) { 44417 this._view.close(); 44418 myDeco.body 44419 .trigger('roomMetadata', room) 44420 .one('roomSaved', saveSuccess) 44421 .one('roomSavingError', saveError) 44422 .trigger('saveRoom', { 44423 forceNew: this._options.forceNew || false, 44424 hqRender: this._options.hqRender || false 44425 }); 44426 } else { 44427 this._view.save(room, errors, this._options.forceNew); 44428 } 44429 } 44430}); 44431 44432myDeco.App.controllers.push(RoomManagementController); 44433/*globals AppView, $, myDeco, tmpl */ 44434/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 44435 sloppy: true, white: true */ 44436/** 44437 * @fileOverview View class for room management app component 44438 */ 44439var RoomManagementView = AppView.extend({ 44440 44441 /** 44442 * Component type 44443 * @type {String} 44444 */ 44445 $type: 'room-management', 44446 44447 /** 44448 * Active canvas holder 44449 * @type {jQuery} 44450 */ 44451 _canvas: null, 44452 44453 /** 44454 * Items holder 44455 * @type {jQuery} 44456 */ 44457 rooms: null, 44458 44459 /** 44460 * Loader holder (WoD) 44461 * @type {jQuery} 44462 */ 44463 _loader: null, 44464 44465 /** 44466 * Rooms list holder 44467 * @type {jQuery} 44468 */ 44469 _holder: null, 44470 44471 /** 44472 * Snapshots list 44473 * @type {jQuery} 44474 */ 44475 snapshots: null, 44476 44477 /** 44478 * flag do we need first empty option for new-created select 44479 * @type {Boolean} 44480 */ 44481 noEmpty: false, 44482 44483 /** 44484 * Make link as string for room visibility 44485 * @param {Object} room 44486 * @returns {String} 44487 */ 44488 _makeVisibilityLink: function(room) { 44489 var invisible = ' style="display:none"'; 44490 return '<a href="" class="visibility private"' + (room['public'] ? invisible : '') + '>' + gettext('Make public') + '</a>' + 44491 '<a href="" class="visibility public"' + (!room['public'] ? invisible : '') + '>' + gettext('Make private') + '</a>'; 44492 }, 44493 44494 /** 44495 * Returns jQset of constructed selector 44496 * @param {String} name Type of element 44497 * @param {Array} options 44498 * @param {String} value Value of the selected element 44499 * @returns {String} 44500 */ 44501 _makeSelector: function(name, options, value, id) { 44502 var type = name.toHtml(), 44503 text = '<select name="' + type + '" class="' + type + '"' 44504 + (id ? ' id="' + id + '"' : '') 44505 + '>' + (this.noEmpty ? '' : '<option></option>'), 44506 i, ilen, option; 44507 44508 for (i = 0, ilen = options.length; i < ilen; i++) { 44509 option = options[i].toHtml(); 44510 text += '<option value="' + option + '"' + (options[i] === value ? 44511 ' selected="selected"' : '') + '>' + gettext(option) + '</option>'; 44512 } 44513 44514 text += '</select>'; 44515 return text; 44516 }, 44517 44518 /** 44519 * Returns jQset of constructed selector 44520 * @param {String} name Type of element 44521 * @param {Array} options 44522 * @param {String} value Value of the selected element 44523 * @returns {String} 44524 */ 44525 _makeSelector2: function(name, options, value, id) { 44526 var type = name.toHtml(), opt, 44527 text = '<select name="' + type + '" class="' + type + '"' 44528 + (id ? ' id="' + id + '"' : '') 44529 + '>' + (this.noEmpty ? '' : '<option></option>'), 44530 i, ilen, _value, _text; 44531 44532 for (i = 0, ilen = options.length; i < ilen; i++) { 44533 opt = options[i]; 44534 44535 if ($.isArray(opt)) { 44536 _value = opt[0]; 44537 _text = opt[1]; 44538 } else { 44539 _value = options[i].toHtml(); 44540 _text = _value; 44541 } 44542 44543 text += '<option value="' + _value + '"' + (_value === value ? 44544 ' selected="selected"' : '') + '>' + _text + '</option>'; 44545 } 44546 44547 text += '</select>'; 44548 return text; 44549 }, 44550 44551 /** 44552 * Returns jQset of constructed selector 44553 * @param {String} name Type of element 44554 * @param {Array} options 44555 * @param {String} value Value of the selected element 44556 * @returns {String} 44557 */ 44558 _makeList: function(name, options, value) { 44559 var type = name.toHtml(), text = '', 44560 i, ilen, option, classname, value, selected; 44561 44562 for (i = 0, ilen = options.length; i < ilen; i++) { 44563 option = options[i]; 44564 _value = option.toHtml(); 44565 selected = (option === value || !value && option === 'Other'); 44566 classname = 'category-thumb ' + option.toLowerCase().split(' ')[0] + '-link' 44567 + (selected ? ' selected' : ''); 44568 44569 text += '<label class="' + classname + '">' + gettext(_value) 44570 + '<input type="radio" name="category" value="' + _value 44571 + (selected ? '" checked="checked' : '') + '" /></label>'; 44572 } 44573 44574 return text; 44575 }, 44576 44577 /** 44578 * Main show function, opens popup and fill internal variables with nodes 44579 */ 44580 openRooms: function() { 44581 var self = this; 44582 myDeco.popin.open({ 44583 url: '#view-' + this.$type + '-rooms', 44584 fullscreen: true, 44585 className: 'vrm-rooms', 44586 afterOpen: function(content) { 44587 self._canvas = content.children(); 44588 self._loader = self._canvas.find('.loading'); 44589 self._holder = self._canvas.find('.items-holder') 44590 .scrollLoader() 44591 .iscroll(); 44592 self.rooms = self._holder.find('.items'); 44593 self.snapshots = self._canvas.find('.snapshots-holder') 44594 .iscroll() 44595 .find('.items'); 44596 self._holder.find('.scroller').scrollTop(0); 44597 }, 44598 tracking: 'room-management' 44599 }); 44600 }, 44601 44602 /** 44603 * Function for calculation of limit of loaded items per request 44604 * @returns {Number} 44605 */ 44606 getLimit: function() { 44607 var result = 100; 44608 switch (this._controller.type) { 44609 case 'normal': 44610 result = Math.floor(this._holder.width() / 211) * 44611 Math.ceil(this._holder.height() / 250); 44612 break; 44613 case 'extended':
44614 result = Math.ceil(this._holder.height() / 446) * 4; 44615 break; 44616 } 44617 return result; 44618 }, 44619 44620 /** 44621 * Rendering list 44622 * @param {Array} rooms 44623 * @param {Boolean} append Append to list 44624 */ 44625 render: function(rooms, append) { 44626 var self = this, 44627 source = this._controller.mode ? 'snapshots' : 'rooms'; 44628 44629 rooms = rooms || self._model[source]; 44630 44631 if (!append || this._controller.mode) { 44632 this[source] 44633 .html('') 44634 .removeClass('normal extended') 44635 .addClass(this._controller.type); 44636 } 44637 44638 $.each(rooms, function() { 44639 self[source].append(self.room(this, self._controller.mode)); 44640 }); 44641 }, 44642 44643 /** 44644 * Render and return room representation 44645 * @param {Object} room 44646 * @param {Boolean} snapshots Snapshot mode 44647 * @returns {String} 44648 */ 44649 room: function(room, snapshots) { 44650 return tmpl('view-' + this.$type + '-room-' + this._controller.type, { 44651 room: room, 44652 category: this._makeSelector('category', 44653 myDeco.settings.ROOM_CATEGORY_LIST, room.category), 44654 style: this._makeSelector('style', 44655 myDeco.settings.ROOM_STYLE_LIST, room.style), 44656 visibility: this._makeVisibilityLink(room), 44657 snapshots: snapshots 44658 }); 44659 }, 44660 44661 /** 44662 * Replace room view inplace in rooms holder 44663 * @param {Object} room 44664 */ 44665 replaceRoom: function(room) { 44666 var index = this._model.rooms.indexOf(room); 44667 this.rooms.children().eq(index) 44668 .replaceWith(this.room(room)); 44669 }, 44670 44671 /** 44672 * Siwitch open popup to snapshots mode 44673 * @param {Object} room 44674 */ 44675 openSnapshots: function(room) { 44676 this._canvas 44677 .addClass('snapshots') 44678 .find('h1.snapshots span').html(room.name).end() 44679 .find('.item') 44680 .data({index: this._model.rooms.indexOf(room)}) 44681 .html(tmpl('view-' + this.$type + '-room-info', { 44682 room: room, 44683 category: this._makeSelector('category', 44684 myDeco.settings.ROOM_CATEGORY_LIST, room.category), 44685 style: this._makeSelector('style', 44686 myDeco.settings.ROOM_STYLE_LIST, room.style), 44687 visibility: this._makeVisibilityLink(room) 44688 })); 44689 }, 44690 44691 /** 44692 * small method wich select checked input's label 44693 * @param {Object} container with inputs 44694 */ 44695 selectLabels: function(container, disableParent) { 44696 container 44697 .find('input[type="checkbox"], input[type="radio"]') 44698 .change(function() { 44699 var box = $(this), text = box.data('text'), daddy = box.parent(); 44700 44701 //checkboxes can have multiple selection 44702 if (this.type !== 'checkbox') { 44703 box.closest('div.input-container') 44704 .find('label').removeClass('selected'); 44705 } 44706 44707 daddy.toggleClass('selected'); 44708 44709 if (text) { 44710 text = text.split(';') 44711 daddy.find('i').text(daddy.hasClass('selected') ? text[1] : text[0]); 44712 } 44713 44714 if (disableParent) { 44715 container.find('form').toggleClass('disabled', !container.find('input:checked').length) 44716 } 44717 }); 44718 }, 44719 44720 /** 44721 * Show save popup with errors if exists 44722 * @param {Object} room 44723 * @param {Array} errors Array of error names 44724 * @param {Boolean} forceNew Full save dialogue flag 44725 */ 44726 save: function(room, errors, forceNew) { 44727 errors = errors || []; 44728 var self = this, 44729 type = forceNew || errors.length !== 0 ? 'save-as' : 'save'; 44730 44731 // set public to false by default, if we are saving a room, created by other user 44732 if ((type == 'save-as') && (room.author) && (room.author !== myDeco.userData.username) && !myDeco.settings.WL) { 44733 room["public"] = false; 44734 } 44735 44736 myDeco.popin.open({ 44737 url: '#view-' + this.$type + '-' + type, 44738 width: 775, 44739 holderClassName: 'vrm-popin', 44740 context: { 44741 room: room, 44742 category: this._makeList('category', myDeco.settings.ROOM_CATEGORY_LIST, room.category), 44743 style: this._makeSelector('style', myDeco.settings.ROOM_STYLE_LIST, room.style || gettext('Other'), 'vrm-category') 44744 }, 44745 afterOpen: function(content) { 44746 var exposure = room.exposure || 0, 44747 form = content.find('form'); 44748 44749 function showPostcode (value) { 44750 var show_postcode = ['France', 'United Kingdom', 'United States'].indexOf(value) !== -1; 44751 form.find('.postcode-container').toggleClass('inactive', !show_postcode); 44752 } 44753 44754 content.parent().iscroll(); 44755 44756 $.each(errors, function(i, name) { 44757 var input; 44758 form.find('.error.' + name) 44759 .text(gettext('This field is required.')) 44760 .show(); 44761 if (i === 0) { 44762 input = form.find('[name=' + name + ']').focus(); 44763 if (input.get(0).select) { 44764 input.get(0).select(); 44765 } 44766 } 44767 }); 44768 44769 self.selectLabels(form); //common for fpe and montage 44770 44771 if (myDeco.App.model._metadata.type.indexOf('3d') > -1) { //fpe 44772 44773 $.ajax({url:'https://vault.floorplanner.com/geo/ip2country.js', dataType: '
44773script', cache: true}).done(function() { 44774 content.find('label.country-anchor').after( 44775 self._makeSelector2('country', myDeco.COUNTRIES, room.country || geoip_country_code(), 'vrm-country') 44776 ); 44777 content.find('select.country, select.style').htmlDropdown({ 44778 template: function(obj) { return '<li>' + this.insertText(obj) + '</li>'}, 44779 relative: true 44780 }) 44781 showPostcode(room.country || geoip_country_name()); 44782 }); 44783 44784 self.addProductsToWishlist(content); 44785 myDeco.popin.resize(); 44786 44787 form 44788 .find('input#vrm-country-combo.combo').bind('activateAutocomplete', function() { 44789 showPostcode(this.value); 44790 }).end() 44791 .find('input[name=name], input[name=postcode]').blur(self.validateInputs.bind(this)).end() 44792 .find('input[name=prop]').change(function() { 44793 var operand = (+this.value === 1) ? 1 : -1; 44794 content 44795 .find('.country-container').toggleClass('opened') 44796 .end() 44797 .parent().css({height: content.parent().outerHeight() + (70 * operand)}); 44798 }).end() 44799 .find('.exposure').slider({ 44800 min: -4, 44801 max: 4, 44802 value: exposure, 44803 onChange: function(value) { 44804 exposure = value; 44805 } 44806 }).end() 44807 .find('.button.save').click(function(e) { 44808 e.stop(); 44809 if (!form.hasClass('disabled')) { 44810 form.submit(); 44811 } 44812 }).end() 44813 .submit(function(e) { 44814 e.stop(); 44815 44816 var publicRadio = content.find('input[name=public]:checked'), address, 44817 _showCountry = content.find('input[name=public]:checked'), 44818 showCountry = _showCountry.length && +_showCountry.val() === 1; 44819 if (publicRadio.size() !== 0) { 44820 room['public'] = !!parseInt(publicRadio.val(), 10); 44821 } 44822 44823 $.extend(room, { 44824 hq_renderer: content.find('input[name=hq_renderer]:checked').val() || 44825 'realityserver', 44826 exposure: exposure 44827 }); 44828 44829 room.force_thickness = form.find('#with_thickness:checked').size() > 0; 44830 44831 if (showCountry) { 44832 address = { 44833 'country': form.find('select.country').val(), 44834 'post_code': form.find('input[name="postcode"]').val() 44835 } 44836 } else { 44837 address = {}; 44838 } 44839 44840 if (type === 'save-as') { 44841 $.extend(room, { 44842 'style': form.find('select[name=style]').val(), 44843 'name': form.find('input[name=name]').val(), 44844 'category': form.find('input.category, select.category, input[name=category]:checked').val(), 44845 'address': address, 44846 'prop': showCountry 44847 }); 44848 } 44849 44850 self._controller.saveRoom(room); 44851 }); 44852 } else { //montage 44853 form 44854 .find('input[name=name]').blur(self.validateInputs.bind(this)).end() 44855 .find('.button.save').click(function(e) { 44856 e.stop(); 44857 if (!form.hasClass('disabled')) { 44858 form.submit(); 44859 } 44860 }).end() 44861 .submit(function(e) { 44862 var publicRadio = content.find('input[name=public]:checked'); 44863 e.stop(); 44864 if (publicRadio.size() !== 0) { 44865 room['public'] = !!parseInt(publicRadio.val(), 10); 44866 } 44867 44868 if (type === 'save-as') { 44869 $.extend(room, { 44870 'style': form.find('select[name=style]').val(), 44871 'name': form.find('input[name=name]').val(), 44872 'category': form.find('input.category, select.category, input[name=category]:checked').val() 44873 }); 44874 } 44875 self._controller.saveRoom(room); 44876 }); 44877 } 44878 } 44879 }); 44880 44881 }, 44882 44883 /** 44884 * Close opened room management popin 44885 */ 44886 close: function() { 44887 myDeco.popin.close(); 44888 this._model.reset(); 44889 }, 44890 44891 validateInputs: function(e) { 44892 var element = $(e.target), 44893 parent = element.parent(), 44894 form = element.closest('form'), 44895 message = myDeco.formValidation.isInvalid(element, { 44896 required: gettext('This field is required'), 44897 email: gettext('Please enter address in correct format') 44898 }, 44899 form.find('#vrm-country').val()) || ''; 44900 44901 parent.find('div.error').addClass(element.attr('name')).text(message).toggle(!!message); 44902 myDeco.popin.resize(); 44903 44904 if (!!message) { 44905 parent.removeClass('valid'); 44906 parent.addClass('invalid'); 44907 } else { 44908 parent.addClass('valid'); 44909 parent.removeClass('invalid'); 44910 } 44911 44912 if (element.hasClass('can-be-empty') && !e.target.value) { 44913 parent.removeClass('valid'); 44914 } 44915
44916 form.toggleClass('disabled', form.find('.invalid').length > 0); 44917 }, 44918 44919 addProductsToWishlist: function(content) { 44920 var comp_filtered = [], 44921 self = this, 44922 ids = myDeco.App.model.uniqueComponentIds(), 44923 excludedCategories = $.map(myDeco.settings.ROOM_IMAGEMAP_EXCLUDED_CATEGORIES, function(item) { 44924 if (item[0] === myDeco.settings.LOCALE) { 44925 return item[1]; 44926 } 44927 }); 44928 44929 //filter ids of wishlisted components 44930 myDeco.body.trigger('component.wishlisted', [ids, function(results) { 44931 if (typeof results !== 'undefined') { 44932 comp_filtered = $.grep(ids, function(guid) { 44933 return $.inArray(guid, results) === -1; 44934 }); 44935 } 44936 }]); 44937 44938 //get components by ids 44939 comp_filtered = $.grep( 44940 $.map(comp_filtered, function(id) { 44941 return $.first(myDeco.App.model.components, function(c) { 44942 return c.component_id === id; 44943 }); 44944 }), function(c) { 44945 //check if it's available and has proper category 44946 return c.componentData.available && 44947 $.inArray(c.componentData.category_id, excludedCategories) === -1; 44948 }); 44949 44950 if (comp_filtered.length > 0) { 44951 content.find('.room-wishlist-button').show().click(function(e) { 44952 e.stop(); 44953 self.wishlistButton = $(this); 44954 44955 self.wishlistButton.toggleClass('active', !self.wishlistButton.hasClass('active')); 44956 44957 myDeco.body.one('roomSaving', function() { 44958 if (self.wishlistButton.hasClass('active')) { 44959 myDeco.body.trigger('component.wishlist', 44960 [$.map(comp_filtered, function(el) {return el.component_id;})] 44961 ); 44962 } 44963 }); 44964 }); 44965 } 44966 }, 44967 44968 /** 44969 * Show/hide loading indicator 44970 * @param {Boolean} state indicator state 44971 */ 44972 loading: function(state) { 44973 state = typeof state !== 'undefined' ? state : true; 44974 this._loader.toggle(state); 44975 this._canvas.find('h1 .room-count') 44976 .text('(' + this._model.start + 44977 (this._model.isLoadingAvailable ? '+' : '') + ')') 44978 .toggle(!state && this._model.start > 0); 44979 }, 44980 44981 /** 44982 * Show error popin with title and message 44983 * @param {String} title 44984 * @param {String} message 44985 */ 44986 showError: function(message, code) { 44987 myDeco.popin.error({ 44988 className: 'vrm-rooms vrm-message', 44989 message: (code ? '<span class="code">' + code + '</span>' : '') + message, 44990 width: 400, 44991 height: 180 44992 }); 44993 }, 44994 44995 /** 44996 * Show success message 44997 */ 44998 success: function(is_private) { 44999 var self = this, classname="", closeIn = false; 45000 is_private = is_private || myDeco.settings.WL; 45001 if (is_private) { 45002 classname = ' private-room'; 45003 } 45004 // pre-load facebook cookies 45005 myDeco.facebook.getLoginStatus(); 45006 45007 myDeco.popin.open({ 45008 url: '#view-' + this.$type + '-save-success', 45009 className: 'vrm-success' + classname, 45010 width: 775, 45011 closeIn: closeIn, 45012 holderClassName: 'vrm-popin', 45013 context: {}, 45014 afterOpen: function(content) { 45015 var link = content.find('a.rooms-list'); 45016 if (/*myDeco.settings.WL || */myDeco.App.model._metadata.type === 'montage') { 45017 link.click(function(e) { 45018 e.stop(); 45019 myDeco.body.trigger('roomOpenRequest'); 45020 }); 45021 } else { 45022 if(myDeco.settings.WL) { 45023 link.attr('href', 'https://' + myDeco.settings.WL.WL_PARTNER_NAME + '.roomstyler.com/rooms/' + myDeco.App.model._room_id); 45024 } else { 45025 link.attr('href', 'https://roomstyler.com/rooms/' + myDeco.App.model._room_id); 45026 } 45027 } 45028 } 45029 }); 45030 }, 45031 45032 /* 45033 * automatically follow mydeco users 45034 */ 45035 follow: function(action, data) { 45036 if (data && data.length) { 45037 if (typeof data !== 'string') { 45038 data = $.param({id: data}); 45039 } 45040 $.ajax({ 45041 url: myDeco.urls.follow, 45042 data: 'profile_type=user&' + data + '&action=' + action, 45043 dataType: 'json', 45044 type: 'post', 45045 error: function(error) { 45046 console.log(error) 45047 } 45048 }); 45049 } 45050 }, 45051 45052 observeEmails: function(content, data) { 45053 var form = content.find('form'), self = this; 45054 console.log(content.find('input[name$="name"], input[name$="email"]')) 45055 content.find('input[name$="name"], input[name$="email"]') 45056 .blur(self.validateInputs.bind(this)).end() 45057 .find('a.invite-friends').click(function(e) { 45058 e.stop(); 45059 if (form.hasClass('disabled')) { 45060 return; 45061 } 45062 45063 var names = form.find('input[name=name]'), 45064 emails = form.find('input[name=email]'), 45065 len = emails.length, arr = [], i, email, prefix, concat; 45066 45067 for (i=0; i < len; i++) { 45068 email = emails[i].value; 45069 if (names.length) { 45070 if (names[i].value && !email) { 45071 $(emails[i]).parent() 45072 .addClass('invalid').find('div.error') 45073 .text('This field is required'); 45074 } else { 45075 arr[i] = []; 45076 arr[i].push(names[i].value); 45077 arr[i].push(email); 45078 } 45079 prefix = 'home'; 45080 } else if (!!email) { 45081 arr.push(email); 45082 prefix = 'email'; 45083 } 45084 } 45085 45086 $.ajax({ 45087 url: '/rooms/ask-advice/ajax/', 45088 data: form.serialize(), 45089 type: 'post', 45090 success: function() { 45091 myDeco.body.trigger('saveFormFriendInvite', [prefix]); 45092 self.inviteConfirmation.call(self); 45093 } 45094 }); 45095 45096 }); 45097 45098 }, 45099 45100 collectCheckboxValues: function(form, name, selector) { 45101 selector = selector || ''; 45102 var inp = form.find('input[name=' + name + ']' + selector), len = inp.length, i, values = []; 45103 for (i = len; i--;) { 45104 values.push(inp[i].value); 45105 } 45106 return values; 45107 }, 45108 45109 observeFB: function(content, dummy, context) { 45110 this.selectLabels(content, true); 45111 var form = content.find('form'), el, userId, len = context.friends_not_on_mydeco.length, 45112 fb_inputs = content.find('input[name=fb_friends]'), li, ul = content.find('ul.vrm-fb-friends'), 45113 count = 100, li_height = $('ul.vrm-fb-friends li').outerHeight(); 45114 45115 function buildFriend(obj) { 45116 return '<li class="li' + obj.id + '"><span class="image" style="background-image: url(http://graph.facebook.com/'
45117 + obj.id + '/picture/)"></span>' 45118 + '<span class="face-name">' + obj.name + '</span>' 45119 + '<label data-value="' + obj.id + '" class="radio">Invite</label></li>'; 45120 } 45121 45122 function scrolltoFriend(num) { 45123 content.find('div.scrolled').animate({scrollTop: num * li_height}); 45124 }; 45125 45126 content.find('ul.vrm-fb-friends').delegate('label.radio', 'click', function(e) { 45127 el = $(this); 45128 if (!el.hasClass('selected')) { 45129 userId = el.data('value'); 45130 FB.ui({ 45131 to: userId, 45132 method: 'send', 45133 link: myDeco.App.model.weburl 45134 }, function (data) { 45135 if (!!data) { 45136 myDeco.body.trigger('saveFormFriendInvite', ['fb']); 45137 el.addClass('selected').text('Invited'); 45138 } 45139 }); 45140 } 45141 }).end() 45142 .find('a.move-fb-list').toggleClass('invisible', len < 100).click(function(e) { 45143 e.stop(); 45144 count += 100; 45145 var _array = context.friends_not_on_mydeco.splice(100, count), i = 100; 45146 for ( i > -1; i--;) { 45147 if (array[i]) { 45148 $(buildFriend(a[i])).appendTo(ul) 45149 } 45150 } 45151 scrolltoFriend(count); 45152 if (len - count < 101) { 45153 $(this).addClass('invisible'); 45154 } 45155 45156 }).end() 45157 .find('input.fb-autocomplete') 45158 .bind('activateAutocomplete', function(e, originValue) { 45159 if (originValue) { 45160 li = ul.find('li.li' + originValue.id); 45161 if (!li.length) { 45162 li = $(buildFriend(originValue)).prependTo(ul); 45163 } 45164 scrolltoFriend(li.index()); 45165 li.find('label.radio').trigger('click'); 45166 } 45167 }) 45168 .autocomplete({ 45169 param: 'q', 45170 ajax: myDeco.urls.facebookFriendsAutocomplete, 45171 list: $.map(context.friends_not_on_mydeco, function (name) {return name; }), 45172 insertText: function (obj) {return obj.name; }, 45173 template: buildFriend, 45174 wrapper: '<div class="autocomplete super-top"><ul class="share-list"></ul></div>' 45175 }).end() 45176 .find('form').submit(function(e) {e.stop()}); 45177 45178 }, 45179 45180 inviteConfirmation: function() { 45181 myDeco.popin.open({ 45182 url: '#view-' + this.$type + '-invite-confirm', 45183 className: 'vrm-confirm', 45184 width: 775, 45185 holderClassName: 'vrm-popin', 45186 closeIn: 30, 45187 afterOpen: function(content) { 45188 content.find('a.save').click(function(e) { 45189 e.stop(); 45190 window.open(this.href); 45191 }) 45192 } 45193 }); 45194 }, 45195 45196 invite: function(callback, context, data, beforeClose) { 45197 var self = this, _content; 45198 myDeco.popin.open({ 45199 url: '#view-' + self.$type + '-save-invite', 45200 className: 'vrm-invite', 45201 width: 775, 45202 context: context, 45203 holderClassName: 'vrm-popin', 45204 afterOpen: function(content) { 45205 _content = content; 45206 callback.call(self, content, data, context) 45207 }, 45208 beforeClose: function(content) { 45209 if ($.isFunction(beforeClose)) { 45210 beforeClose.call(self, _content); 45211 } 45212 } 45213 }); 45214 } 45215}); 45216 45217myDeco.App.views.push(RoomManagementView); 45218myDeco.moveTracker = { 45219 45220 items: $(), 45221 45222 properties: [], 45223 45224 active: false, 45225 45226 init: function(elements) { 45227 var self = this; 45228 45229 this.items = elements.bind('moved restored minimized maximized raised lowered', function(e) { 45230 self.setProperties($(this)); 45231 }); 45232 45233 myDeco.window.bind('resize', this.setAllProperties.bind(this)); 45234 45235 myDeco.body.mousemove(this.findActive.bind(this)).mousedown(this.findActive.bind(this)); 45236 45237 this.setAllProperties(); 45238 }, 45239 45240 setAllProperties: function() { 45241 var self = this; 45242 this.items.each2(function(i, item) { 45243 self.setProperties(item); 45244 }); 45245 }, 45246 45247 setProperties: function(item) { 45248 var params = { 45249 enabled: item.is(':visible'), 45250 x1: 0, x2: 0, y1: 0, y2: 0, z: 0 45251 }, offset; 45252 45253 if (params.enabled) { 45254 offset = item.offset(); 45255 $.extend(params, { 45256 x1: offset.left, 45257 x2: offset.left + item.width(), 45258 y1: offset.top, 45259 y2: offset.top + item.height(), 45260 z: +item.css('zIndex') || 0 45261 }); 45262 } 45263 this.properties[this.items.index(item.get(0))] = params; 45264 }, 45265 45266 inArea: function(e, area) { 45267 return e.pageX >= area.x1 && e.pageX <= area.x2 && 45268 e.pageY >= area.y1 && e.pageY <= area.y2; 45269 }, 45270 45271 findActive: function(e) { 45272 var zIndex = -1, 45273 self = this, 45274 active = -1, 45275 offset, item, area, z; 45276 45277 $.each(this.properties, function(i, area) { 45278 if (area.enabled && self.inArea(e, area) && area.z > zIndex) { 45279 zIndex = area.z; 45280 active = i; 45281 } 45282 }); 45283 45284 if (this.active && (active == -1 || this.active.get(0) !== this.items.get(active))) { 45285 this.active.trigger('canvasOut', [e]); 45286 this.active = false;
45287 } 45288 45289 if (active !== -1 && !this.active) { 45290 this.active = this.items.eq(active); 45291 this.active.trigger('canvasIn', [e]); 45292 } 45293 } 45294 45295}; 45296 45297$(function(e) { 45298 myDeco.moveTracker.init($('.move-tracker')); 45299}); 45300$(function() { 45301 $(window).bind('message', function(e) { 45302 var data = e.originalEvent.data; 45303 45304 if ((typeof data === 'string') && data[0] === '{') { 45305 data = JSON.parse(data); 45306 } 45307 45308 var name = typeof data === 'object' ? data.name : data; 45309 delete data.name; 45310 45311 switch (name) { 45312 case 'refresh_userinfo': 45313 myDeco.setUserData(); 45314 myDeco.user.updateLoginArea(); 45315 break; 45316 case 'logout': 45317 myDeco.user.ajaxLogoutClickHandler(); 45318 break; 45319 case 'save': 45320 var opts = {forceNew: false}; 45321 if (typeof data === 'object') { 45322 opts.forceNew = data.forceNew || false; 45323 data = _.pick(data, 'room_attributes', 'render_hq', 'hide_dialog'); 45324 if (data.room_attributes) { 45325 data.room_attributes = _.pick(data.room_attributes, 'name', 'public', 'category'); 45326 } 45327 opts.user_options = data; 45328 } 45329 myDeco.body.trigger('roomSaveRequest', [opts]); 45330 break; 45331 case 'save_and_render': 45332 myDeco.body.trigger('roomSaveRequest', [{showProperties: true, hqRender: true}]); 45333 break; 45334 } 45335 }); 45336}); 45337// Underscore.js 1.8.2 45338// http://underscorejs.org 45339// (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 45340// Underscore may be freely distributed under the MIT license. 45341(function(){function n(n){function t(t,r,e,u,i,o){for(;i>=0&&o>i;i+=n){var a=u?u[i]:i;e=r(e,t[a],a,t)}return e}return function(r,e,u,i){e=d(e,i,4);var o=!w(r)&&m.keys(r),a=(o||r).length,c=n>0?0:a-1;return arguments.length<3&&(u=r[o?o[c]:c],c+=n),t(r,e,u,o,c,a)}}function t(n){return function(t,r,e){r=b(r,e);for(var u=null!=t&&t.length,i=n>0?0:u-1;i>=0&&u>i;i+=n)if(r(t[i],i,t))return i;return-1}}function r(n,t){var r=S.length,e=n.constructor,u=m.isFunction(e)&&e.prototype||o,i="constructor";for(m.has(n,i)&&!m.contains(t,i)&&t.push(i);r--;)i=S[r],i in n&&n[i]!==u[i]&&!m.contains(t,i)&&t.push(i)}var e=this,u=e._,i=Array.prototype,o=Object.prototype,a=Function.prototype,c=i.push,l=i.slice,f=o.toString,s=o.hasOwnProperty,p=Array.isArray,h=Object.keys,v=a.bind,g=Object.create,y=function(){},m=function(n){return n instanceof m?n:this instanceof m?void(this._wrapped=n):new m(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=m),exports._=m):e._=m,m.VERSION="1.8.2";var d=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}},b=function(n,t,r){return null==n?m.identity:m.isFunction(n)?d(n,t,r):m.isObject(n)?m.matcher(n):m.property(n)};m.iteratee=function(n,t){return b(n,t,1/0)};var x=function(n,t){return function(r){var e=arguments.length;if(2>e||null==r)return r;for(var u=1;e>u;u++)for(var i=arguments[u],o=n(i),a=o.length,c=0;a>c;c++){var l=o[c];t&&r[l]!==void 0||(r[l]=i[l])}return r}},_=function(n){if(!m.isObject(n))return{};if(g)return g(n);y.prototype=n;var t=new y;return y.prototype=null,t},j=Math.pow(2,53)-1,w=function(n){var t=n&&n.length;return"number"==typeof t&&t>=0&&j>=t};m.each=m.forEach=function(n,t,r){t=d(t,r);var e,u;if(w(n))for(e=0,u=n.length;u>e;e++)t(n[e],e,n);else{var i=m.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},m.map=m.collect=function(n,t,r){t=b(t,r);for(var e=!w(n)&&m.keys(n),u=(e||n).length,i=Array(u),o=0;u>o;o++){var a=e?e[o]:o;i[o]=t(n[a],a,n)}return i},m.reduce=m.foldl=m.inject=n(1),m.reduceRight=m.foldr=n(-1),m.find=m.detect=function(n,t,r){var e;return e=w(n)?m.findIndex(n,t,r):m.findKey(n,t,r),e!==void 0&&e!==-1?n[e]:void 0},m.filter=m.select=function(n,t,r){var e=[];return t=b(t,r),m.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e},m.reject=function(n,t,r){return m.filter(n,m.negate(b(t)),r)},m.every=m.all=function(n,t,r){t=b(t,r);for(var e=!w(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(!t(n[o],o,n))return!1}return!0},m.some=m.any=function(n,t,r){t=b(t,r);for(var e=!w(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(t(n[o],o,n))return!0}return!1},m.contains=m.includes=m.include=function(n,t,r){return w(n)||(n=m.values(n)),m.indexOf(n,t,"number"==typeof r&&r)>=0},m.invoke=function(n,t){var r=l.call(arguments,2),e=m.isFunction(t);return m.map(n,function(n){var u=e?t:n[t];return null==u?u:u.apply(n,r)})},m.pluck=function(n,t){return m.map(n,m.property(t))},m.where=function(n,t){return m.filter(n,m.matcher(t))},m.findWhere=function(n,t){return m.find(n,m.matcher(t))},m.max=function(n,t,r){var e,u,i=-1/0,o=-1/0;
45341if(null==t&&null!=n){n=w(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],e>i&&(i=e)}else t=b(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(u>o||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},m.min=function(n,t,r){var e,u,i=1/0,o=1/0;if(null==t&&null!=n){n=w(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],i>e&&(i=e)}else t=b(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(o>u||1/0===u&&1/0===i)&&(i=n,o=u)});return i},m.shuffle=function(n){for(var t,r=w(n)?n:m.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=m.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},m.sample=function(n,t,r){return null==t||r?(w(n)||(n=m.values(n)),n[m.random(n.length-1)]):m.shuffle(n).slice(0,Math.max(0,t))},m.sortBy=function(n,t,r){return t=b(t,r),m.pluck(m.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var A=function(n){return function(t,r,e){var u={};return r=b(r,e),m.each(t,function(e,i){var o=r(e,i,t);n(u,e,o)}),u}};m.groupBy=A(function(n,t,r){m.has(n,r)?n[r].push(t):n[r]=[t]}),m.indexBy=A(function(n,t,r){n[r]=t}),m.countBy=A(function(n,t,r){m.has(n,r)?n[r]++:n[r]=1}),m.toArray=function(n){return n?m.isArray(n)?l.call(n):w(n)?m.map(n,m.identity):m.values(n):[]},m.size=function(n){return null==n?0:w(n)?n.length:m.keys(n).length},m.partition=function(n,t,r){t=b(t,r);var e=[],u=[];return m.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},m.first=m.head=m.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:m.initial(n,n.length-t)},m.initial=function(n,t,r){return l.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},m.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:m.rest(n,Math.max(0,n.length-t))},m.rest=m.tail=m.drop=function(n,t,r){return l.call(n,null==t||r?1:t)},m.compact=function(n){return m.filter(n,m.identity)};var k=function(n,t,r,e){for(var u=[],i=0,o=e||0,a=n&&n.length;a>o;o++){var c=n[o];if(w(c)&&(m.isArray(c)||m.isArguments(c))){t||(c=k(c,t,r));var l=0,f=c.length;for(u.length+=f;f>l;)u[i++]=c[l++]}else r||(u[i++]=c)}return u};m.flatten=function(n,t){return k(n,t,!1)},m.without=function(n){return m.difference(n,l.call(arguments,1))},m.uniq=m.unique=function(n,t,r,e){if(null==n)return[];m.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=b(r,e));for(var u=[],i=[],o=0,a=n.length;a>o;o++){var c=n[o],l=r?r(c,o,n):c;t?(o&&i===l||u.push(c),i=l):r?m.contains(i,l)||(i.push(l),u.push(c)):m.contains(u,c)||u.push(c)}return u},m.union=function(){return m.uniq(k(arguments,!0,!0))},m.intersection=function(n){if(null==n)return[];for(var t=[],r=arguments.length,e=0,u=n.length;u>e;e++){var i=n[e];if(!m.contains(t,i)){for(var o=1;r>o&&m.contains(arguments[o],i);o++);o===r&&t.push(i)}}return t},m.difference=function(n){var t=k(arguments,!0,!0,1);return m.filter(n,function(n){return!m.contains(t,n)})},m.zip=function(){return m.unzip(arguments)},m.unzip=function(n){for(var t=n&&m.max(n,"length").length||0,r=Array(t),e=0;t>e;e++)r[e]=m.pluck(n,e);return r},m.object=function(n,t){for(var r={},e=0,u=n&&n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},m.indexOf=function(n,t,r){var e=0,u=n&&n.length;if("number"==typeof r)e=0>r?Math.max(0,u+r):r;else if(r&&u)return e=m.sortedIndex(n,t),n[e]===t?e:-1;if(t!==t)return m.findIndex(l.call(n,e),m.isNaN);for(;u>e;e++)if(n[e]===t)return e;return-1},m.lastIndexOf=function(n,t,r){var e=n?n.length:0;if("number"==typeof r&&(e=0>r?e+r+1:Math.min(e,r+1)),t!==t)return m.findLastIndex(l.call(n,0,e),m.isNaN);for(;--e>=0;)if(n[e]===t)return e;return-1},m.findIndex=t(1),m.findLastIndex=t(-1),m.sortedIndex=function(n,t,r,e){r=b(r,e,1);for(var u=r(t),i=0,o=n.length;o>i;){var a=Math.floor((i+o)/2);r(n[a])<u?i=a+1:o=a}return i},m.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=r||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=Array(e),i=0;e>i;i++,n+=r)u[i]=n;return u};var O=function(n,t,r,e,u){if(!(e instanceof t))return n.apply(r,u);var i=_(n.prototype),o=n.apply(i,u);return m.isObject(o)?o:i};m.bind=function(n,t){if(v&&n.bind===v)return v.apply(n,l.call(arguments,1
vendor: 9,077 bytes, lines 45341-45343
45341));if(!m.isFunction(n))throw new TypeError("Bind must be called on a function");var r=l.call(arguments,2),e=function(){return O(n,e,t,this,r.concat(l.call(arguments)))};return e},m.partial=function(n){var t=l.call(arguments,1),r=function(){for(var e=0,u=t.length,i=Array(u),o=0;u>o;o++)i[o]=t[o]===m?arguments[e++]:t[o];for(;e<arguments.length;)i.push(arguments[e++]);return O(n,r,this,this,i)};return r},m.bindAll=function(n){var t,r,e=arguments.length;if(1>=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=m.bind(n[r],n);return n},m.memoize=function(n,t){var r=function(e){var u=r.cache,i=""+(t?t.apply(this,arguments):e);return m.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},m.delay=function(n,t){var r=l.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},m.defer=m.partial(m.delay,m,1),m.throttle=function(n,t,r){var e,u,i,o=null,a=0;r||(r={});var c=function(){a=r.leading===!1?0:m.now(),o=null,i=n.apply(e,u),o||(e=u=null)};return function(){var l=m.now();a||r.leading!==!1||(a=l);var f=t-(l-a);return e=this,u=arguments,0>=f||f>t?(o&&(clearTimeout(o),o=null),a=l,i=n.apply(e,u),o||(e=u=null)):o||r.trailing===!1||(o=setTimeout(c,f)),i}},m.debounce=function(n,t,r){var e,u,i,o,a,c=function(){var l=m.now()-o;t>l&&l>=0?e=setTimeout(c,t-l):(e=null,r||(a=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,o=m.now();var l=r&&!e;return e||(e=setTimeout(c,t)),l&&(a=n.apply(i,u),i=u=null),a}},m.wrap=function(n,t){return m.partial(t,n)},m.negate=function(n){return function(){return!n.apply(this,arguments)}},m.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},m.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},m.before=function(n,t){var r;return function(){return--n>0&&(r=t.apply(this,arguments)),1>=n&&(t=null),r}},m.once=m.partial(m.before,2);var F=!{toString:null}.propertyIsEnumerable("toString"),S=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];m.keys=function(n){if(!m.isObject(n))return[];if(h)return h(n);var t=[];for(var e in n)m.has(n,e)&&t.push(e);return F&&r(n,t),t},m.allKeys=function(n){if(!m.isObject(n))return[];var t=[];for(var e in n)t.push(e);return F&&r(n,t),t},m.values=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},m.mapObject=function(n,t,r){t=b(t,r);for(var e,u=m.keys(n),i=u.length,o={},a=0;i>a;a++)e=u[a],o[e]=t(n[e],e,n);return o},m.pairs=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},m.invert=function(n){for(var t={},r=m.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},m.functions=m.methods=function(n){var t=[];for(var r in n)m.isFunction(n[r])&&t.push(r);return t.sort()},m.extend=x(m.allKeys),m.extendOwn=m.assign=x(m.keys),m.findKey=function(n,t,r){t=b(t,r);for(var e,u=m.keys(n),i=0,o=u.length;o>i;i++)if(e=u[i],t(n[e],e,n))return e},m.pick=function(n,t,r){var e,u,i={},o=n;if(null==o)return i;m.isFunction(t)?(u=m.allKeys(o),e=d(t,r)):(u=k(arguments,!1,!1,1),e=function(n,t,r){return t in r},o=Object(o));for(var a=0,c=u.length;c>a;a++){var l=u[a],f=o[l];e(f,l,o)&&(i[l]=f)}return i},m.omit=function(n,t,r){if(m.isFunction(t))t=m.negate(t);else{var e=m.map(k(arguments,!1,!1,1),String);t=function(n,t){return!m.contains(e,t)}}return m.pick(n,t,r)},m.defaults=x(m.allKeys,!0),m.clone=function(n){return m.isObject(n)?m.isArray(n)?n.slice():m.extend({},n):n},m.tap=function(n,t){return t(n),n},m.isMatch=function(n,t){var r=m.keys(t),e=r.length;if(null==n)return!e;for(var u=Object(n),i=0;e>i;i++){var o=r[i];if(t[o]!==u[o]||!(o in u))return!1}return!0};var E=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof m&&(n=n._wrapped),t instanceof m&&(t=t._wrapped);var u=f.call(n);if(u!==f.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}var i="[object Array]"===u;if(!i){if("object"!=typeof n||"object"!=typeof t)return!1;var o=n.constructor,a=t.constructor;if(o!==a&&!(m.isFunction(o)&&o instanceof o&&m.isFunction(a)&&a instanceof a)&&"constructor"in n&&"constructor"in t)return!1}r=r||[],e=e||[];for(var c=r.length;c--;)if(r[c]===n)return e[c]===t;if(r.push(n),e.push(t),i){if(c=n.length,c!==t.length)return!1;for(;c--;)if(!E(n[c],t[c],r,e))return!1}else{var l,s=m.keys(n);if(c=s.length,m.keys(t).length!==c)return!1;for(;c--;)if(l=s[c],!m.has(t,l)||!E(n[l],t[l],r,e))return!1}return r.pop(),e.pop(),!0};m.isEqual=function(n,t){return E(n,t)},m.isEmpty=function(n){return null==n?!0:w(n)&&(m.isArray(n)||m.isString(n)||m.isArguments(n))?0===n.length:0===m.keys(n).length},m.isElement=function(n){return!(!n||1!==n.nodeType)},m.isArray=p||function(n){return"[object Array]"===f.call(n)},m.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},m.each(["Arguments","Function","String","Number","Date","RegExp","Error"],function(n){m["is"+n]=function(t){return f.call(t)==="[object "+n+"]"}}),m.isArguments(arguments)||(m.isArguments=function(n){return m.has(n,"callee")}),"function"!=typeof/./&&"object"!=typeof Int8Array&&(m.isFunction=function(n){return"function"==typeof n||!1}),m.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},m.isNaN=function(n){return m.isNumber(n)&&n!==+n},m.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===f.call(n)},m.isNull=function(n){return null===n},m.isUndefined=function(n){return n===void 0},m.has=function(n,t){return null!=n&&s.call(n,t)},m.noConflict=function(){return e._=u,this},m.identity=function(n){return n},m.constant=function(n){return function(){return n}},m.noop=function(){},m.property=function(n){return function(t){return null==t?void 0:t[n]}},m.propertyOf=function(n){return null==n?function(){}:function(t){return n[t]}},m.matcher=m.matches=function(n){return n=m.extendOwn({},n),function(t){return m.isMatch(t,n)}},m.times=function(n,t,r){var e=Array(Math.max(0,n));t=d(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},m.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},m.now=Date.now||function(){return(new Date).getTime()};var M={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},N=m.invert(M),I=function(n){var t=function(t){return n[t]},r="(?:"+m.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};m.escape=I(M),m.unescape=I(N),m.result=function(n,t,r){var e=null==n?void 0:n[t];return e===void 0&&(e=r),m.isFunction(e)?e.call(n):e};var B=0;m.uniqueId=function(n){var t=++B+"";return n?n+t:t},m.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var T=/(.)^/,R={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},q=/\\|'|\r|\n|\u2028|\u2029/g,K=function(n){return"\\"+R[n]};m.template=function(n,t,r){!t&&r&&(t=r),t=m.defaults({},t,m.templateSettings);var e=RegExp([(t.escape||T).source,(t.interpolate||T).source,(t.evaluate||T).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,o,a){return i+=n.slice(u,a).replace(q,K),u=a+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":o&&(i+="';\n"+o+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var o=new Function(t.variable||"obj","_",i)}catch(a){throw a.source=i,a}var c=function(n){return o.call(this,n,m)},l=t.variable||"obj";return c.source="function("+l+"){\n"+i+"}",c},m.chain=function(n){var t=m(n);return t._chain=!0,t};var z=function(n,t){return n._chain?m(t).chain():t};m.mixin=function(n){m.each(m.functions(n),function(t){var r=m[t]=n[t];m.prototype[t]=function(){var n=[this._wrapped];return c.apply(n,arguments),z(this,r.apply(m,n))}})},m.mixin(m),m.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=i[n];m.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],z(this,r)}}),m.each(["concat","join","slice"],function(n){var t=i[n];m.prototype[n]=function(){return z(this,t.apply(this._wrapped,arguments))}}),m.prototype.value=function(){return this._wrapped},m.prototype.valueOf=m.prototype.toJSON=m.prototype.value,m.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return m})}).call(this); 45342 45343(function(t,e){if(typeof define==="function"&&define.amd){define(["underscore","jquery","exports"],function(i,r,s){t.Backbone=e(t,s,i,r)})}else if(typeof exports!=="undefined"){var i=require("underscore");e(t,exports,i)}else{t.Backbone=e(t,{},t._,t.jQuery||t.Zepto||t.ender||t.$)}}
45343)(this,function(t,e,i,r){var s=t.Backbone;var n=[];var a=n.push;var o=n.slice;var h=n.splice;e.VERSION="1.1.2";e.$=r;e.noConflict=function(){t.Backbone=s;return this};e.emulateHTTP=false;e.emulateJSON=false;var u=e.Events={on:function(t,e,i){if(!c(this,"on",t,[e,i])||!e)return this;this._events||(this._events={});var r=this._events[t]||(this._events[t]=[]);r.push({callback:e,context:i,ctx:i||this});return this},once:function(t,e,r){if(!c(this,"once",t,[e,r])||!e)return this;var s=this;var n=i.once(function(){s.off(t,n);e.apply(this,arguments)});n._callback=e;return this.on(t,n,r)},off:function(t,e,r){var s,n,a,o,h,u,l,f;if(!this._events||!c(this,"off",t,[e,r]))return this;if(!t&&!e&&!r){this._events=void 0;return this}o=t?[t]:i.keys(this._events);for(h=0,u=o.length;h<u;h++){t=o[h];if(a=this._events[t]){this._events[t]=s=[];if(e||r){for(l=0,f=a.length;l<f;l++){n=a[l];if(e&&e!==n.callback&&e!==n.callback._callback||r&&r!==n.context){s.push(n)}}}if(!s.length)delete this._events[t]}}return this},trigger:function(t){if(!this._events)return this;var e=o.call(arguments,1);if(!c(this,"trigger",t,e))return this;var i=this._events[t];var r=this._events.all;if(i)f(i,e);if(r)f(r,arguments);return this},stopListening:function(t,e,r){var s=this._listeningTo;if(!s)return this;var n=!e&&!r;if(!r&&typeof e==="object")r=this;if(t)(s={})[t._listenId]=t;for(var a in s){t=s[a];t.off(e,r,this);if(n||i.isEmpty(t._events))delete this._listeningTo[a]}return this}};var l=/\s+/;var c=function(t,e,i,r){if(!i)return true;if(typeof i==="object"){for(var s in i){t[e].apply(t,[s,i[s]].concat(r))}return false}if(l.test(i)){var n=i.split(l);for(var a=0,o=n.length;a<o;a++){t[e].apply(t,[n[a]].concat(r))}return false}return true};var f=function(t,e){var i,r=-1,s=t.length,n=e[0],a=e[1],o=e[2];switch(e.length){case 0:while(++r<s)(i=t[r]).callback.call(i.ctx);return;case 1:while(++r<s)(i=t[r]).callback.call(i.ctx,n);return;case 2:while(++r<s)(i=t[r]).callback.call(i.ctx,n,a);return;case 3:while(++r<s)(i=t[r]).callback.call(i.ctx,n,a,o);return;default:while(++r<s)(i=t[r]).callback.apply(i.ctx,e);return}};var d={listenTo:"on",listenToOnce:"once"};i.each(d,function(t,e){u[e]=function(e,r,s){var n=this._listeningTo||(this._listeningTo={});var a=e._listenId||(e._listenId=i.uniqueId("l"));n[a]=e;if(!s&&typeof r==="object")s=this;e[t](r,s,this);return this}});u.bind=u.on;u.unbind=u.off;i.extend(e,u);var p=e.Model=function(t,e){var r=t||{};e||(e={});this.cid=i.uniqueId("c");this.attributes={};if(e.collection)this.collection=e.collection;if(e.parse)r=this.parse(r,e)||{};r=i.defaults({},r,i.result(this,"defaults"));this.set(r,e);this.changed={};this.initialize.apply(this,arguments)};i.extend(p.prototype,u,{changed:null,validationError:null,idAttribute:"id",initialize:function(){},toJSON:function(t){return i.clone(this.attributes)},sync:function(){return e.sync.apply(this,arguments)},get:function(t){return this.attributes[t]},escape:function(t){return i.escape(this.get(t))},has:function(t){return this.get(t)!=null},set:function(t,e,r){var s,n,a,o,h,u,l,c;if(t==null)return this;if(typeof t==="object"){n=t;r=e}else{(n={})[t]=e}r||(r={});if(!this._validate(n,r))return false;a=r.unset;h=r.silent;o=[];u=this._changing;this._changing=true;if(!u){this._previousAttributes=i.clone(this.attributes);this.changed={}}c=this.attributes,l=this._previousAttributes;if(this.idAttribute in n)this.id=n[this.idAttribute];for(s in n){e=n[s];if(!i.isEqual(c[s],e))o.push(s);if(!i.isEqual(l[s],e)){this.changed[s]=e}else{delete this.changed[s]}a?delete c[s]:c[s]=e}if(!h){if(o.length)this._pending=r;for(var f=0,d=o.length;f<d;f++){this.trigger("change:"+o[f],this,c[o[f]],r)}}if(u)return this;if(!h){while(this._pending){r=this._pending;this._pending=false;this.trigger("change",this,r)}}this._pending=false;this._changing=false;return this},unset:function(t,e){return this.set(t,void 0,i.extend({},e,{unset:true}))},clear:function(t){var e={};for(var r in this.attributes)e[r]=void 0;return this.set(e,i.extend({},t,{unset:true}))},hasChanged:function(t){if(t==null)return!i.isEmpty(this.changed);return i.has(this.changed,t)},changedAttributes:function(t){if(!t)return this.hasChanged()?i.clone(this.changed):false;var e,r=false;var s=this._changing?this._previousAttributes:this.attributes;for(var n in t){if(i.isEqual(s[n],e=t[n]))continue;(r||(r={}))[n]=e}return r},previous:function(t){if(t==null||!this._previousAttributes)return null;return this._previousAttributes[t]},previousAttributes:function(){return i.clone(this._previousAttributes)},fetch:function(t){t=t?i.clone(t):{};if(t.parse===void 0)t.parse=true;var e=this;var r=t.success;t.success=function(i){if(!e.set(e.parse(i,t),t))return false;if(r)r(e,i,t);e.trigger("sync",e,i,t)};q(this,t);return this.sync("read",this,t)},save:function(t,e,r){var s,n,a,o=this.attributes;if(t==null||typeof t==="object"){s=t;r=e}else{(s={})[t]=e}r=i.extend({validate:true},r);if(s&&!r.wait){if(!this.set(s,r))return false}
45343else{if(!this._validate(s,r))return false}if(s&&r.wait){this.attributes=i.extend({},o,s)}if(r.parse===void 0)r.parse=true;var h=this;var u=r.success;r.success=function(t){h.attributes=o;var e=h.parse(t,r);if(r.wait)e=i.extend(s||{},e);if(i.isObject(e)&&!h.set(e,r)){return false}if(u)u(h,t,r);h.trigger("sync",h,t,r)};q(this,r);n=this.isNew()?"create":r.patch?"patch":"update";if(n==="patch")r.attrs=s;a=this.sync(n,this,r);if(s&&r.wait)this.attributes=o;return a},destroy:function(t){t=t?i.clone(t):{};var e=this;var r=t.success;var s=function(){e.trigger("destroy",e,e.collection,t)};t.success=function(i){if(t.wait||e.isNew())s();if(r)r(e,i,t);if(!e.isNew())e.trigger("sync",e,i,t)};if(this.isNew()){t.success();return false}q(this,t);var n=this.sync("delete",this,t);if(!t.wait)s();return n},url:function(){var t=i.result(this,"urlRoot")||i.result(this.collection,"url")||M();if(this.isNew())return t;return t.replace(/([^\/])$/,"$1/")+encodeURIComponent(this.id)},parse:function(t,e){return t},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return!this.has(this.idAttribute)},isValid:function(t){return this._validate({},i.extend(t||{},{validate:true}))},_validate:function(t,e){if(!e.validate||!this.validate)return true;t=i.extend({},this.attributes,t);var r=this.validationError=this.validate(t,e)||null;if(!r)return true;this.trigger("invalid",this,r,i.extend(e,{validationError:r}));return false}});var v=["keys","values","pairs","invert","pick","omit"];i.each(v,function(t){p.prototype[t]=function(){var e=o.call(arguments);e.unshift(this.attributes);return i[t].apply(i,e)}});var g=e.Collection=function(t,e){e||(e={});if(e.model)this.model=e.model;if(e.comparator!==void 0)this.comparator=e.comparator;this._reset();this.initialize.apply(this,arguments);if(t)this.reset(t,i.extend({silent:true},e))};var m={add:true,remove:true,merge:true};var y={add:true,remove:false};i.extend(g.prototype,u,{model:p,initialize:function(){},toJSON:function(t){return this.map(function(e){return e.toJSON(t)})},sync:function(){return e.sync.apply(this,arguments)},add:function(t,e){return this.set(t,i.extend({merge:false},e,y))},remove:function(t,e){var r=!i.isArray(t);t=r?[t]:i.clone(t);e||(e={});var s,n,a,o;for(s=0,n=t.length;s<n;s++){o=t[s]=this.get(t[s]);if(!o)continue;delete this._byId[o.id];delete this._byId[o.cid];a=this.indexOf(o);this.models.splice(a,1);this.length--;if(!e.silent){e.index=a;o.trigger("remove",o,this,e)}this._removeReference(o,e)}return r?t[0]:t},set:function(t,e){e=i.defaults({},e,m);if(e.parse)t=this.parse(t,e);var r=!i.isArray(t);t=r?t?[t]:[]:i.clone(t);var s,n,a,o,h,u,l;var c=e.at;var f=this.model;var d=this.comparator&&c==null&&e.sort!==false;var v=i.isString(this.comparator)?this.comparator:null;var g=[],y=[],_={};var b=e.add,w=e.merge,x=e.remove;var E=!d&&b&&x?[]:false;for(s=0,n=t.length;s<n;s++){h=t[s]||{};if(h instanceof p){a=o=h}else{a=h[f.prototype.idAttribute||"id"]}if(u=this.get(a)){if(x)_[u.cid]=true;if(w){h=h===o?o.attributes:h;if(e.parse)h=u.parse(h,e);u.set(h,e);if(d&&!l&&u.hasChanged(v))l=true}t[s]=u}else if(b){o=t[s]=this._prepareModel(h,e);if(!o)continue;g.push(o);this._addReference(o,e)}o=u||o;if(E&&(o.isNew()||!_[o.id]))E.push(o);_[o.id]=true}if(x){for(s=0,n=this.length;s<n;++s){if(!_[(o=this.models[s]).cid])y.push(o)}if(y.length)this.remove(y,e)}if(g.length||E&&E.length){if(d)l=true;this.length+=g.length;if(c!=null){for(s=0,n=g.length;s<n;s++){this.models.splice(c+s,0,g[s])}}else{if(E)this.models.length=0;var k=E||g;for(s=0,n=k.length;s<n;s++){this.models.push(k[s])}}}if(l)this.sort({silent:true});if(!e.silent){for(s=0,n=g.length;s<n;s++){(o=g[s]).trigger("add",o,this,e)}if(l||E&&E.length)this.trigger("sort",this,e)}return r?t[0]:t},reset:function(t,e){e||(e={});for(var r=0,s=this.models.length;r<s;r++){this._removeReference(this.models[r],e)}e.previousModels=this.models;this._reset();t=this.add(t,i.extend({silent:true},e));if(!e.silent)this.trigger("reset",this,e);return t},push:function(t,e){return this.add(t,i.extend({at:this.length},e))},pop:function(t){var e=this.at(this.length-1);this.remove(e,t);return e},unshift:function(t,e){return this.add(t,i.extend({at:0},e))},shift:function(t){var e=this.at(0);this.remove(e,t);return e},slice:function(){return o.apply(this.models,arguments)},get:function(t){if(t==null)return void 0;return this._byId[t]||this._byId[t.id]||this._byId[t.cid]},at:function(t){return this.models[t]},where:function(t,e){if(i.isEmpty(t))return e?void 0:[];return this[e?"find":"filter"](function(e){for(var i in t){if(t[i]!==e.get(i))return false}return true})},findWhere:function(t){return this.where(t,true)},sort:function(t){if(!this.comparator)throw new Error("Cannot sort a set without a comparator");t||(t={});if(i.isString(this.comparator)||this.comparator.length===1){this.models=this.sortBy(this.comparator,this)}else{this.models.sort(i.bind(this.comparator,this))}if(!t.silent)this.trigger("sort",this,t);return this},pluck:function(t){return i.invoke(this.models,"get",t)},fetch:function(t){t=t?i.clone(t):{};if(t.parse===void 0)t.parse=true;var e=t.success;var r=this;t.success=function(i){var s=t.reset?"reset":"set";r[s](i,t);if(e)e(r,i,t);r.trigger("sync",r,i,t)};q(this,t);return this.sync("read",this,t)},create:function(t,e){e=e?i.clone(e):{};if(!(t=this._prepareModel(t,e)))return false;if(!e.wait)this.add(t,e);var r=this;var s=e.success;e.success=function(t,i){if(e.wait)r.add(t,e);if(s)s(t,i,e)};t.save(null,e);return t},parse:function(t,e){return t},clone:function(){return new this.constructor(this.models)},_reset:function(){this.length=0;this.models=[];this._byId={}},_prepareModel:function(t,e){if(t instanceof p)return t;e=e?i.clone(e):{};e.collection=this;var r=new this.model(t,e);if(!r.validationError)return r;this.trigger("invalid",this,r.validationError,e);return false},_addReference:function(t,e){this._byId[t.cid]=t;if(t.id!=null)this._byId[t.id]=t;if(!t.collection)t.collection=this;t.on("all",this._onModelEvent,this)},_removeReference:function(t,e){if(this===t.collection)delete t.collection;t.off("all",this._onModelEvent,this)}
45343,_onModelEvent:function(t,e,i,r){if((t==="add"||t==="remove")&&i!==this)return;if(t==="destroy")this.remove(e,r);if(e&&t==="change:"+e.idAttribute){delete this._byId[e.previous(e.idAttribute)];if(e.id!=null)this._byId[e.id]=e}this.trigger.apply(this,arguments)}});var _=["forEach","each","map","collect","reduce","foldl","inject","reduceRight","foldr","find","detect","filter","select","reject","every","all","some","any","include","contains","invoke","max","min","toArray","size","first","head","take","initial","rest","tail","drop","last","without","difference","indexOf","shuffle","lastIndexOf","isEmpty","chain","sample"];i.each(_,function(t){g.prototype[t]=function(){var e=o.call(arguments);e.unshift(this.models);return i[t].apply(i,e)}});var b=["groupBy","countBy","sortBy","indexBy"];i.each(b,function(t){g.prototype[t]=function(e,r){var s=i.isFunction(e)?e:function(t){return t.get(e)};return i[t](this.models,s,r)}});var w=e.View=function(t){this.cid=i.uniqueId("view");t||(t={});i.extend(this,i.pick(t,E));this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()};var x=/^(\S+)\s*(.*)$/;var E=["model","collection","el","id","attributes","className","tagName","events"];i.extend(w.prototype,u,{tagName:"div",$:function(t){return this.$el.find(t)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();this.stopListening();return this},setElement:function(t,i){if(this.$el)this.undelegateEvents();this.$el=t instanceof e.$?t:e.$(t);this.el=this.$el[0];if(i!==false)this.delegateEvents();return this},delegateEvents:function(t){if(!(t||(t=i.result(this,"events"))))return this;this.undelegateEvents();for(var e in t){var r=t[e];if(!i.isFunction(r))r=this[t[e]];if(!r)continue;var s=e.match(x);var n=s[1],a=s[2];r=i.bind(r,this);n+=".delegateEvents"+this.cid;if(a===""){this.$el.on(n,r)}else{this.$el.on(n,a,r)}}return this},undelegateEvents:function(){this.$el.off(".delegateEvents"+this.cid);return this},_ensureElement:function(){if(!this.el){var t=i.extend({},i.result(this,"attributes"));if(this.id)t.id=i.result(this,"id");if(this.className)t["class"]=i.result(this,"className");var r=e.$("<"+i.result(this,"tagName")+">").attr(t);this.setElement(r,false)}else{this.setElement(i.result(this,"el"),false)}}});e.sync=function(t,r,s){var n=T[t];i.defaults(s||(s={}),{emulateHTTP:e.emulateHTTP,emulateJSON:e.emulateJSON});var a={type:n,dataType:"json"};if(!s.url){a.url=i.result(r,"url")||M()}if(s.data==null&&r&&(t==="create"||t==="update"||t==="patch")){a.contentType="application/json";a.data=JSON.stringify(s.attrs||r.toJSON(s))}if(s.emulateJSON){a.contentType="application/x-www-form-urlencoded";a.data=a.data?{model:a.data}:{}}if(s.emulateHTTP&&(n==="PUT"||n==="DELETE"||n==="PATCH")){a.type="POST";if(s.emulateJSON)a.data._method=n;var o=s.beforeSend;s.beforeSend=function(t){t.setRequestHeader("X-HTTP-Method-Override",n);if(o)return o.apply(this,arguments)}}if(a.type!=="GET"&&!s.emulateJSON){a.processData=false}if(a.type==="PATCH"&&k){a.xhr=function(){return new ActiveXObject("Microsoft.XMLHTTP")}}var h=s.xhr=e.ajax(i.extend(a,s));r.trigger("request",r,h,s);return h};var k=typeof window!=="undefined"&&!!window.ActiveXObject&&!(window.XMLHttpRequest&&(new XMLHttpRequest).dispatchEvent);var T={create:"POST",update:"PUT",patch:"PATCH","delete":"DELETE",read:"GET"};e.ajax=function(){return e.$.ajax.apply(e.$,arguments)};var $=e.Router=function(t){t||(t={});if(t.routes)this.routes=t.routes;this._bindRoutes();this.initialize.apply(this,arguments)};var S=/\((.*?)\)/g;var H=/(\(\?)?:\w+/g;var A=/\*\w+/g;var I=/[\-{}\[\]+?.,\\\^$|#\s]/g;i.extend($.prototype,u,{initialize:function(){},route:function(t,r,s){if(!i.isRegExp(t))t=this._routeToRegExp(t);if(i.isFunction(r)){s=r;r=""}if(!s)s=this[r];var n=this;e.history.route(t,function(i){var a=n._extractParameters(t,i);n.execute(s,a);n.trigger.apply(n,["route:"+r].concat(a));n.trigger("route",r,a);e.history.trigger("route",n,r,a)});return this},execute:function(t,e){if(t)t.apply(this,e)},navigate:function(t,i){e.history.navigate(t,i);return this},_bindRoutes:function(){if(!this.routes)return;this.routes=i.result(this,"routes");var t,e=i.keys(this.routes);while((t=e.pop())!=null){this.route(t,this.routes[t])}},_routeToRegExp:function(t){t=t.replace(I,"\\$&").replace(S,"(?:$1)?").replace(H,function(t,e){return e?t:"([^/?]+)"}).replace(A,"([^?]*?)");return new RegExp("^"+t+"(?:\\?([\\s\\S]*))?$")},_extractParameters:function(t,e){var r=t.exec(e).slice(1);return i.map(r,function(t,e){if(e===r.length-1)return t||null;return t?decodeURIComponent(t):null})}});var N=e.History=function(){this.handlers=[];i.bindAll(this,"checkUrl");if(typeof window!=="undefined"){this.location=window.location;this.history=window.history}};var R=/^[#\/]|\s+$/g;var O=/^\/+|\/+$/g;var P=/msie [\w.]+/;var C=/\/$/;var j=/#.*$/;N.started=false;i.extend(N.prototype,u,{interval:50,atRoot:function(){return this.location.pathname.replace(/[^\/]$/,"$&/")===this.root},getHash:function(t){var e=(t||this).location.href.match(/#(.*)$/);return e?e[1]:""},getFragment:function(t,e){if(t==null){if(this._hasPushState||!this._wantsHashChange||e){t=decodeURI(this.location.pathname+this.location.search);var i=this.root.replace(C,"");if(!t.indexOf(i))t=t.slice(i.length)}else{t=this.getHash()}}return t.replace(R,"")},start:function(t){if(N.started)throw new Error("Backbone.history has already been started");N.started=true;this.options=i.extend({root:"/"},this.options,t);this.root=this.options.root;this._wantsHashChange=this.options.hashChange!==false;this._wantsPushState=!!this.options.pushState;this._hasPushState=!!(this.options.pushState&&this.history&&this.history.pushState);var r=this.getFragment();var s=document.documentMode;var n=P.exec(navigator.userAgent.toLowerCase())&&(!s||s<=7);this.root=("/"+this.root+"/").replace(O,"/");if(n&&this._wantsHashChange){var a=e.$('<iframe src="javascript:0" tabindex="-1">');this.iframe=a.hide().appendTo("body")[0].contentWindow;this.navigate(r)}if(this._hasPushState){e.$(window).on("popstate",this.checkUrl)}else if(this._wantsHashChange&&"onhashchange"in window&&!n){e.$(window).on("hashchange",this.checkUrl)}else if(this._wantsHashChange){this._checkUrlInterval=setInterval(this.checkUrl,this.interval)}this.fragment=r;var o=this.location;if(this._wantsHashChange&&this._wantsPushState){if(!this._hasPushState&&!this.atRoot()){this.fragment=this.getFragment(null,true);this.location.replace(this.root+"#"+this.fragment);return true}else if(this._hasPushState&&this.atRoot()&&o.hash){this.fragment=this.getHash().replace(R,"");this.history.replaceState({},document.title,this.root+this.fragment)}}if(!this.options.silent)return this.loadUrl()},stop:function(){e.$(window).off("popstate",this.checkUrl).off("hashchange",this.checkUrl);if(this._checkUrlInterval)clearInterval(this._checkUrlInterval);N.started=false},route:function(t,e){this.handlers.unshift({route:t,callback:e})},checkUrl:function(t){var e=this.getFragment();if(e===this.fragment&&this.iframe){e=this.getFragment(this.getHash(this.iframe))}if(e===this.fragment)return false;if(this.iframe)this.navigate(e);this.loadUrl()},loadUrl:function(t){t=this.fragment=this.getFragment(t);return i.any(this.handlers,function(e){if(e.route.test(t)){e.callback(t);return true}})},navigate:function(t,e){if(!N.started)return false;if(!e||e===true)e={trigger:!!e};var i=this.root+(t=this.getFragment(t||""));t=t.replace(j,"");if(this.fragment===t)return;this.fragment=t;if(t===""&&i!=="/")i=i.slice(0,-1);if(this._hasPushState){this.history[e.replace?"replaceState":"pushState"]({},document.title,i)}else if(this._wantsHashChange){this._updateHash(this.location,t,e.replace);if(this.iframe&&t!==this.getFragment(this.getHash(this.iframe))){if
45343(!e.replace)this.iframe.document.open().close();this._updateHash(this.iframe.location,t,e.replace)}}else{return this.location.assign(i)}if(e.trigger)return this.loadUrl(t)},_updateHash:function(t,e,i){if(i){var r=t.href.replace(/(javascript:|#).*$/,"");t.replace(r+"#"+e)}else{t.hash="#"+e}}});e.history=new N;var U=function(t,e){var r=this;var s;if(t&&i.has(t,"constructor")){s=t.constructor}else{s=function(){return r.apply(this,arguments)}}i.extend(s,r,e);var n=function(){this.constructor=s};n.prototype=r.prototype;s.prototype=new n;if(t)i.extend(s.prototype,t);s.__super__=r.prototype;return s};p.extend=g.extend=$.extend=w.extend=N.extend=U;var M=function(){throw new Error('A "url" property or function must be specified')};var q=function(t,e){var i=e.error;e.error=function(r){if(i)i(t,r,e);t.trigger("error",t,r,e)}};return e}); 45344 45345/* 45346colpick Color Picker 45347Copyright 2013 Jose Vargas. Licensed under GPL license. Based on Stefan Petre's Color Picker www.eyecon.ro, dual licensed under the MIT and GPL licenses 45348 45349For usage and examples: colpick.com/plugin 45350 */ 45351 45352(function ($) { 45353 var colpick = function() { 45354 var 45355 tpl = '<div class="colpick"><div class="colpick_color"><div class="colpick_color_overlay1"><div class="colpick_color_overlay2"><div class="colpick_selector_outer"><div class="colpick_selector_inner"></div></div></div></div></div><div class="colpick_hue"><div class="colpick_hue_arrs"><div class="colpick_hue_larr"></div><div class="colpick_hue_rarr"></div></div></div><div class="colpick_new_color"></div><div class="colpick_current_color"></div><div class="colpick_hex_field"><div class="colpick_field_letter">#</div><input type="text" maxlength="6" size="6" /></div><div class="colpick_rgb_r colpick_field"><div class="colpick_field_letter">R</div><input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div><div class="colpick_rgb_g colpick_field"><div class="colpick_field_letter">G</div><input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div><div class="colpick_rgb_b colpick_field"><div class="colpick_field_letter">B</div><input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div><div class="colpick_hsb_h colpick_field"><div class="colpick_field_letter">H</div><input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div><div class="colpick_hsb_s colpick_field"><div class="colpick_field_letter">S</div><input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div><div class="colpick_hsb_b colpick_field"><div class="colpick_field_letter">B</div><input type="text" maxlength="3" size="3" /><div class="colpick_field_arrs"><div class="colpick_field_uarr"></div><div class="colpick_field_darr"></div></div></div><div class="colpick_submit"></div></div>', 45356 defaults = { 45357 showEvent: 'click', 45358 onShow: function() {}, 45359 onBeforeShow: function(){}, 45360 onHide: function() {}, 45361 onChange: function() {}, 45362 onSubmit: function() {}, 45363 colorScheme: 'light', 45364 color: '3289c7', 45365 livePreview: true, 45366 flat: false, 45367 layout: 'full', 45368 submit: 1, 45369 submitText: 'OK', 45370 height: 156 45371 }, 45372 //Fill the inputs of the plugin 45373 fillRGBFields = function (hsb, cal) { 45374 var rgb = hsbToRgb(hsb); 45375 $(cal).data('colpick').fields 45376 .eq(1).val(rgb.r).end() 45377 .eq(2).val(rgb.g).end() 45378 .eq(3).val(rgb.b).end(); 45379 }, 45380 fillHSBFields = function (hsb, cal) { 45381 $(cal).data('colpick').fields 45382 .eq(4).val(Math.round(hsb.h)).end() 45383 .eq(5).val(Math.round(hsb.s)).end() 45384 .eq(6).val(Math.round(hsb.b)).end(); 45385 }, 45386 fillHexFields = function (hsb, cal) { 45387 $(cal).data('colpick').fields.eq(0).val(hsbToHex(hsb)); 45388 }, 45389 //Set the round selector position 45390 setSelector = function (hsb, cal) { 45391 $(cal).data('colpick').selector.css('backgroundColor', '#' + hsbToHex({h: hsb.h, s: 100, b: 100})); 45392 $(cal).data('colpick').selectorIndic.css({ 45393 left: parseInt($(cal).data('colpick').height * hsb.s/100, 10),
45394 top: parseInt($(cal).data('colpick').height * (100-hsb.b)/100, 10) 45395 }); 45396 }, 45397 //Set the hue selector position 45398 setHue = function (hsb, cal) { 45399 $(cal).data('colpick').hue.css('top', parseInt($(cal).data('colpick').height - $(cal).data('colpick').height * hsb.h/360, 10)); 45400 }, 45401 //Set current and new colors 45402 setCurrentColor = function (hsb, cal) { 45403 $(cal).data('colpick').currentColor.css('backgroundColor', '#' + hsbToHex(hsb)); 45404 }, 45405 setNewColor = function (hsb, cal) { 45406 $(cal).data('colpick').newColor.css('backgroundColor', '#' + hsbToHex(hsb)); 45407 }, 45408 //Called when the new color is changed 45409 change = function (ev) { 45410 var cal = $(this).parent().parent(), col; 45411 if (this.parentNode.className.indexOf('_hex') > 0) { 45412 cal.data('colpick').color = col = hexToHsb(fixHex(this.value)); 45413 fillRGBFields(col, cal.get(0)); 45414 fillHSBFields(col, cal.get(0)); 45415 } else if (this.parentNode.className.indexOf('_hsb') > 0) { 45416 cal.data('colpick').color = col = fixHSB({ 45417 h: parseInt(cal.data('colpick').fields.eq(4).val(), 10), 45418 s: parseInt(cal.data('colpick').fields.eq(5).val(), 10), 45419 b: parseInt(cal.data('colpick').fields.eq(6).val(), 10) 45420 }); 45421 fillRGBFields(col, cal.get(0)); 45422 fillHexFields(col, cal.get(0)); 45423 } else { 45424 cal.data('colpick').color = col = rgbToHsb(fixRGB({ 45425 r: parseInt(cal.data('colpick').fields.eq(1).val(), 10), 45426 g: parseInt(cal.data('colpick').fields.eq(2).val(), 10), 45427 b: parseInt(cal.data('colpick').fields.eq(3).val(), 10) 45428 })); 45429 fillHexFields(col, cal.get(0)); 45430 fillHSBFields(col, cal.get(0)); 45431 } 45432 setSelector(col, cal.get(0)); 45433 setHue(col, cal.get(0)); 45434 setNewColor(col, cal.get(0)); 45435 cal.data('colpick').onChange.apply(cal.parent(), [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 0]); 45436 }, 45437 //Change style on blur and on focus of inputs 45438 blur = function (ev) { 45439 $(this).parent().removeClass('colpick_focus'); 45440 }, 45441 focus = function() { 45442 $(this).parent().parent().data('colpick').fields.parent().removeClass('colpick_focus'); 45443 $(this).parent().addClass('colpick_focus'); 45444 }, 45445 //Increment/decrement arrows functions 45446 downIncrement = function (ev) { 45447 ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; 45448 var field = $(this).parent().find('input').focus(); 45449 var current = { 45450 el: $(this).parent().addClass('colpick_slider'), 45451 max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 : (this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255), 45452 y: ev.pageY, 45453 field: field, 45454 val: parseInt(field.val(), 10), 45455 preview: $(this).parent().parent().data('colpick').livePreview 45456 }; 45457 $(document).mouseup(current, upIncrement); 45458 $(document).mousemove(current, moveIncrement); 45459 }, 45460 moveIncrement = function (ev) { 45461 ev.data.field.val(Math.max(0, Math.min(ev.data.max, parseInt(ev.data.val - ev.pageY + ev.data.y, 10)))); 45462 if (ev.data.preview) { 45463 change.apply(ev.data.field.get(0), [true]); 45464 } 45465 return false; 45466 }, 45467 upIncrement = function (ev) { 45468 change.apply(ev.data.field.get(0), [true]); 45469 ev.data.el.removeClass('colpick_slider').find('input').focus(); 45470 $(document).unbind('mouseup', upIncrement); 45471 $(document).unbind('mousemove', moveIncrement); 45472 return false; 45473 }, 45474 //Hue slider functions 45475 downHue = function (ev) { 45476 ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; 45477 var current = { 45478 cal: $(this).parent(), 45479 y: $(this).offset().top 45480 }; 45481 $(document).bind('mouseup touchend',current,upHue); 45482 $(document).bind('mousemove touchmove',current,moveHue); 45483 45484 var pageY = ((ev.type == 'touchstart') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY ); 45485 change.apply( 45486 current.cal.data('colpick') 45487 .fields.eq(4).val(parseInt(360*(current.cal.data('colpick').height - (pageY - current.y))/current.cal.data('colpick').height, 10)) 45488 .get(0), 45489 [current.cal.data('colpick').livePreview] 45490 ); 45491 return false; 45492 }, 45493 moveHue = function (ev) { 45494 var pageY = ((ev.type == 'touchmove') ? ev.originalEvent.changedTouches[0].pageY : ev.pageY ); 45495 change.apply( 45496 ev.data.cal.data('colpick') 45497 .fields.eq(4).val(parseInt(360*(ev.data.cal.data('colpick').height - Math.max(0,Math.min(ev.data.cal.data('colpick').height,(pageY - ev.data.y))))/ev.data.cal.data('colpick').height, 10)) 45498 .get(0), 45499 [ev.data.preview] 45500 ); 45501 return false;
45502 }, 45503 upHue = function (ev) { 45504 fillRGBFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); 45505 fillHexFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); 45506 $(document).unbind('mouseup touchend',upHue); 45507 $(document).unbind('mousemove touchmove',moveHue); 45508 return false; 45509 }, 45510 //Color selector functions 45511 downSelector = function (ev) { 45512 ev.preventDefault ? ev.preventDefault() : ev.returnValue = false; 45513 var current = { 45514 cal: $(this).parent(), 45515 pos: $(this).offset() 45516 }; 45517 current.preview = current.cal.data('colpick').livePreview; 45518 45519 $(document).bind('mouseup touchend',current,upSelector); 45520 $(document).bind('mousemove touchmove',current,moveSelector); 45521 45522 var payeX,pageY; 45523 if(ev.type == 'touchstart') { 45524 pageX = ev.originalEvent.changedTouches[0].pageX, 45525 pageY = ev.originalEvent.changedTouches[0].pageY; 45526 } else { 45527 pageX = ev.pageX; 45528 pageY = ev.pageY; 45529 } 45530 45531 change.apply( 45532 current.cal.data('colpick').fields 45533 .eq(6).val(parseInt(100*(current.cal.data('colpick').height - (pageY - current.pos.top))/current.cal.data('colpick').height, 10)).end() 45534 .eq(5).val(parseInt(100*(pageX - current.pos.left)/current.cal.data('colpick').height, 10)) 45535 .get(0), 45536 [current.preview] 45537 ); 45538 return false; 45539 }, 45540 moveSelector = function (ev) { 45541 var payeX,pageY; 45542 if(ev.type == 'touchmove') { 45543 pageX = ev.originalEvent.changedTouches[0].pageX, 45544 pageY = ev.originalEvent.changedTouches[0].pageY; 45545 } else { 45546 pageX = ev.pageX; 45547 pageY = ev.pageY; 45548 } 45549 45550 change.apply( 45551 ev.data.cal.data('colpick').fields 45552 .eq(6).val(parseInt(100*(ev.data.cal.data('colpick').height - Math.max(0,Math.min(ev.data.cal.data('colpick').height,(pageY - ev.data.pos.top))))/ev.data.cal.data('colpick').height, 10)).end() 45553 .eq(5).val(parseInt(100*(Math.max(0,Math.min(ev.data.cal.data('colpick').height,(pageX - ev.data.pos.left))))/ev.data.cal.data('colpick').height, 10)) 45554 .get(0), 45555 [ev.data.preview] 45556 ); 45557 return false; 45558 }, 45559 upSelector = function (ev) { 45560 fillRGBFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); 45561 fillHexFields(ev.data.cal.data('colpick').color, ev.data.cal.get(0)); 45562 $(document).unbind('mouseup touchend',upSelector); 45563 $(document).unbind('mousemove touchmove',moveSelector); 45564 return false; 45565 }, 45566 //Submit button 45567 clickSubmit = function (ev) { 45568 var cal = $(this).parent(); 45569 var col = cal.data('colpick').color; 45570 cal.data('colpick').origColor = col; 45571 setCurrentColor(col, cal.get(0)); 45572 cal.data('colpick').onSubmit(col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el); 45573 }, 45574 //Show/hide the color picker 45575 show = function (ev) { 45576 // Prevent the trigger of any direct parent 45577 ev.stopPropagation(); 45578 var cal = $('#' + $(this).data('colpickId')); 45579 cal.data('colpick').onBeforeShow.apply(this, [cal.get(0)]); 45580 var pos = $(this).offset(); 45581 var top = pos.top + this.offsetHeight; 45582 var left = pos.left; 45583 var viewPort = getViewport(); 45584 var calW = cal.width(); 45585 if (left + calW > viewPort.l + viewPort.w) { 45586 left -= calW; 45587 } 45588 cal.css({left: left + 'px', top: top + 'px'}); 45589 if (cal.data('colpick').onShow.apply(this, [cal.get(0)]) != false) { 45590 cal.show(); 45591 } 45592 //Hide when user clicks outside
45593 $('html').mousedown({cal:cal}, hide); 45594 cal.mousedown(function(ev){ev.stopPropagation();}) 45595 }, 45596 hide = function (ev) { 45597 if (ev.data.cal.data('colpick').onHide.apply(this, [ev.data.cal.get(0)]) != false) { 45598 ev.data.cal.hide(); 45599 } 45600 $('html').unbind('mousedown', hide); 45601 }, 45602 getViewport = function() { 45603 var m = document.compatMode == 'CSS1Compat'; 45604 return { 45605 l : window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft), 45606 w : window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth) 45607 }; 45608 }, 45609 //Fix the values if the user enters a negative or high value 45610 fixHSB = function (hsb) { 45611 return { 45612 h: Math.min(360, Math.max(0, hsb.h)), 45613 s: Math.min(100, Math.max(0, hsb.s)), 45614 b: Math.min(100, Math.max(0, hsb.b)) 45615 }; 45616 }, 45617 fixRGB = function (rgb) { 45618 return { 45619 r: Math.min(255, Math.max(0, rgb.r)), 45620 g: Math.min(255, Math.max(0, rgb.g)), 45621 b: Math.min(255, Math.max(0, rgb.b)) 45622 }; 45623 }, 45624 fixHex = function (hex) { 45625 var len = 6 - hex.length; 45626 if (len > 0) { 45627 var o = []; 45628 for (var i=0; i<len; i++) { 45629 o.push('0'); 45630 } 45631 o.push(hex); 45632 hex = o.join(''); 45633 } 45634 return hex; 45635 }, 45636 restoreOriginal = function() { 45637 var cal = $(this).parent(); 45638 var col = cal.data('colpick').origColor; 45639 cal.data('colpick').color = col; 45640 fillRGBFields(col, cal.get(0)); 45641 fillHexFields(col, cal.get(0)); 45642 fillHSBFields(col, cal.get(0)); 45643 setSelector(col, cal.get(0)); 45644 setHue(col, cal.get(0)); 45645 setNewColor(col, cal.get(0)); 45646 }; 45647 return { 45648 init: function (opt) { 45649 opt = $.extend({}, defaults, opt||{}); 45650 //Set color 45651 if (typeof opt.color == 'string') { 45652 opt.color = hexToHsb(opt.color); 45653 } else if (opt.color.r != undefined && opt.color.g != undefined && opt.color.b != undefined) { 45654 opt.color = rgbToHsb(opt.color); 45655 } else if (opt.color.h != undefined && opt.color.s != undefined && opt.color.b != undefined) { 45656 opt.color = fixHSB(opt.color); 45657 } else { 45658 return this; 45659 } 45660 45661 //For each selected DOM element 45662 return this.each(function() { 45663 //If the element does not have an ID 45664 if (!$(this).data('colpickId')) { 45665 var options = $.extend({}, opt); 45666 options.origColor = opt.color; 45667 //Generate and assign a random ID 45668 var id = 'collorpicker_' + parseInt(Math.random() * 1000); 45669 $(this).data('colpickId', id); 45670 //Set the tpl's ID and get the HTML 45671 var cal = $(tpl).attr('id', id); 45672 //Add class according to layout 45673 cal.addClass('colpick_'+options.layout+(options.submit?'':' colpick_'+options.layout+'_ns')); 45674 //Add class if the color scheme is not default 45675 if(options.colorScheme != 'light') { 45676 cal.addClass('colpick_'+options.colorScheme); 45677 } 45678 //Setup submit button 45679 cal.find('div.colpick_submit').html(options.submitText).click(clickSubmit); 45680 //Setup input fields 45681 options.fields = cal.find('input').change(change).blur(blur).focus(focus); 45682 cal.find('div.colpick_field_arrs').mousedown(downIncrement).end().find('div.colpick_current_color').click(restoreOriginal); 45683 //Setup hue selector 45684 options.selector = cal.find('div.colpick_color').bind('mousedown touchstart',downSelector); 45685 options.selectorIndic = options.selector.find('div.colpick_selector_outer'); 45686 //Store parts of the plugin 45687 options.el = this; 45688 options.hue = cal.find('div.colpick_hue_arrs'); 45689 huebar = options.hue.parent(); 45690 //Paint the hue bar 45691 var UA = navigator.userAgent.toLowerCase(); 45692 var isIE = navigator.appName === 'Microsoft Internet Explorer'; 45693 var IEver = isIE ? parseFloat( UA.match( /msie ([0-9]{1,}[\.0-9]{0,})/ )[1] ) : 0; 45694 var ngIE = ( isIE && IEver < 10 ); 45695 var stops = ['#ff0000','#ff0080','#ff00ff','#8000ff','#0000ff','#0080ff','#00ffff','#00ff80','#00ff00','#80ff00','#ffff00','#ff8000','#ff0000']; 45696 if(ngIE) { 45697 var i, div; 45698 for(i=0; i<=11; i++) { 45699 div = $('<div></div>').attr('style','height:8.333333%; filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='+stops[i]+', endColorstr='+stops[i+1]+'); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='+stops[i]+', endColorstr='+stops[i+1]+')";'); 45700 huebar.append(div); 45701 } 45702 } else { 45703 stopList = stops.join(','); 45704 huebar.attr('style','background:-webkit-linear-gradient(top,'+stopList+'); background: -o-linear-gradient(top,'+stopList+'); background: -ms-linear-gradient(top,'+stopList+'); background:-moz-linear-gradient(top,'+stopList+'); -webkit-linear-gradient(top,'+stopList+'); background:linear-gradient(to bottom,'+stopList+'); '); 45705 } 45706 cal.find('div.colpick_hue').bind('mousedown touchstart',downHue); 45707 options.newColor = cal.find('div.colpick_new_color'); 45708 options.currentColor = cal.find('div.colpick_current_color'); 45709 //Store options and fill with default color 45710 cal.data('colpick', options); 45711 fillRGBFields(options.color, cal.get(0)); 45712 fillHSBFields(options.color, cal.get(0)); 45713 fillHexFields(options.color, cal.get(0)); 45714 setHue(options.color, cal.get(0)); 45715 setSelector(options.color, cal.get(0)); 45716 setCurrentColor(options.color, cal.get(0));
45717 setNewColor(options.color, cal.get(0)); 45718 //Append to body if flat=false, else show in place 45719 if (options.flat) { 45720 cal.appendTo(this).show(); 45721 cal.css({ 45722 position: 'relative', 45723 display: 'block' 45724 }); 45725 } else { 45726 cal.appendTo(document.body); 45727 $(this).bind(options.showEvent, show); 45728 cal.css({ 45729 position:'absolute' 45730 }); 45731 } 45732 } 45733 }); 45734 }, 45735 //Shows the picker 45736 showPicker: function() { 45737 return this.each( function() { 45738 if ($(this).data('colpickId')) { 45739 show.apply(this); 45740 } 45741 }); 45742 }, 45743 //Hides the picker 45744 hidePicker: function() { 45745 return this.each( function() { 45746 if ($(this).data('colpickId')) { 45747 $('#' + $(this).data('colpickId')).hide(); 45748 $('html').unbind('mousedown', hide); 45749 } 45750 }); 45751 }, 45752 //Sets a color as new and current (default) 45753 setColor: function(col, setCurrent) { 45754 setCurrent = (typeof setCurrent === "undefined") ? 1 : setCurrent; 45755 if (typeof col == 'string') { 45756 col = hexToHsb(col); 45757 } else if (col.r != undefined && col.g != undefined && col.b != undefined) { 45758 col = rgbToHsb(col); 45759 } else if (col.h != undefined && col.s != undefined && col.b != undefined) { 45760 col = fixHSB(col); 45761 } else { 45762 return this; 45763 } 45764 return this.each(function(){ 45765 if ($(this).data('colpickId')) { 45766 var cal = $('#' + $(this).data('colpickId')); 45767 cal.data('colpick').color = col; 45768 cal.data('colpick').origColor = col; 45769 fillRGBFields(col, cal.get(0)); 45770 fillHSBFields(col, cal.get(0)); 45771 fillHexFields(col, cal.get(0)); 45772 setHue(col, cal.get(0)); 45773 setSelector(col, cal.get(0)); 45774 45775 setNewColor(col, cal.get(0)); 45776 cal.data('colpick').onChange.apply(cal.parent(), [col, hsbToHex(col), hsbToRgb(col), cal.data('colpick').el, 1]); 45777 if(setCurrent) { 45778 setCurrentColor(col, cal.get(0)); 45779 } 45780 } 45781 }); 45782 } 45783 }; 45784 }(); 45785 //Color space convertions 45786 var hexToRgb = function (hex) { 45787 var hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16); 45788 return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)}; 45789 }; 45790 var hexToHsb = function (hex) { 45791 return rgbToHsb(hexToRgb(hex)); 45792 }; 45793 var rgbToHsb = function (rgb) { 45794 var hsb = {h: 0, s: 0, b: 0}; 45795 var min = Math.min(rgb.r, rgb.g, rgb.b); 45796 var max = Math.max(rgb.r, rgb.g, rgb.b); 45797 var delta = max - min; 45798 hsb.b = max; 45799 hsb.s = max != 0 ? 255 * delta / max : 0; 45800 if (hsb.s != 0) { 45801 if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta; 45802 else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta; 45803 else hsb.h = 4 + (rgb.r - rgb.g) / delta; 45804 } else hsb.h = -1; 45805 hsb.h *= 60; 45806 if (hsb.h < 0) hsb.h += 360; 45807 hsb.s *= 100/255; 45808 hsb.b *= 100/255; 45809 return hsb; 45810 }; 45811 var hsbToRgb = function (hsb) { 45812 var rgb = {}; 45813 var h = hsb.h; 45814 var s = hsb.s*255/100; 45815 var v = hsb.b*255/100; 45816 if(s == 0) { 45817 rgb.r = rgb.g = rgb.b = v; 45818 } else { 45819 var t1 = v; 45820 var t2 = (255-s)*v/255; 45821 var t3 = (t1-t2)*(h%60)/60; 45822 if(h==360) h = 0; 45823 if(h<60) {rgb.r=t1; rgb.b=t2; rgb.g=t2+t3} 45824 else if(h<120) {rgb.g=t1; rgb.b=t2; rgb.r=t1-t3} 45825 else if(h<180) {rgb.g=t1; rgb.r=t2; rgb.b=t2+t3} 45826 else if(h<240) {rgb.b=t1; rgb.r=t2; rgb.g=t1-t3} 45827 else if(h<300) {rgb.b=t1; rgb.g=t2; rgb.r=t2+t3} 45828 else if(h<360) {rgb.r=t1; rgb.g=t2; rgb.b=t1-t3} 45829 else {rgb.r=0; rgb.g=0; rgb.b=0} 45830 } 45831 return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)}; 45832 }; 45833 var rgbToHex = function (rgb) { 45834 var hex = [ 45835 rgb.r.toString(16), 45836 rgb.g.toString(16), 45837 rgb.b.toString(16) 45838 ]; 45839 $.each(hex, function (nr, val) { 45840 if (val.length == 1) { 45841 hex[nr] = '0' + val; 45842 } 45843 }); 45844 return hex.join(''); 45845 }; 45846 var hsbToHex = function (hsb) { 45847 return rgbToHex(hsbToRgb(hsb)); 45848 }; 45849 $.fn.extend({ 45850 colpick: colpick.init, 45851 colpickHide: colpick.hidePicker, 45852 colpickShow: colpick.showPicker, 45853 colpickSetColor: colpick.setColor 45854 }); 45855 $.extend({ 45856 colpick:{ 45857 rgbToHex: rgbToHex, 45858 rgbToHsb: rgbToHsb, 45859 hsbToHex: hsbToHex, 45860 hsbToRgb: hsbToRgb, 45861 hexToHsb: hexToHsb, 45862 hexToRgb: hexToRgb 45863 } 45864 }); 45865})(jQuery); 45866(function() { 45867 this.LightsUI = {}; 45868 45869 $(function() { 45870 var $component_info, $lights_container, add_light, info_view, light_env, light_env_view, lights, remove_info_view, setup_info_view, store_lights, trigger_change; 45871 $lights_container = $('#lights-list'); 45872 lights = new LightsUI.ItemLightCollection; 45873 light_env = new LightsUI.RoomEnvironment({ 45874 setting: 'day', 45875 intensity: 30, 45876 azimuth: 45, 45877 altitude: 60 45878 }); 45879 light_env_view = new LightsUI.RoomEnvironmentView({ 45880 model: light_env 45881 }); 45882 $('#lights-env').append(light_env_view.render().el); 45883 light_env_view.init_slider(); 45884 add_light = function(attrs) { 45885 var light, view; 45886 light = new LightsUI.ItemLight(attrs); 45887 lights.add(light); 45888 view = new LightsUI.ItemLightView({ 45889 model: light 45890 }); 45891 $lights_container.append(view.render().el); 45892 return view.init_slider(); 45893 }; 45894 store_lights = function(room) { 45895 var model; 45896 model = room.model; 45897 model.light_env = light_env.attributes; 45898 return _.each(model.components, function(c) { 45899 var light; 45900 if (light = lights.get(c.ID)) { 45901 return c.light = _.pick(light.attributes, 'on', 'wattage', 'color'); 45902 } 45903 }); 45904 }; 45905 trigger_change = function() { 45906 return myDeco.App.model.operationEnd(['lights']); 45907 }; 45908 light_env.bind('change', function(model, options) { 45909 if (options != null ? options.source : void 0) { 45910 return trigger_change(); 45911 } 45912 }); 45913 lights.bind('change', trigger_change); 45914 $component_info = $('#view-component-info'); 45915 info_view = null; 45916 remove_info_view = function() { 45917 info_view.remove_with_picker(); 45918 return info_view = null; 45919 }; 45920 setup_info_view = function(light) { 45921 info_view = new LightsUI.ItemLightView({ 45922 model: light 45923 }); 45924 $component_info.append(info_view.render().el); 45925 return info_view.init_slider(); 45926 };
45927 $('body').bind('componentAdded', function(e, c) { 45928 if (c.light) { 45929 return add_light(_.extend({ 45930 id: c.ID, 45931 name: c.componentData.name, 45932 render_path: c.render_path 45933 }, c.light)); 45934 } 45935 }).bind('componentRemoved', function(e, c) { 45936 return lights.remove(c.ID); 45937 }).bind('roomCleared', function() { 45938 lights.reset(); 45939 return $lights_container.html(''); 45940 }).bind('roomLoaded', function() { 45941 return light_env.set(myDeco.App.model.light_env); 45942 }).bind('showInfo', function(event, obj) { 45943 var light; 45944 if (info_view) { 45945 remove_info_view(); 45946 } 45947 if (obj) { 45948 light = lights.get(obj.ID); 45949 if (light) { 45950 return setup_info_view(light); 45951 } 45952 } 45953 }); 45954 return myDeco.settings.SAVE_CALLBACKS.push(store_lights); 45955 }); 45956 45957}).call(this); 45958(function() { 45959 LightsUI.ItemLightCollection = Backbone.Collection.extend({ 45960 model: LightsUI.ItemLight 45961 }); 45962 45963}).call(this); 45964(function() { 45965 LightsUI.ItemLight = Backbone.Model.extend({ 45966 initialize: function() { 45967 return this.set('thumb', this.get('render_path').replace('/cdb/renders/', '/cdb/scaled/renders/') + '_w110h110.png'); 45968 }, 45969 toggle: function() { 45970 return this.set('on', !this.get('on')); 45971 } 45972 }); 45973 45974}).call(this); 45975(function() { 45976 LightsUI.RoomEnvironment = Backbone.Model.extend({ 45977 toggle_setting: function(options) { 45978 var new_value; 45979 new_value = this.get('setting') === 'day' ? 'night' : 'day'; 45980 return this.set('setting', new_value, options); 45981 } 45982 }); 45983 45984}).call(this); 45985(function() { this.JST || (this.JST = {}); this.JST["lights/templates/item-light"] = function template(locals) { 45986 var buf = []; 45987 var jade_mixins = {}; 45988 var jade_interp; 45989 ;var locals_for_with = (locals || {});(function (gettext, name, on, thumb, wattage, wattage_fixed) { 45990 buf.push("<img" + (jade.attr("src", thumb, true, false)) + " class=\"image\"/><div class=\"title\">" + (jade.escape(null == (jade_interp = name) ? "" : jade_interp)) + "</div><div class=\"toggle on-off-toggle\"><div data-value=\"on\"" + (jade.cls(['option',(on ? 'active' : '')], [null,true])) + ">" + (jade.escape(null == (jade_interp = gettext("On")) ? "" : jade_interp)) + "</div><div data-value=\"off\"" + (jade.cls(['option',(!on ? 'active' : '')], [null,true])) + ">" + (jade.escape(null == (jade_interp = gettext("Off")) ? "" : jade_interp)) + "</div></div><div" + (jade.cls(['slider','wattage',(wattage_fixed ? 'disabled' : '')], [null,null,true])) + "><div class=\"slider-track\"><div" + (jade.attr("title", wattage, true, false)) + " class=\"slider-handle\"></div></div></div>");}.call(this,"gettext" in locals_for_with?locals_for_with.gettext:typeof gettext!=="undefined"?gettext:undefined,"name" in locals_for_with?locals_for_with.name:typeof name!=="undefined"?name:undefined,"on" in locals_for_with?locals_for_with.on:typeof on!=="undefined"?on:undefined,"thumb" in locals_for_with?locals_for_with.thumb:typeof thumb!=="undefined"?thumb:undefined,"wattage" in locals_for_with?locals_for_with.wattage:typeof wattage!=="undefined"?wattage:undefined,"wattage_fixed" in locals_for_with?locals_for_with.wattage_fixed:typeof wattage_fixed!=="undefined"?wattage_fixed:undefined));;return buf.join(""); 45991 }; 45992}).call(this); 45993(function() { this.JST || (this.JST = {}); this.JST["lights/templates/room-light-env"] = function template(locals) { 45994 var buf = []; 45995 var jade_mixins = {}; 45996 var jade_interp; 45997 ;var locals_for_with = (locals || {});(function (gettext, intensity, setting) { 45998 buf.push("<div class=\"general\"><div class=\"title\">" + (jade.escape(null == (jade_interp = gettext('General lighting settings')) ? "" : jade_interp)) + "</div><div class=\"toggle day-night-toggle\"><div data-value=\"day\"" + (jade.cls(['option',(setting == 'day' ? "active" : '')], [null,true])) + ">" + (jade.escape(null == (jade_interp = gettext('Day')) ? "" : jade_interp)) + "</div><div data-value=\"night\"" + (jade.cls(['option',(setting == 'night' ? "active" : '')], [null,true])) + ">" + (jade.escape(null == (jade_interp = gettext('Night')) ? "" : jade_interp)) + "</div></div><div class=\"slider intensity\"><div class=\"slider-track\"><div" + (jade.attr("title", intensity, true, false)) + " class=\"slider-handle\"></div></div></div></div><div class=\"sun-position\"><div class=\"title\">" + (jade.escape(null == (jade_interp = gettext('Position of the Sun')) ? "" : jade_interp)) + "</div><label>" + (jade.escape(null == (jade_interp = gettext('Direction')) ? "" : jade_interp)) + "</label><select class=\"azimuth\"><option value=\"0\">" + (jade.escape(null == (jade_interp = gettext('North')) ? "" : jade_interp)) + "</option><option value=\"45\">" + (jade.escape(null == (jade_interp = gettext('Northeast')) ? "" : jade_interp)) + "</option><option value=\"90\">" + (jade.escape(null == (jade_interp = gettext('East')) ? "" : jade_interp)) + "</option><option value=\"135\">" + (jade.escape(null == (jade_interp = gettext('Southeast')) ? "" : jade_interp)) + "</option><option value=\"180\">" + (jade.escape(null == (jade_interp = gettext('South')) ? "" : jade_interp)) + "</option><option value=\"225\">" + (jade.escape(null == (jade_interp = gettext('Southwest')) ? "" : jade_interp)) + "</option><option value=\"270\">" + (jade.escape(null == (jade_interp = gettext('West')) ? "" : jade_interp)) + "</option><option value=\"315\">" + (jade.escape(null == (jade_interp = gettext('Northwest')) ? "" : jade_interp)) + "</option></select><br/><label>" + (jade.escape(null == (jade_interp = gettext('Altitude')) ? "" : jade_interp)) + "</label><select class=\"altitude\"><option value=\"15\">" + (jade.escape(null == (jade_interp = gettext('low')) ? "" : jade_interp)) + "</option><option value=\"40\">" + (jade.escape(null == (jade_interp = gettext('medium')) ? "" : jade_interp)) + "</option><option value=\"60\">" + (jade.escape(null == (jade_interp = gettext('high')) ? "" : jade_interp)) + "</option></select></div>");}.call(this,"gettext" in locals_for_with?locals_for_with.gettext:typeof gettext!=="undefined"?gettext:undefined,"intensity" in locals_for_with?locals_for_with.intensity:typeof intensity!=="undefined"?intensity:undefined,"setting" in locals_for_with?locals_for_with.setting:typeof setting!=="undefined"?setting:undefined));;return buf.join(""); 45999 }; 46000}).call(this); 46001(function() { 46002 LightsUI.ItemLightView = Backbone.View.extend({ 46003 className: 'light-view', 46004 template: JST["lights/templates/item-light"], 46005 initialize: function() { 46006 _.bindAll(this, 'change_wattage', 'change_color', 'toggle_light', 'remove_with_picker', 'wattage_changed', 'color_changed', 'state_changed'); 46007 this.listenTo(this.model, 'remove', this.remove_with_picker); 46008 this.listenTo(this.model, 'change:wattage', this.wattage_changed); 46009 this.listenTo(this.model, 'change:color', this.color_changed); 46010 return this.listenTo(this.model, 'change:on', this.state_changed); 46011 }, 46012 render: function() { 46013 var acolor, hex, picker, rgb; 46014 this.$el.html(this.template(this.model.attributes)); 46015 acolor = this.model.attributes.color; 46016 rgb = { 46017 r: acolor[0], 46018 g: acolor[1], 46019 b: acolor[2] 46020 }; 46021 hex = $.colpick.rgbToHex(rgb); 46022 picker = this.$('.picker'); 46023 picker.css('background-color', '#' + hex); 46024 if (!this.model.get('color_fixed')) { 46025 picker.colpick({ 46026 layout: 'rgbhex', 46027 color: rgb, 46028 onSubmit: this.change_color 46029 }); 46030 } 46031 this.$('.on-off-toggle .option').click(this.toggle_light); 46032 return this; 46033 }, 46034 init_slider: function() { 46035 var wattage; 46036 wattage = this.model.attributes.wattage; 46037 this.$('.wattage').slider({ 46038 value: wattage, 46039 onChange: this.change_wattage, 46040 max: Math.max(wattage, 100) 46041 }); 46042 return this.$(".wattage").toggle(this.model.attributes.on); 46043 }, 46044 remove_with_picker: function() { 46045 var picker_id; 46046 if (!this.model.get('color_fixed')) { 46047 picker_id = this.$('.picker').data('colpickId'); 46048 $('#' + picker_id).remove(); 46049 } 46050 return this.remove(); 46051 }, 46052 change_wattage: function(value) { 46053 return this.model.set("wattage", Math.round(value), { 46054 source: this 46055 }); 46056 }, 46057 change_color: function(hsb, hex, rgb, el, not_from_ui) { 46058 if (!not_from_ui) { 46059 this.$('.picker').colpickHide(); 46060 return this.model.set("color", [rgb.r, rgb.g, rgb.b]); 46061 } 46062 }, 46063 toggle_light: function(e) { 46064 var value; 46065 value = $(e.target).data('value') === 'on'; 46066 return this.model.set("on", value); 46067 }, 46068 state_changed: function(model, value) { 46069 var str_value; 46070 this.$('.on-off-toggle .option').removeClass('active'); 46071 str_value = value ? 'on' : 'off'; 46072 this.$(".on-off-toggle .option[data-value=" + str_value + "]").addClass('active'); 46073 return this.$(".wattage").toggle(value); 46074 }, 46075 wattage_changed: function(model, value, options) { 46076 this.$('.wattage .slider-handle').attr('title', value); 46077 if (options.source !== this) { 46078 return this.$('.wattage').trigger('set-slider-value', { 46079 value: value 46080 }); 46081 } 46082 }, 46083 color_changed: function(model, value, options) { 46084 var hex, rgb; 46085 rgb = { 46086 r: value[0], 46087 g: value[1], 46088 b: value[2] 46089 }; 46090 hex = $.colpick.rgbToHex(rgb); 46091 return this.$('.picker').css('background-color', "#" + hex).colpickSetColor(hex, true); 46092 } 46093 }); 46094 46095}).call(this); 46096(function() { 46097 LightsUI.RoomEnvironmentView = Backbone.View.extend({ 46098 className: 'room-light-env-view', 46099 template: JST["lights/templates/room-light-env"], 46100 initialize: function() { 46101 _.bindAll(this, 'change_intensity', 'toggle_setting', 'setting_changed', 'intensity_changed', 'change_azimuth', 'change_altitude', 'azimuth_changed', 'altitude_changed'); 46102 this.listenTo(this.model, 'change:setting', this.setting_changed); 46103 this.listenTo(this.model, 'change:intensity', this.intensity_changed); 46104 this.listenTo(this.model, 'change:azimuth', this.azimuth_changed); 46105 return this.listenTo(this.model, 'change:altitude', this.altitude_changed); 46106 }, 46107 render: function() { 46108 this.$el.html(this.template(this.model.attributes)); 46109 this.$('.day-night-toggle').click(this.toggle_setting); 46110 this.$('.azimuth').val(this.model.attributes.azimuth).change(this.change_azimuth); 46111 this.$('.altitude').val(this.model.attributes.altitude).change(this.change_altitude); 46112 return this; 46113 }, 46114 init_slider: function() { 46115 this.$('.intensity').slider({ 46116 value: this.model.attributes.intensity, 46117 onChange: this.change_intensity 46118 }); 46119 return this.$(".intensity").toggle(this.model.attributes.setting === 'day'); 46120 }, 46121 change_intensity: function(value) { 46122 return this.model.set("intensity", Math.round(value), { 46123 source: this 46124 }); 46125 }, 46126 change_azimuth: function(e) { 46127 var value; 46128 value = parseInt($(e.target).val()); 46129 return this.model.set("azimuth", value, { 46130 source: this 46131 }); 46132 }, 46133 change_altitude: function(e) { 46134 var value; 46135 value = parseInt($(e.target).val()); 46136 return this.model.set("altitude", value, { 46137 source: this 46138 }); 46139 }, 46140 toggle_setting: function(e) { 46141 var value; 46142 value = $(e.target).data('value'); 46143 return this.model.set("setting", value, { 46144 source: this 46145 }); 46146 }, 46147 setting_changed: function(model, value) { 46148 this.$('.day-night-toggle .option').removeClass('active'); 46149 this.$(".day-night-toggle .option[data-value=" + value + "]").addClass('active'); 46150 return this.$(".intensity").toggle(value === 'day'); 46151 }, 46152 intensity_changed: function(model, value, options) { 46153 this.$('.intensity .slider-handle').attr('title', value); 46154 if (options.source !== this) { 46155 return this.$('.intensity').trigger('set-slider-value', { 46156 value: value 46157 }); 46158 } 46159 }, 46160 azimuth_changed: function(model, value, options) { 46161 if (options.source !== this) { 46162 return this.$('.azimuth').val(value); 46163 } 46164 }, 46165 altitude_changed: function(model, value, options) { 46166 if (options.source !== this) { 46167 return this.$('.altitude').val(value); 46168 } 46169 } 46170 }); 46171 46172}).call(this); 46173var RoomstylerLive = { 46174 render: function(model) { 46175 var render_url = myDeco.urls.live; 46176 if (model && render_url) { 46177 46178 // let plugins add data to the room before saving it 46179 var roomData = {model: model}; 46180 for (var i=0; i < myDeco.settings.SAVE_CALLBACKS.length; i++) { 46181 myDeco.settings.SAVE_CALLBACKS[i](roomData); 46182 } 46183 46184 $.ajax({ 46185 type: 'POST', 46186 url: render_url, 46187 contentType: "application/json", 46188 dataType: "json", 46189 data: JSON.stringify(roomData.model) 46190 }); 46191 } 46192 } 46193}; 46194/*globals myDeco, $ */ 46195/*jslint browser: true, forin: true, nomen: true, plusplus: true, regexp: true, 46196 sloppy: true, white: true */ 46197$(function() { 46198 if(myDeco.browser.isIOSWebView) { 46199 document.getElementById('user_section').style.display = 'none'; 46200 document.getElementById('guest_section').style.display = 'none'; 46201 document.querySelector('#menu .help.menu').style.display = 'none'; 46202 } 46203}); 46204 46205 46206 46207 46208 46209 46210 46211 46212 46213 46214 46215 46216 46217 46218 46219 46220 46221 46222 46223 46224 46225 46226 46227 46228 46229 46230 46231 46232 46233 46234 46235 46236 46237 46238 46239 46240 46241 46242 46243 46244 46245 46246 46247 46248 46249 46250 46251 46252 46253 46254 46255 46256 46257 46258 46259 46260 46261 46262 46263 46264 46265 46266 46267 46268 46269 46270 46271 46272 46273 46274 46275 46276 46277 46278 46279 46280 46281 46282 46283 46284 46285 46286 46287 46288 46289 46290 46291 46292 46293 46294 46295 46296 46297 46298 46299 46300 46301 46302 46303 46304 46305 46306 46307 46308 46309 46310 46311 46312 46313 46314 46315 46316 46317 46318 46319 46320 46321 46322 46323 46324 46325 46326 46327 46328 46329 46330 46331 46332 46333 46334 46335 46336 46337 46338 46339 46340 46341 46342 46343 46344 46345 46346 46347 46348 46349 46350 46351 46352 46353 46354 46355 46356
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.