1/** Define class */ 2var Printful_Product_Size_Guide; 3 4(function () { 5 'use strict'; 6 7 /** 8 * @type {{modal: null, onSizeGuideClick: onSizeGuideClick, closeModal: closeModal, createModal: (function(string): HTMLDivElement)}} 9 */ 10 Printful_Product_Size_Guide = { 11 modal: null, 12 modalContentNode: null, 13 modalBodyNode: null, 14 sizeChartNode: null, 15 /** 16 * @var {{}} 17 * @var {Array<string>} sizeGuideData.availableSizes 18 * @var {{}} sizeGuideData.modelMeasurements 19 * @var {{}} sizeGuideData.productMeasurements 20 */ 21 sizeGuideData: null, 22 23 /** 24 * Key is unit identifier (e.g. 'inch') value is translated unit name (e.g. 'Inches') 25 * @var {{}} 26 */ 27 translatedUnitNames: null, 28 29 /** 30 * Handle click event 31 */ 32 onSizeGuideClick: function () { 33 if (!window.pfGlobal || !window.pfGlobal.sg_data_raw) { 34 return; 35 } 36 this.sizeGuideData = JSON.parse(window.pfGlobal.sg_data_raw); 37 38 this.translatedUnitNames = {}; 39 if (window.pfGlobal && window.pfGlobal.sg_unit_translations) { 40 this.translatedUnitNames = JSON.parse(window.pfGlobal.sg_unit_translations); 41 } 42 document.body.appendChild(this.createModal()); 43 }, 44 45 /** 46 * Close the modal element 47 */ 48 closeModal: function () { 49 this.removeNode(this.sizeChartNode); 50 this.removeNode(this.modalContentNode); 51 this.removeNode(this.modalBodyNode); 52 this.removeNode(this.modal); 53 this.sizeChartNode = null; 54 this.modalContentNode = null; 55 this.modalBodyNode = null; 56 this.modal = null; 57 }, 58 59 /** 60 * Create modal content 61 * @returns {HTMLDivElement} 62 */ 63 createModal: function () { 64 // Clear the old one just to be sure 65 this.closeModal(); 66 this.modal = this.buildEl('div', 'pf-size-guide-modal-wrapper'); 67 this.modalBodyNode = this.buildEl('div', 'pf-size-guide-modal'); 68 this.modalBodyNode.style.color = this.getTextColor(); 69 this.modalBodyNode.style.backgroundColor = this.getBackGroundColor(); 70 this.modalBodyNode.appendChild(this.buildModalHeader()); 71 72 // Render initial active tab 73 this.renderModalContent(Printful_Product_Size_Guide.TAB_TYPE_PERSON); 74 this.modal.appendChild(this.modalBodyNode); 75 76 return this.modal; 77 }, 78 79 buildEl: function (tagName, className, attributes) { 80 attributes = attributes || {}; 81 var el = document.createElement(tagName); 82 if (className) { 83 el.className = className; 84 } 85 86 if (attributes.id) { 87 el.setAttribute('id', attributes.id); 88 } 89 90 if (attributes.innerHTML) { 91 el.innerHTML = attributes.innerHTML; 92 } 93 94 if (attributes.src) { 95 el.setAttribute('src', attributes.src); 96 } 97 98 if (attributes.onclick) { 99 el.onclick = attributes.onclick; 100 } 101 102 if (['td', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'].indexOf(tagName) > -1) { 103 el.style.backgroundColor = this.getBackGroundColor(); 104 el.style.color = this.getTextColor(); 105 } 106 107 return el; 108 }, 109 110 removeNode: function (node) { 111 if (node && node.parentNode && node.parentNode.tagName) { 112 node.parentNode.removeChild(node); 113 } 114 }, 115 116 renderModalContent: function (activeTabType) { 117 this.removeNode(this.modalContentNode); 118 this.modalContentNode = this.buildEl('div', 'pf-size-guide-modal__content'); 119 120 var tabData = this.getSizeGuideDataForTab(activeTabType), 121 tabTitle = this.getTabTitle(activeTabType); 122 123 this.modalContentNode.appendChild(this.buildSizeGuideTabsNode(activeTabType)); 124 this.modalContentNode.appendChild(this.buildTabContent(tabData, tabTitle)); 125 126 this.modalBodyNode.appendChild(this.modalContentNode); 127 }, 128 129 getSizeGuideDataForTab: function (tabType) { 130 if (tabType === Printful_Product_Size_Guide.TAB_TYPE_PERSON) { 131 return this.sizeGuideData.modelMeasurements; 132 } 133 134 if (tabType === Printful_Product_Size_Guide.TAB_TYPE_PRODUCT) { 135 return this.sizeGuideData.productMeasurements; 136 } 137 138 return {}; 139 }, 140 141 getTabTitle: function (tabType) { 142 if (tabType === Printful_Product_Size_Guide.TAB_TYPE_PERSON) { 143 return window.pfGlobal && window.pfGlobal.sg_tab_title_person ? window.pfGlobal.sg_tab_title_person : 'Measure yourself'; 144 } 145 146 return window.pfGlobal && window.pfGlobal.sg_tab_title_product ? window.pfGlobal.sg_tab_title_product : 'Product measurements'; 147 }, 148 149 renderSizeChart: function (measurementData, selectedUnit) { 150 if (this.sizeChartNode) { 151 this.removeNode(this.sizeChartNode.firstChild); 152 } else { 153 this.sizeChartNode = this.buildEl('div', 'pf-size-guide-modal-size-chart'); 154 } 155 156 this.sizeChartNode.appendChild(this.buildSizeChartBlock(measurementData, selectedUnit)); 157 }, 158 159 buildModalHeader: function () { 160 var title = window.pfGlobal && window.pfGlobal.sg_modal_title ? window.pfGlobal.sg_modal_title : 'Size guide', 161 wrapper = this.buildEl('div', 'pf-size-guide-modal__header'); 162 163 wrapper.appendChild(this.buildEl('h4', 'pf-size-guide-modal__title', {innerHTML: title})); 164 165 var closeBtn = this.buildEl('button', 'pf-size-guide-modal__close'), 166 img = this.buildEl('img'); 167 168 img.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAAXNSR0IArs4c6QAAAgVJREFUaAXtmU2KwkAQhZ0hQbyDu7mIB3PhwoN5EXfeIYguJo9JQRMydurnNYxTDTH+dNWr71VD2mSzyZEOpAPpQDqQDqQD6UA6kA6kA80dOJ1O++Px+NVKGFrQ9Oh9WoMhPAzD5fF4XFpAQwNa0PRAf1iABXaMle7e+r4/nM/nqyVfLUZgx3nS3etutzuMddxqsfPf1cALsJKTAr0AK3omaPWSfj6f21ERx3zso5f3C1hob6da5nW8/KzuMLJVCgnpNEvDBMyGZsGibjMwC5oJ6waOhmbDhgBHQbeADQP2QreCDQW2QreEDQfWQreGpQCvhcY8bFTGk2wX8ZWMkGu5JCvPrstSmWj+vta9aX5TWGjSgJG8Ao0p80HrrAhRgSGigKbDoh468EroJrCoRf1vCUF/edA7/K+WtAJWFg19adM6XIGVWzPvcVmqweL+F1r6FhuPNbBys08zV9a89xy6pC0AlhgPdBiwp3BPrBY+BDii4Igca+DdwJGFRub6Dd4FzCiQkbOENwMzC2PmNgEzC5JusDTUwKxCBLQ8M7TU/5a6rruPReGYj/B9MDYo065MtqKl5n2qpfyu+l7dYWRceIIYDltWvtBp05ND5DQBI7CA3jKfDUMLo4C+W58N/2RyvAIahThSqEKhBU1VUE5OB9KBdCAdSAfSgXQgHUgH0oEQB74BG1sUIwNoL3cAAAAASUVORK5CYII="; 169 closeBtn.appendChild(img); 170 closeBtn.onclick = (this.closeModal).bind(this); 171 172 wrapper.appendChild(closeBtn); 173 wrapper.append(this.buildEl('div', 'pf-size-guide-modal-clear')); 174 175 return wrapper; 176 }, 177 178 buildSizeGuideTabsNode: function (activeTabType) { 179 var tabs = this.buildEl('ul', 'pf-product-size-guide__tabs'); 180 tabs.appendChild(this.buildSizeGuideTabNode(this.getTabTitle(Printful_Product_Size_Guide.TAB_TYPE_PERSON), Printful_Product_Size_Guide.TAB_TYPE_PERSON, activeTabType)); 181 tabs.appendChild(this.buildSizeGuideTabNode(this.getTabTitle(Printful_Product_Size_Guide.TAB_TYPE_PRODUCT), Printful_Product_Size_Guide.TAB_TYPE_PRODUCT, activeTabType)); 182 183 return tabs; 184 }, 185 186 buildSizeGuideTabNode: function (title, type, activeType) { 187 var className = 'pf-product-size-guide__tab'; 188 189 if (type === activeType) { 190 className += ' pf-product-size-guide__tab--active'; 191 } 192 193 var tab = this.buildEl('li', className, { 194 innerHTML: title, 195 onclick: (this.renderModalContent).bind(this, type) 196 }); 197 198 if (type === activeType) { 199 tab.style.backgroundColor = this.getTabBackGroundColor(true); 200 } else { 201 tab.style.backgroundColor = this.getTabBackGroundColor(false); 202 } 203 204 return tab; 205 }, 206 207 buildTabContent: function (measurementData, title) { 208 measurementData = measurementData || {}; 209 var node = this.buildEl('div'); 210 node.appendChild(this.buildEl('h4', null, {innerHTML: title})); 211 if (measurementData.hasOwnProperty('description')) { 212 node.appendChild(this.buildEl('div', null, {innerHTML: measurementData.description})); 213 } 214 215 // Model description 216 node.appendChild(this.buildDescriptionBlock(measurementData)); 217 218 // Size table 219 this.renderSizeChart(measurementData); 220 node.appendChild(this.sizeChartNode); 221 222 return node; 223 }, 224 225 buildDescriptionBlock: function (measurementData) { 226 var node = this.buildEl('div', 'pf-size-guide-modal-measurements'); 227 if (measurementData.hasOwnProperty('imageUrl') && measurementData.imageUrl) { 228 var measurementImgNode = this.buildEl('div', 'pf-size-guide-modal-measurements__image'); 229 measurementImgNode.appendChild(this.buildEl('img', null, {src: measurementData.imageUrl})); 230 231 if (measurementData.hasOwnProperty('modelDescription')) { 232 measurementImgNode.appendChild(this.buildEl('div', null, {innerHTML: measurementData.modelDescription})); 233 } 234 node.appendChild(measurementImgNode); 235 } 236 237 if (measurementData.hasOwnProperty('imageDescription')) { 238 node.appendChild(this.buildEl('div', 'pf-size-guide-modal-measurements__description', {innerHTML: measurementData.imageDescription})); 239 } 240 node.append(this.buildEl('div', 'pf-size-guide-modal-clear')); 241 242 return node; 243 }, 244 245 /** 246 * @param {{}} measurementData 247 * @param {string} [selectedUnit] 248 * @return {*} 249 */ 250 buildSizeChartBlock: function (measurementData, selectedUnit) { 251 var node = this.buildEl('div'); 252 253 if (!measurementData.hasOwnProperty('sizeTableRows') || measurementData.sizeTableRows.length < 1) { 254 return node; 255 } 256 var sizeRows = measurementData.sizeTableRows, 257 availableUnits = this.getUniqueUnits(sizeRows); 258 259 // Selected or first if nothing selected 260 selectedUnit = selectedUnit || this.getDefaultUnit(availableUnits); 261 node.appendChild(this.buildSizeChartTabsNode(measurementData, availableUnits, selectedUnit)); 262 var wrapper = this.buildEl('div'); 263 wrapper.style.overflowX = 'auto'; 264 wrapper.appendChild(this.buildSizeChartTable(this.getSortedChartRows(sizeRows, selectedUnit))); 265 node.appendChild(wrapper); 266 267 return node; 268 }, 269 270 /** 271 * @param {{}} measurementData 272 * @param {Array<{key, title}>} availableUnits 273 * @param {string} selectedUnit 274 * @return {*} 275 */ 276 buildSizeChartTabsNode: function (measurementData, availableUnits, selectedUnit) { 277 availableUnits = availableUnits || []; 278 var tabsNode = this.buildEl('ul', 'pf-size-guide-modal-size-chart__tabs'); 279 280 availableUnits.map((function (item) { 281 var className = 'pf-size-guide-modal-size-chart__tab'; 282 if (item.key === selectedUnit) { 283 className += ' pf-size-guide-modal-size-chart__tab--active'; 284 } 285 tabsNode.appendChild(this.buildEl('li', className, { 286 innerHTML: item.title, 287 onclick: (this.renderSizeChart).bind(this, measurementData, item.key) 288 })); 289 }).bind(this)); 290 291 return tabsNode; 292 }, 293 294 buildSizeChartTable: function (rows) { 295 var tableNode = this.buildEl('table', 'pf-size-guide-modal-size-chart__table'), 296 availableSizes = this.sizeGuideData.availableSizes; 297 298 var tableHeader = this.buildEl('thead'), 299 tableHeaderRow = this.buildEl('tr'); 300 301 var tableHeaderRowText = window.pfGlobal && window.pfGlobal.sg_table_header_size ? window.pfGlobal.sg_table_header_size : 'Size'; 302 303 tableHeaderRow.appendChild(this.buildEl('td', null, {innerHTML: tableHeaderRowText})); 304 rows.map((function (row) { 305 tableHeaderRow.appendChild(this.buildEl('td', null, {innerHTML: row.title})); 306 }).bind(this)); 307 308 tableHeader.appendChild(tableHeaderRow); 309 tableNode.appendChild(tableHeader); 310 311 // Loop all available sizes and print out a row for each type 312 var tableBody = this.buildEl('tbody'); 313 availableSizes.map((function (size) { 314 var tableBodyRow = this.buildEl('tr'); 315 tableBodyRow.appendChild(this.buildEl('td', null, {innerHTML: size})); 316 rows.map((function (row) { 317 tableBodyRow.appendChild(this.buildEl('td', null, {innerHTML: row.sizes[size] || ''})); 318 }).bind(this)); 319 320 tableBody.appendChild(tableBodyRow); 321 }).bind(this)); 322 323 tableNode.appendChild(tableBody); 324 325 return tableNode; 326 }, 327 328 getSortedChartRows: function (sizeTableRows, selectedUnit) { 329 return sizeTableRows 330 .filter(function (row) { 331 return row.unit === selectedUnit; 332 }).map((function (row) { 333 var sizes = {}; 334 for (var k in row.sizes) { 335 if (!row.sizes.hasOwnProperty(k)) { 336 continue; 337 } 338
339 // Convert to fractions if needed, round and join 340 sizes[k] = row.sizes[k].map((function (size) { 341 if (row.unit === Printful_Product_Size_Guide.UNIT_INCH) { 342 return this.convertToFraction(size); 343 } else { 344 return size.toFixed(1); 345 } 346 }).bind(this)).join(' - '); 347 } 348 349 return { 350 title: row.title, 351 sizes: sizes 352 }; 353 }).bind(this)); 354 }, 355 356 convertToFraction: function (value) { 357 var split = String(value).split('.'), 358 integer = parseInt(split[0], 10); 359 360 if (!split[1]) { 361 return value; 362 } 363 364 var decimal = parseFloat('0.' + split[1]), 365 fraction = 0; 366 367 for (var fra in Printful_Product_Size_Guide.FRACTION_MAP) { 368 var compareValue = Printful_Product_Size_Guide.FRACTION_MAP[fra]; 369 370 if (decimal < compareValue) { 371 fraction = fra; 372 break; 373 } 374 } 375 376 if (fraction === 0 && integer) { 377 integer++; 378 fraction = ''; 379 } 380 381 return (integer > 0 ? integer + ' ' : '') + fraction; 382 }, 383 384 /** 385 * @param {Array<{unit, unitName}>}sizeTableRows 386 * @return {Array<{key, title}>} 387 */ 388 getUniqueUnits: function (sizeTableRows) { 389 var uniqueUnits = {}; 390 sizeTableRows.map(function (row) { 391 // Gather unique units 392 if (!uniqueUnits.hasOwnProperty(row.unit)) { 393 uniqueUnits[row.unit] = row.unitName; 394 } 395 }); 396 397 var units = []; 398 for (var i in uniqueUnits) { 399 if (uniqueUnits.hasOwnProperty(i)) { 400 units.push({ 401 key: i, 402 title: this.getTranslatedUnitName(i, uniqueUnits[i]) 403 }); 404 } 405 } 406 407 return units; 408 }, 409 410 /** 411 * @param {string} unit 412 * @param {string} defaultValue 413 * @return {null|string} 414 */ 415 getTranslatedUnitName: function(unit, defaultValue) { 416 if (this.translatedUnitNames[unit]) { 417 return this.translatedUnitNames[unit]; 418 } 419 420 return defaultValue; 421 }, 422 423 getBackGroundColor: function () { 424 return window.pfGlobal && window.pfGlobal.sg_modal_background_color ? window.pfGlobal.sg_modal_background_color : '#FFF'; 425 }, 426 427 getTextColor: function () { 428 return window.pfGlobal && window.pfGlobal.sg_modal_text_color ? window.pfGlobal.sg_modal_text_color : '#000'; 429 }, 430 431 getTabBackGroundColor: function (isActive) { 432 if (isActive) { 433 return window.pfGlobal && window.pfGlobal.sg_active_tab_background_color ? window.pfGlobal.sg_active_tab_background_color : '#FFF'; 434 } 435 436 return window.pfGlobal && window.pfGlobal.sg_tab_background_color ? window.pfGlobal.sg_tab_background_color : '#EEE'; 437 }, 438 439 getDefaultUnit: function (availableUnits) { 440 availableUnits = availableUnits || []; 441 var uniqueUnitTypes = availableUnits.map(function (item) { 442 return item.key; 443 }); 444 445 var defaultUnit = uniqueUnitTypes[0]; 446 if (window.pfGlobal && window.pfGlobal.sg_primary_unit && uniqueUnitTypes.indexOf(window.pfGlobal.sg_primary_unit) > -1) { 447 return window.pfGlobal.sg_primary_unit; 448 } 449 450 return defaultUnit; 451 } 452 }; 453 454 Printful_Product_Size_Guide.TAB_TYPE_PERSON = 'person'; 455 Printful_Product_Size_Guide.TAB_TYPE_PRODUCT = 'product'; 456 Printful_Product_Size_Guide.UNIT_INCH = 'inch'; 457 458 Printful_Product_Size_Guide.FRACTION_MAP = { 459 ' ': 0.0625, //don't add any fraction 460 'â ': 0.187, 461 '¼': 0.3125, 462 'â ': 0.4375, 463 '½': 0.5625, 464 'â ': 0.6875, 465 '¾': 0.8125, 466 'â ': 0.9375 467 }; 468})();
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.