1var MindworkingUnitHandler; 2 3(function ($) { 4 MindworkingUnitHandler = function (configuration) { 5 this.unitTypes = {}; 6 this.formatting = {}; 7 8 // todo should we also use a ration setting, like for currency? 9 this.renderOptions = { 10 optimize: true, 11 format: true, 12 currency: "dkk", 13 group: "metric", 14 locale: "da_DK" 15 }; 16 17 this.renderIndicator = "data-render"; 18 19 if (configuration !== undefined) { 20 this.loadConfigurationJson(configuration); 21 } 22 }; 23 24 /** 25 * set the configuration from a json object 26 * 27 * @param {Object} configuration 28 * @return {MindworkingUnitHandler} 29 */ 30 MindworkingUnitHandler.prototype.loadConfigurationJson = function (configuration) { 31 // todo sanitize data 32 this.unitTypes = configuration.unitTypes; 33 this.formatting = configuration.formatting; 34 this.renderOptions.currency = this.unitTypes.currency.base; 35 this.renderOptions.group = this.unitTypes.area.base.split("/")[0]; 36 37 // switch locale in renderOptions to defaults exiting in config 38 if (!this.formatting.hasOwnProperty(this.renderOptions.locale) && this.formatting.length > 0) { 39 this.renderOptions.locale = Object.keys(this.formatting)[0]; 40 } 41 return this; 42 }; 43 44 /** 45 * sets the render options, can trigger a re-render 46 * 47 * @param {Object} options 48 * @param [$updateNode] the html element to update, as a jQuery Object. set this to trigger an automatic re-render of all render fields in the object. 49 * @returns {MindworkingUnitHandler} 50 */ 51 MindworkingUnitHandler.prototype.setRenderOptions = function (options, $updateNode) { 52 if ($updateNode === undefined) $updateNode = $('body'); 53 var changed = false; 54 for (var key in options) { 55 if (this.renderOptions.hasOwnProperty(key)) { 56 if (this.renderOptions[key] !== options[key]) { 57 this.renderOptions[key] = options[key]; 58 changed = true; 59 } 60 } 61 } 62 63 if ($updateNode !== undefined && changed) { 64 this.renderElementsIn($updateNode); 65 } 66 67 return this; 68 }; 69 70 /** 71 * convert a value 72 * @param {Number} value 73 * @param {Object} config the unit configuration 74 * @param {String} from 75 * @param {String} to 76 * @returns {number} 77 */ 78 MindworkingUnitHandler.prototype.convert = function (value, config, from, to) { 79 var factorFrom = this.getUnitFactor(from, config); 80 var factorTo = this.getUnitFactor(to, config); 81 82 return value * factorFrom / factorTo; 83 }; 84 85 /** 86 * optimize a value within the given unit type (and optionally also group) 87 * this will try to select a unit so the value is above 1 88 * if there is none, it will select the one where the value will be closest to one 89 * 90 * @param {Number} value 91 * @param {Object} config the unit configuration 92 * @param {String} from 93 * @param {String} [groupTo] the unit group, fx metric or imperial, if available 94 * @returns {[Number, Object]} 95 */ 96 MindworkingUnitHandler.prototype.optimize = function (value, config, from, groupTo) { 97 98 var units = config.grouped ? config.units[groupTo] : config.units; 99 100 var unit = this.getUnitConfiguration(from, config); 101 var valueOptimized = false; 102 var unitOptimized = false; 103 var valueTmp, unitTmp; 104 for (var key in units) { 105 unitTmp = units[key]; 106 valueTmp = value * unit.factor / unitTmp.factor; 107 if (valueOptimized === false || (valueTmp > 1 && (valueTmp < valueOptimized || valueOptimized < 0))) { 108 valueOptimized = valueTmp; 109 unitOptimized = unitTmp; 110 } 111 } 112 113 if (valueOptimized === false) { 114 valueOptimized = value; 115 unitOptimized = unit; 116 } 117 118 return [valueOptimized, unitOptimized]; 119 }; 120 121 /** 122 * get the factor for a unit 123 * 124 * @param {String} unit 125 * @param {Object} config 126 * @returns {Number|Boolean} 127 */ 128 MindworkingUnitHandler.prototype.getUnitFactor = function (unit, config) { 129 var unitConfiguration = this.getUnitConfiguration(unit, config); 130 if (unitConfiguration !== false) { 131 return unitConfiguration.factor; 132 } 133 return false;
134 }; 135 136 /** 137 * get the configuration for a unit 138 * 139 * @param {String} unit the unit key, can also contain a group key, like "metric/m2" 140 * @param {Object} config 141 * @returns {*} 142 */ 143 MindworkingUnitHandler.prototype.getUnitConfiguration = function (unit, config) { 144 if (config !== false) { 145 if (config.grouped) { 146 if (unit.indexOf("/") !== -1) { 147 var tmp = unit.split("/"); 148 if (config.units.hasOwnProperty(tmp[0])) { 149 var group = config.units[tmp[0]]; 150 if (group.hasOwnProperty(tmp[1])) { 151 return group[tmp[1]]; 152 } 153 } 154 } 155 for (var groupKey in config.units) { 156 if (config.units[groupKey].hasOwnProperty(unit)) { 157 return config.units[groupKey][unit]; 158 } 159 } 160 } else { 161 if (config.units.hasOwnProperty(unit)) { 162 return config.units[unit]; 163 } 164 } 165 } 166 return false; 167 }; 168 169 /** 170 * create a concatenated element (consisting of multiple others) 171 * 172 * @param {Object} object the temporary object with the data we need 173 * @param {String} nodeType the node type to embed this into 174 * @param {String} nodeClass the node class to embed this into 175 * @returns {string} 176 */ 177 MindworkingUnitHandler.prototype.createElementConcatenated = function (object, nodeType, nodeClass) { 178 var fields = []; 179 if (nodeClass !== undefined) { 180 nodeClass = "class='" + nodeClass + "'"; 181 } else { 182 nodeClass = ""; 183 } 184 for (var i in object.data) { 185 fields.push(this.createElement(object.data[i], 'span')); 186 } 187 var concatenated = ""; 188 189 var action = object.action; 190 191 // right now the only action available is join, not sure if any others will be needed. 192 switch (action[0]) { 193 case "join": 194 concatenated = fields.join(action[1]); 195 break; 196 } 197 198 return "<" + nodeType + " " + nodeClass + ">" + concatenated + "</" + nodeType + ">"; 199 }; 200 201 /** 202 * create an element that can be re-rendered with the UnitHandler from a fact field object 203 * the value will be inserted at this point as well 204 * will also create non re-renderable elements if they don't have a renderable type 205 * 206 * @param {Object} object the fact field object, providing type, value, unit 207 * @param {String} nodeType the type of html element to create 208 * @param {String} [nodeClass] the class of the created element 209 * @returns {string} 210 */ 211 MindworkingUnitHandler.prototype.createElement = function (object, nodeType, nodeClass) { 212 if (object.hasOwnProperty('action')) { 213 return this.createElementConcatenated(object, nodeType, nodeClass); 214 } 215 if (nodeType === undefined) nodeType = "span"; 216 if (nodeClass !== undefined) { 217 nodeClass = "class='" + nodeClass + "'"; 218 } else { 219 nodeClass = ""; 220 } 221 var type = object.hasOwnProperty('type') ? object.type : "string"; 222 var value = object.hasOwnProperty('value') ? object.value : ""; 223 var unit = object.hasOwnProperty('unit') ? object.unit : this.hasUnitType(type) ? this.unitTypes[type].base : false; 224 225 var output = value; 226 227 var attributes = ""; 228 229 switch (type) { 230 case "area": 231 case "distance": 232 case "currency": 233 case "ratio": 234 // these can be converted and formatted, according to their type 235 attributes += this.renderIndicator + "='1'"; 236 attributes += "data-type='" + type + "'"; 237 if (unit !== false) attributes += "data-unit='" + unit + "'"; 238 attributes += "data-value='" + value + "'"; 239 output = this.renderValue(value, type, unit); 240 break; 241 case "integer": 242 case "number": 243 // standard numbers are without unit, so they just get formatted, not converted 244 attributes += this.renderIndicator + "='1'"; 245 attributes += "data-type='number'"; 246 if (unit !== false) attributes += "data-unit='none'"; 247 attributes += "data-value='" + value + "'"; 248 output = this.renderValue(value, type, unit); 249 break; 250 case "date": 251 // dates can be formatted with their type, but don't have a unit 252 attributes += this.renderIndicator + "='1'"; 253 attributes += "data-type='date'"; 254 if (unit !== false) attributes += "data-unit='none'"; 255 attributes += "data-value='" + value + "'"; 256 output = this.renderValue(value, type, unit); 257 break; 258 case "period": 259 // periods can have a unit 260 attributes += this.renderIndicator + "='1'"; 261 attributes += "data-type='period'"; 262 if (unit !== false) attributes += "data-unit='" + unit + "'"; 263 attributes += "data-value='" + value + "'"; 264 output = this.renderValue(value, type, unit); 265 break; 266 case "months": 267 attributes += this.renderIndicator + "='1'"; 268 attributes += "data-type='months'"; 269 if (unit !== false) attributes += "data-unit='" + unit + "'"; 270 attributes += "data-value='" + value + "'"; 271 output = this.renderValue(value, type, unit); 272 break; 273 case "stringpercent": 274 attributes += this.renderIndicator + "='1'"; 275 attributes += "data-type='stringpercent'"; 276 if (unit !== false) attributes += "data-unit='" + unit + "'"; 277 attributes += "data-value='" + value + "'"; 278 output = this.renderValue(value, type, unit); 279 break; 280 } 281 282 return "<" + nodeType + " " + nodeClass + " " + attributes + ">" + output + "</" + nodeType + ">"; 283 }; 284 285 /** 286 * renders the renderable elements in a given jQuery element 287 * 288 * @param $node 289 * 290 * @returns {MindworkingUnitHandler} 291 */ 292 MindworkingUnitHandler.prototype.renderElementsIn = function ($node) { 293 if ($node === undefined) $node = $('body'); 294 //var ts = Date.now(); 295
296 var $elements = $node.find("[" + this.renderIndicator + "=1]"); 297 var _self = this; 298 299 $elements.each(function () { 300 _self.renderElement($(this)); 301 }); 302 303 //console.log("rendering data for " + $elements.length + " fields took " + (Math.round((Date.now() - ts) * 1000) / 1000) + "ms"); 304 305 return this; 306 }; 307 308 /** 309 * render the value as specified in the attributes and the current config 310 * 311 * @param {jQuery} $node 312 * 313 * @returns {MindworkingUnitHandler} 314 */ 315 MindworkingUnitHandler.prototype.renderElement = function ($node) { 316 var type = $node.data('type'); 317 var value = $node.data('value'); 318 var unit = $node.data('unit'); 319 320 $node.html(this.renderValue(value, type, unit)); 321 322 return this; 323 }; 324 325 /** 326 * generate the value of a given field with its unit configuration 327 * 328 * @param {*} value 329 * @param {String} type 330 * @param {String} [unit] 331 * @returns {{}|syntax} 332 */ 333 MindworkingUnitHandler.prototype.renderValue = function (value, type, unit) { 334 var optimize = this.renderOptions.optimize; 335 var currency = this.renderOptions.currency; 336 var group = this.renderOptions.group; 337 var format = this.renderOptions.format; 338 var locale = this.renderOptions.locale; 339 340 var config = this.unitTypes.hasOwnProperty(type) ? this.unitTypes[type] : false; 341 if (unit === false || unit === undefined) unit = config.base; 342 343 var newValue = value; 344 var newUnitCfg = this.getUnitConfiguration(unit, config); 345 var formatType = "number"; 346 347 switch (type) { 348 case "currency": 349 // currency should not be optimized, just converted 350 newValue = this.convert(value, config, unit, currency); 351 newUnitCfg = this.getUnitConfiguration(currency, config); 352 formatType = "currency"; 353 break; 354 case "string": 355 case "year": 356 case "stringpercent": 357 // strings will not be formatted 358 formatType = "string"; 359 break; 360 case "date": 361 formatType = "date"; 362 break; 363 case "period": 364 formatType = "period"; 365 break; 366 case "integer": 367 newValue = Math.round(value); 368 // fallthrough is intended, integer should be handled as number 369 case "number": 370 case "percentage": 371 case "ratio": 372 formatType = "number"; 373 break; 374 case "area": 375 formatType = "area"; 376 // fallthrough is intended, area supports optimization 377 default: 378 if (optimize) { 379 var optimized = this.optimize(value, config, unit, group); 380 newValue = optimized[0]; 381 newUnitCfg = optimized[1]; 382 } 383 } 384 385 if (format) { 386 switch (formatType) { 387 case "string": 388 // no formatting for strings 389 break; 390 case "date": 391 var dateFormat = this.formatting[locale]['time']['date']; 392 newValue = this.formatDate(newValue, dateFormat); 393 break; 394 case "period": 395 var periodFormat = this.formatting[locale]['time']['period']; 396 newValue = this.formatPeriod(newValue, unit, periodFormat); 397 break; 398 default: 399 // all kinds of straightforward numbers 400 if (!isNaN(parseFloat(newValue))) { 401 var fc = this.formatting[locale][formatType]; 402 newValue = this.formatNumber(newValue, fc.decimals, fc.separatorDecimals, fc.separatorThousands, fc.forceDecimals, fc.roundMethod); 403 } 404 405 } 406 } 407 408 if (config !== false) { 409 var renderValue = config.syntax; 410 renderValue = renderValue.replace("%value%", newValue).replace("%unit%", newUnitCfg !== false ? newUnitCfg.display : ""); 411 } else { 412 renderValue = newValue; 413 } 414 415 return renderValue; 416 }; 417 418 /** 419 * switch the unit group (imperial/metric) 420 * @param {String} group 421 * @param {jQuery} $updateNode 422 * @return {Boolean} 423 */ 424 MindworkingUnitHandler.prototype.switchGroup = function (group, $updateNode) { 425 if (this.hasUnitGroup('area', group)) { 426 this.setRenderOptions({'group': group}, $updateNode); 427 return true; 428 } 429 return false;
430 }; 431 432 /** 433 * switch the currency unit 434 * @param {String} unit 435 * @param {jQuery} $updateNode 436 * @return {Boolean} 437 */ 438 MindworkingUnitHandler.prototype.switchCurrency = function (unit, $updateNode) { 439 if (this.hasUnit('currency', unit)) { 440 this.setRenderOptions({'currency': unit}, $updateNode); 441 return true; 442 } 443 return false; 444 }; 445 446 /** 447 * check if unit exists 448 * 449 * @param {String} type 450 * @param {String} unit 451 * @returns {boolean} 452 */ 453 MindworkingUnitHandler.prototype.hasUnit = function (type, unit) { 454 if (this.hasUnitType(type)) { 455 var unitType = this.unitTypes[type]; 456 var group; 457 if (unitType.grouped) { 458 for (var g in unitType.units) { 459 if (unitType.units.hasOwnProperty(g)) { 460 group = unitType[g]; 461 if (group.hasOwnProperty(unit)) return true; 462 } 463 } 464 } else { 465 if (unitType.units.hasOwnProperty(unit)) return true; 466 } 467 } 468 return false; 469 }; 470 471 /** 472 * check if type exists 473 * 474 * @param {String} type 475 * @returns {boolean} 476 */ 477 MindworkingUnitHandler.prototype.hasUnitType = function (type) { 478 return this.unitTypes.hasOwnProperty(type); 479 }; 480 481 /** 482 * check if group exists 483 * 484 * @param {String} type 485 * @param {String} group 486 * @returns {boolean} 487 */ 488 MindworkingUnitHandler.prototype.hasUnitGroup = function (type, group) { 489 if (this.hasUnitType(type)) { 490 var unitType = this.unitTypes[type]; 491 if (unitType.grouped) { 492 return unitType.units.hasOwnProperty(group); 493 } 494 } 495 return false; 496 }; 497 498 /** 499 * limit date to date between min and max 500 * @param {String} date 501 * @param {String} min 502 * @param {String} max 503 * 504 * todo, not finished, not used yet anyways 505 */ 506 MindworkingUnitHandler.prototype.limitDate = function (date, min, max) { 507 var d = new Date(date); 508 509 }; 510 511 /** 512 * format a mysql date for rendering 513 * 514 * @param {String} mySqlDateString 515 * @param {String} format 516 * @returns {string} 517 */ 518 MindworkingUnitHandler.prototype.formatDate = function (mySqlDateString, format) { 519 var ret = ""; 520 var tmp = mySqlDateString.split("-"); 521 if (tmp.length === 3) { 522 var date = new Date(mySqlDateString); 523 // js starts months at 0 524 var month = date.getMonth() + 1; 525 var replaces = { 526 "d": date.getDate(), 527 "m": month, 528 "M": Translations.translate("scripts-unit-date-month-" + month), 529 "Y": date.getFullYear(), 530 "dd": this.fillString(date.getDay()), 531 "mm": this.fillString(month) 532 }; 533 534 ret = format; 535 for (var i in replaces) { 536 ret = ret.replace("%" + i + "%", replaces[i]); 537 } 538 } 539 return ret; 540 }; 541 542 /** 543 * format a period 544 * @param {String} value 545 * @param {String} unit 546 * @param {String} format 547 * @returns {*} 548 */ 549 MindworkingUnitHandler.prototype.formatPeriod = function (value, unit, format) { 550 var add = (value === 1) ? "" : "s"; 551 var unitTranslation = Translations.translate("scripts-unit-period-" + unit + add); 552 553 return format.replace("%value%", value).replace("%unit%", unitTranslation); 554 }; 555 556 /** 557 * fill a string 558 * @param {String} string 559 * @param {Number} length 560 * @param {String} fill 561 * @param {String} prepend 562 * @returns {string} 563 */ 564 MindworkingUnitHandler.prototype.fillString = function (string, length, fill, prepend) { 565 if (length === undefined) length = 2; 566 if (fill === undefined) fill = "0"; 567 if (prepend === undefined) prepend = true; 568 569 var ret = string.toString(); 570 var stringLength = ret.length; 571 if (stringLength < length) { 572 var fillString = new Array(length - stringLength + 1).join("0"); 573 ret = (prepend ? fillString : "") + ret + (!prepend ? fillString : ""); 574 } 575 return ret; 576 }; 577 578 /** 579 * format a number 580 * provides similar functionality to PHP's number_format 581 * 582 * @param {Number} value the value to format 583 * @param {Number} decimals the number of decimals to round to 584 * @param {String} separatorDecimals the decimal separator (normally '.') 585 * @param {String} separatorThousands the separator for thousands (normally '') 586 * @param {Boolean} forceDecimals set to true to enforce showing the set number of decimals even if they evaluate to 0 587 * @param {String}
587 [roundMethod] the method to apply when rounding: round/ceil/floor 588 * 589 * @returns {*} 590 */ 591 MindworkingUnitHandler.prototype.formatNumber = function (value, decimals, separatorDecimals, separatorThousands, forceDecimals, roundMethod) { 592 var ret = this.round(value, decimals, roundMethod); 593 594 var tmp = ret.toString().split("."); 595 var valInt = tmp[0]; 596 var valDec = tmp.length > 1 ? tmp[1] : ""; 597 598 if (separatorThousands !== undefined && separatorThousands !== '') { 599 var rev = valInt.split("").reverse().join(""); 600 valInt = rev.match(/.{1,3}/g).join(separatorThousands).split("").reverse().join(""); 601 } 602 603 ret = valInt; 604 605 if (decimals > 0) { 606 if (forceDecimals && valDec.length < decimals) { 607 // fill up the decimal value with zeros to force decimals to be displayed 608 valDec += new Array(decimals - valDec.length + 1).join("0"); 609 } 610 if (valDec !== "") ret += separatorDecimals + valDec; 611 } 612 613 return ret; 614 }; 615 616 /** 617 * round a value 618 * 619 * @param {Number} value 620 * @param {Number} decimals 621 * @param {String} [method] the method to use, 'round', 'ceil' or 'floor'. invalid method will use 'round' 622 * 623 * @returns {number} 624 */ 625 MindworkingUnitHandler.prototype.round = function (value, decimals, method) { 626 var factor = Math.pow(10, decimals); 627 var tmp = value * factor; 628 switch (method) { 629 case 'ceil': 630 tmp = Math.ceil(tmp); 631 break; 632 case 'floor': 633 tmp = Math.floor(tmp); 634 break; 635 default: 636 tmp = Math.round(tmp); 637 } 638 return tmp / factor; 639 }; 640 641 642})(jQuery);
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.