1/** 2 * Created by user on 29-May-19. 3 */ 4/** 5 * Copyright (c) 2017 Boris Seang 6 * Manage Libs Versions 7 * ISC License 8 */ 9// Adapted from Rollup 10(function (global, factory) { 11 typeof exports === 'object' && typeof module !== 'undefined' 12 ? factory(exports) 13 : typeof define === 'function' && define.amd 14 ? define(['exports'], factory) 15 : (factory((global.manageLibsVersions = {}))); 16}(this, (function (exports) { 17 'use strict'; 18 19 /** 20 * Create a new Bundle that contains a set of interdependent Libraries. 21 * BundleOptions is an Object with the following expected keys: 22 * - name? <String> mandatory in case BundleOptions is passed as first argument. 23 * - libs? <Array<LibSpec|LibOptions>> 24 * @param bundleName <String|BundleOptions> 25 * @param options? <BundleOptions> 26 * @constructor 27 */ 28 function LibsBundle(bundleName, options) { 29 if (typeof bundleName !== 'string') { 30 options = bundleName; 31 bundleName = options.name; 32 } 33 this.name = bundleName; 34 this.libs = []; 35 this.options = options = options || {}; 36 37 var self = this, 38 libsData = options.libs || []; 39 40 libsData.forEach(function (libData) { 41 self.addLib(libData); 42 }); 43 } 44 45 /** 46 * Add a Library into the current Bundle. 47 * @param libName <string|Library|LibOptions> 48 * @param options? <LibOptions> 49 * @returns {LibSpec} 50 */ 51 LibsBundle.prototype.addLib = function (libName, options) { 52 var libSpec = (libName instanceof LibSpec) 53 ? libName 54 : new LibSpec(libName, options); 55 56 libSpec.bundle = this; 57 this.libs.push(libSpec); 58 59 return libSpec; 60 }; 61 62 /** 63 * Get the named Library. 64 * @param libName <String> 65 * @returns {LibSpec|undefined} 66 */ 67 LibsBundle.prototype.getLib = function (libName) { 68 var libSpecs = this.libs, 69 lib_i = 0; 70 71 for (; lib_i < libSpecs.length; lib_i += 1) { 72 if (libSpecs[lib_i].name === libName) { 73 return libSpecs[lib_i]; 74 } 75 } 76 77 console.warn('Could not find requested library "' + libName + '"'); 78 }; 79 80 /** 81 * Get the named Version of the named Library. 82 * @param libName <String> 83 * @param versionName <String> 84 */ 85 LibsBundle.prototype.getLibVersion = function (libName, versionName) { 86 var libSpec = this.getLib(libName); 87 88 if (libSpec) { 89 return libSpec.getVersionFallback(versionName); 90 } 91 92 console.warn('Could not find requested library "' + libName + '" and version "' + versionName + '"'); 93 }; 94 95 /** 96 * Make Radio Inputs for the list of named Library Versions. 97 * @param libName <String> 98 * @param versionsNames <String[]> 99 * @param fragment? <Node|DocumentFragment> 100 * @returns {undefined|Node|DocumentFragment} 101 */ 102 LibsBundle.prototype.makeLibVersionsRadios = function (libName, versionsNames, fragment) { 103 var libSpec = this.getLib(libName); 104 105 if (libSpec) { 106 fragment = libSpec.makeVersionsRadios(versionsNames, fragment); 107 } 108 109 return fragment; 110 }; 111 112 /** 113 * For each Bundle Library, execute fillPlaceholders() method. 114 * Look for placeholders with `data-manage-lib` attribute which value is 115 * equal to the Library name, and insert Radio Input for each Version 116 * specified in `data-manage-versions` attribute. 117 */ 118 LibsBundle.prototype.fillPlaceholders = function () { 119 this.libs.forEach(function (libSpec) { 120 libSpec.fillPlaceholders(); 121 }); 122 }; 123 124 /** 125 * Make and insert Radio Inputs for the list of named Library Versions, 126 * after the script Element where this method is called. 127 * @param libName <String> 128 * @param versionsNames <String[]> 129 * @param fragment? <Node|DocumentFragment> 130 */ 131 LibsBundle.prototype.insertLibVersionsRadioHere = function (libName, versionsNames, fragment) { 132 fragment = this.makeLibVersionsRadios(libName, versionsNames, fragment); 133 _insertFragmentHere(fragment); 134 }; 135 136 /** 137 * Retrieve the user selected (checked) Radio Inputs, in order to determine 138 * which Library Versions are requested. 139 * @returns {{[Library.name]: Version.name}} 140 */ 141 LibsBundle.prototype.readSelectedVersionsNames = function () { 142 var selectedVersionsNames = {}; 143
144 this.libs.forEach(function (libSpec) { 145 selectedVersionsNames[libSpec.name] = libSpec.readSelectedVersionName(); 146 }); 147 148 return selectedVersionsNames; 149 }; 150 151 /** 152 * Extract the list of AssetSpecs for the requested Library Versions, 153 * and select (check) the corresponding Radio Inputs. 154 * Used typically to display the requested Libraries Versions and prepare the list 155 * of assets to be loaded (with loadJsCss external library). 156 * @param libVersionsDict <{[Library.name]: Version.name}> 157 * @returns {Array<AssetSpec>} 158 */ 159 LibsBundle.prototype.getAndSelectVersionsAssetsList = function (libVersionsDict) { 160 var libSpecs = this.libs, 161 lib_i = 0, 162 assetsLoadList = [], 163 libSpec, versionName, versionSpec; 164 165 for (; lib_i < libSpecs.length; lib_i += 1) { 166 libSpec = libSpecs[lib_i]; 167 versionName = libVersionsDict[libSpec.name]; 168 versionSpec = libSpec.getAndSelectVersionSpec(versionName); 169 assetsLoadList = assetsLoadList.concat(versionSpec.assets); 170 } 171 172 return assetsLoadList; 173 }; 174 175 176 /** 177 * Create a new Library that contains a set of Versions. 178 * LibOptions is an Object with the following expected keys: 179 * - name? <String> mandatory if LibOptions is passed as first argument. 180 * - mandatory? <Boolean> if true, one of the Versions must be set as defaultVersion. 181 * - versions? <Array<VersionSpec|VersionOptions>> 182 * @param libName <String|LibOptions> 183 * @param options? <LibOptions> 184 * @constructor 185 */ 186 function LibSpec(libName, options) { 187 if (typeof libName !== 'string') { 188 options = libName; 189 libName = options.name; 190 } 191 this.name = libName; 192 this.options = options = options || {}; 193 this.versions = []; 194 this.mandatory = options.mandatory; 195 196 var self = this, 197 versionsData = options.versions || []; 198 199 versionsData.forEach(function (versionData) { 200 self.addVersion(versionData); 201 }); 202 } 203 204 /** 205 * Add a Version into the current Library. 206 * @param versionName <String|VersionOptions> 207 * @param options? <VersionOptions> 208 * @returns {VersionSpec} 209 */ 210 LibSpec.prototype.addVersion = function (versionName, options) { 211 var versionSpec = (versionName instanceof VersionSpec) 212 ? versionName 213 : new VersionSpec(versionName, options); 214 215 versionSpec.libSpec = this; 216 this.versions.push(versionSpec); 217 218 return versionSpec; 219 }; 220 221 LibSpec.prototype.makeVersionsRadios = function (versionsNames, fragment) { 222 var self = this, 223 afterFirst = false; 224 225 versionsNames.forEach(function (versionName) { 226 var versionSpec = self.getVersion(versionName); 227 228 if (versionSpec) { 229 if (afterFirst) { 230 if (fragment) { 231 fragment.appendChild(document.createElement('br')); 232 } 233 } else { 234 afterFirst = true; 235 } 236 fragment = versionSpec.makeRadio(fragment); 237 } 238 }); 239 240 return fragment; 241 }; 242 243 /** 244 * Look for placeholders in the document where `data-manage-lib` attribute 245 * value is equal to the current Library name, retrieve the Versions names 246 * specified in the `data-manage-versions` attribute, then create their 247 * Radio Input and insert them in those placeholders. 248 * `data-manage-versions` is a list of Version names, separated by at least 249 * one or a combination of: white space, comma (,), semi-colon (;). 250 */ 251 LibSpec.prototype.fillPlaceholders = function () { 252 var self = this, 253 selector = '[data-manage-lib="' + this.name + '"]'; 254 255 document.querySelectorAll(selector).forEach(function (placeholder) { 256 var versionsNames = placeholder.dataset.manageVersions; 257 258 versionsNames = versionsNames.split(/[ ,;]+/); 259 260 placeholder.appendChild(self.makeVersionsRadios(versionsNames)); 261 }); 262 }; 263 264 /** 265 * Retrieve the user selected (checked) Radio Input, in order to determine 266 * which Version of the current Library is requested. 267 */ 268 LibSpec.prototype.readSelectedVersionName = function () { 269 var inputName = _makeLibInputGroupName(this); 270 271 return _getSelectedRadioValue(inputName); 272 }; 273 274 /** 275 * Get the named Version, and also select (check) the associated Radio Input. 276 * Used typically to display the requested Version and prepare the list of 277 * assets to be loaded. 278 * @param versionName <String> 279 * @returns {VersionSpec} 280 */ 281 LibSpec.prototype.getAndSelectVersionSpec = function (versionName) { 282 var versionSpec = this.getVersionFallback(versionName); 283 284 if (versionSpec) { 285 versionSpec.selectRadio(); 286 return versionSpec; 287 } 288 289 this.selectRadioNone(); 290 291 // If no version spec found, return a dummy one. 292 return { 293 assets: [] 294 }; 295 }; 296 297 /**
298 * Make a Radio Input that correspond to no Version. 299 * Should not be used when the Library is set as mandatory. 300 * @param fragment? <Node|DocumentFragment> 301 * @returns {Node|DocumentFragment} 302 */ 303 LibSpec.prototype.makeRadioNone = function (fragment) { 304 fragment = fragment || document.createDocumentFragment(); 305 306 var attrs = this.mandatory ? _makeDisabledVersionInputAttributes() : null; 307 308 _makeRadioInputAndLabel(this, '(none)', fragment, attrs); 309 310 this.radioNoneElement = fragment.lastChild.previousSibling; 311 312 return fragment; 313 }; 314 315 /** 316 * Select (check) the Radio Input that corresponds to no Version. 317 */ 318 LibSpec.prototype.selectRadioNone = function () { 319 if (this.radioNoneElement) { 320 this.radioNoneElement.checked = true; 321 } 322 }; 323 324 /** 325 * Get the named Version, or the default one if the Library is set as 326 * mandatory and no or an incorrect version name is passed. 327 * @param versionName <String> 328 * @returns {Version|undefined} 329 */ 330 LibSpec.prototype.getVersionFallback = function (versionName) { 331 // If no version string is provided. 332 if (typeof versionName !== 'string' || versionName === '(none)') { 333 // If library is configured as mandatory, load the default version. 334 if (this.mandatory) { 335 console.warn('No version requested for mandatory library "' + this.name + '".'); 336 return this.getDefaultVersion(); 337 } 338 return; 339 } 340 341 // Load the requested version. 342 var libVersionSpec = this.getVersion(versionName); 343 344 if (libVersionSpec) { 345 return libVersionSpec; 346 } else { 347 console.warn('Requested library "' + this.name + '" with version "' + versionName + '", but not found in configuration.'); 348 return this.getDefaultVersion(); 349 } 350 }; 351 352 /** 353 * Get the named Version exactly. 354 * If no match is found, returns undefined. 355 * @param versionName <String> 356 * @returns {Version|undefined} 357 */ 358 LibSpec.prototype.getVersion = function (versionName) { 359 var versionSpecs = this.versions || [], 360 version_i = 0; 361 362 for (; version_i < versionSpecs.length; version_i += 1) { 363 if (versionSpecs[version_i].name === versionName) { 364 return versionSpecs[version_i]; 365 } 366 } 367 }; 368 369 /** 370 * Get the default Version. 371 * In case several Versions are set as defaultVersion, the first one is returned. 372 * @returns {Version|undefined} 373 */ 374 LibSpec.prototype.getDefaultVersion = function () { 375 var libDefaultVersionSpec = this._getDefaultVersion(); 376 377 if (libDefaultVersionSpec) { 378 console.log('Using default configuration (version: "' + libDefaultVersionSpec.name + '").'); 379 380 var onDefault = this.options.onDefault; 381 382 if (onDefault && typeof onDefault === 'function') { 383 onDefault.call(this); 384 } 385 return libDefaultVersionSpec; 386 } else { 387 console.warn('No default configuration specified.'); 388 389 var onError = this.options.onError; 390 391 if (onError && typeof onError === 'function') { 392 onError.call(this); 393 } 394 } 395 }; 396 397 LibSpec.prototype._getDefaultVersion = function () { 398 var versionSpecs = this.versions || [], 399 version_i = 0; 400 401 for (; version_i < versionSpecs.length; version_i += 1) { 402 if (versionSpecs[version_i].defaultVersion) { 403 return versionSpecs[version_i]; 404 } 405 } 406 }; 407 408 409 /** 410 * Create a Version that contains Assets. 411 * VersionOptions is an Object with the following expected keys: 412 * - name? <String> mandatory in case VersionOptions is passed as first argument. 413 * - defaultVersion? <Boolean> if true, this Version is used as the Library default Version. 414 * - disabled? <Boolean> if true, the built Radio Input will be disabled. defaultVersion should not be true. 415 * - assets? <Array<AssetSpec|AssetOption>> 416 * @param versionName <String|VersionOptions> 417 * @param options? <VersionOptions> 418 * @constructor 419 */ 420 function VersionSpec(versionName, options) { 421 if (typeof versionName !== 'string') { 422 options = versionName; 423 versionName = options.name; 424 } 425 this.name = versionName; 426 this.assets = []; 427 this.options = options = options || {}; 428 this.defaultVersion = options.defaultVersion; 429 this.disabled = options.disabled; 430 431 var self = this, 432 assetsData = options.assets || []; 433 434 assetsData.forEach(function (assetData) { 435 self.addAsset(assetData); 436 }); 437 } 438 439 /** 440 * Add an Asset to the current Version. 441 * @param assetSpec <AssetSpec|AssetOptions> 442 * @returns {Version} 443 */ 444 VersionSpec.prototype.addAsset = function (assetSpec) { 445 var assetSpec = (assetSpec instanceof AssetSpec) 446 ? assetSpec 447 : new AssetSpec(assetSpec); 448 449 this.assets.push(assetSpec); 450 451 return this; 452 }; 453 454 /** 455 * Test the availability of each Asset in the current Version. 456 * If automaticallyEnableOrDisable is true, it will enable the associated 457 * Radio Input if all Assets are found. If any is missing, it will disable 458 * it and flag the Version as non default. 459 * @param automaticallyEnableOrDisable <Boolean> 460 * @returns {Promise} 461 */ 462 VersionSpec.prototype.checkAssetsAvailability = function (automaticallyEnableOrDisable) { 463 var checks = [], 464 self = this; 465
466 this.assets.forEach(function (assetSpec) { 467 checks.push(_checkFileAvailability(assetSpec.path)); 468 }); 469 470 var all = Promise.all(checks); 471 472 if (automaticallyEnableOrDisable) { 473 all 474 .then(function () { 475 self.setDisabled(false); 476 }) 477 .catch(function () { 478 self.defaultVersion = false; 479 self.setDisabled(true); 480 }); 481 } 482 483 return all; 484 }; 485 486 /** 487 * Disable the Version associated Radio Input, typically when the Version is not available 488 * or incompatible with other Libraries. 489 * @param disabled? <Boolean> if true, disables the associated Radio Input element. 490 */ 491 VersionSpec.prototype.setDisabled = function (disabled) { 492 disabled = !!disabled; 493 this.disabled = disabled; 494 495 var radio = this.radioElement; 496 497 if (radio) { 498 _disableElement(radio, disabled); 499 500 var label = radio.nextSibling; 501 502 if (label) { 503 _disableElement(label, disabled); 504 } 505 } 506 }; 507 508 /** 509 * Make a Radio Input associated with the current Library Version. 510 * @param fragment? <Node|DocumentFragment> 511 * @returns {Node|DocumentFragment} 512 */ 513 VersionSpec.prototype.makeRadio = function (fragment) { 514 fragment = fragment || document.createDocumentFragment(); 515 516 var attrs = this.disabled ? _makeDisabledVersionInputAttributes() : null; 517 518 _makeRadioInputAndLabel(this.libSpec, this.name, fragment, attrs); 519 520 this.radioElement = fragment.lastChild.previousSibling; 521 522 return fragment; 523 }; 524 525 /** 526 * Select (check) the Radio Input associated with the current Library Version. 527 * Used typically to display the requested Version. 528 */ 529 VersionSpec.prototype.selectRadio = function () { 530 if (this.radioElement) { 531 this.radioElement.checked = true; 532 } 533 }; 534 535 536 /** 537 * Create an Asset that represents a Resource to be loaded. 538 * AssetOptions is an Object with the following expected keys: 539 * - type <'script'|'stylesheet'> type of Resource (JS or CSS). 540 * - path <String> URL from where to load the Resource. 541 * - attrs <{[attributeName]: attributeValue}> dictionary of 542 * attributes to apply to the Resource HTML Tag (typically 543 * for "integrity" and "crossorigin"). 544 * @param spec <AssetOptions> 545 * @constructor 546 */ 547 function AssetSpec(spec) { 548 this.type = spec.type; 549 this.path = spec.path; 550 this.attrs = spec.attrs || {}; 551 } 552 553 function _insertFragmentHere(fragment) { 554 var currentScriptParent = document.currentScript.parentNode; 555 currentScriptParent.appendChild(fragment); 556 } 557 558 function _makeLibInputGroupName(libSpec) { 559 return '_manageLibsVersions_' + libSpec.bundle.name + '_' + libSpec.name; 560 } 561 562 function _getSelectedRadioValue(radioName) { 563 var radios = document.getElementsByName(radioName), 564 i = 0; 565 566 for (; i < radios.length; i += 1) { 567 if (radios[i].checked) { 568 return radios[i].value; 569 } 570 } 571 } 572 573 function _makeRadioInputAndLabel(libSpec, versionName, fragment, attrs) { 574 var groupName = _makeLibInputGroupName(libSpec); 575 576 var input = document.createElement('input'); 577 input.type = 'radio'; 578 input.name = groupName; 579 input.value = versionName; 580 input.id = groupName + versionName; 581 if (attrs) { 582 _setAttributes(input, attrs); 583 } 584 fragment.appendChild(input); 585 586 var label = document.createElement('label'); 587 label.setAttribute('for', input.id); 588 label.innerText = versionName; 589 if (attrs) { 590 _setAttributes(label, attrs); 591 } 592 fragment.appendChild(label); 593 594 return fragment; 595 } 596 597 598 /** 599 * Utility function to easily make AssetOptions out of a pathTemplate 600 * and a versionName (typically for CDN URL that includes version, like 601 * for unpkg, cdnjs or jsDelivr).
602 * Type "stylesheet" (CSS). 603 * @param pathTemplate <String> including "{{VESION}}" placeholder. 604 * @param version <String> will replace the "{{VESION}}" placeholder in the above template. 605 * @param sri? <String> Sub Resource Integrity hash 606 */ 607 function makeStylesheet(pathTemplate, version, sri) { 608 return _makeAssetSpec('stylesheet', pathTemplate, version, sri); 609 } 610 611 /** 612 * Utility function to easily make AssetOptions out of a pathTemplate 613 * and a versionName (typically for CDN URL that includes version, like 614 * for unpkg, cdnjs or jsDelivr). 615 * Type "script" (JS). 616 * @param pathTemplate <String> including "{{VESION}}" placeholder. 617 * @param version <String> will replace the "{{VESION}}" placeholder in the above template. 618 * @param sri? <String> Sub Resource Integrity hash 619 */ 620 function makeScript(pathTemplate, version, sri) { 621 return _makeAssetSpec('script', pathTemplate, version, sri); 622 } 623 624 function _makeAssetSpec(type, pathTemplate, version, sri) { 625 var assetSpec = { 626 type: type, 627 path: _makePath(pathTemplate, version) 628 }; 629 630 return _addSri(assetSpec, sri); 631 } 632 633 function _makePath(pathTemplate, version) { 634 return _replaceAll(pathTemplate, '{{VERSION}}', version) 635 } 636 637 function _addSri(assetSpec, integrity, crossorigin) { 638 assetSpec.attrs = assetSpec.attrs || {}; 639 640 if (integrity) { 641 assetSpec.attrs.integrity = integrity; 642 assetSpec.attrs.crossorigin = crossorigin || ''; 643 } 644 645 return assetSpec; 646 } 647 648 function _replaceAll(target, search, replacement) { 649 return target.split(search).join(replacement); 650 } 651 652 function _setAttributes(element, attributes) { 653 for (var attributeName in attributes) { 654 if (attributes.hasOwnProperty(attributeName)) { 655 element.setAttribute(attributeName, attributes[attributeName]); 656 } 657 } 658 } 659 660 function _addClassName(element, className, add) { 661 if (element) { 662 if (typeof add === 'undefined') { 663 add = true; 664 } 665 element.classList[add ? 'add' : 'remove'](className); 666 } 667 } 668 669 function _makeDisabledVersionInputAttributes() { 670 return { 671 disabled: 'disabled',
672 class: 'version-disabled', 673 title: 'This version is disabled' 674 } 675 } 676 677 function _disableElement(element, disable) { 678 if (element) { 679 if (typeof disable === 'undefined') { 680 disable = true; 681 } 682 disable = !!disable; 683 element.disabled = disable; 684 _addClassName(element, 'version-disabled', disable); 685 element.setAttribute('title', disable ? 'This version is disabled' : ''); 686 } 687 } 688 689 function _checkFileAvailability(filePath) { 690 return new Promise(function (resolve, reject) { 691 var xhr = new XMLHttpRequest(); 692 693 xhr.open('HEAD', filePath); 694 xhr.onreadystatechange = function () { 695 if (xhr.readyState === XMLHttpRequest.DONE) { 696 if (xhr.status === 200) { 697 resolve(); 698 } else { 699 // xhr.status === 404 => file not found. 700 reject(xhr.status); 701 } 702 } 703 }; 704 xhr.send(); 705 }); 706 } 707 708 709 exports.Bundle = LibsBundle; 710 exports.Lib = LibSpec; 711 exports.Version = VersionSpec; 712 exports.Asset = AssetSpec; 713 714 exports.makeScript = makeScript; 715 exports.makeStylesheet = makeStylesheet; 716})));
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.