vendor: 10,460 bytes, lines 1-254
1/*! 2 * Web Experience Toolkit (WET) / Boîte à outils de l'expérience Web (BOEW) 3 * wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 4 * v4.0.75 - 2024-03-05 5 * 6 *//*! Modernizr (Custom Build) | MIT & BSD */ 7/*! @license DOMPurify 2.4.4 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.4.4/LICENSE */ 8 9(function (global, factory) { 10 typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 11 typeof define === 'function' && define.amd ? define(factory) : 12 (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.DOMPurify = factory()); 13})(this, (function () { 'use strict'; 14 15 function _typeof(obj) { 16 "@babel/helpers - typeof"; 17 18 return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { 19 return typeof obj; 20 } : function (obj) { 21 return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 22 }, _typeof(obj); 23 } 24 25 function _setPrototypeOf(o, p) { 26 _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { 27 o.__proto__ = p; 28 return o; 29 }; 30 31 return _setPrototypeOf(o, p); 32 } 33 34 function _isNativeReflectConstruct() { 35 if (typeof Reflect === "undefined" || !Reflect.construct) return false; 36 if (Reflect.construct.sham) return false; 37 if (typeof Proxy === "function") return true; 38 39 try { 40 Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); 41 return true; 42 } catch (e) { 43 return false; 44 } 45 } 46 47 function _construct(Parent, args, Class) { 48 if (_isNativeReflectConstruct()) { 49 _construct = Reflect.construct; 50 } else { 51 _construct = function _construct(Parent, args, Class) { 52 var a = [null]; 53 a.push.apply(a, args); 54 var Constructor = Function.bind.apply(Parent, a); 55 var instance = new Constructor(); 56 if (Class) _setPrototypeOf(instance, Class.prototype); 57 return instance; 58 }; 59 } 60 61 return _construct.apply(null, arguments); 62 } 63 64 function _toConsumableArray(arr) { 65 return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); 66 } 67 68 function _arrayWithoutHoles(arr) { 69 if (Array.isArray(arr)) return _arrayLikeToArray(arr); 70 } 71 72 function _iterableToArray(iter) { 73 if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); 74 } 75 76 function _unsupportedIterableToArray(o, minLen) { 77 if (!o) return; 78 if (typeof o === "string") return _arrayLikeToArray(o, minLen); 79 var n = Object.prototype.toString.call(o).slice(8, -1); 80 if (n === "Object" && o.constructor) n = o.constructor.name; 81 if (n === "Map" || n === "Set") return Array.from(o); 82 if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); 83 } 84 85 function _arrayLikeToArray(arr, len) { 86 if (len == null || len > arr.length) len = arr.length; 87 88 for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; 89 90 return arr2; 91 } 92 93 function _nonIterableSpread() { 94 throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); 95 } 96 97 var hasOwnProperty = Object.hasOwnProperty, 98 setPrototypeOf = Object.setPrototypeOf, 99 isFrozen = Object.isFrozen, 100 getPrototypeOf = Object.getPrototypeOf, 101 getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; 102 var freeze = Object.freeze, 103 seal = Object.seal, 104 create = Object.create; // eslint-disable-line import/no-mutable-exports 105 106 var _ref = typeof Reflect !== 'undefined' && Reflect, 107 apply = _ref.apply, 108 construct = _ref.construct; 109 110 if (!apply) { 111 apply = function apply(fun, thisValue, args) { 112 return fun.apply(thisValue, args); 113 }; 114 } 115 116 if (!freeze) { 117 freeze = function freeze(x) { 118 return x; 119 }; 120 } 121 122 if (!seal) { 123 seal = function seal(x) { 124 return x; 125 }; 126 } 127 128 if (!construct) { 129 construct = function construct(Func, args) { 130 return _construct(Func, _toConsumableArray(args)); 131 }; 132 } 133 134 var arrayForEach = unapply(Array.prototype.forEach); 135 var arrayPop = unapply(Array.prototype.pop); 136 var arrayPush = unapply(Array.prototype.push); 137 var stringToLowerCase = unapply(String.prototype.toLowerCase); 138 var stringToString = unapply(String.prototype.toString); 139 var stringMatch = unapply(String.prototype.match); 140 var stringReplace = unapply(String.prototype.replace); 141 var stringIndexOf = unapply(String.prototype.indexOf); 142 var stringTrim = unapply(String.prototype.trim); 143 var regExpTest = unapply(RegExp.prototype.test); 144 var typeErrorCreate = unconstruct(TypeError); 145 function unapply(func) { 146 return function (thisArg) { 147 for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 148 args[_key - 1] = arguments[_key]; 149 } 150 151 return apply(func, thisArg, args); 152 }; 153 } 154 function unconstruct(func) { 155 return function () { 156 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { 157 args[_key2] = arguments[_key2]; 158 } 159 160 return construct(func, args); 161 }; 162 } 163 /* Add properties to a lookup table */ 164 165 function addToSet(set, array, transformCaseFunc) { 166 transformCaseFunc = transformCaseFunc ? transformCaseFunc : stringToLowerCase; 167 168 if (setPrototypeOf) { 169 // Make 'in' and truthy checks like Boolean(set.constructor) 170 // independent of any properties defined on Object.prototype. 171 // Prevent prototype setters from intercepting set as a this value. 172 setPrototypeOf(set, null); 173 } 174 175 var l = array.length; 176 177 while (l--) { 178 var element = array[l]; 179 180 if (typeof element === 'string') { 181 var lcElement = transformCaseFunc(element); 182 183 if (lcElement !== element) { 184 // Config presets (e.g. tags.js, attrs.js) are immutable. 185 if (!isFrozen(array)) { 186 array[l] = lcElement; 187 } 188 189 element = lcElement; 190 } 191 } 192 193 set[element] = true; 194 } 195 196 return set; 197 } 198 /* Shallow clone an object */ 199 200 function clone(object) { 201 var newObject = create(null); 202 var property; 203 204 for (property in object) { 205 if (apply(hasOwnProperty, object, [property]) === true) { 206 newObject[property] = object[property]; 207 } 208 } 209 210 return newObject; 211 } 212 /* IE10 doesn't support __lookupGetter__ so lets' 213 * simulate it. It also automatically checks 214 * if the prop is function or getter and behaves 215 * accordingly. */ 216 217 function lookupGetter(object, prop) { 218 while (object !== null) { 219 var desc = getOwnPropertyDescriptor(object, prop); 220 221 if (desc) { 222 if (desc.get) { 223 return unapply(desc.get); 224 } 225 226 if (typeof desc.value === 'function') { 227 return unapply(desc.value); 228 } 229 } 230 231 object = getPrototypeOf(object); 232 } 233 234 function fallbackValue(element) { 235 console.warn('fallback value for', element); 236 return null; 237 } 238 239 return fallbackValue; 240 } 241 242 var html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']); // SVG 243 244 var svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']); 245 var svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']); // List of SVG elements that are disallowed by default. 246 // We still need to know them so that we can do namespace 247 // checks properly in case one wants to add them to 248 // allow-list. 249 250 var svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'fedropshadow', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']); 251 var mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover']); // Similarly to SVG, we want to know all MathML elements, 252 // even those that we disallow by default. 253 254 var mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'n
254one']); 255 var text = freeze(['#text']); 256 257 var html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'xmlns', 'slot']); 258 var svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']); 259 var mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']); 260 var xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']); 261 262 var MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode 263 264 var ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm); 265 var TMPLIT_EXPR = seal(/\${[\w\W]*}/gm); 266 var DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape 267 268 var ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape 269
vendor: 5,388 bytes, lines 270-403
270 var IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape 271 ); 272 var IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i); 273 var ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex 274 ); 275 var DOCTYPE_NAME = seal(/^html$/i); 276 277 var getGlobal = function getGlobal() { 278 return typeof window === 'undefined' ? null : window; 279 }; 280 /** 281 * Creates a no-op policy for internal use only. 282 * Don't export this function outside this module! 283 * @param {?TrustedTypePolicyFactory} trustedTypes The policy factory. 284 * @param {Document} document The document object (to determine policy name suffix) 285 * @return {?TrustedTypePolicy} The policy created (or null, if Trusted Types 286 * are not supported). 287 */ 288 289 290 var _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, document) { 291 if (_typeof(trustedTypes) !== 'object' || typeof trustedTypes.createPolicy !== 'function') { 292 return null; 293 } // Allow the callers to control the unique policy name 294 // by adding a data-tt-policy-suffix to the script element with the DOMPurify. 295 // Policy creation with duplicate names throws in Trusted Types. 296 297 298 var suffix = null; 299 var ATTR_NAME = 'data-tt-policy-suffix'; 300 301 if (document.currentScript && document.currentScript.hasAttribute(ATTR_NAME)) { 302 suffix = document.currentScript.getAttribute(ATTR_NAME); 303 } 304 305 var policyName = 'dompurify' + (suffix ? '#' + suffix : ''); 306 307 try { 308 return trustedTypes.createPolicy(policyName, { 309 createHTML: function createHTML(html) { 310 return html; 311 }, 312 createScriptURL: function createScriptURL(scriptUrl) { 313 return scriptUrl; 314 } 315 }); 316 } catch (_) { 317 // Policy creation failed (most likely another DOMPurify script has 318 // already run). Skip creating the policy, as this will only cause errors 319 // if TT are enforced. 320 console.warn('TrustedTypes policy ' + policyName + ' could not be created.'); 321 return null; 322 } 323 }; 324 325 function createDOMPurify() { 326 var window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal(); 327 328 var DOMPurify = function DOMPurify(root) { 329 return createDOMPurify(root); 330 }; 331 /** 332 * Version label, exposed for easier checks 333 * if DOMPurify is up to date or not 334 */ 335 336 337 DOMPurify.version = '2.4.4'; 338 /** 339 * Array of elements that DOMPurify removed during sanitation. 340 * Empty if nothing was removed. 341 */ 342 343 DOMPurify.removed = []; 344 345 if (!window || !window.document || window.document.nodeType !== 9) { 346 // Not running in a browser, provide a factory function 347 // so that you can pass your own Window 348 DOMPurify.isSupported = false; 349 return DOMPurify; 350 } 351 352 var originalDocument = window.document; 353 var document = window.document; 354 var DocumentFragment = window.DocumentFragment, 355 HTMLTemplateElement = window.HTMLTemplateElement, 356 Node = window.Node, 357 Element = window.Element, 358 NodeFilter = window.NodeFilter, 359 _window$NamedNodeMap = window.NamedNodeMap, 360 NamedNodeMap = _window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap, 361 HTMLFormElement = window.HTMLFormElement, 362 DOMParser = window.DOMParser, 363 trustedTypes = window.trustedTypes; 364 var ElementPrototype = Element.prototype; 365 var cloneNode = lookupGetter(ElementPrototype, 'cloneNode'); 366 var getNextSibling = lookupGetter(ElementPrototype, 'nextSibling'); 367 var getChildNodes = lookupGetter(ElementPrototype, 'childNodes'); 368 var getParentNode = lookupGetter(ElementPrototype, 'parentNode'); // As per issue #47, the web-components registry is inherited by a 369 // new document created via createHTMLDocument. As per the spec 370 // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries) 371 // a new empty registry is used when creating a template contents owner 372 // document, so we use that as our parent document to ensure nothing 373 // is inherited. 374 375 if (typeof HTMLTemplateElement === 'function') { 376 var template = document.createElement('template'); 377 378 if (template.content && template.content.ownerDocument) { 379 document = template.content.ownerDocument; 380 } 381 } 382 383 var trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, originalDocument); 384 385 var emptyHTML = trustedTypesPolicy ? trustedTypesPolicy.createHTML('') : ''; 386 var _document = document, 387 implementation = _document.implementation, 388 createNodeIterator = _document.createNodeIterator, 389 createDocumentFragment = _document.createDocumentFragment, 390 getElementsByTagName = _document.getElementsByTagName; 391 var importNode = originalDocument.importNode; 392 var documentMode = {}; 393 394 try { 395 documentMode = clone(document).documentMode ? document.documentMode : {}; 396 } catch (_) {} 397 398 var hooks = {}; 399 /** 400 * Expose whether this browser supports running the full DOMPurify. 401 */ 402 403 DOMPurify.isSupported = typeof getParentNode === 'function' && implementation && typeof implementation.createHTMLDocument !== 'undefined' && documentMode !== 9
vendor: 18,492 bytes, lines 403-882
403; 404 var MUSTACHE_EXPR$1 = MUSTACHE_EXPR, 405 ERB_EXPR$1 = ERB_EXPR, 406 TMPLIT_EXPR$1 = TMPLIT_EXPR, 407 DATA_ATTR$1 = DATA_ATTR, 408 ARIA_ATTR$1 = ARIA_ATTR, 409 IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA, 410 ATTR_WHITESPACE$1 = ATTR_WHITESPACE; 411 var IS_ALLOWED_URI$1 = IS_ALLOWED_URI; 412 /** 413 * We consider the elements and attributes below to be safe. Ideally 414 * don't add any new ones but feel free to remove unwanted ones. 415 */ 416 417 /* allowed element names */ 418 419 var ALLOWED_TAGS = null; 420 var DEFAULT_ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray(html$1), _toConsumableArray(svg$1), _toConsumableArray(svgFilters), _toConsumableArray(mathMl$1), _toConsumableArray(text))); 421 /* Allowed attribute names */ 422 423 var ALLOWED_ATTR = null; 424 var DEFAULT_ALLOWED_ATTR = addToSet({}, [].concat(_toConsumableArray(html), _toConsumableArray(svg), _toConsumableArray(mathMl), _toConsumableArray(xml))); 425 /* 426 * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements. 427 * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements) 428 * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list) 429 * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`. 430 */ 431 432 var CUSTOM_ELEMENT_HANDLING = Object.seal(Object.create(null, { 433 tagNameCheck: { 434 writable: true, 435 configurable: false, 436 enumerable: true, 437 value: null 438 }, 439 attributeNameCheck: { 440 writable: true, 441 configurable: false, 442 enumerable: true, 443 value: null 444 }, 445 allowCustomizedBuiltInElements: { 446 writable: true, 447 configurable: false, 448 enumerable: true, 449 value: false 450 } 451 })); 452 /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */ 453 454 var FORBID_TAGS = null; 455 /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */ 456 457 var FORBID_ATTR = null; 458 /* Decide if ARIA attributes are okay */ 459 460 var ALLOW_ARIA_ATTR = true; 461 /* Decide if custom data attributes are okay */ 462 463 var ALLOW_DATA_ATTR = true; 464 /* Decide if unknown protocols are okay */ 465 466 var ALLOW_UNKNOWN_PROTOCOLS = false; 467 /* Decide if self-closing tags in attributes are allowed. 468 * Usually removed due to a mXSS issue in jQuery 3.0 */ 469 470 var ALLOW_SELF_CLOSE_IN_ATTR = true; 471 /* Output should be safe for common template engines. 472 * This means, DOMPurify removes data attributes, mustaches and ERB 473 */ 474 475 var SAFE_FOR_TEMPLATES = false; 476 /* Decide if document with <html>... should be returned */ 477 478 var WHOLE_DOCUMENT = false; 479 /* Track whether config is already set on this instance of DOMPurify. */ 480 481 var SET_CONFIG = false; 482 /* Decide if all elements (e.g. style, script) must be children of 483 * document.body. By default, browsers might move them to document.head */ 484 485 var FORCE_BODY = false; 486 /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html 487 * string (or a TrustedHTML object if Trusted Types are supported). 488 * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead 489 */ 490 491 var RETURN_DOM = false; 492 /* Decide if a DOM `DocumentFragment` should be returned, instead of a html 493 * string (or a TrustedHTML object if Trusted Types are supported) */ 494 495 var RETURN_DOM_FRAGMENT = false; 496 /* Try to return a Trusted Type object instead of a string, return a string in 497 * case Trusted Types are not supported */ 498 499 var RETURN_TRUSTED_TYPE = false; 500 /* Output should be free from DOM clobbering attacks? 501 * This sanitizes markups named with colliding, clobberable built-in DOM APIs. 502 */ 503 504 var SANITIZE_DOM = true; 505 /* Achieve full DOM Clobbering protection by isolating the namespace of named 506 * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules. 507 * 508 * HTML/DOM spec rules that enable DOM Clobbering: 509 * - Named Access on Window (§7.3.3) 510 * - DOM Tree Accessors (§3.1.5) 511 * - Form Element Parent-Child Relations (§4.10.3) 512 * - Iframe srcdoc / Nested WindowProxies (§4.8.5) 513 * - HTMLCollection (§4.2.10.2) 514 * 515 * Namespace isolation is implemented by prefixing `id` and `name` attributes 516 * with a constant string, i.e., `user-content-` 517 */ 518 519 var SANITIZE_NAMED_PROPS = false; 520 var SANITIZE_NAMED_PROPS_PREFIX = 'user-content-'; 521 /* Keep element content when removing element? */ 522 523 var KEEP_CONTENT = true; 524 /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead 525 * of importing it into a new Document and returning a sanitized copy */ 526 527 var IN_PLACE = false; 528 /* Allow usage of profiles like html, svg and mathMl */ 529 530 var USE_PROFILES = {}; 531 /* Tags to ignore content of when KEEP_CONTENT is true */ 532 533 var FORBID_CONTENTS = null; 534 var DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']); 535 /* Tags that are safe for data: URIs */ 536 537 var DATA_URI_TAGS = null; 538 var DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']); 539 /* Attributes safe for values like "javascript:" */ 540 541 var URI_SAFE_ATTRIBUTES = null; 542 var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']); 543 var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML'; 544 var SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; 545 var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'; 546 /* Document namespace */ 547 548 var NAMESPACE = HTML_NAMESPACE; 549 var IS_EMPTY_INPUT = false; 550 /* Allowed XHTML+XML namespaces */ 551 552 var ALLOWED_NAMESPACES = null; 553 var DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString); 554 /* Parsing of strict XHTML documents */ 555 556 var PARSER_MEDIA_TYPE; 557 var SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html']; 558 var DEFAULT_PARSER_MEDIA_TYPE = 'text/html'; 559 var transformCaseFunc; 560 /* Keep a reference to config to pass to hooks */ 561 562 var CONFIG = null; 563 /* Ideally, do not touch anything below this line */ 564 565 /* ______________________________________________ */ 566 567 var formElement = document.createElement('form'); 568 569 var isRegexOrFunction = function isRegexOrFunction(testValue) { 570 return testValue instanceof RegExp || testValue instanceof Function; 571 }; 572 /** 573 * _parseConfig 574 * 575 * @param {Object} cfg optional config literal 576 */ 577 // eslint-disable-next-line complexity 578 579 580 var _parseConfig = function _parseConfig(cfg) { 581 if (CONFIG && CONFIG === cfg) { 582 return; 583 } 584 /* Shield configuration object from tampering */ 585 586 587 if (!cfg || _typeof(cfg) !== 'object') { 588 cfg = {}; 589 } 590 /* Shield configuration object from prototype pollution */ 591 592 593 cfg = clone(cfg); 594 PARSER_MEDIA_TYPE = // eslint-disable-next-line unicorn/prefer-includes 595 SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE : PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE; // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is. 596 597 transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase; 598 /* Set configuration parameters */ 599 600 ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS; 601 ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR; 602 ALLOWED_NAMESPACES = 'ALLOWED_NAMESPACES' in cfg ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES; 603 URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), // eslint-disable-line indent 604 cfg.ADD_URI_SAFE_ATTR, // eslint-disable-line indent 605 transformCaseFunc // eslint-disable-line indent 606 ) // eslint-disable-line indent 607 : DEFAULT_URI_SAFE_ATTRIBUTES; 608 DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), // eslint-disable-line indent 609 cfg.ADD_DATA_URI_TAGS, // eslint-disable-line indent 610 transformCaseFunc // eslint-disable-line indent 611 ) // eslint-disable-line indent 612 : DEFAULT_DATA_URI_TAGS; 613 FORBID_CONTENTS = 'FORBID_CONTENTS' in cfg ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS; 614 FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : {}; 615 FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {}; 616 USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false; 617 ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true 618 619 ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true 620 621 ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false 622 623 ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true 624 625 SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false 626 627 WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false 628 629 RETURN_DOM = cfg.RETURN_DOM || false; // Default false 630 631 RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false 632 633 RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false 634 635 FORCE_BODY = cfg.FORCE_BODY || false; // Default false 636 637 SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true 638 639 SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false 640 641 KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true 642 643 IN_PLACE = cfg.IN_PLACE || false; // Default false 644 645 IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$1; 646 NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE; 647 648 if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) { 649 CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck; 650 } 651 652 if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) { 653 CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck; 654 } 655 656 if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') { 657 CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements; 658 } 659 660 if (SAFE_FOR_TEMPLATES) { 661 ALLOW_DATA_ATTR = false; 662 } 663 664 if (RETURN_DOM_FRAGMENT) { 665 RETURN_DOM = true; 666 } 667 /* Parse profile info */ 668 669 670 if (USE_PROFILES) { 671 ALLOWED_TAGS = addToSet({}, _toConsumableArray(text)); 672 ALLOWED_ATTR = []; 673 674 if (USE_PROFILES.html === true) { 675 addToSet(ALLOWED_TAGS, html$1); 676 addToSet(ALLOWED_ATTR, html); 677 } 678 679 if (USE_PROFILES.svg === true) { 680 addToSet(ALLOWED_TAGS, svg$1); 681 addToSet(ALLOWED_ATTR, svg); 682 addToSet(ALLOWED_ATTR, xml); 683 } 684 685 if (USE_PROFILES.svgFilters === true) { 686 addToSet(ALLOWED_TAGS, svgFilters); 687 addToSet(ALLOWED_ATTR, svg); 688 addToSet(ALLOWED_ATTR, xml); 689 } 690 691 if (USE_PROFILES.mathMl === true) { 692 addToSet(ALLOWED_TAGS, mathMl$1); 693 addToSet(ALLOWED_ATTR, mathMl); 694 addToSet(ALLOWED_ATTR, xml); 695 } 696 } 697 /* Merge configuration parameters */ 698 699 700 if (cfg.ADD_TAGS) { 701 if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) { 702 ALLOWED_TAGS = clone(ALLOWED_TAGS); 703 } 704 705 addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc); 706 } 707 708 if (cfg.ADD_ATTR) { 709 if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) { 710 ALLOWED_ATTR = clone(ALLOWED_ATTR); 711 } 712 713 addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc); 714 } 715 716 if (cfg.ADD_URI_SAFE_ATTR) { 717 addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc); 718 } 719 720 if (cfg.FORBID_CONTENTS) { 721 if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) { 722 FORBID_CONTENTS = clone(FORBID_CONTENTS); 723 } 724 725 addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc); 726 } 727 /* Add #text in case KEEP_CONTENT is set to true */ 728 729 730 if (KEEP_CONTENT) { 731 ALLOWED_TAGS['#text'] = true; 732 } 733 /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */ 734 735 736 if (WHOLE_DOCUMENT) { 737 addToSet(ALLOWED_TAGS, ['html', 'head', 'body']); 738 } 739 /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */ 740 741 742 if (ALLOWED_TAGS.table) { 743 addToSet(ALLOWED_TAGS, ['tbody']); 744 delete FORBID_TAGS.tbody; 745 } // Prevent further manipulation of configuration. 746 // Not available in IE8, Safari 5, etc. 747 748 749 if (freeze) { 750 freeze(cfg); 751 } 752 753 CONFIG = cfg; 754 }; 755 756 var MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']); 757 var HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']); // Certain elements are allowed in both SVG and HTML 758 // namespace. We need to specify them explicitly 759 // so that they don't get erroneously deleted from 760 // HTML namespace. 761 762 var COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']); 763 /* Keep track of all possible SVG and MathML tags 764 * so that we can perform the namespace checks 765 * correctly. */ 766 767 var ALL_SVG_TAGS = addToSet({}, svg$1); 768 addToSet(ALL_SVG_TAGS, svgFilters); 769 addToSet(ALL_SVG_TAGS, svgDisallowed); 770 var ALL_MATHML_TAGS = addToSet({}, mathMl$1); 771 addToSet(ALL_MATHML_TAGS, mathMlDisallowed); 772 /** 773 * 774 * 775 * @param {Element} element a DOM element whose namespace is being checked 776 * @returns {boolean} Return false if the element has a 777 * namespace that a spec-compliant parser would never 778 * return. Return true otherwise. 779 */ 780 781 var _checkValidNamespace = function _checkValidNamespace(element) { 782 var parent = getParentNode(element); // In JSDOM, if we're inside shadow DOM, then parentNode 783 // can be null. We just simulate parent in this case. 784 785 if (!parent || !parent.tagName) { 786 parent = { 787 namespaceURI: NAMESPACE, 788 tagName: 'template' 789 }; 790 } 791 792 var tagName = stringToLowerCase(element.tagName); 793 var parentTagName = stringToLowerCase(parent.tagName); 794 795 if (!ALLOWED_NAMESPACES[element.namespaceURI]) { 796 return false; 797 } 798 799 if (element.namespaceURI === SVG_NAMESPACE) { 800 // The only way to switch from HTML namespace to SVG 801 // is via <svg>. If it happens via any other tag, then 802 // it should be killed. 803 if (parent.namespaceURI === HTML_NAMESPACE) { 804 return tagName === 'svg'; 805 } // The only way to switch from MathML to SVG is via` 806 // svg if parent is either <annotation-xml> or MathML 807 // text integration points. 808 809 810 if (parent.namespaceURI === MATHML_NAMESPACE) { 811 return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]); 812 } // We only allow elements that are defined in SVG 813 // spec. All others are disallowed in SVG namespace. 814 815 816 return Boolean(ALL_SVG_TAGS[tagName]); 817 } 818 819 if (element.namespaceURI === MATHML_NAMESPACE) { 820 // The only way to switch from HTML namespace to MathML 821 // is via <math>. If it happens via any other tag, then 822 // it should be killed. 823 if (parent.namespaceURI === HTML_NAMESPACE) { 824 return tagName === 'math'; 825 } // The only way to switch from SVG to MathML is via 826 // <math> and HTML integration points 827 828 829 if (parent.namespaceURI === SVG_NAMESPACE) { 830 return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName]; 831 } // We only allow elements that are defined in MathML 832 // spec. All others are disallowed in MathML namespace. 833 834 835 return Boolean(ALL_MATHML_TAGS[tagName]); 836 } 837 838 if (element.namespaceURI === HTML_NAMESPACE) { 839 // The only way to switch from SVG to HTML is via 840 // HTML integration points, and from MathML to HTML 841 // is via MathML text integration points 842 if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) { 843 return false; 844 } 845 846 if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) { 847 return false; 848 } // We disallow tags that are specific for MathML 849 // or SVG and should never appear in HTML namespace 850 851 852 return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]); 853 } // For XHTML and XML documents that support custom namespaces 854 855 856 if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) { 857 return true; 858 } // The code should never reach this place (this means 859 // that the element somehow got namespace that is not 860 // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES). 861 // Return false just in case. 862 863 864 return false; 865 }; 866 /** 867 * _forceRemove 868 * 869 * @param {Node} node a DOM node 870 */ 871 872 873 var _forceRemove = function _forceRemove(node) { 874 arrayPush(DOMPurify.removed, { 875 element: node 876 }); 877 878 try { 879 // eslint-disable-next-line unicorn/prefer-dom-node-remove 880 node.parentNode.removeChild(node); 881 } catch (_) { 882
vendor: 7,032 bytes, lines 882-1103
882try { 883 node.outerHTML = emptyHTML; 884 } catch (_) { 885 node.remove(); 886 } 887 } 888 }; 889 /** 890 * _removeAttribute 891 * 892 * @param {String} name an Attribute name 893 * @param {Node} node a DOM node 894 */ 895 896 897 var _removeAttribute = function _removeAttribute(name, node) { 898 try { 899 arrayPush(DOMPurify.removed, { 900 attribute: node.getAttributeNode(name), 901 from: node 902 }); 903 } catch (_) { 904 arrayPush(DOMPurify.removed, { 905 attribute: null, 906 from: node 907 }); 908 } 909 910 node.removeAttribute(name); // We void attribute values for unremovable "is"" attributes 911 912 if (name === 'is' && !ALLOWED_ATTR[name]) { 913 if (RETURN_DOM || RETURN_DOM_FRAGMENT) { 914 try { 915 _forceRemove(node); 916 } catch (_) {} 917 } else { 918 try { 919 node.setAttribute(name, ''); 920 } catch (_) {} 921 } 922 } 923 }; 924 /** 925 * _initDocument 926 * 927 * @param {String} dirty a string of dirty markup 928 * @return {Document} a DOM, filled with the dirty markup 929 */ 930 931 932 var _initDocument = function _initDocument(dirty) { 933 /* Create a HTML document */ 934 var doc; 935 var leadingWhitespace; 936 937 if (FORCE_BODY) { 938 dirty = '<remove></remove>' + dirty; 939 } else { 940 /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */ 941 var matches = stringMatch(dirty, /^[\r\n\t ]+/); 942 leadingWhitespace = matches && matches[0]; 943 } 944 945 if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) { 946 // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict) 947 dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>'; 948 } 949 950 var dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty; 951 /* 952 * Use the DOMParser API by default, fallback later if needs be 953 * DOMParser not work for svg when has multiple root element. 954 */ 955 956 if (NAMESPACE === HTML_NAMESPACE) { 957 try { 958 doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE); 959 } catch (_) {} 960 } 961 /* Use createHTMLDocument in case DOMParser is not available */ 962 963 964 if (!doc || !doc.documentElement) { 965 doc = implementation.createDocument(NAMESPACE, 'template', null); 966 967 try { 968 doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload; 969 } catch (_) {// Syntax error if dirtyPayload is invalid xml 970 } 971 } 972 973 var body = doc.body || doc.documentElement; 974 975 if (dirty && leadingWhitespace) { 976 body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null); 977 } 978 /* Work on whole document or just its body */ 979 980 981 if (NAMESPACE === HTML_NAMESPACE) { 982 return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0]; 983 } 984 985 return WHOLE_DOCUMENT ? doc.documentElement : body; 986 }; 987 /** 988 * _createIterator 989 * 990 * @param {Document} root document/fragment to create iterator for 991 * @return {Iterator} iterator instance 992 */ 993 994 995 var _createIterator = function _createIterator(root) { 996 return createNodeIterator.call(root.ownerDocument || root, root, // eslint-disable-next-line no-bitwise 997 NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, null, false); 998 }; 999 /** 1000 * _isClobbered 1001 * 1002 * @param {Node} elm element to check for clobbering attacks 1003 * @return {Boolean} true if clobbered, false if safe 1004 */ 1005 1006 1007 var _isClobbered = function _isClobbered(elm) { 1008 return elm instanceof HTMLFormElement && (typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function' || typeof elm.namespaceURI !== 'string' || typeof elm.insertBefore !== 'function' || typeof elm.hasChildNodes !== 'function'); 1009 }; 1010 /** 1011 * _isNode 1012 * 1013 * @param {Node} obj object to check whether it's a DOM node 1014 * @return {Boolean} true is object is a DOM node 1015 */ 1016 1017 1018 var _isNode = function _isNode(object) { 1019 return _typeof(Node) === 'object' ? object instanceof Node : object && _typeof(object) === 'object' && typeof object.nodeType === 'number' && typeof object.nodeName === 'string'; 1020 }; 1021 /** 1022 * _executeHook 1023 * Execute user configurable hooks 1024 * 1025 * @param {String} entryPoint Name of the hook's entry point 1026 * @param {Node} currentNode node to work on with the hook 1027 * @param {Object} data additional hook parameters 1028 */ 1029 1030 1031 var _executeHook = function _executeHook(entryPoint, currentNode, data) { 1032 if (!hooks[entryPoint]) { 1033 return; 1034 } 1035 1036 arrayForEach(hooks[entryPoint], function (hook) { 1037 hook.call(DOMPurify, currentNode, data, CONFIG); 1038 }); 1039 }; 1040 /** 1041 * _sanitizeElements 1042 * 1043 * @protect nodeName 1044 * @protect textContent 1045 * @protect removeChild 1046 * 1047 * @param {Node} currentNode to check for permission to exist 1048 * @return {Boolean} true if node was killed, false if left alive 1049 */ 1050 1051 1052 var _sanitizeElements = function _sanitizeElements(currentNode) { 1053 var content; 1054 /* Execute a hook if present */ 1055 1056 _executeHook('beforeSanitizeElements', currentNode, null); 1057 /* Check if element is clobbered or can clobber */ 1058 1059 1060 if (_isClobbered(currentNode)) { 1061 _forceRemove(currentNode); 1062 1063 return true; 1064 } 1065 /* Check if tagname contains Unicode */ 1066 1067 1068 if (regExpTest(/[\u0080-\uFFFF]/, currentNode.nodeName)) { 1069 _forceRemove(currentNode); 1070 1071 return true; 1072 } 1073 /* Now let's check the element's type and name */ 1074 1075 1076 var tagName = transformCaseFunc(currentNode.nodeName); 1077 /* Execute a hook if present */ 1078 1079 _executeHook('uponSanitizeElement', currentNode, { 1080 tagName: tagName, 1081 allowedTags: ALLOWED_TAGS 1082 }); 1083 /* Detect mXSS attempts abusing namespace confusion */ 1084 1085 1086 if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) { 1087 _forceRemove(currentNode); 1088 1089 return true; 1090 } 1091 /* Mitigate a problem with templates inside select */ 1092 1093 1094 if (tagName === 'select' && regExpTest(/<template/i, currentNode.innerHTML)) { 1095 _forceRemove(currentNode); 1096 1097 return true; 1098 } 1099 /* Remove element if anything forbids its presence */ 1100 1101 1102 if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) { 1103 /* Check if we have a custom element to handle */
vendor: 1,196 bytes, lines 1104-1135
1104 if (!FORBID_TAGS[tagName] && _basicCustomElementTest(tagName)) { 1105 if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false; 1106 if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) return false; 1107 } 1108 /* Keep content except for bad-listed elements */ 1109 1110 1111 if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) { 1112 var parentNode = getParentNode(currentNode) || currentNode.parentNode; 1113 var childNodes = getChildNodes(currentNode) || currentNode.childNodes; 1114 1115 if (childNodes && parentNode) { 1116 var childCount = childNodes.length; 1117 1118 for (var i = childCount - 1; i >= 0; --i) { 1119 parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode)); 1120 } 1121 } 1122 } 1123 1124 _forceRemove(currentNode); 1125 1126 return true; 1127 } 1128 /* Check whether element has a valid namespace */ 1129 1130 1131 if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) { 1132 _forceRemove(currentNode); 1133 1134 return true; 1135 }
vendor: 10,142 bytes, lines 1135-1412
1135 1136 1137 if ((tagName === 'noscript' || tagName === 'noembed') && regExpTest(/<\/no(script|embed)/i, currentNode.innerHTML)) { 1138 _forceRemove(currentNode); 1139 1140 return true; 1141 } 1142 /* Sanitize element content to be template-safe */ 1143 1144 1145 if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) { 1146 /* Get the element's text content */ 1147 content = currentNode.textContent; 1148 content = stringReplace(content, MUSTACHE_EXPR$1, ' '); 1149 content = stringReplace(content, ERB_EXPR$1, ' '); 1150 content = stringReplace(content, TMPLIT_EXPR$1, ' '); 1151 1152 if (currentNode.textContent !== content) { 1153 arrayPush(DOMPurify.removed, { 1154 element: currentNode.cloneNode() 1155 }); 1156 currentNode.textContent = content; 1157 } 1158 } 1159 /* Execute a hook if present */ 1160 1161 1162 _executeHook('afterSanitizeElements', currentNode, null); 1163 1164 return false; 1165 }; 1166 /** 1167 * _isValidAttribute 1168 * 1169 * @param {string} lcTag Lowercase tag name of containing element. 1170 * @param {string} lcName Lowercase attribute name. 1171 * @param {string} value Attribute value. 1172 * @return {Boolean} Returns true if `value` is valid, otherwise false. 1173 */ 1174 // eslint-disable-next-line complexity 1175 1176 1177 var _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) { 1178 /* Make sure attribute cannot clobber */ 1179 if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) { 1180 return false; 1181 } 1182 /* Allow valid data-* attributes: At least one character after "-" 1183 (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes) 1184 XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804) 1185 We don't need to check the value; it's always URI safe. */ 1186 1187 1188 if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) { 1189 if ( // First condition does a very basic check if a) it's basically a valid custom element tagname AND 1190 // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck 1191 // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck 1192 _basicCustomElementTest(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)) || // Alternative, second condition checks if it's an `is`-attribute, AND 1193 // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck 1194 lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ; else { 1195 return false; 1196 } 1197 /* Check value is safe. First, is attr inert? If so, is safe */ 1198 1199 } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if (!value) ; else { 1200 return false; 1201 } 1202 1203 return true; 1204 }; 1205 /** 1206 * _basicCustomElementCheck 1207 * checks if at least one dash is included in tagName, and it's not the first char 1208 * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name 1209 * @param {string} tagName name of the tag of the node to sanitize 1210 */ 1211 1212 1213 var _basicCustomElementTest = function _basicCustomElementTest(tagName) { 1214 return tagName.indexOf('-') > 0; 1215 }; 1216 /** 1217 * _sanitizeAttributes 1218 * 1219 * @protect attributes 1220 * @protect nodeName 1221 * @protect removeAttribute 1222 * @protect setAttribute 1223 * 1224 * @param {Node} currentNode to sanitize 1225 */ 1226 1227 1228 var _sanitizeAttributes = function _sanitizeAttributes(currentNode) { 1229 var attr; 1230 var value; 1231 var lcName; 1232 var l; 1233 /* Execute a hook if present */ 1234 1235 _executeHook('beforeSanitizeAttributes', currentNode, null); 1236 1237 var attributes = currentNode.attributes; 1238 /* Check if we have attributes; if not we might have a text node */ 1239 1240 if (!attributes) { 1241 return; 1242 } 1243 1244 var hookEvent = { 1245 attrName: '', 1246 attrValue: '', 1247 keepAttr: true, 1248 allowedAttributes: ALLOWED_ATTR 1249 }; 1250 l = attributes.length; 1251 /* Go backwards over all attributes; safely remove bad ones */ 1252 1253 while (l--) { 1254 attr = attributes[l]; 1255 var _attr = attr, 1256 name = _attr.name, 1257 namespaceURI = _attr.namespaceURI; 1258 value = name === 'value' ? attr.value : stringTrim(attr.value); 1259 lcName = transformCaseFunc(name); 1260 /* Execute a hook if present */ 1261 1262 hookEvent.attrName = lcName; 1263 hookEvent.attrValue = value; 1264 hookEvent.keepAttr = true; 1265 hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set 1266 1267 _executeHook('uponSanitizeAttribute', currentNode, hookEvent); 1268 1269 value = hookEvent.attrValue; 1270 /* Did the hooks approve of the attribute? */ 1271 1272 if (hookEvent.forceKeepAttr) { 1273 continue; 1274 } 1275 /* Remove attribute */ 1276 1277 1278 _removeAttribute(name, currentNode); 1279 /* Did the hooks approve of the attribute? */ 1280 1281 1282 if (!hookEvent.keepAttr) { 1283 continue; 1284 } 1285 /* Work around a security issue in jQuery 3.0 */ 1286 1287 1288 if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) { 1289 _removeAttribute(name, currentNode); 1290 1291 continue; 1292 } 1293 /* Sanitize attribute content to be template-safe */ 1294 1295 1296 if (SAFE_FOR_TEMPLATES) { 1297 value = stringReplace(value, MUSTACHE_EXPR$1, ' '); 1298 value = stringReplace(value, ERB_EXPR$1, ' '); 1299 value = stringReplace(value, TMPLIT_EXPR$1, ' '); 1300 } 1301 /* Is `value` valid for this attribute? */ 1302 1303 1304 var lcTag = transformCaseFunc(currentNode.nodeName); 1305 1306 if (!_isValidAttribute(lcTag, lcName, value)) { 1307 continue; 1308 } 1309 /* Full DOM Clobbering protection via namespace isolation, 1310 * Prefix id and name attributes with `user-content-` 1311 */ 1312 1313 1314 if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) { 1315 // Remove the attribute with this value 1316 _removeAttribute(name, currentNode); // Prefix the value and later re-create the attribute with the sanitized value 1317 1318 1319 value = SANITIZE_NAMED_PROPS_PREFIX + value; 1320 } 1321 /* Handle attributes that require Trusted Types */ 1322 1323 1324 if (trustedTypesPolicy && _typeof(trustedTypes) === 'object' && typeof trustedTypes.getAttributeType === 'function') { 1325 if (namespaceURI) ; else { 1326 switch (trustedTypes.getAttributeType(lcTag, lcName)) { 1327 case 'TrustedHTML': 1328 value = trustedTypesPolicy.createHTML(value); 1329 break; 1330 1331 case 'TrustedScriptURL': 1332 value = trustedTypesPolicy.createScriptURL(value); 1333 break; 1334 } 1335 } 1336 } 1337 /* Handle invalid data-* attribute set by try-catching it */ 1338 1339 1340 try { 1341 if (namespaceURI) { 1342 currentNode.setAttributeNS(namespaceURI, name, value); 1343 } else { 1344 /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */ 1345 currentNode.setAttribute(name, value); 1346 } 1347 1348 arrayPop(DOMPurify.removed); 1349 } catch (_) {} 1350 } 1351 /* Execute a hook if present */ 1352 1353 1354 _executeHook('afterSanitizeAttributes', currentNode, null); 1355 }; 1356 /** 1357 * _sanitizeShadowDOM 1358 * 1359 * @param {DocumentFragment} fragment to iterate over recursively 1360 */ 1361 1362 1363 var _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) { 1364 var shadowNode; 1365 1366 var shadowIterator = _createIterator(fragment); 1367 /* Execute a hook if present */ 1368 1369 1370 _executeHook('beforeSanitizeShadowDOM', fragment, null); 1371 1372 while (shadowNode = shadowIterator.nextNode()) { 1373 /* Execute a hook if present */ 1374 _executeHook('uponSanitizeShadowNode', shadowNode, null); 1375 /* Sanitize tags and elements */ 1376 1377 1378 if (_sanitizeElements(shadowNode)) { 1379 continue; 1380 } 1381 /* Deep shadow DOM detected */ 1382 1383 1384 if (shadowNode.content instanceof DocumentFragment) { 1385 _sanitizeShadowDOM(shadowNode.content); 1386 } 1387 /* Check attributes, sanitize if necessary */ 1388 1389 1390 _sanitizeAttributes(shadowNode); 1391 } 1392 /* Execute a hook if present */ 1393 1394 1395 _executeHook('afterSanitizeShadowDOM', fragment, null); 1396 }; 1397 /** 1398 * Sanitize 1399 * Public method providing core sanitation functionality 1400 * 1401 * @param {String|Node} dirty string or DOM node 1402 * @param {Object} configuration object 1403 */ 1404 // eslint-disable-next-line complexity 1405 1406 1407 DOMPurify.sanitize = function (dirty) { 1408 var cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 1409 var body; 1410 var importedNode; 1411 var currentNode; 1412 var
vendor: 4,689 bytes, lines 1412-1569
1412oldNode; 1413 var returnNode; 1414 /* Make sure we have a string to sanitize. 1415 DO NOT return early, as this will return the wrong type if 1416 the user has requested a DOM object rather than a string */ 1417 1418 IS_EMPTY_INPUT = !dirty; 1419 1420 if (IS_EMPTY_INPUT) { 1421 dirty = '<!-->'; 1422 } 1423 /* Stringify, in case dirty is an object */ 1424 1425 1426 if (typeof dirty !== 'string' && !_isNode(dirty)) { 1427 // eslint-disable-next-line no-negated-condition 1428 if (typeof dirty.toString !== 'function') { 1429 throw typeErrorCreate('toString is not a function'); 1430 } else { 1431 dirty = dirty.toString(); 1432 1433 if (typeof dirty !== 'string') { 1434 throw typeErrorCreate('dirty is not a string, aborting'); 1435 } 1436 } 1437 } 1438 /* Check we can run. Otherwise fall back or ignore */ 1439 1440 1441 if (!DOMPurify.isSupported) { 1442 if (_typeof(window.toStaticHTML) === 'object' || typeof window.toStaticHTML === 'function') { 1443 if (typeof dirty === 'string') { 1444 return window.toStaticHTML(dirty); 1445 } 1446 1447 if (_isNode(dirty)) { 1448 return window.toStaticHTML(dirty.outerHTML); 1449 } 1450 } 1451 1452 return dirty; 1453 } 1454 /* Assign config vars */ 1455 1456 1457 if (!SET_CONFIG) { 1458 _parseConfig(cfg); 1459 } 1460 /* Clean up removed elements */ 1461 1462 1463 DOMPurify.removed = []; 1464 /* Check if dirty is correctly typed for IN_PLACE */ 1465 1466 if (typeof dirty === 'string') { 1467 IN_PLACE = false; 1468 } 1469 1470 if (IN_PLACE) { 1471 /* Do some early pre-sanitization to avoid unsafe root nodes */ 1472 if (dirty.nodeName) { 1473 var tagName = transformCaseFunc(dirty.nodeName); 1474 1475 if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) { 1476 throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place'); 1477 } 1478 } 1479 } else if (dirty instanceof Node) { 1480 /* If dirty is a DOM element, append to an empty document to avoid 1481 elements being stripped by the parser */ 1482 body = _initDocument('<!---->'); 1483 importedNode = body.ownerDocument.importNode(dirty, true); 1484 1485 if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') { 1486 /* Node is already a body, use as is */ 1487 body = importedNode; 1488 } else if (importedNode.nodeName === 'HTML') { 1489 body = importedNode; 1490 } else { 1491 // eslint-disable-next-line unicorn/prefer-dom-node-append 1492 body.appendChild(importedNode); 1493 } 1494 } else { 1495 /* Exit directly if we have nothing to do */ 1496 if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT && // eslint-disable-next-line unicorn/prefer-includes 1497 dirty.indexOf('<') === -1) { 1498 return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty; 1499 } 1500 /* Initialize the document to work on */ 1501 1502 1503 body = _initDocument(dirty); 1504 /* Check we have a DOM node from the data */ 1505 1506 if (!body) { 1507 return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : ''; 1508 } 1509 } 1510 /* Remove first element node (ours) if FORCE_BODY is set */ 1511 1512 1513 if (body && FORCE_BODY) { 1514 _forceRemove(body.firstChild); 1515 } 1516 /* Get node iterator */ 1517 1518 1519 var nodeIterator = _createIterator(IN_PLACE ? dirty : body); 1520 /* Now start iterating over the created document */ 1521 1522 1523 while (currentNode = nodeIterator.nextNode()) { 1524 /* Fix IE's strange behavior with manipulated textNodes #89 */ 1525 if (currentNode.nodeType === 3 && currentNode === oldNode) { 1526 continue; 1527 } 1528 /* Sanitize tags and elements */ 1529 1530 1531 if (_sanitizeElements(currentNode)) { 1532 continue; 1533 } 1534 /* Shadow DOM detected, sanitize it */ 1535 1536 1537 if (currentNode.content instanceof DocumentFragment) { 1538 _sanitizeShadowDOM(currentNode.content); 1539 } 1540 /* Check attributes, sanitize if necessary */ 1541 1542 1543 _sanitizeAttributes(currentNode); 1544 1545 oldNode = currentNode; 1546 } 1547 1548 oldNode = null; 1549 /* If we sanitized `dirty` in-place, return it. */ 1550 1551 if (IN_PLACE) { 1552 return dirty; 1553 } 1554 /* Return sanitized string or DOM */ 1555 1556 1557 if (RETURN_DOM) { 1558 if (RETURN_DOM_FRAGMENT) { 1559 returnNode = createDocumentFragment.call(body.ownerDocument); 1560 1561 while (body.firstChild) { 1562 // eslint-disable-next-line unicorn/prefer-dom-node-append 1563 returnNode.appendChild(body.firstChild); 1564 } 1565 } else { 1566 returnNode = body; 1567 } 1568 1569 if (ALLOWED_ATTR.shadowroot
vendor: 4,276 bytes, lines 1569-1717
1569 || ALLOWED_ATTR.shadowrootmod) { 1570 /* 1571 AdoptNode() is not used because internal state is not reset 1572 (e.g. the past names map of a HTMLFormElement), this is safe 1573 in theory but we would rather not risk another attack vector. 1574 The state that is cloned by importNode() is explicitly defined 1575 by the specs. 1576 */ 1577 returnNode = importNode.call(originalDocument, returnNode, true); 1578 } 1579 1580 return returnNode; 1581 } 1582 1583 var serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML; 1584 /* Serialize doctype if allowed */ 1585 1586 if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) { 1587 serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML; 1588 } 1589 /* Sanitize final string template-safe */ 1590 1591 1592 if (SAFE_FOR_TEMPLATES) { 1593 serializedHTML = stringReplace(serializedHTML, MUSTACHE_EXPR$1, ' '); 1594 serializedHTML = stringReplace(serializedHTML, ERB_EXPR$1, ' '); 1595 serializedHTML = stringReplace(serializedHTML, TMPLIT_EXPR$1, ' '); 1596 } 1597 1598 return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML; 1599 }; 1600 /** 1601 * Public method to set the configuration once 1602 * setConfig 1603 * 1604 * @param {Object} cfg configuration object 1605 */ 1606 1607 1608 DOMPurify.setConfig = function (cfg) { 1609 _parseConfig(cfg); 1610 1611 SET_CONFIG = true; 1612 }; 1613 /** 1614 * Public method to remove the configuration 1615 * clearConfig 1616 * 1617 */ 1618 1619 1620 DOMPurify.clearConfig = function () { 1621 CONFIG = null; 1622 SET_CONFIG = false; 1623 }; 1624 /** 1625 * Public method to check if an attribute value is valid. 1626 * Uses last set config, if any. Otherwise, uses config defaults. 1627 * isValidAttribute 1628 * 1629 * @param {string} tag Tag name of containing element. 1630 * @param {string} attr Attribute name. 1631 * @param {string} value Attribute value. 1632 * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false. 1633 */ 1634 1635 1636 DOMPurify.isValidAttribute = function (tag, attr, value) { 1637 /* Initialize shared config vars if necessary. */ 1638 if (!CONFIG) { 1639 _parseConfig({}); 1640 } 1641 1642 var lcTag = transformCaseFunc(tag); 1643 var lcName = transformCaseFunc(attr); 1644 return _isValidAttribute(lcTag, lcName, value); 1645 }; 1646 /** 1647 * AddHook 1648 * Public method to add DOMPurify hooks 1649 * 1650 * @param {String} entryPoint entry point for the hook to add 1651 * @param {Function} hookFunction function to execute 1652 */ 1653 1654 1655 DOMPurify.addHook = function (entryPoint, hookFunction) { 1656 if (typeof hookFunction !== 'function') { 1657 return; 1658 } 1659 1660 hooks[entryPoint] = hooks[entryPoint] || []; 1661 arrayPush(hooks[entryPoint], hookFunction); 1662 }; 1663 /** 1664 * RemoveHook 1665 * Public method to remove a DOMPurify hook at a given entryPoint 1666 * (pops it from the stack of hooks if more are present) 1667 * 1668 * @param {String} entryPoint entry point for the hook to remove 1669 * @return {Function} removed(popped) hook 1670 */ 1671 1672 1673 DOMPurify.removeHook = function (entryPoint) { 1674 if (hooks[entryPoint]) { 1675 return arrayPop(hooks[entryPoint]); 1676 } 1677 }; 1678 /** 1679 * RemoveHooks 1680 * Public method to remove all DOMPurify hooks at a given entryPoint 1681 * 1682 * @param {String} entryPoint entry point for the hooks to remove 1683 */ 1684 1685 1686 DOMPurify.removeHooks = function (entryPoint) { 1687 if (hooks[entryPoint]) { 1688 hooks[entryPoint] = []; 1689 } 1690 }; 1691 /** 1692 * RemoveAllHooks 1693 * Public method to remove all DOMPurify hooks 1694 * 1695 */ 1696 1697 1698 DOMPurify.removeAllHooks = function () { 1699 hooks = {}; 1700 }; 1701 1702 return DOMPurify; 1703 } 1704 1705 var purify = createDOMPurify(); 1706 1707 return purify; 1708 1709})); 1710//# sourceMappingURL=purify.js.map 1711 1712/** 1713 * jQuery fixes 1714 * 1715 * Note: The following analysis and/or changes was required also to be applied in our code base: 1716 * - Ensure there is no JSONP ajax call 1717 * - Ensure to not use self closing tag when a closing tag is usally needed.
1718 * ex: `<div />` need to change for `<div></div>` 1719 * This is applicable to all tags except the one in this list: area|br|col|embed|hr|img|input|link|meta|param 1720 * - Review how `<tr>`, `<td>`, `<script>` and `<link>` are inserted with `$()`. 1721 * Their insersion need to be completed differently like the by using the javascript DOM interface. 1722 * - All content that related to jQuery DOM manipulation are sanitized with DOMPurify 1723 * - You can't use jQuery for parsing XML document, you can use DOMParser() as an alternative 1724 * - Ensure AJAX and fetch are sanitized before they response are used 1725 */ 1726/* 1727 1728// Test case for the jQuery fixes bellow 1729 1730Test: jQuery() 1731 1732 - Run in your browser console 1733 > $( "<p>abc<iframe//src=jAva	script:alert(3)>def</p>" ).appendTo( "#wb-cont" ); 1734 result: No iframe inserted around the heading level 1 1735 1736Test: 1737jQuery.html() 1738jQuery.append() 1739jQuery.prepend() 1740jQuery.before() 1741jQuery.after() 1742 1743 - Use the data-ajax demo page: /demos/data-ajax/data-ajax-en.html 1744 - Insert the following in the ajaxed in file: <p>abc<iframe//src=jAva	script:alert(3)>def</p> 1745 - Check if the dirty HTML are cleaned for the example: "append", "prepend", "before", "after" 1746 - The data-ajax-replace will also test the jQuery.html() function. 1747 1748Test: jQuery.replaceWith() 1749 1750 - Run in your browser console 1751 > $("#wb-cont").replaceWith( "<p>abc<iframe//src=jAva	script:alert(3)>def</p>" ); 1752 result: No iframe inserted around the heading level 1 1753 1754Test: jQuery.parseHTML() 1755 1756 - Run in your browser console, pass unsafe HTML and it should return sanitized HTML 1757 > jQuery.parseHTML( "<p>abc<iframe//src=jAva	script:alert(3)>def</p>" ); 1758 result: return an array with 1 element, [ p ] 1759 1760Test: jQuery.htmlPrefilter() 1761 1762 - Run in your browser console 1763 > jQuery.htmlPrefilter( "<tr/>" ); 1764 result: return <tr/> 1765 1766Test: jQuery.extend 1767 1768 - Run in your browser console 1769 > var dataFromAPI = JSON.parse( '{ "new": "property", "__proto__": { "polluted": "true" } }' ); 1770 > var myObject = jQuery.extend( '{ "new": "property" }', dataFromAPI ); 1771 > console.log( myObject.polluted ); 1772 result: > undefined 1773 1774*/ 1775 1776( function( jQuery, DOMPurify, window ) { 1777"use strict"; 1778 1779/** 1780 * Copied from: https://git.drupalcode.org/project/drupal/-/commit/d2f26902ef5d6de58caaf2a7c766eb2115b1c17e 1781 * 1782 * This is almost verbatim copied from jQuery 3.4.0. 1783 * 1784 * Only two minor changes have been made: 1785 * - The call to isFunction() is changed to jQuery.isFunction(). 1786 * - The two calls to Array.isArray() is changed to jQuery.isArray(). 1787 * 1788 * The above two changes ensure compatibility with all older jQuery versions 1789 * (1.4.4 - 3.3.1) and older browser versions (e.g., IE8). 1790 */ 1791jQuery.extend = jQuery.fn.extend = function() { 1792 var options, name, src, copy, copyIsArray, clone, 1793 target = arguments[ 0 ] || {}, 1794 i = 1, 1795 length = arguments.length, 1796 deep = false; 1797 1798 // Handle a deep copy situation 1799 if ( typeof target === "boolean" ) { 1800 deep = target; 1801 1802 // Skip the boolean and the target 1803 target = arguments[ i ] || {}; 1804 i++; 1805 } 1806 1807 // Handle case when target is a string or something (possible in deep copy) 1808 if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { 1809 target = {}; 1810 } 1811 1812 // Extend jQuery itself if only one argument is passed 1813 if ( i === length ) { 1814 target = this; 1815 i--; 1816 } 1817 1818 for ( ; i < length; i++ ) { 1819 1820 // Only deal with non-null/undefined values 1821 options = arguments[ i ]; 1822 if ( options !== undefined || options !== null ) { 1823 1824 // Extend the base object 1825 for ( name in options ) { 1826 copy = options[ name ]; 1827 1828 // Prevent Object.prototype pollution 1829 // Prevent never-ending loop 1830 if ( name === "__proto__" || target === copy ) { 1831 continue; 1832 } 1833 1834 // Recurse if we're merging plain objects or arrays 1835 if ( deep && copy && ( jQuery.isPlainObject( copy ) || 1836 ( copyIsArray = jQuery.isArray( copy ) ) ) ) { 1837 src = target[ name ]; 1838 1839 // Ensure proper type for the source value 1840 if ( copyIsArray && !jQuery.isArray( src ) ) { 1841 clone = []; 1842 } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { 1843 clone = {}; 1844 } else { 1845 clone = src; 1846 } 1847 copyIsArray = false;
1848 1849 // Never move original objects, clone them 1850 target[ name ] = jQuery.extend( deep, clone, copy ); 1851 1852 // Don't bring in undefined values 1853 } else if ( copy !== undefined ) { 1854 target[ name ] = copy; 1855 } 1856 } 1857 } 1858 } 1859 1860 // Return the modified object 1861 return target; 1862}; 1863 1864/** 1865 * Security advisories: https://github.com/jquery/jquery/security/advisories/GHSA-gxr4-xjj5-5px2 1866 */ 1867jQuery.htmlPrefilter = function( html ) { 1868 return html; 1869}; 1870 1871/** 1872 * Security advisories: https://github.com/jquery/jquery/security/advisories/GHSA-jpcq-cgw6-v4j6 1873 * 1874 * This implementation leverage DOMPurify for filtering every string prior DOM manipulation by jQuery 1875 * 1876 */ 1877var localParseHTML = jQuery.parseHTML, 1878 append = jQuery.fn.append, 1879 prepend = jQuery.fn.prepend, 1880 before = jQuery.fn.before, 1881 after = jQuery.fn.after, 1882 replaceWith = jQuery.fn.replaceWith, 1883 jqInit = jQuery.fn.init, 1884 dataTableAllowedTag = [ 1885 "<tbody/>", 1886 "<tr/>", 1887 "<td />", 1888 "<td/>" 1889 ], 1890 sanitize = function( html ) { 1891 1892 // Add an exception for DataTable plugin 1893 if ( window.DataTable && dataTableAllowedTag.indexOf( html ) !== -1 ) { 1894 return html; 1895 } 1896 1897 return DOMPurify.sanitize( html ); 1898 }; 1899 1900jQuery.parseHTML = function( data, context, keepScripts ) { 1901 return localParseHTML( sanitize( data ), context, keepScripts ); 1902}; 1903 1904jQuery.domManip = null; 1905 1906jQuery.append = jQuery.fn.append = function() { 1907 var args = arguments, 1908 value = args[ 0 ]; 1909 if ( typeof value === "string" ) { 1910 value = sanitize( value ); 1911 args[ 0 ] = value; 1912 } 1913 return append.apply( this, args ); 1914}; 1915 1916jQuery.prepend = jQuery.fn.prepend = function() { 1917 var args = arguments, 1918 value = args[ 0 ]; 1919 if ( typeof value === "string" ) { 1920 value = sanitize( value ); 1921 args[ 0 ] = value; 1922 } 1923 return prepend.apply( this, args ); 1924}; 1925 1926jQuery.before = jQuery.fn.before = function() { 1927 var args = arguments, 1928 value = args[ 0 ]; 1929 if ( typeof value === "string" ) { 1930 value = sanitize( value ); 1931 args[ 0 ] = value; 1932 } 1933 return before.apply( this, args ); 1934}; 1935 1936jQuery.after = jQuery.fn.after = function() { 1937 var args = arguments, 1938 value = args[ 0 ]; 1939 if ( typeof value === "string" ) { 1940 value = sanitize( value ); 1941 args[ 0 ] = value; 1942 } 1943 return after.apply( this, args ); 1944}; 1945 1946jQuery.replaceWith = jQuery.fn.replaceWith = function() { 1947 var args = arguments, 1948 value = args[ 0 ]; 1949 if ( typeof value === "string" ) { 1950 value = sanitize( value ); 1951 args[ 0 ] = value; 1952 } 1953 return replaceWith.apply( this, args ); 1954}; 1955 1956jQuery.fn.init = function( selector, context, root ) { 1957 if ( typeof selector === "string" ) { 1958 selector = sanitize( selector ); 1959 } 1960 return new jqInit( selector, context, root ); 1961}; 1962 1963jQuery.html = function( value ) { 1964 return jQuery.html( sanitize( value ) ); 1965}; 1966 1967} )( jQuery, DOMPurify, window ); 1968 1969/* Modernizr (Custom Build) | MIT & BSD 1970 * Build: https://modernizr.com/download/#-elem_details-elem_progress_meter-mathml-cors-load-mq-css3-input-inputtypes-svg-cssclasses-csstransitions-fontface-backgroundsize-borderimage-teststyles-testprops-testallprops-hasevents-prefixes-domprefixes 1971 */ 1972;window.Modernizr=function(e,t,n){function L(e){f.cssText=e}function A(e,t){return L(p.join(e+";")+(t||""))}function O(e,t){return typeof e===t}function M(e,t){return!!~(""+e).indexOf(t)}function _(e,t){for(var r in e){var i=e[r];if(!M(i,"-")&&f[i]!==n)return t=="pfx"?i:!0}return!1}function D(e,t,r){for(var i in e){var s=t[e[i]];if(s!==n)return r===!1?e[i]:O(s,"function")?s.bind(r||t):s}return!1}function P(e,t,n){var r=e.charAt(0).toUpperCase()+e.slice(1),i=(e+" "+v.join(r+" ")+r).split(" ");return O(t,"string")||O(t,"undefined")?_(i,t):(i=(e+" "+m.join(r+" ")+r).split(" "),D(i,t,n))}function H(){i.input=function(n){for(var r=0,i=n.length;r<i;r++)w[n[r]]=n[r]in l;return w.list&&(w.list=!!t.createElement("datalist")&&!!e.HTMLDataListElement),w}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" ")),i.inputtypes=function(e){for(var r=0,i,s,u,a=e.length;r<a;r++)l.setAttribute("type",s=e[r]),i=l.type!=="text",i&&(l.value=c,l.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(s)&&l.style.WebkitAppearance!==n?(o.appendChild(l),u=t.defaultView,i=u.getComputedStyle&&u.getComputedStyle(l,null).WebkitAppearance!=="textfield"&&l.offsetHeight!==0,o.removeChild(l)):/^(search|tel)$/.test(s)||(/^(url|email)$/.test(s)?i=l.checkValidity&&l.checkValidity()===!1:i=l.value!=c)),b[e[r]]=!!i;return b}("search tel url email datetime date month week time datetime-local number range color".split(" "))}
1972var r="2.8.3",i={},s=!0,o=t.documentElement,u="modernizr",a=t.createElement(u),f=a.style,l=t.createElement("input"),c=":)",h={}.toString,p=" -webkit- -moz- -o- -ms- ".split(" "),d="Webkit Moz O ms",v=d.split(" "),m=d.toLowerCase().split(" "),g={svg:"http://www.w3.org/2000/svg"},y={},b={},w={},E=[],S=E.slice,x,T=function(e,n,r,i){var s,a,f,l,c=t.createElement("div"),h=t.body,p=h||t.createElement("body");if(parseInt(r,10))while(r--)f=t.createElement("div"),f.id=i?i[r]:u+(r+1),c.appendChild(f);return s=["­",'<style id="s',u,'">',e,"</style>"].join(""),c.id=u,(h?c:p).innerHTML+=s,p.appendChild(c),h||(p.style.background="",p.style.overflow="hidden",l=o.style.overflow,o.style.overflow="hidden",o.appendChild(p)),a=n(c,e),h?c.parentNode.removeChild(c):(p.parentNode.removeChild(p),o.style.overflow=l),!!a},N=function(t){var n=e.matchMedia||e.msMatchMedia;if(n)return n(t)&&n(t).matches||!1;var r;return T("@media "+t+" { #"+u+" { position: absolute; } }",function(t){r=(e.getComputedStyle?getComputedStyle(t,null):t.currentStyle)["position"]=="absolute"}),r},C={}.hasOwnProperty,k;!O(C,"undefined")&&!O(C.call,"undefined")?k=function(e,t){return C.call(e,t)}:k=function(e,t){return t in e&&O(e.constructor.prototype[t],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(t){var n=this;if(typeof n!="function")throw new TypeError;var r=S.call(arguments,1),i=function(){if(this instanceof i){var e=function(){};e.prototype=n.prototype;var s=new e,o=n.apply(s,r.concat(S.call(arguments)));return Object(o)===o?o:s}return n.apply(t,r.concat(S.call(arguments)))};return i}),y.backgroundsize=function(){return P("backgroundSize")},y.borderimage=function(){return P("borderImage")},y.csstransitions=function(){return P("transition")},y.fontface=function(){var e;return T('@font-face {font-family:"font";src:url("https://")}',function(n,r){var i=t.getElementById("smodernizr"),s=i.sheet||i.styleSheet,o=s?s.cssRules&&s.cssRules[0]?s.cssRules[0].cssText:s.cssText||"":"";e=/src/i.test(o)&&o.indexOf(r.split(" ")[0])===0}),e},y.svg=function(){return!!t.createElementNS&&!!t.createElementNS(g.svg,"svg").createSVGRect};for(var B in y)k(y,B)&&(x=B.toLowerCase(),i[x]=y[B](),E.push((i[x]?"":"no-")+x));return i.input||H(),i.addTest=function(e,t){if(typeof e=="object")for(var r in e)k(e,r)&&i.addTest(r,e[r]);else{e=e.toLowerCase();if(i[e]!==n)return i;t=typeof t=="function"?t():t,typeof s!="undefined"&&s&&(o.className+=" "+(t?"":"no-")+e),i[e]=t}return i},L(""),a=l=null,i._version=r,i._prefixes=p,i._domPrefixes=m,i._cssomPrefixes=v,i.mq=N,i.testProp=function(e){return _([e])},i.testAllProps=P,i.testStyles=T,o.className=o.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(s?" js "+E.join(" "):""),i}(this,this.document),function(e,t,n){function r(e){return"[object Function]"==d.call(e)}function i(e){return"string"==typeof e}function s(){}function o(e){return!e||"loaded"==e||"complete"==e||"uninitialized"==e}function u(){var e=v.shift();m=1,e?e.t?h(function(){("c"==e.t?k.injectCss:k.injectJs)(e.s,0,e.a,e.x,e.e,1)},0):(e(),u()):m=0}function a(e,n,r,i,s,a,f){function l(t){if(!d&&o(c.readyState)&&(w.r=d=1,!m&&u(),c.onload=c.onreadystatechange=null,t)){"img"!=e&&h(function(){b.removeChild(c)},50);for(var r in T[n])T[n].hasOwnProperty(r)&&T[n][r].onload()}}var f=f||k.errorTimeout,c=t.createElement(e),d=0,g=0,w={t:r,s:n,e:s,a:a,x:f};1===T[n]&&(g=1,T[n]=[]),"object"==e?c.data=n:(c.src=n,c.type=e),c.width=c.height="0",c.onerror=c.onload=c.onreadystatechange=function(){l.call(this,g)},v.splice(i,0,w),"img"!=e&&(g||2===T[n]?(b.insertBefore(c,y?null:p),h(l,f)):T[n].push(c))}function f(e,t,n,r,s){return m=0,t=t||"j",i(e)?a("c"==t?E:w,e,t,this.i++,n,r,s):(v.splice(this.i++,0,e),1==v.length&&u()),this}function l(){var e=k;return e.loader={load:f,i:0},e}var c=t.documentElement,h=e.setTimeout,p=t.getElementsByTagName("script")[0],d={}.toString,v=[],m=0,g="MozAppearance"in c.style,y=g&&!!t.createRange().compareNode,b=y?c:p.parentNode,c=e.opera&&"[object Opera]"==d.call(e.opera),c=!!t.attachEvent&&!c,w=g?"object":c?"script":"img",E=c?"script":w,S=Array.isArray||function(e){return"[object Array]"==d.call(e)},x=[],T={},N={timeout:function(e,t){return t.length&&(e.timeout=t[0]),e}},C,k;k=function(e){function t(e){var e=e.split("!"),t=x.length,n=e.pop(),r=e.length,n={url:n,origUrl:n,prefixes:e},i,s,o;for(s=0;s<r;s++)o=e[s].split("="),(i=N[o.shift()])&&(n=i(n,o));for(s=0;s<t;s++)n=x[s](n);return n}function o(e,i,s,o,u){var a=t(e),f=a.autoCallback;a.url.split(".").pop().split("?").shift(),a.bypass||(i&&(i=r(i)?i:i[e]||i[o]||i[e.split("/").pop().split("?")[0]]),a.instead?a.instead(e,i,s,o,u):(T[a.url]?a.noexec=!0:T[a.url]=1,s.load(a.url,a.forceCSS||!a.forceJS&&"css"==a.url.split(".").pop().split("?").shift()?"c":n,a.noexec,a.attrs,a.timeout),(r(i)||r(f))&&s.load(function(){l(),i&&i(a.origUrl,u,o),f&&f(a.origUrl,u,o),T[a.url]=2})))}function u(e,t){function n(e,n){if(e){if(i(e))n||(f=function(){var e=[].slice.call(arguments);l.apply(this,e),c()}),o(e,f,t,0,u);else if(Object(e)===e)for(p in h=function(){var t=0,n;for(n in e)e.hasOwnProperty(n)&&t++;return t}(),e)e.hasOwnProperty(p)&&(!n&&!--h&&(r(f)?f=function(){var e=[].slice.call(arguments);l.apply(this,e),c()}:f[p]=function(e){return function(){var t=[].slice.call(arguments);e&&e.apply(this,t),c()}}(l[p])),o(e[p],f,t,p,u))}else!n&&c()}var u=!!e.test,a=e.load||e.both,f=e.callback||s,l=f,c=e.complete||s,h,p;n(u?e.yep:e.nope,!!a),a&&n(a)}var a,f,c=this.yepnope.loader;if(i(e))o(e,0,c,0);else if(S(e))for(a=0;a<e.length;a++)f=e[a],i(f)?o(f,0,c,0):S(f)?k(f):Object(f)===f&&u(f,c);else Object(e)===e&&u(e,c)}
1972,k.addPrefix=function(e,t){N[e]=t},k.addFilter=function(e){x.push(e)},k.errorTimeout=1e4,null==t.readyState&&t.addEventListener&&(t.readyState="loading",t.addEventListener("DOMContentLoaded",C=function(){t.removeEventListener("DOMContentLoaded",C,0),t.readyState="complete"},0)),e.yepnope=l(),e.yepnope.executeStack=u,e.yepnope.injectJs=function(e,n,r,i,a,f){var l=t.createElement("script"),c,d,i=i||k.errorTimeout;l.src=e;for(d in r)l.setAttribute(d,r[d]);n=f?u:n||s,l.onreadystatechange=l.onload=function(){!c&&o(l.readyState)&&(c=1,n(),l.onload=l.onreadystatechange=null)},h(function(){c||(c=1,n(1))},i),a?l.onload():p.parentNode.insertBefore(l,p)},e.yepnope.injectCss=function(e,n,r,i,o,a){var i=t.createElement("link"),f,n=a?u:n||s;i.href=e,i.rel="stylesheet",i.type="text/css";for(f in r)i.setAttribute(f,r[f]);o||(p.parentNode.insertBefore(i,p),h(n,0))}}(this,document),Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0))},Modernizr.addTest("details",function(){var e=document,t=e.createElement("details"),n,r,i;return"open"in t?(r=e.body||function(){var t=e.documentElement;return n=!0,t.insertBefore(e.createElement("body"),t.firstElementChild||t.firstChild)}(),t.innerHTML="<summary>a</summary>b",t.style.display="block",r.appendChild(t),i=t.offsetHeight,t.open=!0,i=i!=t.offsetHeight,r.removeChild(t),n&&r.parentNode.removeChild(r),i):!1}),Modernizr.addTest("progressbar",function(){return document.createElement("progress").max!==undefined}),Modernizr.addTest("meter",function(){return document.createElement("meter").max!==undefined}),Modernizr.addTest("mathml",function(){var e=!1;if(document.createElementNS){var t="http://www.w3.org/1998/Math/MathML",n=document.createElement("div");n.style.position="absolute";var r=n.appendChild(document.createElementNS(t,"math")).appendChild(document.createElementNS(t,"mfrac"));r.appendChild(document.createElementNS(t,"mi")).appendChild(document.createTextNode("xx")),r.appendChild(document.createElementNS(t,"mi")).appendChild(document.createTextNode("yy")),document.body.appendChild(n),e=n.offsetHeight>n.offsetWidth}return e}),Modernizr.addTest("cors",!!(window.XMLHttpRequest&&"withCredentials"in new XMLHttpRequest)); 1973 1974/** 1975 * String.prototype.includes() polyfill 1976 * @author Ricokola 1977 * @license MIT 1978 */ 1979if ( !String.prototype.includes ) { 1980 String.prototype.includes = function( string ) { 1981 1982 return this.indexOf( string ) !== -1; 1983 1984 }; 1985} 1986 1987/** 1988 * String.prototype.replaceAll() polyfill 1989 * https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/ 1990 * @author Chris Ferdinandi 1991 * @license MIT 1992 */ 1993if ( !String.prototype.replaceAll ) { 1994 String.prototype.replaceAll = function( str, newStr ) { 1995 1996 // If a regex pattern 1997 if ( Object.prototype.toString.call( str ).toLowerCase() === "[object regexp]" ) { 1998 return this.replace( str, newStr ); 1999 } 2000 2001 // If a string 2002 return this.replace( new RegExp( str, "g" ), newStr ); 2003 2004 }; 2005} 2006 2007/** 2008 * @title WET-BOEW Vapour loader 2009 * @overview Helper methods for WET 2010 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 2011 * @author WET Community 2012 */ 2013/* 2014 * Vapour Object that will store tombstone data for plugins to leverage 2015 */ 2016( function( $, window, document, undef ) { 2017"use strict"; 2018 2019/** 2020 * @method getUrlParts 2021 * @param {String} url of URL to examine 2022 * @return {Object} of the parts of passed URL 2023 */ 2024var getUrlParts = function( url ) { 2025 var a = document.createElement( "a" ); 2026 a.href = url; 2027 return { 2028 href: a.href, 2029 absolute: a.href, 2030 host: a.host, 2031 hostname: a.hostname, 2032 port: a.port, 2033 pathname: a.pathname.replace( /^([^/])/, "/$1" ), // Prefix pathname with a slash in browsers that don't natively do it (i.e. all versions of IE and possibly early versions of Edge). See pull request #8110. 2034 protocol: a.protocol, 2035 hash: a.hash, 2036 search: a.search, 2037 2038 // A collection of the parameters of the query string part of the URL. 2039 params: ( function() { 2040 var queryString = a.search.replace( /(^\?)/, "" ); 2041 if ( !queryString ) { 2042 return {}; 2043 } 2044 return queryString.split( "&" ).map( function( n ) { 2045 return ( n = n.split( "=" ), this[ n[ 0 ] ] = decodeURIComponent( n[ 1 ] ), this ); 2046 }.bind( {} ) )[ 0 ]; 2047 }() ) 2048 }; 2049 }, 2050 2051 /** 2052 * @variable seed 2053 * @return a unique number for auto-generating ids 2054 */
2055 seed = 0, 2056 2057 /** 2058 * @variable $src 2059 * @return {jQuery Element} of wb script element 2060 */ 2061 $src = $( "script[src*='wet-boew.js'],script[src*='wet-boew.min.js'],script[data-wb-core]" ) 2062 .last(), 2063 2064 /** 2065 * @variable i18n 2066 * @return {string} of WET document language 2067 */ 2068 lang = ( function( ele ) { 2069 let lang = document.documentElement.lang; 2070 const shortLangLength = 2; 2071 2072 // Perform extra checks if the page uses a long language code 2073 if ( lang.length > shortLangLength ) { 2074 let longLangs = [ "pt-BR", "zh-Hans" ]; // Built-in long language codes 2075 2076 // Check if any custom long language codes have been specified 2077 // Specify by adding data-wb-core and data-lang-long="en-CA en-US etc" attributes to WET's script element (e.g. wet-boew.js or wet-boew.min.js) 2078 if ( ele[ 0 ].hasAttribute( "data-wb-core" ) && ele[ 0 ].hasAttribute( "data-lang-long" ) ) { 2079 const longLangsCustom = ele.attr( "data-lang-long" ).split( " " ); 2080 2081 // Add extra language codes to the beginning of the longLangs array to match them more quickly 2082 longLangs = longLangsCustom.concat( longLangs ); 2083 } 2084 2085 // Shorten the language code if it doesn't exist in the longLangs array 2086 if ( longLangs.indexOf( lang ) === -1 ) { 2087 lang = lang.substring( 0, shortLangLength ); 2088 } 2089 } 2090 2091 return lang; 2092 }( $src ) ), 2093 2094 paths = ( function( ele ) { 2095 var paths = {}; 2096 2097 paths.home = ele.prop( "src" ) 2098 .split( "?" )[ 0 ].split( "/" ) 2099 .slice( 0, -1 ) 2100 .join( "/" ); 2101 paths.asset = paths.home + "/../assets"; 2102 paths.template = paths.home + "/assets/templates"; 2103 paths.dep = paths.home + "/deps"; 2104 paths.js = paths.home; 2105 paths.css = paths.home.substring( 0, paths.home.length - 2 ) + "css"; 2106 paths.mode = ele.prop( "src" ).indexOf( ".min" ) < 0 ? "" : ".min"; 2107 2108 if ( ele[ 0 ].hasAttribute( "data-wb-core" ) ) { 2109 $.extend( paths, { 2110 home: ele.attr( "data-home" ), 2111 asset: ele.attr( "data-asset" ), 2112 template: ele.attr( "data-template" ), 2113 dep: ele.attr( "data-dep" ), 2114 js: ele.attr( "data-js" ), 2115 css: ele.attr( "data-css" ), 2116 mode: ele.attr( "data-mode" ) 2117 } ); 2118 } 2119 2120 return paths; 2121 }( $src ) ), 2122 2123 /** 2124 * @variable oldie 2125 * @return {integer} of IE version 2126 */ 2127 oldie = ( function() { 2128 var undef, 2129 v = 3, 2130 div = document.createElement( "div" ), 2131 all = div.getElementsByTagName( "i" ); 2132 2133 while ( ( 2134 div.innerHTML = "<!--[if gt IE " + ( v += 1 ) + "]><i></i><![endif]-->", 2135 all[ 0 ] 2136 ) ) { /* empty */ } 2137 2138 return v > 4 ? v : undef; 2139 }() ), 2140 2141 /** 2142 * @variable currentpage 2143 * @return {Object} of parts of the current page URL 2144 */ 2145 currentpage = getUrlParts( window.location.href ), 2146 2147 /** 2148 * @variable disabled 2149 * @return {boolean} of state of disabled flag 2150 */ 2151 disabled = ( function() { 2152 var disabledSaved = "false", 2153 disabled; 2154 2155 try { 2156 disabledSaved = localStorage.getItem( "wbdisable" ) || disabledSaved; 2157 } catch ( e ) { 2158 2159 /* swallow error */ 2160 } 2161 2162 disabled = currentpage.params.wbdisable || disabledSaved; 2163 return ( typeof disabled === "string" ) ? ( disabled.toLowerCase() === "true" ) : Boolean( disabled ); 2164 }() ), 2165 2166 /*----------------------------- 2167 * Core Library Object 2168 *----------------------------- 2169 */ 2170 wb = { 2171 "/": paths.home, 2172 "/assets": paths.asset, 2173 "/templates": paths.template, 2174 "/deps": paths.dep, 2175 lang: lang, 2176 mode: paths.mode, 2177 doc: $( document ), 2178 win: $( window ), 2179 html: $( "html" ), 2180 pageUrlParts: currentpage, 2181 getUrlParts: getUrlParts, 2182 isDisabled: disabled, 2183 isStarted: false, 2184 isReady: false, 2185 ignoreHashChange: false, 2186 initQueue: 0, 2187 2188 getPath: function( property ) { 2189 return Object.prototype.hasOwnProperty.call( this, property ) ? this[ property ] : undef; 2190 }, 2191 2192 getMode: function() { 2193 return this.mode; 2194 }, 2195 2196 getId: function() { 2197 return "wb-auto-" + ( seed += 1 ); 2198 }, 2199 2200 init: function( event, componentName, selector, noAutoId ) { 2201 var eventTarget = event.target, 2202 isEvent = !!eventTarget, 2203 node = isEvent ? eventTarget : event, 2204 initedClass = componentName + "-inited", 2205 isDocumentNode = node === document; 2206 2207 // Filter out any events triggered by descendants and only initializes 2208 // the element once (if is an event and document node is not the target) 2209 if ( !isEvent || isDocumentNode || ( event.currentTarget === node && 2210 node.className.indexOf( initedClass ) === -1 ) ) { 2211 2212 this.initQueue += 1; 2213 this.remove( selector ); 2214 if ( !isDocumentNode ) { 2215 node.className += " " + initedClass; 2216 2217 if ( !noAutoId && !node.id ) { 2218 node.id = wb.getId(); 2219 } 2220 } 2221 2222 return node; 2223 } 2224 2225 return undef; 2226 }, 2227 2228 ready: function( $elm, componentName, context ) { 2229 if ( $elm ) { 2230 2231 // Trigger any nested elements (excluding nested within nested) 2232 $elm 2233 .find( wb.allSelectors ) 2234 .addClass( "wb-init" ) 2235 .filter( ":not(#" + $elm.attr( "id" ) + " .wb-init .wb-init)" ) 2236 .trigger( "timerpoke.wb" ); 2237 2238 // Identify that the component is ready 2239 $elm.trigger( "wb-ready." + componentName, context ); 2240 this.initQueue -= 1; 2241 } else { 2242 this.doc.trigger( "wb-ready." + componentName, context ); 2243 } 2244 2245 // Identify that global initialization is complete 2246 if ( !this.isReady && this.isStarted && this.initQueue < 1 ) { 2247 this.isReady = true; 2248 this.doc.trigger( "wb-ready.wb" ); 2249 } 2250 }, 2251 2252 // Lets load some variables into wb for IE detection 2253 other: !oldie, 2254 desktop: ( window.orientation === undefined ), 2255 ie: !!oldie, 2256 ie6: ( oldie === 6 ), 2257 ie7: ( oldie === 7 ), 2258 ie8: ( oldie === 8 ),
2259 ie9: ( oldie === 9 ), 2260 ielt7: ( oldie < 7 ), 2261 ielt8: ( oldie < 8 ), 2262 ielt9: ( oldie < 9 ), 2263 ielt10: ( oldie < 10 ), 2264 ie11: ( navigator.userAgent.includes( "Trident/7." ) ), 2265 2266 selectors: [], 2267 2268 resizeEvents: "xxsmallview.wb xsmallview.wb smallview.wb mediumview.wb largeview.wb xlargeview.wb", 2269 2270 // For Charts and Geomap 2271 drawColours: [ 2272 "#8d201c", 2273 "#EE8310", 2274 "#2a7da6", 2275 "#5a306b", 2276 "#285228", 2277 "#154055", 2278 "#555555", 2279 "#f6d200", 2280 "#d73d38", 2281 "#418541", 2282 "#87aec9", 2283 "#23447e", 2284 "#999999" 2285 ], 2286 2287 // Get and generate a unique session id 2288 sessionGUID: function() { 2289 var sessionId = sessionStorage.getItem( "wb-session-GUID" ); 2290 if ( !sessionId ) { 2291 sessionId = wb.guid(); 2292 sessionStorage.setItem( "wb-session-GUID", sessionId ); 2293 } 2294 return sessionId; 2295 }, 2296 2297 // Add a selector to be targeted by timerpoke 2298 add: function( selector ) { 2299 var exists = false, 2300 len = wb.selectors.length, 2301 i; 2302 2303 // Lets ensure we are not running if things are disabled 2304 if ( wb.isDisabled && selector !== "#wb-tphp" ) { 2305 return 0; 2306 } 2307 2308 // Check to see if the selector is already targeted 2309 for ( i = 0; i !== len; i += 1 ) { 2310 if ( wb.selectors[ i ] === selector ) { 2311 exists = true; 2312 break; 2313 } 2314 } 2315 2316 // Add the selector if it isn't already targeted 2317 if ( !exists ) { 2318 wb.selectors.push( selector ); 2319 } 2320 }, 2321 2322 // Remove a selector targeted by timerpoke 2323 remove: function( selector ) { 2324 var len = this.selectors.length, 2325 i; 2326 2327 for ( i = 0; i !== len; i += 1 ) { 2328 if ( this.selectors[ i ] === selector ) { 2329 this.selectors.splice( i, 1 ); 2330 break; 2331 } 2332 } 2333 }, 2334 2335 // Handles triggering of timerpoke events 2336 timerpoke: function( initial ) { 2337 var selectorsLocal = wb.selectors.slice( 0 ), 2338 len = selectorsLocal.length, 2339 selector, $elms, $foundElms, i; 2340 2341 if ( initial ) { 2342 $foundElms = $(); 2343 for ( i = 0; i !== len; i += 1 ) { 2344 selector = selectorsLocal[ i ]; 2345 $elms = $( selector ); 2346 if ( $elms.length !== 0 ) { 2347 $foundElms = $foundElms.add( $elms ); 2348 2349 // If the selector returns no elements, remove the selector 2350 } else { 2351 wb.remove( selector ); 2352 } 2353 } 2354 2355 // Keep only the non-nested plugin/polyfill elements 2356 $elms = $foundElms.filter( ":not(.wb-init .wb-init)" ).addClass( "wb-init" ); 2357 } else { 2358 $elms = $( selectorsLocal.join( ", " ) ); 2359 } 2360 $elms.trigger( "timerpoke.wb" ); 2361 }, 2362 2363 start: function() { 2364 2365 // Save a copy of all the possible selectors 2366 wb.allSelectors = wb.selectors.join( ", " ); 2367 2368 // Initiate timerpoke events right way 2369 wb.timerpoke( true ); 2370 this.isStarted = true; 2371 this.ready(); 2372 2373 // Initiate timerpoke events again every half second 2374 setInterval( wb.timerpoke, 500 ); 2375 }, 2376 2377 i18nDict: {}, 2378 i18n: function( key, state, mixin ) { 2379 var dictionary = wb.i18nDict, 2380 2381 // eg. 000 or 001 ie. 0 or 1 2382 truthiness = ( typeof key === "string" && key !== "" ) | 2383 2384 // eg. 000 or 010 ie. 0 or 2 2385 ( typeof state === "string" && state !== "" ) << 1 | 2386 2387 // eg. 000 or 100 ie. 0 or 4 2388 ( typeof mixin === "string" && mixin !== "" ) << 2; 2389 2390 switch ( truthiness ) { 2391 case 1: 2392 2393 // only key was provided 2394 return dictionary[ key ]; 2395 2396 case 3: 2397 2398 // key and state were provided 2399 return dictionary[ key ][ state ]; 2400 2401 case 7: 2402 2403 // key, state, and mixin were provided 2404 return dictionary[ key ][ state ].replace( "[MIXIN]", mixin ); 2405 default: 2406 return ""; 2407 } 2408 }, 2409 2410 hashString: function( str ) { 2411 2412 // Sources: 2413 // https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/ 2414 // https://jsperf.com/hashing-strings 2415 var hash = 0, 2416 chr, i; 2417 2418 if ( str.length === 0 ) { 2419 return hash; 2420 } 2421 2422 for ( i = 0; i < str.length; i++ ) { 2423 chr = str.charCodeAt( i ); 2424 hash = ( ( hash << 5 ) - hash ) + chr; 2425 2426 // Convert to 32bit integer 2427 hash = hash & hash; 2428 } 2429 2430 return hash; 2431 }, 2432 2433 stripWhitespace: function( str ) { 2434 return str.replace( /\s+/g, "" ); 2435 }, 2436 2437 // Core function to deal with the dependency racing issue 2438 whenLibReady: function( testCallback, readyCallback ) { 2439 if ( testCallback() ) { 2440 readyCallback(); 2441 } else { 2442 setTimeout( function() { 2443 wb.whenLibReady( testCallback, readyCallback ); 2444 }, 50 ); 2445 } 2446 2447 } 2448 }; 2449 2450window.wb = wb; 2451 2452/*----------------------------- 2453 * Yepnope Prefixes 2454 *-----------------------------*/ 2455/* 2456 * Establish the base path to be more flexible in terms of WCMS where JS can 2457 * reside in theme folders and not in the root of sites 2458 * @TODO: For modularity the prefixes where written independently as we are 2459 * flushing out some use cases on better grouping and optimization of polyfills. 2460 * Once this more hashed out, we could optimize the prefixes down to one or tw
2460o 2461 * prefixes "site" and "disabled" to thin out the codeblock a bit more 2462 * increase performance due to redundant chaining of the prefixes. 2463 */ 2464 2465/** 2466 * @prefix: site! - adds the root js directory of yepnope resources 2467 */ 2468yepnope.addPrefix( "site", function( resourceObj ) { 2469 resourceObj.url = paths.js + "/" + resourceObj.url; 2470 return resourceObj; 2471} ); 2472 2473/** 2474 * @prefix: plyfll! - builds the path for the polyfill resource 2475 */ 2476yepnope.addPrefix( "plyfll", function( resourceObj ) { 2477 var path, 2478 url = resourceObj.url; 2479 2480 if ( disabled && url.indexOf( "svg" ) === -1 ) { 2481 resourceObj.bypass = true; 2482 } else if ( !paths.mode ) { 2483 url = url.replace( ".min", "" ); 2484 } 2485 2486 if ( url.indexOf( ".css" ) !== -1 ) { 2487 resourceObj.forceCSS = true; 2488 path = paths.css; 2489 } else { 2490 path = paths.js; 2491 } 2492 resourceObj.url = path + "/polyfills/" + url; 2493 2494 return resourceObj; 2495} ); 2496 2497/** 2498 * @prefix: i18n! - adds the correct document language for our i18n library 2499 */ 2500yepnope.addPrefix( "i18n", function( resourceObj ) { 2501 resourceObj.url = paths.js + "/" + resourceObj.url + lang + paths.mode + ".js"; 2502 return resourceObj; 2503} ); 2504 2505/** 2506 * @prefix: mthjx! - adds the root directory of MathJax resources 2507 */ 2508yepnope.addPrefix( "mthjx", function( resourceObj ) { 2509 resourceObj.url = paths.js + "/MathJax/" + resourceObj.url; 2510 return resourceObj; 2511} ); 2512 2513/*----------------------------- 2514 * Deps loading, call "complete" callback when the deps is ready if a testReady is defined 2515 *-----------------------------*/ 2516wb.modernizrLoad = Modernizr.load; 2517Modernizr.load = function( options ) { 2518 var i, i_len, i_cache, 2519 testReady, complete; 2520 if ( !$.isArray( options ) ) { 2521 options = [ options ]; 2522 } 2523 i_len = options.length; 2524 for ( i = 0; i !== i_len; i += 1 ) { 2525 i_cache = options[ i ]; 2526 testReady = i_cache.testReady; 2527 complete = i_cache.complete; 2528 if ( testReady && complete ) { 2529 i_cache.complete = wb.whenLibReady( testReady, complete ); 2530 } 2531 } 2532 wb.modernizrLoad( options ); 2533}; 2534 2535/*----------------------------- 2536 * Modernizr Polyfill Loading 2537 *-----------------------------*/ 2538Modernizr.load( [ 2539 { 2540 test: Modernizr.details, 2541 nope: [ 2542 "plyfll!details.min.js", 2543 "plyfll!details.min.css" 2544 ] 2545 }, { 2546 test: Modernizr.input.list, 2547 nope: [ 2548 "plyfll!datalist.min.js", 2549 "plyfll!datalist.min.css" 2550 ] 2551 }, { 2552 test: Modernizr.inputtypes.date, 2553 nope: [ 2554 "plyfll!datepicker.min.js", 2555 "plyfll!datepicker.min.css" 2556 ] 2557 }, { 2558 test: Modernizr.inputtypes.range, 2559 nope: [ 2560 "plyfll!slider.min.js", 2561 "plyfll!slider_wrapper.min.js", 2562 "plyfll!slider.min.css" 2563 ], 2564 callback: function( url ) { 2565 if ( url === "slider.min.js" ) { 2566 window.fdSlider.onDomReady(); 2567 } 2568 } 2569 }, { 2570 test: Modernizr.progressbar, 2571 nope: [ 2572 "plyfll!progress.min.js", 2573 "plyfll!progress.min.css" 2574 ] 2575 }, { 2576 test: Modernizr.mathml, 2577 2578 // Cleanup Modernizr test and add selector to global timer 2579 complete: function() { 2580 var componentName = "wb-math", 2581 selector = "math", 2582 math = document.getElementsByTagName( selector ), 2583 $document = wb.doc; 2584 2585 // Cleanup elements that Modernizr.mathml test leaves behind. 2586 if ( math.length ) { 2587 document.body.removeChild( math[ math.length - 1 ].parentNode ); 2588 } 2589 2590 // Defer loading the polyfill till an element is detected due to the size 2591 if ( !Modernizr.mathml ) { 2592 let isTrident = new Boolean( window.navigator.msSaveOrOpenBlob ); 2593 2594 // Bind the init event of the plugin 2595 $document.one( "timerpoke.wb wb-init." + componentName, selector, function() { 2596 2597 // Start initialization 2598 wb.init( document, componentName, selector ); 2599 2600 // Disable MathJax's context menu to more closely mimic native MathML implementations 2601 window.MathJax = { 2602 options: { 2603 enableMenu: false 2604 } 2605 }; 2606 2607 // Extra tasks for IE11 2608 if ( isTrident ) { 2609 2610 // Load an ES6 polyfill 2611 Modernizr.load( "timeout=500!https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=es6" ); 2612 2613 // Specify the CDN's font URL 2614 // Note: IE11 is unable to resolve this on its own 2615 window.MathJax.chtml = { 2616 fontURL: "https://cdn.jsdelivr.net/npm/mathjax@3/es5/output/chtml/fonts/woff-v2" 2617 }; 2618 } 2619 2620 // Load the MathML dependency. Since the polyfill is only loaded 2621 // when !Modernizr.mathml, we can skip the test here. 2622 Modernizr.load( [ { 2623 2624 // Load latest version of MathJax 3 from a CDN 2625 // Also load a CSS workaround for a MathJax 3.2.0 bug (refer to CSS file for details) 2626 load: [ 2627 "timeout=500!https://cdn.jsdelivr.net/npm/mathjax@3/es5/mml-chtml.js", 2628 "plyfll!mathml.min.css" 2629 ], 2630 complete: function() { 2631 2632 // Wait a moment to reduce the risk of a race condition 2633 setTimeout( function() { 2634 2635 // Specify a font URL for a local copy of MathJax 3 for IE11 2636 // Note: Useful if IE11 has internet access but fails to reach the CDN 2637 if ( isTrident && !window.MathJax.startup ) { 2638 window.MathJax.chtml.fontURL = paths.js + "/MathJax/output/chtml/fonts/woff-v2"; 2639 } 2640 2641 // Fall back on a local copy of MathJax 3 if the CDN is unreachable 2642 // Note: Won't work with IE11 in isolated networks (ES6 polyfill has no local fallback) 2643 Modernizr.load( [ { 2644 test: window.MathJax.startup, 2645 nope: "mthjx!mml-chtml.js", 2646 complete: function() { 2647 2648 // Try loading a local copy of MathJax 2 as a last dith effort 2649 Modernizr.load( [ { 2650 test: window.MathJax.startup, 2651 nope: "mthjx!MathJax.js?config=Accessible", 2652 complete: function() { 2653 2654 // Identify that initialization has completed 2655 wb.ready( $document, componentName ); 2656 } 2657 } ] ); 2658 } 2659 } ] ); 2660 }, 100 ); 2661 } 2662 } ] ); 2663 } ); 2664 2665 wb.add( selector ); 2666 } 2667 } 2668 }, { 2669 test: Modernizr.meter, 2670 nope: [ 2671 "plyfll!meter.min.js", 2672 "plyfll!meter.min.css" 2673 ] 2674 }, { 2675 test: Modernizr.touch, 2676 yep: "plyfll!mobile.min.js" 2677 }, { 2678 test: Modernizr.svg, 2679 nope: "plyfll!svg.min.js" 2680 }, { 2681 load: "i18n!i18n/", 2682 testReady: function() { 2683 return wb.i18nDict.tphp; 2684 }, 2685 complete: function() { 2686 wb.start(); 2687 } 2688 } 2689] ); 2690 2691} )( jQuery, window, document ); 2692 2693/** 2694 * @title WET-BOEW JQuery Helper Methods 2695 * @overview Helper methods for WET 2696 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 2697 * @author WET Community 2698 * Credits: https://web.archive.org/web/20130826230640/http://kaibun.net/blog/2013/04/19/a-fully-fledged-coffeescript-boilerplate-for-jquery-plugins/ 2699 */ 2700( function( $, wb ) { 2701 2702wb.getData = function( element, dataName ) { 2703 var elm = !element.jquery ? element : element[ 0 ],
2704 dataAttr = elm.getAttribute( "data-" + dataName ), 2705 dataObj; 2706 2707 if ( dataAttr ) { 2708 try { 2709 dataObj = JSON.parse( dataAttr ); 2710 $.data( elm, dataName, dataObj ); 2711 } catch ( error ) { 2712 console.info( elm ); 2713 $.error( "Bad JSON array in data-" + dataName + " attribute" ); 2714 } 2715 } 2716 2717 return dataObj; 2718}; 2719 2720/* 2721 * Initiate an in-browser download from a blob 2722 * @param blob: a reference to a blob object 2723 * @param filename: a suggested file name to save as under 2724 * @param title: (Optional) a title added to the link. Its use case is for web analytics tracking. 2725*/ 2726wb.download = function( blob, filename, title ) { 2727 2728 var objectURL = URL.createObjectURL( blob ), 2729 anchor = document.createElement( "a" ); 2730 2731 filename = filename || "unnamed"; // Ensure a filename is defined 2732 2733 anchor.textContent = title || ""; 2734 anchor.download = filename; 2735 2736 anchor.hidden = true; 2737 document.body.appendChild( anchor ); // Added to the body for the web analytic tracking use case. 2738 2739 if ( window.navigator.msSaveOrOpenBlob ) { 2740 2741 // This is for IE11 support 2742 anchor.addEventListener( "click", function( ) { 2743 window.navigator.msSaveOrOpenBlob( blob, filename ); 2744 } ); 2745 anchor.setAttribute( "target", "_blank" ); 2746 } else { 2747 anchor.href = objectURL; 2748 } 2749 2750 anchor.click(); 2751 2752 // Clean the DOM, remove the accessory anchor at the next tick 2753 setTimeout( function() { 2754 document.body.removeChild( anchor ); 2755 }, 1 ); 2756 2757 // Revoke the ojbect, A setTimeout is used because Blob API don't have a download complete event. 2758 setTimeout( function() { 2759 if ( typeof objectURL === "string" ) { 2760 URL.revokeObjectURL( objectURL ); 2761 } else { 2762 objectURL.remove(); 2763 } 2764 }, 40000 ); // The revoking time is arbitrary 2765 2766}; 2767 2768/* --------------------------------- 2769@extension: shuffleDOM 2770@returns: [list] shuffles a list of items randomly 2771-------------------------------- */ 2772wb.shuffleDOM = function( $elm ) { 2773 var allElems = $elm.get(), 2774 shuffled = $.map( allElems, function() { 2775 var random = Math.floor( Math.random() * allElems.length ), 2776 randEl = $( allElems[ random ] ).clone( true )[ 0 ]; 2777 allElems.splice( random, 1 ); 2778 return randEl; 2779 } ), 2780 elm_len = $elm.length, 2781 i; 2782 2783 for ( i = 0; i < elm_len; i++ ) { 2784 $( $elm[ i ] ).replaceWith( $( shuffled[ i ] ) ); 2785 } 2786 2787 return $( shuffled ); 2788}; 2789 2790/* --------------------------------- 2791@extension: pickElements 2792@returns: [collection] of randoms elements 2793-------------------------------- */ 2794wb.pickElements = function( $elm, numOfElm ) { 2795 var nbElm = $elm.size(), 2796 elmCopies, 2797 i, swap; 2798 2799 numOfElm = numOfElm || 1; 2800 2801 // Special cases 2802 if ( numOfElm > nbElm ) { 2803 return $elm.pushStack( $elm ); 2804 } else if ( numOfElm === 1 ) { 2805 return $elm.filter( ":eq(" + Math.floor( Math.random() * nbElm ) + ")" ); 2806 } 2807 2808 // Create a randomized copy of the set of elements, 2809 // using Fisher-Yates sorting 2810 elmCopies = $elm.get(); 2811 2812 for ( i = 0; i < nbElm - 1; i++ ) { 2813 swap = Math.floor( Math.random() * ( nbElm - i ) ) + i; 2814 elmCopies[ swap ] = elmCopies.splice( i, 1, elmCopies[ swap ] )[ 0 ]; 2815 } 2816 elmCopies = elmCopies.slice( 0, numOfElm ); 2817 2818 // Finally, filter jQuery stack 2819 return $elm.filter( function( idx ) { 2820 return $.inArray( $elm.get( idx ), elmCopies ) > -1; 2821 } ); 2822}; 2823 2824/* --------------------------------- 2825Adds a link to the Skip links navigation 2826@param text: Text to display in the anchor or button 2827@param attr: JSO with { attribute: value, ... } to add attributes to the anchor or button. Minimum is { href: "#your-anchor" } for the anchor tag 2828@param isBtn: (Optional) Bool if true element is a button, otherwise it is an anchor by default 2829@param isLast: (Optional) Bool if true element will be inserted last in the list 2830-------------------------------- */ 2831wb.addSkipLink = function( text, attr, isBtn, isLast ) { 2832 var list = document.getElementById( "wb-tphp" ), 2833 li = document.createElement( "li" ), 2834 elm = document.createElement( ( isBtn ? "button" : "a" ) ), 2835 key; 2836 2837 // Add skip link's proprietary classes to new element 2838 li.className = "wb-slc"; 2839 elm.className = "wb-sl"; 2840 2841 // Add given attributes to element 2842 for ( key in attr ) { 2843 elm.setAttribute( key, attr[ key ] ); 2844 } 2845 2846 // Append text and new element to the skip link list (after main content) 2847 elm.appendChild( document.createTextNode( text ) ); 2848 li.appendChild( elm ); 2849 2850 if ( isLast ) { 2851 list.appendChild( li ); 2852 } else { 2853 list.insertBefore( li, list.childNodes[ 2 ] ); 2854 } 2855 2856 return true; 2857}; 2858 2859} )( jQuery, wb ); 2860 2861( function( wb, window ) { 2862 2863"use strict"; 2864
2865// Escapes the characters in a string for use in a jQuery selector 2866// Based on https://totaldev.com/content/escaping-characters-get-valid-jquery-id 2867wb.jqEscape = function( selector ) { 2868 // eslint-disable-next-line no-useless-escape 2869 return selector.replace( /([;&,\.\+\*\~':"\\\!\^\/#$%@\[\]\(\)=>\|])/g, "\\$1" ); 2870}; 2871 2872// RegEx used by formattedNumCompare 2873wb.formattedNumCompareRegEx = /(<[^>]*>|[^\d.])/g; 2874 2875// Compares two formatted numbers (e.g., 1.2.12 or 1,000,345) 2876wb.formattedNumCompare = function( a, b ) { 2877 var regEx = wb.formattedNumCompareRegEx, 2878 aMultiple = a.indexOf( "-" ) === -1 ? 1 : -1, 2879 aNumbers = ( ( a === "-" || a === "" ) ? "0" : a.replace( regEx, "" ) ).split( "." ), 2880 bMultiple = b.indexOf( "-" ) === -1 ? 1 : -1, 2881 bNumbers = ( ( b === "-" || b === "" ) ? "0" : b.replace( regEx, "" ) ).split( "." ), 2882 len = aNumbers.length, 2883 i, result; 2884 2885 for ( i = 0; i !== len; i += 1 ) { 2886 result = parseInt( aNumbers[ i ], 10 ) * aMultiple - parseInt( bNumbers[ i ], 10 ) * bMultiple; 2887 if ( result !== 0 ) { 2888 break; 2889 } 2890 } 2891 return result; 2892}; 2893 2894// Compare two strings with special characters (e.g., Cyrillic or Chinese characters) 2895wb.i18nTextCompare = function( a, b ) { 2896 return wb.normalizeDiacritics( a ).localeCompare( wb.normalizeDiacritics( b ) ); 2897}; 2898 2899// Based upon https://gist.github.com/instanceofme/1731620 2900// Licensed under WTFPL v2 http://sam.zoy.org/wtfpl/COPYING 2901wb.normalizeDiacritics = function( str ) { 2902 var diacritics = { 2903 "\u24B6": "A", 2904 "\uFF21": "A", 2905 "\u00C0": "A", 2906 "\u00C1": "A", 2907 "\u00C2": "A", 2908 "\u1EA6": "A", 2909 "\u1EA4": "A", 2910 "\u1EAA": "A", 2911 "\u1EA8": "A", 2912 "\u00C3": "A", 2913 "\u0100": "A", 2914 "\u0102": "A", 2915 "\u1EB0": "A", 2916 "\u1EAE": "A", 2917 "\u1EB4": "A", 2918 "\u1EB2": "A", 2919 "\u0226": "A", 2920 "\u01E0": "A", 2921 "\u00C4": "A", 2922 "\u01DE": "A", 2923 "\u1EA2": "A", 2924 "\u00C5": "A", 2925 "\u01FA": "A", 2926 "\u01CD": "A", 2927 "\u0200": "A", 2928 "\u0202": "A", 2929 "\u1EA0": "A", 2930 "\u1EAC": "A", 2931 "\u1EB6": "A", 2932 "\u1E00": "A", 2933 "\u0104": "A", 2934 "\u023A": "A", 2935 "\u2C6F": "A", 2936 "\uA732": "AA", 2937 "\u00C6": "AE", 2938 "\u01FC": "AE", 2939 "\u01E2": "AE", 2940 "\uA734": "AO", 2941 "\uA736": "AU", 2942 "\uA738": "AV", 2943 "\uA73A": "AV", 2944 "\uA73C": "AY", 2945 "\u24B7": "B", 2946 "\uFF22": "B", 2947 "\u1E02": "B", 2948 "\u1E04": "B", 2949 "\u1E06": "B", 2950 "\u0243": "B", 2951 "\u0182": "B", 2952 "\u0181": "B", 2953 "\u24B8": "C", 2954 "\uFF23": "C", 2955 "\u0106": "C", 2956 "\u0108": "C", 2957 "\u010A": "C", 2958 "\u010C": "C", 2959 "\u00C7": "C", 2960 "\u1E08": "C", 2961 "\u0187": "C", 2962 "\u023B": "C", 2963 "\uA73E": "C", 2964 "\u24B9": "D", 2965 "\uFF24": "D", 2966 "\u1E0A": "D", 2967 "\u010E": "D", 2968 "\u1E0C": "D", 2969 "\u1E10": "D", 2970 "\u1E12": "D", 2971 "\u1E0E": "D", 2972 "\u0110": "D", 2973 "\u018B": "D", 2974 "\u018A": "D", 2975 "\u0189": "D", 2976 "\uA779": "D", 2977 "\u01F1": "DZ", 2978 "\u01C4": "DZ", 2979 "\u01F2": "Dz", 2980 "\u01C5": "Dz", 2981 "\u24BA": "E", 2982 "\uFF25": "E", 2983 "\u00C8": "E", 2984 "\u00C9": "E", 2985 "\u00CA": "E", 2986 "\u1EC0": "E", 2987 "\u1EBE": "E", 2988 "\u1EC4": "E", 2989 "\u1EC2": "E", 2990 "\u1EBC": "E", 2991 "\u0112": "E", 2992 "\u1E14": "E", 2993 "\u1E16": "E", 2994 "\u0114": "E", 2995 "\u0116": "E", 2996 "\u00CB": "E", 2997 "\u1EBA": "E", 2998 "\u011A": "E", 2999 "\u0204": "E", 3000 "\u0206": "E", 3001 "\u1EB8": "E", 3002 "\u1EC6": "E", 3003 "\u0228": "E", 3004 "\u1E1C": "E", 3005 "\u0118": "E", 3006 "\u1E18": "E", 3007 "\u1E1A": "E", 3008 "\u0190": "E", 3009 "\u018E": "E", 3010 "\u24BB": "F", 3011 "\uFF26": "F", 3012 "\u1E1E": "F", 3013 "\u0191": "F", 3014 "\uA77B": "F", 3015 "\u24BC": "G", 3016 "\uFF27": "G", 3017 "\u01F4": "G", 3018 "\u011C": "G", 3019 "\u1E20": "G", 3020 "\u011E": "G", 3021 "\u0120": "G", 3022 "\u01E6": "G", 3023 "\u0122": "G", 3024 "\u01E4": "G", 3025 "\u0193": "G", 3026 "\uA7A0": "G", 3027 "\uA77D": "G", 3028 "\uA77E": "G", 3029 "\u24BD": "H", 3030 "\uFF28": "H", 3031 "\u0124": "H", 3032 "\u1E22": "H", 3033 "\u1E26": "H", 3034 "\u021E": "H", 3035 "\u1E24": "H", 3036 "\u1E28": "H", 3037 "\u1E2A": "H", 3038 "\u0126": "H", 3039 "\u2C67": "H", 3040 "\u2C75": "H", 3041 "\uA78D": "H", 3042 "\u24BE": "I", 3043 "\uFF29": "I", 3044 "\u00CC": "I", 3045 "\u00CD": "I", 3046 "\u00CE": "I", 3047 "\u0128": "I", 3048 "\u012A": "I", 3049 "\u012C": "I", 3050 "\u0130": "I", 3051 "\u00CF": "I", 3052 "\u1E2E": "I", 3053 "\u1EC8": "I", 3054 "\u01CF": "I", 3055 "\u0208": "I", 3056 "\u020A": "I", 3057 "\u1ECA": "I", 3058 "\u012E": "I", 3059 "\u1E2C": "I", 3060 "\u0197": "I", 3061 "\u24BF": "J", 3062 "\uFF2A": "J", 3063 "\u0134": "J", 3064 "\u0248": "J", 3065 "\u24C0": "K", 3066 "\uFF2B": "K", 3067 "\u1E30": "K", 3068 "\u01E8": "K", 3069 "\u1E32": "K", 3070 "\u0136": "K", 3071 "\u1E34": "K", 3072 "\u0198": "K", 3073 "\u2C69": "K", 3074 "\uA740": "K", 3075 "\uA742": "K", 3076 "\uA744": "K", 3077 "\uA7A2": "K", 3078 "\u24C1": "L", 3079 "\uFF2C": "L", 3080 "\u013F": "L", 3081 "\u0139": "L", 3082 "\u013D": "L", 3083 "\u1E36": "L", 3084 "\u1E38": "L", 3085 "\u013B": "L", 3086 "\u1E3C": "L", 3087 "\u1E3A": "L", 3088 "\u0141": "L", 3089 "\u023D": "L", 3090 "\u2C62": "L", 3091 "\u2C60": "L", 3092 "\uA748": "L", 3093 "\uA746": "L", 3094 "\uA780": "L", 3095 "\u01C7": "LJ", 3096 "\u01C8": "Lj", 3097 "\u24C2": "M", 3098 "\uFF2D": "M", 3099 "\u1E3E": "M", 3100 "\u1E40": "M", 3101 "\u1E42": "M", 3102 "\u2C6E": "M", 3103 "\u019C": "M", 3104 "\u24C3": "N", 3105 "\uFF2E": "N", 3106 "\u01F8": "N", 3107 "\u0143": "N", 3108 "\u00D1": "N", 3109 "\u1E44": "N", 3110 "\u0147": "N", 3111 "\u1E46": "N", 3112 "\u0145": "N", 3113 "\u1E4A": "N", 3114 "\u1E48": "N", 3115 "\u0220": "N", 3116 "\u019D": "N", 3117 "\uA790": "N", 3118 "\uA7A4": "N", 3119 "\u01CA": "NJ", 3120 "\u01CB": "Nj", 3121 "\u24C4": "O", 3122 "\uFF2F": "O", 3123 "\u00D2": "O", 3124 "\u00D3": "O", 3125 "\u00D4": "O", 3126 "\u1ED2": "O", 3127 "\u1ED0": "O", 3128 "\u1ED6": "O", 3129 "\u1ED4": "O", 3130 "\u00D5": "O", 3131 "\u1E4C": "O", 3132 "\u022C": "O", 3133 "\u1E4E": "O", 3134 "\u014C": "O", 3135 "\u1E50": "O", 3136 "\u1E52": "O", 3137 "\u014E": "O", 3138 "\u022E": "O", 3139 "\u0230": "O", 3140 "\u00D6": "O", 3141 "\u022A": "O", 3142 "\u1ECE": "O", 3143 "\u0150": "O", 3144 "\u01D1": "O", 3145 "\u020C": "O", 3146 "\u020E": "O", 3147 "\u01A0": "O", 3148 "\u1EDC": "O", 3149 "\u1EDA": "O", 3150 "\u1EE0": "O", 3151 "\u1EDE": "O", 3152 "\u1EE2": "O", 3153 "\u1ECC": "O", 3154 "\u1ED8": "O", 3155 "\u01EA": "O", 3156 "\u01EC": "O", 3157 "\u00D8": "O", 3158 "\u01FE": "O", 3159 "\u0186": "O", 3160 "\u019F": "O", 3161 "\uA74A": "O", 3162 "\uA74C": "O", 3163 "\u0152": "OE", 3164 "\u01A2": "OI", 3165 "\uA74E": "OO", 3166 "\u0222": "OU", 3167 "\u24C5": "P", 3168 "\uFF30": "P", 3169 "\u1E54": "P", 3170 "\u1E56": "P", 3171 "\u01A4": "P", 3172 "\u2C63": "P", 3173 "\uA750": "P", 3174 "\uA752": "P", 3175 "\uA754": "P", 3176 "\u24C6": "Q", 3177 "\uFF31": "Q", 3178 "\uA756": "Q", 3179 "\uA758": "Q", 3180 "\u024A": "Q", 3181 "\u24C7": "R", 3182 "\uFF32": "R", 3183 "\u0154": "R", 3184 "\u1E58": "R", 3185 "\u0158": "R", 3186 "\u0210": "R", 3187 "\u0212": "R", 3188 "\u1E5A": "R", 3189 "\u1E5C": "R", 3190 "\u0156": "R", 3191 "\u1E5E": "R", 3192 "\u024C": "R", 3193 "\u2C64": "R", 3194 "\uA75A": "R", 3195 "\uA7A6": "R", 3196 "\uA782": "R", 3197 "\u24C8": "S", 3198 "\uFF33": "S", 3199 "\u015A": "S", 3200 "\u1E64": "S", 3201 "\u015C": "S", 3202 "\u1E60": "S", 3203 "\u0160": "S", 3204 "\u1E66": "S", 3205 "\u1E62": "S", 3206 "\u1E68": "S", 3207 "\u0218": "S", 3208 "\u015E": "S", 3209 "\u2C7E": "S", 3210 "\uA7A8": "S", 3211 "\uA784": "S", 3212 "\u1E9E": "SS", 3213 "\u24C9": "T", 3214 "\uFF34": "T", 3215 "\u1E6A": "T", 3216 "\u0164": "T", 3217 "\u1E6C": "T", 3218 "\u021A": "T", 3219 "\u0162": "T", 3220 "\u1E70": "T", 3221 "\u1E6E": "T", 3222 "\u0166": "T", 3223 "\u01AC": "T", 3224 "\u01AE": "T", 3225 "\u023E": "T", 3226 "\uA786": "T", 3227 "\uA728": "TZ", 3228 "\u24CA": "U", 3229 "\uFF35": "U", 3230 "\u00D9": "U", 3231 "\u00DA": "U", 3232 "\u00DB": "U", 3233 "\u0168": "U", 3234 "\u1E78": "U", 3235 "\u016A": "U", 3236 "\u1E7A": "U", 3237 "\u016C": "U", 3238 "\u00DC": "U", 3239 "\u01DB": "U", 3240 "\u01D7": "U", 3241 "\u01D5": "U", 3242 "\u01D9": "U", 3243 "\u1EE6": "U", 3244 "\u016E": "U", 3245 "\u0170": "U", 3246 "\u01D3": "U", 3247 "\u0214": "U", 3248 "\u0216": "U", 3249 "\u01AF": "U", 3250 "\u1EEA": "U", 3251 "\u1EE8": "U", 3252 "\u1EEE": "U", 3253 "\u1EEC": "U", 3254 "\u1EF0": "U", 3255 "\u1EE4": "U", 3256 "\u1E72": "U", 3257 "\u0172": "U", 3258 "\u1E76": "U", 3259 "\u1E74": "U", 3260 "\u0244": "U", 3261 "\u24CB": "V", 3262 "\uFF36": "V", 3263 "\u1E7C": "V", 3264 "\u1E7E": "V", 3265 "\u01B2": "V", 3266 "\uA75E": "V", 3267 "\u0245": "V", 3268 "\uA760": "VY", 3269 "\u24CC": "W", 3270 "\uFF37": "W", 3271 "\u1E80": "W", 3272 "\u1E82": "W", 3273 "\u0174": "W", 3274 "\u1E86": "W", 3275 "\u1E84": "W", 3276 "\u1E88": "W", 3277 "\u2C72": "W", 3278 "\u24CD": "X", 3279 "\uFF38": "X", 3280 "\u1E8A": "X", 3281 "\u1E8C": "X", 3282 "\u24CE": "Y", 3283 "\uFF39": "Y", 3284 "\u1EF2": "Y", 3285 "\u00DD": "Y", 3286 "\u0176": "Y", 3287 "\u1EF8": "Y", 3288 "\u0232": "Y", 3289 "\u1E8E": "Y", 3290 "\u0178": "Y", 3291 "\u1EF6": "Y", 3292 "\u1EF4": "Y", 3293 "\u01B3": "Y", 3294 "\u024E": "Y", 3295 "\u1EFE": "Y", 3296 "\u24CF": "Z", 3297 "\uFF3A": "Z", 3298 "\u0179": "Z", 3299 "\u1E90": "Z", 3300 "\u017B": "Z", 3301 "\u017D": "Z", 3302 "\u1E92": "Z", 3303 "\u1E94": "Z", 3304 "\u01B5": "Z", 3305 "\u0224": "Z", 3306 "\u2C7F": "Z", 3307 "\u2C6B": "Z", 3308 "\uA762": "Z", 3309 "\u24D0": "a", 3310 "\uFF41": "a", 3311 "\u1E9A": "a", 3312 "\u00E0": "a", 3313 "\u00E1": "a", 3314 "\u00E2": "a", 3315 "\u1EA7": "a", 3316 "\u1EA5": "a", 3317 "\u1EAB": "a", 3318 "\u1EA9": "a",
3319 "\u00E3": "a", 3320 "\u0101": "a", 3321 "\u0103": "a", 3322 "\u1EB1": "a", 3323 "\u1EAF": "a", 3324 "\u1EB5": "a", 3325 "\u1EB3": "a", 3326 "\u0227": "a", 3327 "\u01E1": "a", 3328 "\u00E4": "a", 3329 "\u01DF": "a", 3330 "\u1EA3": "a", 3331 "\u00E5": "a", 3332 "\u01FB": "a", 3333 "\u01CE": "a", 3334 "\u0201": "a", 3335 "\u0203": "a", 3336 "\u1EA1": "a", 3337 "\u1EAD": "a", 3338 "\u1EB7": "a", 3339 "\u1E01": "a", 3340 "\u0105": "a", 3341 "\u2C65": "a", 3342 "\u0250": "a", 3343 "\uA733": "aa", 3344 "\u00E6": "ae", 3345 "\u01FD": "ae", 3346 "\u01E3": "ae", 3347 "\uA735": "ao", 3348 "\uA737": "au", 3349 "\uA739": "av", 3350 "\uA73B": "av", 3351 "\uA73D": "ay", 3352 "\u24D1": "b", 3353 "\uFF42": "b", 3354 "\u1E03": "b", 3355 "\u1E05": "b", 3356 "\u1E07": "b", 3357 "\u0180": "b", 3358 "\u0183": "b", 3359 "\u0253": "b", 3360 "\u24D2": "c", 3361 "\uFF43": "c", 3362 "\u0107": "c", 3363 "\u0109": "c", 3364 "\u010B": "c", 3365 "\u010D": "c", 3366 "\u00E7": "c", 3367 "\u1E09": "c", 3368 "\u0188": "c", 3369 "\u023C": "c", 3370 "\uA73F": "c", 3371 "\u2184": "c", 3372 "\u24D3": "d", 3373 "\uFF44": "d", 3374 "\u1E0B": "d", 3375 "\u010F": "d", 3376 "\u1E0D": "d", 3377 "\u1E11": "d", 3378 "\u1E13": "d", 3379 "\u1E0F": "d", 3380 "\u0111": "d", 3381 "\u018C": "d", 3382 "\u0256": "d", 3383 "\u0257": "d", 3384 "\uA77A": "d", 3385 "\u01F3": "dz", 3386 "\u01C6": "dz", 3387 "\u24D4": "e", 3388 "\uFF45": "e", 3389 "\u00E8": "e", 3390 "\u00E9": "e", 3391 "\u00EA": "e", 3392 "\u1EC1": "e", 3393 "\u1EBF": "e", 3394 "\u1EC5": "e", 3395 "\u1EC3": "e", 3396 "\u1EBD": "e", 3397 "\u0113": "e", 3398 "\u1E15": "e", 3399 "\u1E17": "e", 3400 "\u0115": "e", 3401 "\u0117": "e", 3402 "\u00EB": "e", 3403 "\u1EBB": "e", 3404 "\u011B": "e", 3405 "\u0205": "e", 3406 "\u0207": "e", 3407 "\u1EB9": "e", 3408 "\u1EC7": "e", 3409 "\u0229": "e", 3410 "\u1E1D": "e", 3411 "\u0119": "e", 3412 "\u1E19": "e", 3413 "\u1E1B": "e", 3414 "\u0247": "e", 3415 "\u025B": "e", 3416 "\u01DD": "e", 3417 "\u24D5": "f", 3418 "\uFF46": "f", 3419 "\u1E1F": "f", 3420 "\u0192": "f", 3421 "\uA77C": "f", 3422 "\u24D6": "g", 3423 "\uFF47": "g", 3424 "\u01F5": "g", 3425 "\u011D": "g", 3426 "\u1E21": "g", 3427 "\u011F": "g", 3428 "\u0121": "g", 3429 "\u01E7": "g", 3430 "\u0123": "g", 3431 "\u01E5": "g", 3432 "\u0260": "g", 3433 "\uA7A1": "g", 3434 "\u1D79": "g", 3435 "\uA77F": "g", 3436 "\u24D7": "h", 3437 "\uFF48": "h", 3438 "\u0125": "h", 3439 "\u1E23": "h", 3440 "\u1E27": "h", 3441 "\u021F": "h", 3442 "\u1E25": "h", 3443 "\u1E29": "h", 3444 "\u1E2B": "h", 3445 "\u1E96": "h", 3446 "\u0127": "h", 3447 "\u2C68": "h", 3448 "\u2C76": "h", 3449 "\u0265": "h", 3450 "\u0195": "hv", 3451 "\u24D8": "i", 3452 "\uFF49": "i", 3453 "\u00EC": "i", 3454 "\u00ED": "i", 3455 "\u00EE": "i", 3456 "\u0129": "i", 3457 "\u012B": "i", 3458 "\u012D": "i", 3459 "\u00EF": "i", 3460 "\u1E2F": "i", 3461 "\u1EC9": "i", 3462 "\u01D0": "i", 3463 "\u0209": "i", 3464 "\u020B": "i", 3465 "\u1ECB": "i", 3466 "\u012F": "i", 3467 "\u1E2D": "i", 3468 "\u0268": "i", 3469 "\u0131": "i", 3470 "\u24D9": "j", 3471 "\uFF4A": "j", 3472 "\u0135": "j", 3473 "\u01F0": "j", 3474 "\u0249": "j", 3475 "\u24DA": "k", 3476 "\uFF4B": "k", 3477 "\u1E31": "k", 3478 "\u01E9": "k", 3479 "\u1E33": "k", 3480 "\u0137": "k", 3481 "\u1E35": "k", 3482 "\u0199": "k", 3483 "\u2C6A": "k", 3484 "\uA741": "k", 3485 "\uA743": "k", 3486 "\uA745": "k", 3487 "\uA7A3": "k", 3488 "\u24DB": "l", 3489 "\uFF4C": "l", 3490 "\u0140": "l", 3491 "\u013A": "l", 3492 "\u013E": "l", 3493 "\u1E37": "l", 3494 "\u1E39": "l", 3495 "\u013C": "l", 3496 "\u1E3D": "l", 3497 "\u1E3B": "l", 3498 "\u0142": "l", 3499 "\u019A": "l", 3500 "\u026B": "l", 3501 "\u2C61": "l", 3502 "\uA749": "l", 3503 "\uA781": "l", 3504 "\uA747": "l", 3505 "\u01C9": "lj", 3506 "\u24DC": "m", 3507 "\uFF4D": "m", 3508 "\u1E3F": "m", 3509 "\u1E41": "m", 3510 "\u1E43": "m", 3511 "\u0271": "m", 3512 "\u026F": "m", 3513 "\u24DD": "n", 3514 "\uFF4E": "n", 3515 "\u01F9": "n", 3516 "\u0144": "n", 3517 "\u00F1": "n", 3518 "\u1E45": "n", 3519 "\u0148": "n", 3520 "\u1E47": "n", 3521 "\u0146": "n", 3522 "\u1E4B": "n", 3523 "\u1E49": "n", 3524 "\u019E": "n", 3525 "\u0272": "n", 3526 "\u0149": "n", 3527 "\uA791": "n", 3528 "\uA7A5": "n", 3529 "\u01CC": "nj", 3530 "\u24DE": "o", 3531 "\uFF4F": "o", 3532 "\u00F2": "o", 3533 "\u00F3": "o", 3534 "\u00F4": "o", 3535 "\u1ED3": "o", 3536 "\u1ED1": "o", 3537 "\u1ED7": "o", 3538 "\u1ED5": "o", 3539 "\u00F5": "o", 3540 "\u1E4D": "o", 3541 "\u022D": "o", 3542 "\u1E4F": "o", 3543 "\u014D": "o", 3544 "\u1E51": "o", 3545 "\u1E53": "o", 3546 "\u014F": "o", 3547 "\u022F": "o", 3548 "\u0231": "o", 3549 "\u00F6": "o", 3550 "\u022B": "o", 3551 "\u1ECF": "o", 3552 "\u0151": "o", 3553 "\u01D2": "o", 3554 "\u020D": "o", 3555 "\u020F": "o", 3556 "\u01A1": "o", 3557 "\u1EDD": "o", 3558 "\u1EDB": "o", 3559 "\u1EE1": "o", 3560 "\u1EDF": "o", 3561 "\u1EE3": "o", 3562 "\u1ECD": "o", 3563 "\u1ED9": "o", 3564 "\u01EB": "o", 3565 "\u01ED": "o", 3566 "\u00F8": "o", 3567 "\u01FF": "o", 3568 "\u0254": "o", 3569 "\uA74B": "o", 3570 "\uA74D": "o", 3571 "\u0275": "o", 3572 "\u0153": "oe", 3573 "\u0276": "oe", 3574 "\u01A3": "oi", 3575 "\u0223": "ou", 3576 "\uA74F": "oo", 3577 "\u24DF": "p", 3578 "\uFF50": "p", 3579 "\u1E55": "p", 3580 "\u1E57": "p", 3581 "\u01A5": "p", 3582 "\u1D7D": "p", 3583 "\uA751": "p", 3584 "\uA753": "p", 3585 "\uA755": "p", 3586 "\u24E0": "q", 3587 "\uFF51": "q", 3588 "\u024B": "q", 3589 "\uA757": "q", 3590 "\uA759": "q", 3591 "\u24E1": "r", 3592 "\uFF52": "r", 3593 "\u0155": "r", 3594 "\u1E59": "r", 3595 "\u0159": "r", 3596 "\u0211": "r", 3597 "\u0213": "r", 3598 "\u1E5B": "r", 3599 "\u1E5D": "r", 3600 "\u0157": "r", 3601 "\u1E5F": "r", 3602 "\u024D": "r", 3603 "\u027D": "r", 3604 "\uA75B": "r", 3605 "\uA7A7": "r", 3606 "\uA783": "r", 3607 "\u24E2": "s", 3608 "\uFF53": "s", 3609 "\u015B": "s", 3610 "\u1E65": "s", 3611 "\u015D": "s", 3612 "\u1E61": "s", 3613 "\u0161": "s", 3614 "\u1E67": "s", 3615 "\u1E63": "s", 3616 "\u1E69": "s", 3617 "\u0219": "s", 3618 "\u015F": "s", 3619 "\u023F": "s", 3620 "\uA7A9": "s", 3621 "\uA785": "s", 3622 "\u017F": "s", 3623 "\u1E9B": "s", 3624 "\u00DF": "ss", 3625 "\u24E3": "t", 3626 "\uFF54": "t", 3627 "\u1E6B": "t", 3628 "\u1E97": "t", 3629 "\u0165": "t", 3630 "\u1E6D": "t", 3631 "\u021B": "t", 3632 "\u0163": "t", 3633 "\u1E71": "t", 3634 "\u1E6F": "t", 3635 "\u0167": "t", 3636 "\u01AD": "t", 3637 "\u0288": "t", 3638 "\u2C66": "t", 3639 "\uA787": "t", 3640 "\uA729": "tz", 3641 "\u24E4": "u", 3642 "\uFF55": "u", 3643 "\u00F9": "u", 3644 "\u00FA": "u", 3645 "\u00FB": "u", 3646 "\u0169": "u", 3647 "\u1E79": "u", 3648 "\u016B": "u", 3649 "\u1E7B": "u", 3650 "\u016D": "u", 3651 "\u00FC": "u", 3652 "\u01DC": "u", 3653 "\u01D8": "u", 3654 "\u01D6": "u", 3655 "\u01DA": "u", 3656 "\u1EE7": "u", 3657 "\u016F": "u", 3658 "\u0171": "u", 3659 "\u01D4": "u", 3660 "\u0215": "u", 3661 "\u0217": "u", 3662 "\u01B0": "u", 3663 "\u1EEB": "u", 3664 "\u1EE9": "u", 3665 "\u1EEF": "u", 3666 "\u1EED": "u", 3667 "\u1EF1": "u", 3668 "\u1EE5": "u", 3669 "\u1E73": "u", 3670 "\u0173": "u", 3671 "\u1E77": "u", 3672 "\u1E75": "u", 3673 "\u0289": "u", 3674 "\u24E5": "v", 3675 "\uFF56": "v", 3676 "\u1E7D": "v", 3677 "\u1E7F": "v", 3678 "\u028B": "v", 3679 "\uA75F": "v", 3680 "\u028C": "v", 3681 "\uA761": "vy", 3682 "\u24E6": "w", 3683 "\uFF57": "w",
3684 "\u1E81": "w", 3685 "\u1E83": "w", 3686 "\u0175": "w", 3687 "\u1E87": "w", 3688 "\u1E85": "w", 3689 "\u1E98": "w", 3690 "\u1E89": "w", 3691 "\u2C73": "w", 3692 "\u24E7": "x", 3693 "\uFF58": "x", 3694 "\u1E8B": "x", 3695 "\u1E8D": "x", 3696 "\u24E8": "y", 3697 "\uFF59": "y", 3698 "\u1EF3": "y", 3699 "\u00FD": "y", 3700 "\u0177": "y", 3701 "\u1EF9": "y", 3702 "\u0233": "y", 3703 "\u1E8F": "y", 3704 "\u00FF": "y", 3705 "\u1EF7": "y", 3706 "\u1E99": "y", 3707 "\u1EF5": "y", 3708 "\u01B4": "y", 3709 "\u024F": "y", 3710 "\u1EFF": "y", 3711 "\u24E9": "z", 3712 "\uFF5A": "z", 3713 "\u017A": "z", 3714 "\u1E91": "z", 3715 "\u017C": "z", 3716 "\u017E": "z", 3717 "\u1E93": "z", 3718 "\u1E95": "z", 3719 "\u01B6": "z", 3720 "\u0225": "z", 3721 "\u0240": "z", 3722 "\u2C6C": "z", 3723 "\uA763": "z", 3724 "\uFF10": "0", 3725 "\u2080": "0", 3726 "\u24EA": "0", 3727 "\u2070": "0", 3728 "\u00B9": "1", 3729 "\u2474": "1", 3730 "\u2081": "1", 3731 "\u2776": "1", 3732 "\u24F5": "1", 3733 "\u2488": "1", 3734 "\u2460": "1", 3735 "\uFF11": "1", 3736 "\u00B2": "2", 3737 "\u2777": "2", 3738 "\u2475": "2", 3739 "\uFF12": "2", 3740 "\u2082": "2", 3741 "\u24F6": "2", 3742 "\u2461": "2", 3743 "\u2489": "2", 3744 "\u00B3": "3", 3745 "\uFF13": "3", 3746 "\u248A": "3", 3747 "\u2476": "3", 3748 "\u2083": "3", 3749 "\u2778": "3", 3750 "\u24F7": "3", 3751 "\u2462": "3", 3752 "\u24F8": "4", 3753 "\u2463": "4", 3754 "\u248B": "4", 3755 "\uFF14": "4", 3756 "\u2074": "4", 3757 "\u2084": "4", 3758 "\u2779": "4", 3759 "\u2477": "4", 3760 "\u248C": "5", 3761 "\u2085": "5", 3762 "\u24F9": "5", 3763 "\u2478": "5", 3764 "\u277A": "5", 3765 "\u2464": "5", 3766 "\uFF15": "5", 3767 "\u2075": "5", 3768 "\u2479": "6", 3769 "\u2076": "6", 3770 "\uFF16": "6", 3771 "\u277B": "6", 3772 "\u2086": "6", 3773 "\u2465": "6", 3774 "\u24FA": "6", 3775 "\u248D": "6", 3776 "\uFF17": "7", 3777 "\u2077": "7", 3778 "\u277C": "7", 3779 "\u24FB": "7", 3780 "\u248E": "7", 3781 "\u2087": "7", 3782 "\u247A": "7", 3783 "\u2466": "7", 3784 "\u2467": "8", 3785 "\u248F": "8", 3786 "\u24FC": "8", 3787 "\u247B": "8", 3788 "\u2078": "8", 3789 "\uFF18": "8", 3790 "\u277D": "8", 3791 "\u2088": "8", 3792 "\u24FD": "9", 3793 "\uFF19": "9", 3794 "\u2490": "9", 3795 "\u277E": "9", 3796 "\u247C": "9", 3797 "\u2089": "9", 3798 "\u2468": "9", 3799 "\u2079": "9" 3800 }, 3801 chars = str.split( "" ), 3802 len = chars.length, 3803 normalized = false, 3804 i, character; 3805 for ( i = 0; i !== len; i += 1 ) { 3806 character = chars[ i ]; 3807 if ( Object.prototype.hasOwnProperty.call( diacritics, character ) ) { 3808 chars[ i ] = diacritics[ character ]; 3809 normalized = true; 3810 } 3811 } 3812 return ( normalized ? chars.join( "" ) : str ); 3813}; 3814 3815/** 3816 * @namespace wb.string 3817 */ 3818wb.string = { 3819 3820 /* 3821 * Left-pads a number with zeros. 3822 * @memberof wb.string 3823 * @param {number} number The original number to pad. 3824 * @param {number} length The width of the resulting padded number, not the number of zeros to add to the front of the string. 3825 * @return {string} The padded string 3826 */ 3827 pad: function( number, length ) { 3828 var str = number + "", 3829 diff = length - str.length, 3830 i; 3831 for ( i = 0; i !== diff; i += 1 ) { 3832 str = "0" + str; 3833 } 3834 return str; 3835 }, 3836 3837 /* 3838 * Convert a base64 string into an ArrayBuffer (Note: this function are not fully UTF-8 supported and may create interoperability issue) 3839 * ref. https://www.isummation.com/blog/convert-arraybuffer-to-base64-string-and-vice-versa/ 3840 * @memberof wb.string 3841 * @param {string} Base64 browser encoded 3842 * @return {ArrayBuffer} string converted into ArrayBuffer 3843 */ 3844 base64ToArrayBuffer: function( base64 ) { 3845 var binary_string = window.atob( base64 ), 3846 len = binary_string.length, 3847 bytes = new Uint8Array( len ), 3848 i; 3849 for ( i = 0; i < len; i++ ) { 3850 bytes[ i ] = binary_string.charCodeAt( i ); 3851 } 3852 return bytes.buffer; 3853 }, 3854 3855 /* 3856 * Convert an ArrayBuffer into base64 string (Note: this function are not fully UTF-8 supported and may create interoperability issue) 3857 * ref. https://www.isummation.com/blog/convert-arraybuffer-to-base64-string-and-vice-versa/ 3858 * @memberof wb.string 3859 * @param {ArrayBuffer} 3860 * @return {string} ArrayBuffer converted into base64 string 3861 */ 3862 arrayBufferToBase64: function( buffer ) { 3863 var binary = "", 3864 bytes = new Uint8Array( buffer ), 3865 len = bytes.byteLength, 3866 i; 3867 for ( i = 0; i < len; i++ ) { 3868 binary += String.fromCharCode( bytes[ i ] ); 3869 } 3870 return window.btoa( binary ); 3871 }, 3872 3873 /* 3874 * Convert an hexadecimal string into an ArrayBuffer 3875 * ref. https://stackoverflow.com/questions/38987784/how-to-convert-a-hexadecimal-string-to-uint8array-and-back-in-javascript/50868276#50868276 3876 * @memberof wb.string 3877 * @param {string} Encoded string in hexadecimal 3878 * @return {Uint8Array} Binary array buffer 3879 */ 3880 fromHexString: function( hexString ) { 3881 return hexString === null ? null : Uint8Array.from( hexString.match( /.{1,2}/g ).map( function( byte ) { 3882 return parseInt( byte, 16 ); 3883 } ) ); 3884 }, 3885 3886 /* 3887 * Convert an ArrayBuffer into an hexadecimal string 3888 * ref. https://stackoverflow.com/questions/38987784/how-to-convert-a-hexadecimal-string-to-uint8array-and-back-in-javascript/50868276#50868276 3889 * @memberof wb.string 3890 * @param {Uint8Array} Binary array buffer 3891 * @return {string} Encoded string in hexadecimal 3892 */ 3893 toHexString: function( bytes ) { 3894 return bytes.reduce( function( str, byte ) { 3895 return str + byte.toString( 16 ).padStart( 2, "0" ); 3896 }, "" ); 3897 } 3898 3899}; 3900 3901/* 3902 * A suite of date related functions for easier parsing of dates 3903 * @namespace wb.date 3904 */ 3905wb.date = { 3906 3907 /* 3908 * Converts the date to a date-object. The input can be: 3909 * <ul> 3910 * <li>a Date object: returned without modification.</li> 3911 * <li>an array: Interpreted as [year,month,day]. NOTE: month is 0-11.</li> 3912 * <li>a number: Interpreted as number of milliseconds since 1 Jan 1970 (a timestamp).</li> 3913 * <li>a string: Any format supported by the javascript engine, like 'YYYY/MM/DD', 'MM/DD/YYYY', 'Jan 31 2009' etc.</li> 3914 * <li>an object: Interpreted as an object with year, month and date attributes. **NOTE** month is 0-11.</li> 3915 * </ul> 3916 * @memberof wb.date 3917 * @param {Date | number[] | number | string | object} dateValue 3918 * @return {Date | NaN} 3919 */ 3920 convert: function( dateValue ) { 3921 var dateConstructor = dateValue.constructor; 3922 3923 switch ( dateConstructor ) { 3924 case Date: 3925 return dateConstructor; 3926 case Array: 3927 return new Date( dateValue[ 0 ], dateValue[ 1 ], dateValue[ 2 ] ); 3928 case Number: 3929 case String: 3930 return new Date( dateValue ); 3931 default: 3932 return typeof dateValue === "object" ? new Date( dateValue.year, dateValue.month, dateValue.date ) : NaN; 3933 } 3934 }, 3935 3936 /* 3937 * Compares two dates (input can be any type supported by the convert function). 3938 * @memberof wb.date 3939 * @param {Date | number[] | number | string | object} dateValue1 3940 * @param {Date | number[] | number | string | object} dateValue2 3941 * @return {number | NaN} 3942 * @example returns 3943 * -1 if dateValue1 < dateValue2 3944 * 0 if dateValue1 = dateValue2 3945 * 1 if dateValue1 > dateValue2 3946 * NaN if dateValue1 or dateValue2 is an illegal date 3947 */ 3948 compare: function( dateValue1, dateValue2 ) { 3949 var convert = wb.date.convert; 3950 3951 if ( isFinite( dateValue1 = convert( dateValue1 ).valueOf() ) && isFinite( dateValue2 = convert( dateValue2 ).valueOf() ) ) { 3952 return ( dateValue1 > dateValue2 ) - ( dateValue1 < dateValue2 ); 3953 } 3954 return NaN; 3955 }, 3956 3957 /* 3958 * Cross-browser safe way of translating a date to ISO format 3959 * @memberof wb.date 3960 * @param {Date | number[] | number | string | object} dateValue 3961 * @param {boolean} withTime Optional. Whether to include the time in the result, or just the date. False if blank. 3962 * @return {string} 3963 * @example 3964 * toDateISO( new Date() ) 3965 * returns "2012-04-27" 3966 * toDateISO( new Date(), true ) 3967 * returns "2012-04-27 13:46" 3968 */ 3969 toDateISO: function( dateValue, withTime ) { 3970 var date = wb.date.convert( dateValue ), 3971 pad = wb.string.pad; 3972 3973 return date.getFullYear() + "-" + pad( date.getMonth() + 1, 2, "0" ) + "-" + pad( date.getDate(), 2, "0" ) + 3974 ( withTime ? " " + pad( date.getHours(), 2, "0" ) + ":" + pad( date.getMinutes(), 2, "0" ) : "" ); 3975 }, 3976 3977 /* 3978 * Cross-browser safe way of creating a date object from a date string in ISO format 3979 * @memberof wb.date 3980 * @param {string} dateISO Date string in ISO format 3981 * @return {Date} 3982 */ 3983 fromDateISO: function( dateISO ) { 3984 var date = null; 3985 3986 if ( dateISO && /\d{4}-\d{2}-\d{2}/.test( dateISO ) ) { 3987 date = new Date( dateISO.substr( 0, 4 ), dateISO.substr( 5, 2 ) - 1, dateISO.substr( 8, 2 ), 0, 0, 0, 0 ); 3988 } 3989 return date; 3990 } 3991}; 3992 3993/* 3994 * Returns a RFC4122 compliant Global Unique ID (GUID). 3995 * Originally from https://stackoverflow.com/a/2117523/455535 3996 */ 3997wb.guid = function() { 3998 return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( /[xy]/g, function( replacementChar ) { 3999 var rand = Math.random() * 16 | 0, 4000 newChar = replacementChar === "x" ? rand : ( rand & 0x3 | 0x8 ); 4001 return newChar.toString( 16 ); 4002 } ); 4003}; 4004 4005wb.escapeAttribute = function( str ) { 4006 return str.replace( /'/g, "'" ).replace( /"/g, """ ); 4007}; 4008 4009/* 4010 * Returns an escaped HTML string 4011 */ 4012wb.escapeHTML = function( str ) { 4013 return wb.escapeAttribute( str 4014 .replace( /&/g, "&" ) 4015 .replace( /</g, "<" ) 4016 .replace( />/g, ">" ) ); 4017}; 4018 4019/* 4020 * Returns a UTF-8 output from Base64 4021 * Reference: https://developer.mozilla.org/fr/docs/Glossary/Base64 (To be reviewed later because escape function is deprecated) 4022 */
4023wb.decodeUTF8Base64 = function( str ) { 4024 return decodeURIComponent( escape( atob( str ) ) ); 4025}; 4026 4027/* 4028* Find most common Personal Identifiable Information (PII) in a string and return either the cleaned string either true/false 4029* @param {string} str (required) - the content that needs to be verified 4030* 4031* @param {boolean} scope - if true will scrub the content 4032* @param {object} (optional) the 2nd param (scope) can also be an object having the following properties (optional): 4033* {string} any key name of the default patterns e.g. email, digits, etc. with the value 1. The function will only scrub the content that match the regex of the default patterns passed in this object 4034* {regex} customCase - this param is a regex. It will search and replace the values corresponding that pattern 4035* 4036* @param {object} opts (optional) - the 3rd param of the function that can contain the following properties (optional): 4037* {boolean} isCustomExclusive - if true, it will scrubb only the custom regex if the regex is the only property of the "scope" object 4038* {bolean} useFullBlock - if true, it will replace the scrubbed characters with the "â" symbol; 4039* {string} replaceWith - this string will replace the scrubbed content 4040* 4041 4042* @return {string | true | false} 4043* @example 4044* wb.findPotentialPII( "email:[email protected], phone:123 123 1234", true ) 4045* returns "email:, phone:", 4046* 4047* wb.findPotentialPII( "email:[email protected], phone:123 123 1234", false ) 4048* returns true 4049* 4050* wb.findPotentialPII( "email:[email protected], phone:123 123 1234", { email:1 }{ replaceWith: [REDACTED/CAVIARDÃ] } ) 4051* returns "email:[REDACTED/CAVIARDÃ], phone:123 123 1234" 4052* 4053* wb.findPotentialPII( "email:[email protected], phone:123 123 1234, numéro de cas 12345678", { "customCase":/\b(?:case[\s-]?number[\s\-\\.]?(?:\d{5,10}))|(?:numéro[\s-]?de[\s-]?cas[\s\-\\.]?(?:\d{5,10}))/ig }, { useFullBlock:1}) 4054* returns "phone:ââââââââââââ, email:âââââââââââââ, postalCode:ââââââ, ââââââââââââââââââââââ" 4055*/ 4056wb.findPotentialPII = function( str, scope, opts ) { 4057 if ( str && typeof str !== "string" ) { 4058 return false; 4059 } 4060 var oRegEx = { 4061 digits: /\d(?:[\s\-\\.\\/]?\d){8,}(?!\d)/ig, //9digits or more pattern 4062 passport: /\b[A-Za-z]{2}[\s\\.-]*?\d{6}\b/ig, //canadian nr passport pattern 4063 email: /\b(?:[a-zA-Z0-9_\-\\.]+)(?:@|%40|%2540)(?:[a-zA-Z0-9_\-\\.]+)\.(?:[a-zA-Z]{2,5})\b/ig, //email pattern 4064 postalCode: /\b[A-Za-z]\d[A-Za-z][ -]?\d[A-Za-z]\d\b/ig, //postal code pattern 4065 username: /(?:(username|user)[%20]?([:=]|(%EF%BC%9A))[^\s&]*)/ig, 4066 password: /(?:(password|pass)[%20]?([:=]|(%EF%BC%9A))[^\s&]*)/ig 4067 }, 4068 isFound = false, 4069 txtMarker = opts && opts.replaceWith ? opts.replaceWith : "", 4070 toClean = typeof scope === "object" ? true : scope, 4071 arMatchedStr, 4072 settings = opts || {}, 4073 defaultSettings = { 4074 isCustomExclusive: false, 4075 useFullBlock: false, 4076 replaceWith: "" 4077 }, 4078 isFullBlock = settings.useFullBlock || false, 4079 validatedScope = typeof scope === "object" ? {} : oRegEx; 4080 settings = $.extend( {}, defaultSettings, settings ); 4081 4082 if ( Object.keys( validatedScope ).length === 0 ) { 4083 if ( settings.isCustomExclusive ) { 4084 for ( var key in scope ) { 4085 if ( scope[ key ] instanceof RegExp ) { 4086 validatedScope[ key ] = scope[ key ]; 4087 } 4088 } 4089 } else { 4090 if ( Object.keys( scope ).length === 1 && Object.values( scope )[ 0 ] instanceof RegExp ) { 4091 validatedScope = oRegEx; 4092 validatedScope[ Object.keys( scope )[ 0 ] ] = Object.values( scope )[ 0 ]; 4093 } else { 4094 for ( var keyScope in scope ) { 4095 if ( Object.prototype.hasOwnProperty.call( oRegEx, keyScope ) ) { 4096 validatedScope [ keyScope ] = oRegEx [ keyScope ]; 4097 } else { 4098 if ( scope[ keyScope ] instanceof RegExp ) { 4099 validatedScope [ keyScope ] = scope [ keyScope ]; 4100 } 4101 } 4102 } 4103 } 4104 } 4105 } 4106 4107 for ( var valKey in validatedScope ) { 4108 arMatchedStr = str.match( validatedScope[ valKey ] ); 4109 if ( arMatchedStr ) { 4110 isFound = true; 4111 if ( toClean ) { 4112 txtMarker = isFullBlock ? "â".repeat( arMatchedStr[ 0 ].length ) : txtMarker; 4113 str = str.replaceAll( validatedScope[ valKey ], txtMarker ); 4114 } 4115 } 4116 } 4117 4118 return toClean && isFound ? str : isFound; 4119}; 4120 4121} )( wb, window ); 4122 4123( function( $, undef ) { 4124"use strict"; 4125 4126var methods, 4127 settings = { 4128 "default": "wet-boew" 4129 }; 4130 4131methods = { 4132 4133 init: function( options ) { 4134 return $.extend( settings, options || {} ); 4135 }, 4136 4137 show: function( onlyAria ) { 4138 $( this ).each( function() { 4139 var $elm = $( this ); 4140 $elm.attr( "aria-hidden", "false" ); 4141 if ( onlyAria === undef ) { 4142 $elm.removeClass( "wb-inv" ); 4143 } 4144 } ); 4145 }, 4146 4147 hide: function( onlyAria ) { 4148 $( this ) 4149 .each( function() { 4150 var $elm = $( this ); 4151 $elm.attr( "aria-hidden", "true" ); 4152 if ( onlyAria === undef ) { 4153 return $elm.addClass( "wb-inv" ); 4154 } 4155 } ); 4156 }, 4157 4158 toggle: function( to, from ) { 4159 $( this ) 4160 .addClass( to ) 4161 .removeClass( from ); 4162 } 4163}; 4164 4165$.fn.wb = function( method ) { 4166 4167 if ( methods[ method ] ) { 4168 methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ) ); 4169 } else if ( typeof method === "object" || !method ) { 4170 methods.init.apply( this, arguments ); 4171 } else { 4172 $.error( "Method " + method + " does not exist on jquery.wb" ); 4173 } 4174}; 4175 4176} )( jQuery ); 4177 4178/* 4179:focusable and :tabable jQuery helper expressions - https://github.com/jquery/jquery-ui/blob/24756a978a977d7abbef5e5bce403837a01d964f/ui/jquery.ui.core.js 4180*/ 4181( function( $ ) { 4182 4183"use strict"; 4184 4185function focusable( element, isTabIndexNotNaN, visibility ) { 4186 var map, mapName, img, 4187 nodeName = element.nodeName.toLowerCase( ); 4188 if ( nodeName === "area" ) { 4189 map = element.parentNode; 4190 mapName = map.name; 4191 if ( !element.href || !mapName || map.nodeName.toLowerCase( ) !== "map" ) { 4192 return false;
4193 } 4194 img = $( "img[usemap=#" + mapName + "]" )[ 0 ]; 4195 return !!img && visible( img ); 4196 } 4197 if ( visibility ) { 4198 return ( /input|select|textarea|button|object/.test( nodeName ) ? !element.disabled : 4199 nodeName === "a" ? 4200 element.href || isTabIndexNotNaN : 4201 isTabIndexNotNaN ) && 4202 visible( element ); /* the element and all of its ancestors must be visible */ 4203 } else { 4204 return ( /input|select|textarea|button|object/.test( nodeName ) ? !element.disabled : 4205 nodeName === "a" ? 4206 element.href || isTabIndexNotNaN : 4207 isTabIndexNotNaN ); 4208 } 4209} 4210 4211function visible( element ) { 4212 return $.expr.filters.visible( element ) && !$( element ) 4213 .parents( ) 4214 .addBack( ) 4215 .filter( function() { 4216 return $.css( this, "visibility" ) === "hidden"; 4217 } ) 4218 .length; 4219} 4220 4221$.extend( $.expr[ ":" ], { 4222 data: $.expr.createPseudo ? $.expr.createPseudo( function( dataName ) { 4223 return function( elem ) { 4224 return !!$.data( elem, dataName ); 4225 }; 4226 } ) : 4227 4228 // support: jQuery <1.8 4229 4230 function( elem, i, match ) { 4231 return !!$.data( elem, match[ 3 ] ); 4232 }, 4233 focusable: function( element ) { 4234 return focusable( element, !isNaN( $.attr( element, "tabindex" ) ), true ); 4235 }, 4236 discoverable: function( element ) { 4237 return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) ); 4238 }, 4239 tabbable: function( element ) { 4240 var tabIndex = $.attr( element, "tabindex" ), 4241 isTabIndexNaN = isNaN( tabIndex ); 4242 return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN ); 4243 } 4244} ); 4245 4246} )( jQuery ); 4247 4248/** 4249 * @title WET-BOEW Add to calendar 4250 * @overview Create an add to calendar button for an event 4251 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 4252 * @author @ricokola 4253 */ 4254( function( $, wb ) { 4255"use strict"; 4256 4257/* 4258 * Variable and function definitions. 4259 * These are global to the plugin - meaning that they will be initialized once per page, 4260 * not once per instance of plugin on the page. So, this is a good place to define 4261 * variables that are common to all instances of the plugin on a page. 4262 */ 4263var componentName = "wb-addcal", 4264 selector = ".provisional." + componentName, 4265 initEvent = "wb-init." + componentName, 4266 $document = wb.doc, 4267 4268 /** 4269 * @method init 4270 * @param {jQuery Event} event Event that triggered the function call 4271 */ 4272 init = function( event ) { 4273 4274 // Start initialization 4275 // returns DOM object = proceed with init 4276 // returns undefined = do not proceed with init (e.g., already initialized) 4277 var elm = wb.init( event, componentName, selector ), 4278 $elm = $( elm ); 4279 4280 if ( elm ) { 4281 4282 wb.ready( $( elm ), componentName ); 4283 4284 var properties = elm.querySelectorAll( "[property]" ), 4285 event_details = new Object(), 4286 place_details = [], 4287 i, 4288 i_len, 4289 prop_cache, 4290 googleLink, 4291 icsFile, 4292 i18nDict = { 4293 en: { 4294 "addcal-addto": "Add to", 4295 "addcal-calendar": "calendar", 4296 "addcal-other": "Other (Outlook, Apple, etc.)" 4297 }, 4298 fr: { 4299 "addcal-addto": "Ajouter au", 4300 "addcal-calendar": "calendrier", 4301 "addcal-other": "Autre (Outlook, Apple, etc.)" 4302 } 4303 }; 4304 4305 // Initiate dictionary 4306 i18nDict = i18nDict[ $( "html" ).attr( "lang" ) || "en" ]; 4307 i18nDict = { 4308 addto: i18nDict[ "addcal-addto" ], 4309 calendar: i18nDict[ "addcal-calendar" ], 4310 ical: i18nDict[ "addcal-other" ] 4311 }; 4312 4313 // Set date stamp with the date modified 4314 event_details.dtStamp = dtToISOString( $( "time[property='dateModified']" ) ); 4315 4316 i_len = properties.length; 4317 for ( i = 0; i < i_len; i++ ) { 4318 prop_cache = properties[ i ]; 4319 switch ( prop_cache.getAttribute( "property" ) ) { 4320 case "name": 4321 4322 // If the property=name is inside an element with typeof=Place defined 4323 if ( $( prop_cache ).parentsUntil( ( "." + componentName ), "[typeof=Place]" ).length ) { 4324 event_details.placeName = prop_cache.textContent; 4325 } else { 4326 event_details.name = prop_cache.textContent; 4327 } 4328 break; 4329 case "description": 4330 event_details.description = prop_cache.textContent.replace( /(\r\n|\n|\r)/gm, " " ); 4331 break; 4332 case "startDate": 4333 event_details.sDate = dtToISOString( $( "time[property='startDate']", $elm ) ); 4334 break; 4335 case "endDate": 4336 event_details.eDate = dtToISOString( $( "time[property='endDate']", $elm ) ); 4337 break; 4338 case "location": 4339 4340 // If the location doesn't have typeof defined OR has typeof=VirtualLocation without URL inside. 4341 if ( !prop_cache.getAttribute( "typeof" ) || ( prop_cache.getAttribute( "typeof" ) === "VirtualLocation" && !$( prop_cache ).find( "[property=url]" ).length ) ) { 4342 event_details.placeName = prop_cache.textContent; 4343 } 4344 break; 4345 case "streetAddress": 4346 event_details.placeAddress = prop_cache.textContent; 4347 break; 4348 case "addressLocality": 4349 event_details.placeLocality = prop_cache.textContent; 4350 break; 4351 case "addressRegion": 4352 event_details.placeRegion = prop_cache.textContent; 4353 break; 4354 case "postalCode": 4355 event_details.placePostalCode = prop_cache.textContent; 4356 break; 4357 case "url": 4358 4359 // If the property=url is inside a property=location 4360 if ( $( prop_cache ).parentsUntil( ( "." + componentName ), "[property=location]" ).length ) { 4361 event_details.placeName = prop_cache.textContent; 4362 } 4363 break; 4364 } 4365 } 4366 4367 place_details.push( ( event_details.placeName || "" ), ( event_details.placeAddress || "" ), ( event_details.placeLocality || "" ), ( event_details.placeRegion || "" ), ( event_details.placePostalCode || "" ) ); 4368 4369 // Error handling 4370 if ( !event_details.name ) { 4371 throw componentName + ": Event title is missing."; 4372 } else if ( !event_details.sDate ) { 4373 throw componentName + ": Start date is missing."; 4374 } else if ( !event_details.eDate ) { 4375 throw componentName + ": End date is missing."; 4376 } 4377 4378 // Set Unique Identifier (UID) and Date Stamp (DSTAMP) 4379 event_details.uid = window.location.href.replace( /\.|-|\/|:|[G-Zg-z]/g, "" ).toUpperCase().substr( -10 ) + "-" + event_details.sDate + "-" + event_details.dtStamp; 4380 4381 // Set google calendar link 4382 googleLink = encodeURI( "https://www.google.com/calendar/render?action=TEMPLATE" + "&text=" + event_details.name + "&details=" + 4383 event_details.description + "&dates=" + event_details.sDate + "/" + event_details.eDate + "&location=" + place_details.join( " " ) ); 4384 4385 // Set ICS file for Outlook, Apple and other calendars 4386 icsFile = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//WET-BOEW//Add to Calendar v4.0//\n
4386BEGIN:VEVENT\nDTSTAMP:" + event_details.dtStamp + "\nSUMMARY:" + event_details.name + "\nDESCRIPTION:" + event_details.description + "\nUID:" + event_details.uid + "\nDTSTART:" + event_details.sDate + "\nDTEND:" + event_details.eDate + "\nLOCATION:" + place_details.join( " " ) + "\nEND:VEVENT\nEND:VCALENDAR"; 4387 4388 elm.dataset.icsFile = icsFile; 4389 4390 // Create and add details summary to the wb-addcal event and initiate the unordered list 4391 $elm.append( "<details class='max-content " + componentName + "-buttons'><summary>" + i18nDict.addto + " " + i18nDict.calendar + 4392 "</summary><ul class='list-unstyled mrgn-bttm-0'><li><a class='btn btn-link' href='" + googleLink.replace( /'/g, "%27" ) + "' target='_blank' rel='noreferrer noopener'>Google<span class='wb-inv'>" + i18nDict.calendar + "</span></a></li><li><button class='btn btn-link download-ics'>" + i18nDict.ical + 4393 "<span class='wb-inv'>Calendar</span></button></li></ul></details>" ); 4394 } 4395 4396 wb.ready( $( elm ), componentName ); 4397 4398 }; 4399 4400// Convert date to ISO string and formating for ICS file 4401var dtToISOString = function( date ) { 4402 if ( date.is( "[datetime]" ) ) { 4403 date = date.attr( "datetime" ); 4404 } else { 4405 date = date.text(); 4406 } 4407 4408 return new Date( date ).toISOString().replace( /\..*[0-9]/g, "" ).replace( /-|:|\./g, "" ); 4409}; 4410 4411$document.on( "click", ".download-ics", function( event ) { 4412 var icsFile = $( event.currentTarget ).parentsUntil( "." + componentName ).parent()[ 0 ]; 4413 icsFile = $( icsFile ).attr( "data-ics-file" ); 4414 wb.download( new Blob( [ icsFile ], { type: "text/calendar;charset=utf-8" } ), "evenement-gc-event.ics" ); 4415} ); 4416 4417// Bind the init event of the plugin 4418$document.on( "timerpoke.wb " + initEvent, selector, init ); 4419 4420// Add the timer poke to initialize the plugin 4421wb.add( selector ); 4422 4423} )( jQuery, wb ); 4424 4425/** 4426 * @title WET-BOEW Ajax Fetch [ ajax-fetch ] 4427 * @overview A basic AjaxLoader wrapper for WET-BOEW 4428 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 4429 * @author WET Community 4430 */ 4431( function( $, wb, DOMPurify ) { 4432"use strict"; 4433 4434/* 4435 * Variable and function definitions. 4436 * These are global to the plugin - meaning that they will be initialized once per page, 4437 * not once per instance of plugin on the page. So, this is a good place to define 4438 * variables that are common to all instances of the plugin on a page. 4439 */ 4440var $document = wb.doc; 4441 4442// Event binding 4443$document.on( "ajax-fetch.wb", function( event ) { 4444 4445 // TODO: Remove event.element in future versions 4446 var caller = event.element || event.target, 4447 fetchOpts = event.fetch, 4448 urlParts = fetchOpts.url.split( " " ), 4449 url = urlParts[ 0 ], 4450 urlSubParts = url.split( "#" ), 4451 urlHash = urlSubParts[ 1 ], 4452 selector = urlParts[ 1 ] || ( urlHash ? "#" + urlHash : false ), 4453 fetchData = {}, 4454 callerId, fetchNoCacheURL, urlSub, 4455 fetchNoCache = fetchOpts.nocache, 4456 fetchNoCacheKey = fetchOpts.nocachekey || wb.cacheBustKey || "wbCacheBust"; 4457 4458 // Separate the URL from the filtering criteria 4459 if ( selector ) { 4460 fetchOpts.url = urlParts[ 0 ]; 4461 } 4462 4463 if ( fetchNoCache ) { 4464 if ( fetchNoCache === "nocache" ) { 4465 fetchNoCacheURL = wb.guid(); 4466 } else { 4467 fetchNoCacheURL = wb.sessionGUID(); 4468 } 4469 fetchNoCacheURL = fetchNoCacheKey + "=" + fetchNoCacheURL; 4470 4471 urlSub = urlSubParts[ 0 ]; 4472 if ( urlSub.indexOf( "?" ) !== -1 ) { 4473 url = urlSub + "&" + fetchNoCacheURL + ( urlHash ? "#" + urlHash : "" ); 4474 } else { 4475 url = urlSub + "?" + fetchNoCacheURL + ( urlHash ? "#" + urlHash : "" ); 4476 } 4477 4478 fetchOpts.url = url; 4479 } 4480 4481 // Filter out any events triggered by descendants 4482 if ( caller === event.target || event.currentTarget === event.target ) { 4483 4484 if ( !caller.id ) { 4485 caller.id = wb.getId(); 4486 } 4487 callerId = caller.id; 4488 4489 // Ensure we don't allow jsonp load 4490 if ( fetchOpts.dataType && fetchOpts.dataType === "jsonp" ) { 4491 fetchOpts.dataType = "json"; 4492 } 4493 if ( fetchOpts.jsonp ) { 4494 fetchOpts.jsonp = false; 4495 } 4496 4497 $.ajax( fetchOpts ) 4498 .done( function( response, status, xhr ) { 4499 var responseType = typeof response; 4500 4501 if ( selector ) { 4502 response = $( "<div>" + response + "</div>" ).find( selector ); 4503 } 4504 4505 fetchData.pointer = $( "<div id='" + wb.getId() + "' data-type='" + responseType + "'></div>" ) 4506 .append( responseType === "string" ? response : "" ); 4507 4508 if ( !xhr.responseJSON ) { 4509 try { 4510 response = $( response ); 4511 } catch ( e ) { 4512 response = DOMPurify.sanitize( xhr.responseText ); 4513 } 4514 } else { 4515 response = xhr.responseText; 4516 } 4517 4518 fetchData.response = response; 4519 fetchData.hasSelector = !!selector; 4520 fetchData.status = status; 4521 fetchData.xhr = xhr; 4522 4523 $( "#" + callerId ).trigger( { 4524 type: "ajax-fetched.wb", 4525 fetch: fetchData 4526 }, this ); 4527 } ) 4528 .fail( function( xhr, status, error ) { 4529 $( "#" + callerId ).trigger( { 4530 type: "ajax-failed.wb", 4531 fetch: { 4532 xhr: xhr, 4533 status: status, 4534 error: error 4535 } 4536 }, this ); 4537 }, this ); 4538 } 4539} ); 4540 4541} )( jQuery, wb, DOMPurify ); 4542 4543/** 4544 * @title WET-BOEW Set background image 4545 * @overview Apply a background image or detects the change in screen width and replace the background image accordingly 4546 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 4547 * @author @namjohn920, @duboisp 4548 */ 4549( function( $, wb ) { 4550"use strict"; 4551 4552/* 4553 * Variable and function definitions. 4554 * These are global to the plugin - meaning that they will be initialized once per page, 4555 * not once per instance of plugin on the page. So, this is a good place to define 4556 * variables that are common to all instances of the plugin on a page. 4557 */ 4558var $document = wb.doc, 4559 $window = wb.win, 4560 componentName = "wb-bgimg", 4561 selector = "[data-bgimg-srcset], [data-bgimg]", 4562 bgViews = {}, 4563 ids = [], 4564 4565 init = function( event ) { 4566 4567 var elm, elmId, 4568 bgImg, bgimgSrcset, bgRawViews, 4569 i, i_len, i_views, 4570 imgSrc, imgSize; 4571 4572 // Start initialization 4573 // returns DOM object = proceed with init 4574 // returns undefined = do not proceed with init (e.g., already initialized) 4575 elm = wb.init( event, componentName, selector ); 4576 4577 if ( elm ) { 4578 4579 // Ensure the feature have an ID. 4580 if ( !elm.id ) { 4581 elm.id = wb.getId(); 4582 } 4583 elmId = elm.id; 4584 4585 // Apply default background image 4586 bgImg = elm.dataset.bgimg; 4587 if ( bgImg ) { 4588 elm.style.backgroundImage = "url(" + bgImg + ")"; 4589 } 4590 4591 // Apply background image set if defined 4592 bgimgSrcset = elm.dataset.bgimgSrcset; 4593 if ( bgimgSrcset ) { 4594 ids.push( elm.id ); 4595 bgRawViews = elm.dataset.bgimgSrcset.split( "," ); 4596 i_len = bgRawViews.length; 4597 bgViews[ elmId ] = []; 4598 4599 for ( i = 0; i < i_len; i++ ) { 4600 i_views = bgRawViews[ i ].trim().split( " " ); 4601 4602 imgSrc = i_views[ 0 ]; 4603 imgSize = i_views[ i_views.length - 1 ]; 4604 4605 imgSize = parseInt( imgSize.substring( 0, imgSize.length - 1 ) ); 4606 bgViews[ elmId ].push( [ imgSrc, imgSize ] ); 4607 } 4608 4609 bgViews[ elmId ].sort( 4610 function( a, b ) { 4611 return a[ 1 ] > b[ 1 ] ? 1 : -1; 4612 } 4613 ); 4614 4615 selectImage(); 4616 4617 // Add the resize listener 4618 $window.on( "resize", selectImage ); 4619 } 4620 4621 // Identify that initialization has completed 4622 wb.ready( $( elm ), componentName ); 4623 } 4624 }, 4625 4626 selectImage = function() { 4627 var screenWidth = window.innerWidth, 4628 optimizedLink = {}, 4629 i, i_len = ids.length, j, 4630 optimizedSize, currentId, currentId_len, 4631 currentInput, 4632 link, elm; 4633 4634 for ( i = 0; i < i_len; i++ ) { 4635 optimizedSize = Infinity; 4636 currentId = bgViews[ ids[ i ] ]; 4637 currentId_len = currentId.length; 4638 4639 for ( j = 0; j < currentId_len; j++ ) { 4640 currentInput = currentId[ j ]; 4641 if ( currentInput[ 1 ] >= screenWidth ) { 4642 if ( optimizedSize > currentInput[ 1 ] ) { 4643 optimizedSize = currentInput[ 1 ]; 4644 optimizedLink[ ids[ i ] ] = currentInput[ 0 ]; 4645 } 4646 } 4647 } 4648 if ( optimizedSize === Infinity ) { 4649 optimizedLink[ ids[ i ] ] = currentId[ currentId_len - 1 ][ 0 ]; 4650 } 4651 } 4652 4653 for ( link in optimizedLink ) { 4654 elm = document.getElementById( link ); 4655 elm.style.backgroundImage = "url(" + optimizedLink[ link ] + ")"; 4656 } 4657 }; 4658 4659// Bind the init event of the plugin 4660$document.on( "timerpoke.wb wb-init." + componentName, selector, init ); 4661 4662// Add the timer poke to initialize the plugin
4663wb.add( selector ); 4664 4665} )( jQuery, wb ); 4666 4667/** 4668 * @title WET-BOEW Events Calendar 4669 * @overview Dynamically generates a calendar interface for navigating a list of events. 4670 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 4671 * @author WET Community 4672 */ 4673( function( $, window, wb ) { 4674"use strict"; 4675 4676/* 4677 * Variable and function definitions. 4678 * These are global to the plugin - meaning that they will be initialized once per page, 4679 * not once per instance of plugin on the page. So, this is a good place to define 4680 * variables that are common to all instances of the plugin on a page. 4681 */ 4682var componentName = "wb-calevt", 4683 selector = "." + componentName, 4684 componentEventName = componentName + "-cal", 4685 selectorEvent = "." + componentEventName, 4686 initEvent = "wb-init" + selector, 4687 evDetails = "ev-details", 4688 setFocusEvent = "focus", 4689 dataAttr = componentName, 4690 $document = wb.doc, 4691 hiddenClass = "hidden", 4692 4693 /** 4694 * @method init 4695 * @param {jQuery Event} event Event that triggered this handler 4696 */ 4697 init = function( event ) { 4698 4699 // Start initialization 4700 // returns DOM object = proceed with init 4701 // returns undefined = do not proceed with init (e.g., already initialized) 4702 var elm = wb.init( event, componentName, selector ), 4703 $elm; 4704 4705 if ( elm ) { 4706 $elm = $( elm ); 4707 4708 // Load ajax content 4709 $.when.apply( $, $.map( $elm.find( "[data-calevt]" ), getAjax ) ) 4710 .always( function() { 4711 processEvents( $elm ); 4712 4713 // Identify that initialization has completed 4714 wb.ready( $elm, componentName ); 4715 } ); 4716 } 4717 }, 4718 4719 getAjax = function( ajaxContainer ) { 4720 var $ajaxContainer = $( ajaxContainer ), 4721 urls = $ajaxContainer.data( "calevt" ).split( /\s+/ ), 4722 dfd = $.Deferred(), 4723 len = urls.length, 4724 promises = [], 4725 i, appendData; 4726 4727 appendData = function( data ) { 4728 $ajaxContainer.append( $.trim( data ) ); 4729 }; 4730 4731 for ( i = 0; i < len; i += 1 ) { 4732 promises.push( $.get( urls[ i ], appendData, "html" ) ); 4733 } 4734 4735 $.when.apply( $, promises ).always( function() { 4736 dfd.resolve(); 4737 } ); 4738 4739 return dfd.promise(); 4740 }, 4741 4742 processEvents = function( $elm ) { 4743 var settings = $.extend( {}, window[ componentName ], $elm.data( dataAttr ) ), 4744 year, month, events, containerId, $container, 4745 minDate, maxDate, minDateTime, maxDateTime, 4746 currDate = new Date(), 4747 currDateTime = currDate.getTime(); 4748 4749 events = getEvents( $elm ); 4750 containerId = $elm.data( "calevtSrc" ); 4751 $container = $( "#" + containerId ).addClass( componentEventName ); 4752 4753 year = settings.year; 4754 month = settings.month; 4755 4756 if ( $elm.data( "calevtMinDate" ) ) { 4757 minDate = getLocaleDate( $elm.data( "calevtMinDate" ) ); 4758 } 4759 if ( $elm.data( "calevtMaxDate" ) ) { 4760 maxDate = getLocaleDate( $elm.data( "calevtMaxDate" ) ); 4761 } 4762 4763 if ( !minDate || ( events.minDate < minDate ) ) { 4764 minDate = events.minDate; 4765 } 4766 if ( !maxDate || ( events.maxDate > maxDate ) ) { 4767 maxDate = events.maxDate; 4768 } 4769 4770 minDateTime = minDate.getTime(); 4771 maxDateTime = maxDate.getTime(); 4772 4773 if ( !year && minDateTime < currDateTime && currDateTime < maxDateTime ) { 4774 year = currDate.getFullYear(); 4775 } else if ( !year && currDateTime < minDateTime ) { 4776 year = minDate.getFullYear(); 4777 } else if ( !year && maxDateTime < currDateTime ) { 4778 year = maxDate.getFullYear(); 4779 } 4780 4781 if ( !month && minDateTime < currDateTime && currDate.getTime() < maxDateTime ) { 4782 month = currDate.getMonth(); 4783 } else if ( !month && currDateTime < minDateTime ) { 4784 month = minDate.getMonth(); 4785 } else if ( !month && maxDateTime < currDateTime ) { 4786 month = maxDate.getMonth(); 4787 } 4788 4789 wb.calendar.create( $container, { 4790 year: year, 4791 month: month, 4792 minDate: minDate, 4793 maxDate: maxDate, 4794 daysCallback: addEvents, 4795 events: events.list, 4796 $events: $elm 4797 } ); 4798 }, 4799 4800 daysBetween = function( dateLow, dateHigh ) { 4801 4802 // Simplified conversion to date object 4803 var date1 = wb.date.convert( dateLow ), 4804 date2 = wb.date.convert( dateHigh ), 4805 dstAdjust = 0, 4806 oneMinute = 1000 * 60, 4807 oneDay = oneMinute * 60 * 24, 4808 diff; 4809 4810 // Equalize times in case date objects have them 4811 date1.setHours( 0 ); 4812 date1.setMinutes( 0 ); 4813 date1.setSeconds( 0 ); 4814 date2.setHours( 0 ); 4815 date2.setMinutes( 0 ); 4816 date2.setSeconds( 0 ); 4817 4818 // Take care of spans across Daylight Saving Time changes 4819 if ( date2 > date1 ) { 4820 dstAdjust = ( date2.getTimezoneOffset() - date1.getTimezoneOffset() ) * oneMinute; 4821 } else { 4822 dstAdjust = ( date1.getTimezoneOffset() - date2.getTimezoneOffset() ) * oneMinute; 4823 } 4824 diff = Math.abs( date2.getTime() - date1.getTime() ) - dstAdjust; 4825 return Math.ceil( diff / oneDay ); 4826 }, 4827 4828 ///TODO: Review this code 4829 getEvents = function( obj ) { 4830 var directLinking = !( $( obj ).hasClass( "evt-anchor" ) ), 4831 events = { 4832 minDate: null, 4833 maxDate: null, 4834 iCount: 0, 4835 list: [ 4836 { 4837 a: 1 4838 } 4839 ] 4840 }, 4841 objEventsList = obj.find( "ul, ol" ).first(), 4842 objEventsListItems = objEventsList.find( "> li:not(.wb-fltr-out)" ), 4843 iLen = objEventsListItems.length, 4844 dateTimeRegExp = /datetime\s+\{date:\s*(\d+-\d+-\d+)\}/, 4845 i, $event, event, $objTitle, title, link, href, target, 4846 linkId, date, tCollection, tCollectionTemp, strDate1, 4847 strDate2, z, zLen, className, dateClass; 4848 4849 for ( i = 0; i !== iLen; i += 1 ) { 4850 $event = objEventsListItems.eq( i ); 4851 event = $event[ 0 ]; 4852 $objTitle = $event.find( "*:header:first" ); 4853 className = $objTitle.attr( "class" ); 4854 title = $objTitle.text(); 4855 link = $event.find( "a" )[ 0 ]; 4856 href = link.getAttribute( "href" ); 4857 target = link.getAttribute( "target" ); 4858 zLen = 1; 4859 4860 /* 4861 * Modification direct-linking or page-linking 4862 * - added the ability to have class set the behaviour of the links 4863 * - default is to use the link of the item as the event link in the calendar 4864 * - 'evt-anchor' class dynamically generates page anchors on the links it maps to the event 4865 */ 4866 if ( !directLinking ) {
4867 linkId = event.id || wb.getId(); 4868 event.id = linkId; 4869 href = "#" + linkId; 4870 } 4871 4872 date = new Date(); 4873 date.setHours( 0, 0, 0, 0 ); 4874 tCollection = event.getElementsByTagName( "time" ); 4875 4876 /* 4877 * Date spanning capability 4878 * - since there may be some dates that are capable of spanning over months we need to identify them 4879 * the process is see how many time nodes are in the event. 2 nodes will trigger a span 4880 */ 4881 if ( tCollection.length !== 0 ) { 4882 tCollectionTemp = tCollection[ 0 ]; 4883 strDate1 = tCollectionTemp.nodeName.toLowerCase() === "time" ? 4884 tCollectionTemp.getAttribute( "datetime" ).substr( 0, 10 ).split( "-" ) : 4885 tCollectionTemp.className.match( dateTimeRegExp )[ 1 ].substr( 0, 10 ).split( "-" ); 4886 4887 // Convert to zero-based month 4888 strDate1[ 1 ] = strDate1[ 1 ] - 1; 4889 4890 date.setFullYear( strDate1[ 0 ], strDate1[ 1 ], strDate1[ 2 ] ); 4891 4892 if ( tCollection.length !== 1 ) { 4893 4894 // This is a spanning event 4895 tCollectionTemp = tCollection[ 1 ]; 4896 strDate2 = tCollectionTemp.nodeName.toLowerCase() === "time" ? 4897 tCollectionTemp.getAttribute( "datetime" ).substr( 0, 10 ).split( "-" ) : 4898 tCollectionTemp.className.match( dateTimeRegExp )[ 1 ].substr( 0, 10 ).split( "-" ); 4899 4900 // Convert to zero-based month 4901 strDate2[ 1 ] = strDate2[ 1 ] - 1; 4902 4903 zLen += daysBetween( strDate1, strDate2 ); 4904 } 4905 4906 // Now loop in events to load up all the days that it would be on tomorrow.setDate(tomorrow.getDate() + 1); 4907 for ( z = 0; z !== zLen; z += 1 ) { 4908 if ( z !== 0 ) { 4909 date = new Date( date.setDate( date.getDate() + 1 ) ); 4910 } 4911 4912 if ( events.minDate === null || date < events.minDate ) { 4913 events.minDate = date; 4914 } 4915 4916 if ( events.maxDate === null || date > events.maxDate ) { 4917 events.maxDate = date; 4918 } 4919 4920 events.list[ events.iCount ] = { 4921 title: title, 4922 date: new Date( date.getTime() ), 4923 href: href, 4924 target: target 4925 }; 4926 4927 // Add a viewfilter 4928 dateClass = "filter-" + ( date.getFullYear() ) + "-" + 4929 wb.string.pad( date.getMonth() + 1, 2 ); 4930 if ( !className ) { 4931 className = dateClass; 4932 } else if ( className.indexOf( dateClass ) === -1 ) { 4933 className += " " + dateClass; 4934 } 4935 events.iCount += 1; 4936 } 4937 $objTitle.attr( "class", className ); 4938 } 4939 4940 // End of loop through objects/events 4941 } 4942 4943 //Sort events 4944 events.list.sort( function( firstEvent, secondEvent ) { 4945 return firstEvent.date - secondEvent.date; 4946 } ); 4947 4948 return events; 4949 }, 4950 4951 addEvents = function( year, month, $days ) { 4952 var eventsList = this.events, 4953 i, eLen, date, dayIndex, $day, $dayEvents, event, eventMonth; 4954 4955 /* 4956 * Determines for each event, if it occurs in the display month 4957 */ 4958 for ( i = 0, eLen = eventsList.length; i !== eLen; i += 1 ) { 4959 event = eventsList[ i ]; 4960 date = event.date; 4961 4962 if ( date.getFullYear() === year ) { 4963 eventMonth = date.getMonth(); 4964 if ( eventMonth > month ) { 4965 4966 //End the loop if the next event is in a future month because events are sorted chronologically 4967 break; 4968 } else if ( date.getMonth() === month ) { 4969 dayIndex = date.getDate() - 1; 4970 $day = $( $days[ dayIndex ] ); 4971 4972 //Get the appropriate day events if a day link exists 4973 if ( $day.parent().get( 0 ).nodeName !== "A" ) { 4974 $dayEvents = $day.next(); 4975 } else { 4976 $dayEvents = $day.parent().next(); 4977 } 4978 4979 //Create the event list container if it doesn't exist 4980 if ( $dayEvents.length !== 1 ) { 4981 $dayEvents = $( "<ul></ul>" ).insertAfter( $day ); 4982 4983 //Determine the focus based on the day before 4984 if ( dayIndex && $days[ dayIndex - 1 ].parentNode.nodeName === "A" ) { 4985 $day.wrap( "<a class='cal-evt' tabindex='-1'></a>" ); 4986 } else { 4987 $day.wrap( "<a class='cal-evt'></a>" ); 4988 } 4989 $day.parent().attr( "href", "javascript:;" ); 4990 } 4991 4992 //Add the event to the list 4993 $dayEvents.append( "<li><a tabindex='-1' class='cal-evt-lnk' href='" + event.href + "'>" + event.title + "</a></li>" ); 4994 } 4995 } 4996 } 4997 }, 4998 4999 filterEvents = function( year, month ) { 5000 this.find( "li.cal-disp-onshow" ) 5001 .addClass( hiddenClass ) 5002 .has( ":header[class*=filter-" + year + "-" + 5003 wb.string.pad( parseInt( month, 10 ) + 1, 2 ) + "]" ) 5004 .removeClass( hiddenClass ); 5005 }, 5006 5007 showEvents = function() { 5008 $( this ) 5009 .next() 5010 .addClass( evDetails ); 5011 }, 5012 5013 hideEvents = function() { 5014 var $link = $( this ), 5015 $cell = $link.closest( "td" ); 5016 5017 setTimeout( function() { 5018 if ( $cell.find( "a:focus" ).length === 0 ) { 5019 $cell.find( "ul" ) 5020 .removeClass( evDetails ) 5021 .find( "a" ) 5022 .attr( "tabindex", "-1" ); 5023 } 5024 }, 5 ); 5025 }, 5026 5027 getLocaleDate = function( dateString ) { 5028 var date = new Date(), 5029 dateComponents = dateString.split( "-" ); 5030 5031 dateComponents[ 1 ] = dateComponents[ 1 ] - 1; // Convert to zero-based month 5032 date.setFullYear( dateComponents[ 0 ], dateComponents[ 1 ], dateComponents[ 2 ] ); 5033 5034 return date; 5035 }; 5036 5037// Bind the init event of the plugin 5038$document.on( "timerpoke.wb " + initEvent + " wb-redraw" + selector, selector, function( event ) { 5039 5040 var eventType = event.type, 5041 $elm = $( "#" + event.target.id ), 5042 calendarId = event.currentTarget.dataset.calevtSrc; 5043 5044 switch ( eventType ) { 5045 case "timerpoke": 5046 case "wb-init": 5047 init( event ); 5048 break; 5049 5050 case "wb-redraw": 5051 $( "#" + calendarId + " .wb-clndr" ).remove(); 5052 processEvents( $elm ); 5053 $elm.trigger( "wb-updated" + selector ); 5054 break; 5055 } 5056} ); 5057 5058$document.on( "wb-navigate.wb-clndr", selectorEvent, function( event ) { 5059 var lib = event.target.lib, 5060 $calEvent; 5061 5062 if ( lib ) { 5063 $calEvent = lib.$events; 5064 5065 if ( $calEvent ) { 5066 filterEvents.call( $calEvent, event.year, event.month ); 5067 5068 //TODO: Added for backwards compatibility. Remove in previous versions 5069 $calEvent.trigger( "wb-updated" + selector ); 5070 } 5071 } 5072} ); 5073 5074$document.on( "focusin focusout keydown", selectorEvent + " .cal-evt", function( event ) { 5075 var eventType = event.type, 5076 $link; 5077 5078 switch ( eventType ) { 5079 case "focusin": 5080 showEvents.call( event.target ); 5081 break; 5082 case "focusout": 5083 hideEvents.call( event.target ); 5084 break; 5085 case "keydown": 5086 $link = $( event.target ); 5087 if ( ( event.which === 13 || event.which === 32 ) && $link.hasClass( "cal-evt" ) ) { 5088 $( event.target ).next().find( "a:first" ).trigger( setFocusEvent ); 5089 } 5090 break; 5091 } 5092} ); 5093 5094$document.on( "keydown", selectorEvent + " td > ul li", function( event ) { 5095 var $item = $( event.currentTarget ), 5096 $toFocus, $itemParent; 5097 5098 switch ( event.which ) { 5099 case 38: 5100 $toFocus = $item.prev().find( "a" ); 5101 if ( $toFocus.length === 0 ) { 5102 $toFocus = $item.siblings( ":last" ).find( "a" ); 5103 } 5104 $toFocus.trigger( setFocusEvent ); 5105 break; 5106 case 40: 5107 $toFocus = $item.next().find( "a" ); 5108 if ( $toFocus.length === 0 ) { 5109 $toFocus = $item.siblings( ":first" ).find( "a" ); 5110 } 5111 $toFocus.trigger( setFocusEvent ); 5112 break; 5113 case 27: 5114 $itemParent = $item.closest( "td" ).children( "a" ); 5115 $itemParent.trigger( setFocusEvent ); 5116 break; 5117 } 5118} ); 5119 5120$document.on( "focusout", selectorEvent + " td > ul", function( event ) { 5121 hideEvents.call( event.target ); 5122} ); 5123 5124// Add the timer poke to initialize the plugin
5125wb.add( selector ); 5126 5127} )( jQuery, window, wb ); 5128 5129( function( $, DOMPurify, window, document, wb, undef ) { 5130 5131var i18nText, 5132 $document = wb.doc, 5133 selector = ".wb-clndr", 5134 navigateEvent = "wb-navigate" + selector, 5135 inited = false, 5136 currDate = new Date(), 5137 currYear = currDate.getFullYear(), 5138 currMonth = currDate.getMonth(), 5139 defaults = { 5140 year: currYear, 5141 month: currMonth, 5142 minDate: new Date( currYear, 0, 1 ), 5143 maxDate: new Date( currYear, 11, 31 ) 5144 }, 5145 $calBase, 5146 5147 init = function() { 5148 var i18n = wb.i18n, 5149 textWeekDayNames, textMonthNames; 5150 5151 i18nText = { 5152 monthNames: i18n( "mnths" ), 5153 prevMonth: i18n( "prvMnth" ), 5154 nextMonth: i18n( "nxtMnth" ), 5155 goToYear: i18n( "cal-goToYr" ), 5156 goToMonth: i18n( "cal-goToMnth" ), 5157 dayNames: i18n( "days" ), 5158 currDay: i18n( "currDay" ), 5159 format: i18n( "cal-format" ), 5160 calendar: i18n( "cal" ) 5161 }; 5162 5163 textWeekDayNames = i18nText.dayNames; 5164 textMonthNames = i18nText.monthNames; 5165 5166 $calBase = $( "<div class='wb-clndr' role='application' aria-label='" + i18nText.calendar + "'>" + 5167 "<div class='cal-nav'>" + 5168 "<span class='wb-inv current-month' aria-live='polite'></span>" + // Added for screen-readers 5169 "<button type='button' class='btn pull-left cal-month-prev'>" + 5170 "<span class='glyphicon glyphicon-arrow-left' aria-hidden='true'></span>" + 5171 "<span class='wb-inv'>" + i18nText.prevMonth + "<span></span></span>" + 5172 "</button>" + 5173 "<button type='button' class='btn pull-right cal-month-next'>" + 5174 "<span class='glyphicon glyphicon-arrow-right' aria-hidden='true'></span>" + 5175 "<span class='wb-inv'>" + i18nText.nextMonth + "<span></span></span>" + 5176 "</button>" + 5177 "<div class='form-group'>" + 5178 "<label><span class='wb-inv'>" + i18nText.goToYear + "</span><select class='cal-year'></select></label>\n" + 5179 "<label><span class='wb-inv'>" + i18nText.goToMonth + "</span><select class='cal-month'>" + 5180 ( function() { 5181 var months = "", 5182 m; 5183 5184 for ( m = 0; m < 12; m += 1 ) { 5185 months += "<option value='" + m + "'>" + textMonthNames[ m ] + "</option>"; 5186 } 5187 5188 return months; 5189 } )() + 5190 "</select></label>" + 5191 "</div>" + 5192 "</div>" + 5193 "<table>" + 5194 "<thead>" + 5195 "<tr>" + 5196 ( function() { 5197 var days = "", 5198 d; 5199 5200 for ( d = 0; d < 7; d += 1 ) { 5201 days += "<th><abbr title='" + textWeekDayNames[ d ] + "'>" + textWeekDayNames[ d ].substr( 0, 1 ) + "</abbr></th>"; 5202 } 5203 5204 return days; 5205 } )() + 5206 "</tr>" + 5207 "</thead>" + 5208 "<tbody class='cal-days'></tbody>" + 5209 "</table>" + 5210 "</div>" ); 5211 5212 inited = true; 5213 }, 5214 5215 createCalendar = function( $container, settings ) { 5216 var calendar = { 5217 reInit: initCalendar 5218 }, $calendarObj, calendarObj; 5219 5220 if ( !inited ) { 5221 init(); 5222 } 5223 5224 $calendarObj = $calBase.clone(); 5225 calendarObj = $calendarObj.get( 0 ); 5226 5227 calendar.$o = $calendarObj; 5228 calendar.o = calendarObj; 5229 calendarObj.lib = calendar; 5230 5231 $calendarObj.appendTo( $container ); 5232 5233 calendar.reInit( settings ); 5234 5235 return calendar; 5236 }, 5237 5238 initCalendar = function( settings ) { 5239 var years = "", 5240 $calendarObj = this.$o, 5241 $yearField, lastYear, y, defaultsPartial; 5242 5243 settings = settings || {}; 5244 5245 if ( settings.year !== undef && settings.month !== undef ) { 5246 defaultsPartial = { 5247 minDate: new Date( settings.year, 0, 1 ), 5248 maxDate: new Date( settings.year, 11, 31 ) 5249 }; 5250 $.extend( this, defaultsPartial, settings ); 5251 } else { 5252 $.extend( this, defaults ); 5253 } 5254 5255 //Generates the list of years 5256 lastYear = this.maxDate.getFullYear(); 5257 $yearField = $calendarObj.find( ".cal-year" ).empty(); 5258 for ( y = this.minDate.getFullYear(); y <= lastYear; y += 1 ) { 5259 years += "<option value='" + y + "'>" + y + "</option>"; 5260 } 5261 $yearField.append( years ); 5262 5263 $calendarObj 5264 .trigger( { 5265 type: navigateEvent, 5266 year: this.year, 5267 month: this.month, 5268 initEvent: true 5269 } ); 5270 }, 5271 5272 createDays = function( calendar, year, month ) { 5273 var $container = $( calendar ).find( ".cal-days" ), 5274 daysContainer = $container.get( 0 ), 5275 dayCount = 1, 5276 textCurrentDay = i18nText.currDay, 5277 lib = calendar.lib, 5278 minDate = lib.minDate, 5279 maxDate = lib.maxDate, 5280 callback = lib.daysCallback, 5281 row, cell, 5282 date, firstDay, lastDay, currYear, currMonth, currDay, week, day, className, isCurrentDate, isoDate, printDate, breakAt
5282End, days, inRange; 5283 5284 date = new Date( year, month, 1 ); 5285 5286 firstDay = date.getDay(); 5287 date.setMonth( month + 1, 0 ); 5288 lastDay = date.getDate(); 5289 5290 // Get the current date 5291 date = new Date(); 5292 currYear = date.getFullYear(); 5293 currMonth = date.getMonth(); 5294 currDay = date.getDate(); 5295 5296 // Clean all existing rows 5297 $container.empty(); 5298 5299 for ( week = 1; week < 7; week += 1 ) { 5300 row = daysContainer.insertRow(); 5301 5302 for ( day = 0; day < 7; day += 1 ) { 5303 5304 if ( ( week === 1 && day < firstDay ) || ( dayCount > lastDay ) ) { 5305 5306 // Creates empty cells | Cree les cellules vides 5307 cell = row.insertCell(); 5308 cell.classList.add( "cal-empty" ); 5309 cell.textContent = " "; 5310 5311 } else { 5312 5313 // Creates date cells | Cree les cellules de date 5314 isCurrentDate = ( dayCount === currDay && month === currMonth && year === currYear ); 5315 className = "cal-index-" + dayCount + ( isCurrentDate ? " cal-curr-day " : "" ); 5316 5317 date.setFullYear( year, month, dayCount ); 5318 isoDate = date.toLocalISOString().substr( 0, 10 ); 5319 printDate = displayDate( date ) + ( isCurrentDate ? "<span class='wb-inv'>" + textCurrentDay + "</span>" : "" ); 5320 5321 cell = row.insertCell(); 5322 cell.setAttribute( "class", className ); 5323 cell.innerHTML = DOMPurify.sanitize( "<time datetime='" + isoDate + "'>" + printDate + "</time>" ); 5324 5325 if ( dayCount >= lastDay ) { 5326 breakAtEnd = true; 5327 } 5328 5329 dayCount += 1; 5330 } 5331 } 5332 if ( breakAtEnd ) { 5333 break; 5334 } 5335 } 5336 5337 if ( callback ) { 5338 days = $container.find( "time" ); 5339 inRange = {}; 5340 5341 if ( year === minDate.getFullYear() && month === minDate.getMonth() ) { 5342 inRange.min = minDate.getDate() - 1; 5343 } 5344 5345 if ( year === maxDate.getFullYear() && month === maxDate.getMonth() ) { 5346 inRange.max = maxDate.getDate() - 1; 5347 } 5348 5349 callback.call( calendar.lib, year, month, days, inRange ); 5350 } 5351 }, 5352 5353 displayDate = function( date ) { 5354 var textWeekDayNames = i18nText.dayNames, 5355 textMonthNames = i18nText.monthNames; 5356 5357 return i18nText.format.replace( /\{ddd\}|\{d\}|\{M\}|\{Y\}/g, function( match ) { 5358 switch ( match ) { 5359 case "{ddd}": 5360 return textWeekDayNames[ parseInt( date.getDay(), 10 ) ]; 5361 case "{d}": 5362 return parseInt( date.getDate(), 10 ); 5363 case "{M}": 5364 return textMonthNames[ parseInt( date.getMonth(), 10 ) ]; 5365 case "{Y}": 5366 return date.getFullYear(); 5367 } 5368 } ); 5369 }; 5370 5371wb.calendar = { 5372 create: createCalendar 5373}; 5374 5375$document.on( navigateEvent, selector, function( event ) { 5376 var calendarObj = event.currentTarget, 5377 $calendar = $( calendarObj ), 5378 year = event.year, 5379 month = event.month, 5380 lib = calendarObj.lib, 5381 maxYear = lib.maxDate.getFullYear(), 5382 maxMonth = lib.maxDate.getMonth(), 5383 minYear = lib.minDate.getFullYear(), 5384 minMonth = lib.minDate.getMonth(), 5385 $prevArrow = $calendar.find( ".cal-month-prev" ), 5386 $nextArrow = $calendar.find( ".cal-month-next" ), 5387 $monthField = $calendar.find( ".cal-month" ), 5388 disabled = "disabled"; 5389 5390 if ( year !== undef ) { 5391 lib.year = year; 5392 } 5393 5394 if ( month !== undef ) { 5395 lib.month = month; 5396 } 5397 5398 //Update UI 5399 $calendar.find( ".cal-year" ).val( year ); 5400 5401 $monthField.val( month ); 5402 5403 $monthField.children( ":" + disabled ).removeAttr( disabled ); 5404 5405 if ( year < minYear || ( year === minYear && month <= minMonth ) ) { 5406 $prevArrow.attr( disabled, disabled ); 5407 } else { 5408 $prevArrow.removeAttr( disabled ); 5409 } 5410 5411 if ( year > maxYear || ( year === maxYear && month >= maxMonth ) ) { 5412 $nextArrow.attr( disabled, disabled ); 5413 } else { 5414 $nextArrow.removeAttr( disabled ); 5415 } 5416 5417 if ( year === minYear ) { 5418 $monthField.children( ":lt(" + minMonth + ")" ).attr( disabled, disabled ); 5419 } 5420 5421 if ( year === maxYear ) { 5422 $monthField.children( ":gt(" + maxMonth + ")" ).attr( disabled, disabled ); 5423 } 5424 5425 createDays( event.currentTarget, event.year, event.month ); 5426 5427 // Added declaration of current month in aria-live="polite" for screen-readers 5428 if ( !event.initEvent ) { 5429 $calendar.find( ".current-month" ).text( i18nText.monthNames[ month ] + " " + year ); 5430 } 5431} ); 5432 5433$document.on( "change", selector, function( event ) { 5434 var target = event.target, 5435 calendar = event.currentTarget, 5436 year, month; 5437 5438 switch ( target.className ) { 5439 case "cal-year": 5440 year = parseInt( target.value, 10 ); 5441 month = calendar.lib.month; 5442 break; 5443 case "cal-month": 5444 year = calendar.lib.year; 5445 month = parseInt( target.value, 10 ); 5446 break; 5447 } 5448 5449 $( calendar ).trigger( { 5450 type: navigateEvent, 5451 year: year, 5452 month: month 5453 } ); 5454} ); 5455 5456$document.on( "click", ".cal-month-prev, .cal-month-next", function( event ) { 5457 var $calendar = $( event.currentTarget ).closest( selector ), 5458 calendar = $calendar.get( 0 ), 5459 className = event.currentTarget.className, 5460 modifier = className.indexOf( "cal-month-prev" ) !== -1 ? -1 : 1, 5461 date = new Date( calendar.lib.year, calendar.lib.month + modifier, 1 ); 5462 5463 $calendar.trigger( { 5464 type: navigateEvent, 5465 year: date.getFullYear(), 5466 month: date.getMonth() 5467 } ); 5468 if ( wb.ie11 ) { 5469 $calendar.trigger( "focusin" ); 5470 } 5471} ); 5472 5473$document.on( "keydown", selector, function( event ) { 5474 var calendar = event.currentTarget, 5475 $days = $( event.currentTarget ).find( ".cal-days" ), 5476 target = event.target, 5477 which = event.which, 5478 lib = calendar.lib, 5479 date = new Date( lib.year, lib.month, 1 ), 5480 minDate = lib.minDate, 5481 maxDate = lib.maxDate, 5482 currentDate = new Date( date ), 5483 navigate = true, 5484 classMatch, isDayLink, lastDay, modifier, day; 5485 5486 if ( !event.altKey && !event.metaKey && which > 32 && which < 41 ) { 5487 5488 classMatch = target.parentNode.className.match( /cal-index-(\d{1,2})/ ); 5489 isDayLink = classMatch !== null; 5490 5491 //Key binding for the entire calendar 5492 switch ( which ) { 5493 5494 //page up 5495 case 33: 5496 date.setDate( minDate.getDate() ); 5497 5498 //page down 5499 /* falls through */ 5500 case 34: 5501 modifier = ( which === 33 ? -1 : 1 ); 5502 5503 if ( event.ctrlKey || event.shiftKey || event.altKey ) { 5504 date.setYear( date.getFullYear() + modifier ); 5505 } else { 5506 date.setMonth( date.getMonth() + modifier ); 5507 } 5508 break; 5509 } 5510 5511 //Key binding for navigating calendar days 5512 if ( isDayLink ) { 5513 day = parseInt( classMatch[ 1 ], 10 ); 5514 date.setMonth( date.getMonth() + 1, 0 ); 5515 lastDay = date.getDate(); 5516 date.setDate( day > lastDay ? lastDay : day ); 5517 5518 switch ( which ) { 5519 5520 // end / home 5521 case 35: 5522 date.setDate( lastDay ); 5523 break; 5524 case 36: 5525 date.setDate( 1 ); 5526 break; 5527 5528 // left / up / right / down arrows 5529 case 37: 5530 date.setDate( day - 1 ); 5531 break; 5532 case 38: 5533 date.setDate( day - 7 ); 5534 break; 5535 case 39: 5536 date.setDate( day + 1 ); 5537 break; 5538 case 40: 5539 date.setDate( day + 7 ); 5540 break; 5541 } 5542 } 5543 5544 if ( date < minDate || date > maxDate ) { 5545 if ( which === 35 ) { 5546 date.setDate( maxDate.getDate() ); 5547 } else if ( which === 36 ) { 5548 date = minDate; 5549 } else { 5550 navigate = false;
5551 } 5552 } 5553 5554 if ( navigate && ( date.getMonth() !== currentDate.getMonth() || date.getFullYear() !== currentDate.getFullYear() ) ) { 5555 $( calendar ).trigger( { 5556 type: navigateEvent, 5557 year: date.getFullYear(), 5558 month: date.getMonth() 5559 } ); 5560 } 5561 5562 if ( isDayLink ) { 5563 $days.find( ".cal-index-" + date.getDate() + " a:first" ).focus(); 5564 } 5565 5566 event.preventDefault(); 5567 return false; 5568 } 5569 5570} ); 5571 5572( function() { 5573 5574 function pad( number ) { 5575 if ( number < 10 ) { 5576 return "0" + number; 5577 } 5578 return number; 5579 } 5580 5581 Date.prototype.toLocalISOString = function() { 5582 var tz = this.getTimezoneOffset(); 5583 if ( tz === 0 ) { 5584 return this.toISOString(); 5585 } 5586 return this.getFullYear() + 5587 "-" + pad( this.getMonth() + 1 ) + 5588 "-" + pad( this.getDate() ) + 5589 "T" + pad( this.getHours() ) + 5590 ":" + pad( this.getMinutes() ) + 5591 ":" + pad( this.getSeconds() ) + 5592 "." + ( this.getMilliseconds() / 1000 ).toFixed( 3 ).slice( 2, 5 ) + 5593 ( tz < 0 ? "+" : "-" ) + 5594 pad( Math.floor( Math.abs( tz / 60 ) ) ) + 5595 ":" + pad( tz % 60 ); 5596 }; 5597 5598 if ( !Date.prototype.toISOString ) { 5599 Date.prototype.toISOString = function() { 5600 return this.getUTCFullYear() + 5601 "-" + pad( this.getUTCMonth() + 1 ) + 5602 "-" + pad( this.getUTCDate() ) + 5603 "T" + pad( this.getUTCHours() ) + 5604 ":" + pad( this.getUTCMinutes() ) + 5605 ":" + pad( this.getUTCSeconds() ) + 5606 "." + ( this.getUTCMilliseconds() / 1000 ).toFixed( 3 ).slice( 2, 5 ) + 5607 "Z"; 5608 }; 5609 } 5610}() ); 5611 5612} )( jQuery, DOMPurify, window, document, wb ); 5613 5614/** 5615 * Web Experience Toolkit (WET) / Boîte à outils de l'expérience Web (BOEW) 5616 * @title Charts and Graph 5617 * @overview Draw charts from an html simple and complex data table 5618 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 5619 * @author @duboisp 5620 * 5621 */ 5622( function( $, window, document, wb ) { 5623"use strict"; 5624 5625/** 5626 * Variable and function definitions. 5627 * These are global to the plugin - meaning that they will be initialized once per page, 5628 * not once per instance of plugin on the page. So, this is a good place to define 5629 * variables that are common to all instances of the plugin on a page. 5630 */ 5631var componentName = "wb-charts", 5632 selector = "." + componentName, 5633 initEvent = "wb-init" + selector, 5634 tableParsingEvent = "passiveparse.wb-tableparser", 5635 tableParsingCompleteEvent = "parsecomplete.wb-tableparser", 5636 $document = wb.doc, 5637 i18n, i18nText, 5638 5639 /** 5640 * Main Entry function to create the charts 5641 * @method createCharts 5642 * @param {jQuery DOM element} $elm table element use to create the chart 5643 */ 5644 createCharts = function( $elm ) { 5645 var allSeries = [], 5646 chartslabels = [], 5647 dataSeries = [], 5648 nbBarChart = 0, 5649 $caption = $( "caption", $elm ), 5650 captionHtml = $caption.html() || "", 5651 captionText = $caption.text() || "", 5652 valuePoint = 0, 5653 dataCellUnitRegExp = /[^+\-., 0-9]+[^\-+0-9]*/, 5654 lowestFlotDelta, $imgContainer, $placeHolder, 5655 $wetChartContainer, htmlPlaceHolder, figurehtml, 5656 cellValue, datacolgroupfound, dataGroup, header, 5657 i, iLength, j, jLength, parsedData, rIndex, currVectorOptions, 5658 currentRowGroup, reverseTblParsing, dataGroupVector, 5659 currentDataGroupVector, dataCell, previousDataCell, currDataVector, 5660 pieQuaterFlotSeries, optionFlot, optionsCharts, globalOptions, 5661 defaultsOptions = { 5662 5663 // Flot Global Options 5664 flot: { 5665 prefix: "wb-charts-", 5666 defaults: { 5667 colors: wb.drawColours, 5668 canvas: true, 5669 xaxis: { 5670 ticks: { } 5671 } 5672 }, 5673 line: { }, 5674 area: { 5675 lines: { 5676 show: true, 5677 fill: true 5678 } 5679 }, 5680 bar: { 5681 bars: { 5682 show: true, 5683 barWidth: 1, 5684 align: "center" 5685 } 5686 }, 5687 pie: { 5688 series: { 5689 pie: { 5690 show: true 5691 } 5692 }, 5693 fn: { 5694 "/series/pie/label/formatter": function( label, series ) { 5695 var textlabel; 5696 if ( !optionsCharts.decimal ) { 5697 textlabel = Math.round( series.percent ); 5698 } else { 5699 textlabel = Math.round( series.percent * Math.pow( 10, optionsCharts.decimal ) ); 5700 textlabel = textlabel / Math.pow( 10, optionsCharts.decimal ); 5701 } 5702 5703 if ( optionsCharts.nolegend ) { 5704 5705 // Add the series label 5706 textlabel = label + "<br />" + textlabel; 5707 } 5708 return textlabel + "%"; 5709 } 5710 } 5711 }, 5712 donut: { 5713 base: "pie", 5714 series: { 5715 pie: { 5716 radius: 1, 5717 label: { 5718 show: true, 5719 radius: 1, 5720 threshold: 0.08 5721 }, 5722 tilt: 0.5, 5723 innerRadius: 0.45, 5724 startAngle: 1 5725 } 5726 }, 5727 grid: { 5728 hoverable: true 5729 } 5730 }, 5731 slicelegend: { 5732 base: "pie", 5733 series: { 5734 pie: { 5735 radius: 1, 5736 label: { 5737 radius: 1, 5738 show: true, 5739 threshold: 0.05 5740 }, 5741 combine: { 5742 threshold: 0.05, 5743 color: "#555", 5744 label: i18nText.slicelegend 5745 } 5746 } 5747 }, 5748 fn: { 5749 "/series/pie/label/formatter": function( label ) { 5750 return label; 5751 } 5752 } 5753 } 5754 }, 5755 5756 // Flot Series Options 5757 series: { 5758 prefix: "wb-charts-", 5759 defaults: { }, 5760 line: { }, 5761 area: { 5762 lines: { 5763 show: true, 5764 fill: true 5765 } 5766 }, 5767 bar: { 5768 bars: { 5769 show: true, 5770 barWidth: 1, 5771 align: "center" 5772 } 5773 }, 5774 stacked: { 5775 base: "bar" 5776 } 5777 }, 5778 5779 // Wet-boew Charts Options 5780 charts: { 5781 prefix: "wb-charts-", 5782 defaults: { 5783
5784 // [string] Class name added at the figure element container 5785 graphclass: "wb-graph", 5786 5787 // [boolean] Wrap or not the table in a details/summary elements 5788 noencapsulation: false, 5789 5790 // [number] false means the deepest vector will be used for labelling 5791 labelposition: false, 5792 5793 // [number] false means the deepest vector will be used for calculate the reference 5794 referencevalue: false, 5795 5796 // [boolean] false means to move the legend from inside the charts to next to it 5797 legendinline: false, 5798 5799 // [boolean] true means that the legend will be destroyed and the label for pie chart will include the legend 5800 nolegend: false, 5801 5802 // [number] Literal number of displayed decimal for a pie charts 5803 decimal: 0, 5804 5805 // [number] Provide a default width for the charts that will be rendered 5806 width: $elm.width(), 5807 5808 // [number] Provide a default height for the charts that will be rendered 5809 height: $elm.height(), 5810 5811 // [boolean] Flag for defining if the data table should be read in reverse compared to HTML spec 5812 reversettblparsing: false, 5813 fn: { 5814 "/getcellvalue": function( elem ) { 5815 5816 // Get the number from the data cell, #3267 5817 var cellValue = $.trim( elem.dataset.wbChartsValue || $( elem ).text() ); 5818 return [ 5819 parseFloat( cellValue.replace( /(\d{1,3}(?:(?: |,)\d{3})*)(?:(?:.|,)(\d{1,2}))?$/, function( a, b, c ) { 5820 return b.replace( / |,/g, "" ) + "." + c || "0"; 5821 } ), 10 ), 5822 cellValue.match( dataCellUnitRegExp ) 5823 ]; 5824 } 5825 } 5826 }, 5827 donut: { 5828 decimal: 1 5829 } 5830 } 5831 }; 5832 5833 /** 5834 * A little function to overwrite and add preset into the default options 5835 * 5836 * @method overwriteDefaultsOptions 5837 * @param {string} scopekey - Key that represent the subject of the setting, [flot, charts, series,...] 5838 * @param {json object} target - DefaultOptions that will be overwritten 5839 * @param {json object} object - User defined object for overwriting options 5840 * @return {json object} - Return the new object 5841 */ 5842 function overwriteDefaultsOptions( scopekey, target, object ) { 5843 var cachedObj, key; 5844 5845 cachedObj = object[ scopekey ]; 5846 if ( !cachedObj ) { 5847 return target; 5848 } 5849 for ( key in cachedObj ) { 5850 if ( !Object.prototype.hasOwnProperty.call( cachedObj, key ) ) { 5851 continue; 5852 } 5853 target[ scopekey ][ key ] = cachedObj[ key ]; 5854 } 5855 return target; 5856 } 5857 5858 // User defined options 5859 if ( !window.chartsGraphOpts ) { 5860 globalOptions = window[ componentName ]; 5861 5862 // Global setting 5863 if ( globalOptions ) { 5864 overwriteDefaultsOptions( "flot", defaultsOptions, globalOptions ); 5865 overwriteDefaultsOptions( "series", defaultsOptions, globalOptions ); 5866 overwriteDefaultsOptions( "charts", defaultsOptions, globalOptions ); 5867 } 5868 5869 // Save the setting here in a case of a second graphic on the same page 5870 window.chartsGraphOpts = defaultsOptions; 5871 } 5872 defaultsOptions = window.chartsGraphOpts; 5873 5874 /** 5875 * A little function to ease the web editor life 5876 * 5877 * Apply preset defined by a set of space-separated tokens from a baseline json object and at the same time extend the result by using the HTML5 data attribute 5878 * 5879 * @method applyPreset 5880 * @param {json object} baseline - Base line json object that includes predefined and userdefined preset 5881 * @param {jQuery} $elem - Element on which the class attribute will be taken for a set of space-separated tokens 5882 * @param {string} attribute - Name of the HTML5 data attribute for extending the object at the end 5883 * @return {json object} - Return a new object build from the ```baseline``` or ```baseline.default``` object with the preset applied. 5884 */ 5885 function applyPreset( baseline, $elem, attribute ) { 5886 5887 var config = $.extend( true, {}, baseline.defaults || baseline ), 5888 fn = $.extend( true, {}, baseline.defaults && baseline.defaults.fn || { } ), 5889 tokens = $elem.attr( "class" ) || "", 5890 tblTokens, i, iLength, token, tokenLength, 5891 5892 // Prefix used in front of the token 5893 prefix, prefixLength,
5894 preset, key, tblFn, localKey, currObj; 5895 5896 if ( tokens.length ) { 5897 5898 prefix = ( baseline.prefix || "" ); 5899 prefixLength = prefix.length; 5900 5901 // split the set of space-separated tokens 5902 tblTokens = tokens.split( " " ); 5903 5904 for ( i = 0, iLength = tblTokens.length; i !== iLength; i += 1 ) { 5905 5906 // Get the current token 5907 token = tblTokens[ i ]; 5908 tokenLength = token.length; 5909 5910 // Remove the token is used 5911 if ( tokenLength <= prefixLength || token.slice( 0, prefixLength ) !== prefix ) { 5912 continue; 5913 } 5914 token = token.slice( prefixLength, tokenLength ); 5915 5916 preset = baseline[ token ]; 5917 5918 // Apply the preset 5919 if ( preset ) { 5920 if ( preset.base ) { 5921 5922 // Like setting herited from a parent config 5923 config = $.extend( true, config, baseline[ preset.base ] ); 5924 fn = $.extend( true, fn, baseline[ preset.base ].fn || { } ); 5925 } 5926 config = $.extend( true, config, preset ); 5927 fn = $.extend( true, fn, preset.fn || { } ); 5928 } 5929 } 5930 } 5931 5932 // Extend the config from the element @data attribute 5933 config = $.extend( true, config, wb.getData( $elem, attribute ) ); 5934 5935 // Merge and override the function. 5936 for ( key in fn ) { 5937 if ( !Object.prototype.hasOwnProperty.call( fn, key ) ) { 5938 continue; 5939 } 5940 tblFn = key.split( "/" ); 5941 currObj = config; 5942 for ( i = 0, iLength = tblFn.length - 1; i !== iLength; i += 1 ) { 5943 localKey = tblFn.shift(); 5944 if ( localKey === "" ) { 5945 continue; 5946 } 5947 if ( !currObj[ localKey ] ) { 5948 currObj[ localKey ] = { }; 5949 } 5950 currObj = currObj[ localKey ]; 5951 } 5952 localKey = tblFn.shift(); 5953 currObj[ localKey ] = fn[ key ]; 5954 } 5955 return config; 5956 } 5957 5958 // Apply any preset 5959 optionFlot = applyPreset( defaultsOptions.flot, $elm, "flot" ); 5960 5961 // Apply any preset 5962 optionsCharts = applyPreset( defaultsOptions.charts, $elm, componentName ); 5963 5964 // Fix default width and height in case the table is hidden or too small. 5965 optionsCharts.width = ( optionsCharts.width && optionsCharts.width > 250 ? optionsCharts.width : 250 ); 5966 optionsCharts.height = ( optionsCharts.height && optionsCharts.height > 250 ? optionsCharts.height : 250 ); 5967 5968 /** 5969 * @method getColumnGroupHeaderCalculateSteps 5970 * @param {object} colGroupHead - Column Group Header Object from the table parser 5971 * @param {number} referenceValuePosition - Vector position use as reference for defining the steps, zero based position 5972 */ 5973 function getColumnGroupHeaderCalculateSteps( colGroupHead, referenceValuePosition ) { 5974 5975 // Get the appropriate ticks 5976 var headerCell, i, iLen, 5977 calcStep = 1, 5978 colRefValue, colCurent; 5979 5980 if ( !colGroupHead ) { 5981 5982 // There is an error. Possibly the series are missing a header. 5983 return; 5984 } 5985 5986 colRefValue = colGroupHead.col[ referenceValuePosition ]; 5987 colCurent = colGroupHead.col[ 0 ]; 5988 5989 for ( i = 0, iLen = colRefValue.cell.length; i !== iLen; i += 1 ) { 5990 5991 headerCell = colRefValue.cell[ i ]; 5992 5993 if ( i === 0 || ( i > 0 && colCurent.cell[ i - 1 ].uid !== headerCell.uid ) ) { 5994 5995 if ( headerCell.rowgroup && headerCell.rowgroup.type === 3 ) { 5996 5997 // We only process the first column data group 5998 break; 5999 } 6000 6001 if ( headerCell.type === 1 || headerCell.type === 7 ) { 6002 if ( headerCell.child.length !== 0 ) { 6003 calcStep = calcStep * groupHeaderCalculateStepsRecursive( headerCell, 1 ); 6004 } 6005 } 6006 } 6007 } 6008 6009 return calcStep; 6010 } 6011 6012 /** 6013 * @method getRowGroupHeaderCalculateSteps 6014 * @param {object} rowGroupHead - Row Group Header Object from the table parser 6015 * @param {number} referenceValuePosition - Vector position use as reference for defining the steps, zero based position 6016 * @param {number} dataColgroupStart - Column position where the column data group start 6017 */ 6018 function getRowGroupHeaderCalculateSteps( rowGroupHead, referenceValuePosition, dataColgroupStart ) { 6019 6020 // Find the range of the first data colgroup 6021 var headerCell, i, iLen, 6022 calcStep = 1, 6023 rowRefValueCells = rowGroupHead[ referenceValuePosition ].elem.cells; 6024 6025 for ( i = 0, iLen = rowRefValueCells.length; i !== iLen; i += 1 ) { 6026 6027 headerCell = $( rowRefValueCells[ i ] ).data().tblparser; 6028 6029 if ( headerCell.colgroup && headerCell.colgroup.type === 3 ) { 6030 6031 // We only process the first column data group 6032 break; 6033 } 6034 6035 if ( headerCell.colpos >= dataColgroupStart && ( headerCell.type === 1 || headerCell.type === 7 ) ) { 6036 if ( headerCell.child.length !== 0 ) { 6037 calcStep = calcStep * headerCell.child.length * groupHeaderCalculateStepsRecursive( headerCell, 1 ); 6038 } 6039 } 6040 } 6041 6042 return calcStep; 6043 } 6044 6045 /** 6046 * @method groupHeaderCalculateStepsRecursive 6047 * @param {object} headerCell - Header cell object from the table parser 6048 * @param {number} refValue - Reference Value (Dénominateur) of headerCell 6049 */ 6050 function groupHeaderCalculateStepsRecursive( headerCell, refValue ) { 6051 var childLength = headerCell.child.length, 6052 calcStep = 1, 6053 kIndex, subRefValue, headerCellChild; 6054 6055 if ( childLength === 0 ) { 6056 return calcStep; 6057 } 6058 6059 subRefValue = childLength * refValue; 6060 6061 calcStep = calcStep * subRefValue; 6062 6063 for ( kIndex = 0; kIndex !== childLength; kIndex += 1 ) { 6064 headerCellChild = headerCell.child[ kIndex ]; 6065 if ( headerCellChild.child.length !== 0 ) { 6066 calcStep = calcStep * groupHeaderCalculateStepsRecursive( headerCellChild, subRefValue ); 6067 } 6068 } 6069 return calcStep; 6070 } 6071 6072 /** 6073 * Set the inner step value (divisor) of an header cell and for his child 6074 * 6075 * @method setInnerStepValues 6076 * @param {object} vectorHead - Group Header Object from the table parser 6077 * @param {number} headerLevel - Hiearchical Level of heading 6078 * @param {number} stepsValue - Step Value for the reference value vector 6079 * @param {number} referenceValue - Reference Value Vector ID 6080 * @param {number} dataColgroupStart - Column position where the column data group start 6081 * 6082 */ 6083 function setInnerStepValues( vectorHead, headerLevel, stepsValue, referenceValue, dataColgroupStart ) { 6084 var i, iLength,
6085 headerCell, 6086 cumulativeValue = 0; 6087 6088 for ( i = 0, iLength = vectorHead.cell.length; i !== iLength; i += 1 ) { 6089 headerCell = vectorHead.cell[ i ]; 6090 if ( i !== 0 && headerCell.uid === vectorHead.cell[ i - 1 ].uid || ( dataColgroupStart && headerCell.colpos < dataColgroupStart ) ) { 6091 continue; 6092 } 6093 6094 // Only process the first data group 6095 if ( !reverseTblParsing ) { 6096 if ( headerCell.colgroup && headerCell.colgroup.type === 3 ) { 6097 break; 6098 } 6099 } else { 6100 if ( headerCell.rowgroup && headerCell.rowgroup.type === 3 ) { 6101 break; 6102 } 6103 } 6104 if ( headerCell.child > 0 && headerLevel < referenceValue ) { 6105 headerCell.flotDelta = stepsValue * headerCell.child.length; 6106 } else { 6107 headerCell.flotDelta = stepsValue; 6108 } 6109 if ( headerCell.type === 1 || headerCell.type === 7 ) { 6110 6111 if ( !lowestFlotDelta || headerCell.flotDelta < lowestFlotDelta ) { 6112 lowestFlotDelta = headerCell.flotDelta; 6113 } 6114 headerCell.flotValue = cumulativeValue; 6115 6116 cumulativeValue = cumulativeValue + stepsValue; 6117 6118 if ( headerCell.child.length > 0 ) { 6119 setInnerStepValuesChildRecursive( headerCell, headerLevel, stepsValue, referenceValue ); 6120 } 6121 } 6122 } 6123 } 6124 6125 /** 6126 * Recursize - Set the inner step value (divisor) of an sub header cell 6127 * 6128 * @method setInnerStepValuesChildRecursive 6129 * @param {object} headerCell - Header cell object from the table parser 6130 * @param {number} headerLevel - Hiearchical Level of heading 6131 * @param {number} stepsValue - Specific Step Value applied for current headerCell 6132 * @param {number} referenceValue - Reference Value Vector ID 6133 */ 6134 function setInnerStepValuesChildRecursive( headerCell, headerLevel, stepsValue, referenceValue ) { 6135 var cumulativeValue = 0, 6136 6137 // Step Values for childs header in headerCell 6138 flotDelta, 6139 i, iLength, currentHeaderCellChild; 6140 6141 headerLevel += 1; 6142 cumulativeValue = headerCell.flotValue; 6143 flotDelta = stepsValue / headerCell.child.length; 6144 6145 // Use to calculate the largest width for a bar in a bar chart 6146 if ( !lowestFlotDelta || flotDelta < lowestFlotDelta ) { 6147 lowestFlotDelta = flotDelta; 6148 } 6149 6150 for ( i = 0, iLength = headerCell.child.length; i !== iLength; i += 1 ) { 6151 currentHeaderCellChild = headerCell.child[ i ]; 6152 if ( headerLevel < referenceValue ) { 6153 currentHeaderCellChild.flotDelta = flotDelta * currentHeaderCellChild.child.length; 6154 } else { 6155 currentHeaderCellChild.flotDelta = flotDelta; 6156 } 6157 currentHeaderCellChild.flotValue = cumulativeValue; 6158 if ( currentHeaderCellChild.child.length > 0 ) { 6159 setInnerStepValuesChildRecursive( currentHeaderCellChild, headerLevel, flotDelta, referenceValue ); 6160 } 6161 cumulativeValue = cumulativeValue + flotDelta; 6162 } 6163 } 6164 6165 /** 6166 * Set the header cell step value (flotDelta) for vector that regroup more than one reference 6167 * 6168 * @method setUpperStepValues 6169 * @param {object} vectorHead - Group Header Object from the table parser 6170 * @param {number} referenceValue - Reference Value Vector ID 6171 */ 6172 function setUpperStepValues( vectorHead, referenceValue ) { 6173 var i, k, m, kLen, mLen, 6174 cumulativeValue, 6175 currentCell, 6176 currentCellChild, 6177 currentVectorHead; 6178 6179 // Calculate upper-step for cells that are 6180 // less precise than the reference value vector 6181 for ( i = referenceValue - 1; i !== -1; i -= 1 ) { 6182 currentVectorHead = vectorHead[ i ]; 6183 6184 for ( k = 0, kLen = currentVectorHead.cell.length; k !== kLen; k += 1 ) { 6185 currentCell = currentVectorHead.cell[ k ]; 6186 6187 if ( currentCell.flotDelta || k > 0 && 6188 currentCell.uid === currentVectorHead.cell[ k - 1 ].uid ) { 6189 6190 continue; 6191 } 6192 6193 if ( !( currentCell.type === 1 || currentCell.type === 7 ) ) { 6194 continue; 6195 } 6196 6197 cumulativeValue = 0; 6198 for ( m = 0, mLen = currentCell.child.length; m !== mLen; m += 1 ) { 6199 currentCellChild = currentCell.child[ m ]; 6200 6201 cumulativeValue = currentCellChild.flotDelta; 6202 if ( !currentCell.flotValue ) { 6203 currentCell.flotValue = currentCellChild.flotValue; 6204 } 6205 } 6206 currentCell.flotDelta = cumulativeValue; 6207 } 6208 } 6209 } 6210 6211 /** 6212 * Get labels for a specific vector 6213 * 6214 * @method getLabels 6215 * @param {object} labelVector - Vector Header Object from the table parser 6216 * @param {number} dataColgroupStart - Column position where the column data group start 6217 */ 6218 function getLabels( labelVector, dataColgroupStart ) { 6219 var labels = [], 6220 i, iLen, currentCell; 6221 6222 for ( i = 0, iLen = labelVector.cell.length; i !== iLen; i += 1 ) { 6223 currentCell = labelVector.cell[ i ]; 6224 6225 if ( ( i !== 0 && currentCell.uid === labelVector.cell[ i - 1 ].uid ) || 6226 ( !( currentCell.type === 1 || currentCell.type === 7 ) ) || 6227 ( dataColgroupStart && currentCell.colpos < dataColgroupStart ) ) { 6228 continue; 6229 } 6230 6231 labels.push( [ currentCell.flotValue, $( currentCell.elem ).text() ] ); 6232 } 6233 return labels; 6234 } 6235 6236 /** 6237 * Get the vector that would be used for labelling x-axis 6238 * 6239 * @method getlabelsVectorPosition 6240 * @param {object[]} arrVectorHeaders - Collection of vector headers 6241 */ 6242 function getlabelsVectorPosition( arrVectorHeaders ) { 6243 var labelPosition = optionsCharts.labelposition; 6244 return ( !labelPosition || ( labelPosition && labelPosition > arrVectorHeaders.length ) ? 6245 parsedData.theadRowStack.length : labelPosition ) - 1; 6246 } 6247 6248 /** 6249 * Get the vertical label and set the appropriate header cell x-axis Value 6250 * 6251 * @method verticalLabels 6252 * @param {object} parsedData - Generic object generated by the table parser 6253 */ 6254 function verticalLabels( parsedData ) { 6255 6256 // Get the appropriate ticks 6257 var headerlevel = 0, 6258 labelsVectorPosition, stepsValue, columnReferenceValue; 6259 6260 if ( !reverseTblParsing || ( reverseTblParsing && optionsCharts.referencevalue === false ) ) { 6261 columnReferenceValue = parsedData.colgrouphead.col.length; 6262 } else { 6263 columnReferenceValue = optionsCharts.referencevalue; 6264 } 6265 6266 columnReferenceValue = columnReferenceValue - 1; 6267 6268 stepsValue = getColumnGroupHeaderCalculateSteps( parsedData.colgrouphead, columnReferenceValue ); 6269 6270 if ( !reverseTblParsing ) { 6271 labelsVectorPosition = parsedData.colgrouphead.col.length - 1; 6272 } else { 6273 labelsVectorPosition = getlabelsVectorPosition( parsedData.colgrouphead.col ); 6274 } 6275 6276 headerlevel = columnReferenceValue; 6277 6278 // Calculate inner-step for cells that are more precise than the reference value vector 6279 setInnerStepValues( parsedData.colgrouphead.col[ columnReferenceValue ], headerlevel, stepsValue, columnReferenceValue ); 6280 6281 // Calculate upper-step for cells that are less precise than the reference value vector 6282 setUpperStepValues( parsedData.colgrouphead.col, columnReferenceValue ); 6283 6284 // Get the labelling 6285 return getLabels( parsedData.colgrouphead.col[ labelsVectorPosition ] ); 6286 } 6287 6288 /** 6289 * Get the horizontal label and set the appropriate header cell x-axis Value 6290 * 6291 * @method horizontalLabels 6292 * @param {object} parsedData - Generic object generated by the table parser 6293 */ 6294 function horizontalLabels( parsedData ) { 6295 6296 // Find the range of the first data colgroup 6297 var dataColgroupStart = -1, 6298 headerlevel = 0, 6299 theadRowStack = parsedData.theadRowStack, 6300 i, iLength, labelsVectorPosition, 6301 stepsValue, rowReferenceValue; 6302 6303 if ( !theadRowStack ) { 6304 return; 6305 } 6306 6307 for ( i = 0, iLength = parsedData.colgroup.length; i !== iLength; i += 1 ) { 6308 if ( parsedData.colgroup[ i ].type === 2 ) { 6309 dataColgroupStart = parsedData.colgroup[ i ].start; 6310 break; 6311 } 6312 } 6313 6314 if ( ( !reverseTblParsing && optionsCharts.referencevalue === false ) || reverseTblParsing ) { 6315 rowReferenceValue = theadRowStack.length; 6316 } else { 6317 rowReferenceValue = optionsCharts.referencevalue; 6318 } 6319 6320 rowReferenceValue = rowReferenceValue - 1; 6321 6322 stepsValue = getRowGroupHeaderCalculateSteps( theadRowStack, rowReferenceValue, dataColgroupStart ); 6323 6324 if ( !reverseTblParsing ) { 6325 labelsVectorPosition = getlabelsVectorPosition( theadRowStack ); 6326 } else { 6327 labelsVectorPosition = theadRowStack.length - 1; 6328 } 6329 6330 headerlevel = rowReferenceValue; 6331 6332 // Calculate inner-step for cells that are more precise than the reference value vector 6333 setInnerStepValues( theadRowStack[ rowReferenceValue ], headerlevel, stepsValue, rowReferenceValue, dataColgroupStart ); 6334 6335 // Calculate upper-step for cells that are less precise than the reference value vector 6336 setUpperStepValues( theadRowStack, rowReferenceValue ); 6337 6338 // Get the labelling 6339 return getLabels( theadRowStack[ labelsVectorPosition ], dataColgroupStart ); 6340 6341 } 6342 6343 /** 6344 * Wrap the table into a smart details/summary element 6345 *
6346 * @method wrapTableIntoDetails 6347 */ 6348 function wrapTableIntoDetails() { 6349 var $summary; 6350 6351 if ( !captionHtml.length ) { 6352 return; 6353 } 6354 6355 $summary = $( "<summary>" + captionHtml + i18nText.tableMention + "</summary>" ); 6356 $elm 6357 .wrap( "<details></details>" ) 6358 .before( $summary ); 6359 6360 $summary.trigger( "wb-init.wb-details" ); 6361 } 6362 6363 function createContainer( withDimension ) { 6364 $elm 6365 .wrap( "<figure class='" + optionsCharts.graphclass + "'/>" ) 6366 .before( 6367 6368 // Copy to the inner table caption 6369 ( captionHtml.length ? "<figcaption>" + captionHtml + "</figcaption>" : "" ) + 6370 6371 // Image Container 6372 "<div role='img' aria-label='" + captionText + i18nText.tableFollowing + "'" + 6373 6374 // Add Dimension 6375 ( withDimension ? "style='height:" + optionsCharts.height + 6376 "px; width:" + optionsCharts.width + "px'" : "" ) + "></div>" 6377 ); 6378 6379 return $( "div:eq(0)", $elm.parent() ); 6380 } 6381 6382 // Retrieve the parsed data 6383 parsedData = $elm.data().tblparser; 6384 6385 // Reverse table parsing 6386 reverseTblParsing = optionsCharts.reversettblparsing; 6387 6388 // first data row group 6389 currentRowGroup = parsedData.lstrowgroup[ 0 ]; 6390 6391 if ( optionFlot.series && optionFlot.series.pie ) { 6392 6393 // WET Charts placeholder 6394 $wetChartContainer = createContainer( false ); 6395 6396 // Flot pie chart placeholder 6397 htmlPlaceHolder = "<div style='height:" + optionsCharts.height + 6398 "px; width:" + optionsCharts.width + "px'></div>"; 6399 6400 if ( !reverseTblParsing ) { 6401 6402 // If normal parsing 6403 dataGroup = parsedData.colgroup[ 0 ].type === 1 ? 6404 parsedData.colgroup[ 1 ] : 6405 parsedData.colgroup[ 0 ]; 6406 6407 rIndex = currentRowGroup.row.length - 1; 6408 } else { 6409 6410 // If reverse parsing 6411 dataGroup = currentRowGroup; 6412 rIndex = ( parsedData.colgroup[ 0 ].type === 1 ? 6413 parsedData.colgroup[ 1 ].col.length : 6414 parsedData.colgroup[ 0 ].col.length ) - 1; 6415 } 6416 6417 for ( rIndex; rIndex >= 0; rIndex -= 1 ) { 6418 6419 dataGroupVector = !reverseTblParsing ? dataGroup.col : dataGroup.row; 6420 6421 // For each row or column 6422 for ( i = 0, iLength = dataGroupVector.length; i !== iLength; i += 1 ) { 6423 dataSeries = []; 6424 valuePoint = 0; 6425 currentDataGroupVector = dataGroupVector[ i ]; 6426 6427 // For each cells 6428 for ( j = 0, jLength = currentDataGroupVector.cell.length; j !== jLength; j += 1 ) { 6429 6430 dataCell = currentDataGroupVector.cell[ j ]; 6431 6432 // Skip the column if 6433 if ( reverseTblParsing && dataCell.col.type === 1 ) { 6434 continue; 6435 } 6436 6437 previousDataCell = undefined; 6438 if ( j !== 0 ) { 6439 previousDataCell = currentDataGroupVector.cell[ j - 1 ]; 6440 } 6441 6442 // Verify if the selected cell still in the scope of a data group in his another axes (eg. row/col) 6443 // Verify if we are still in the same datagroup as the previous data cell 6444 if ( ( !reverseTblParsing && ( dataCell.row.type !== 2 || ( previousDataCell && 6445 previousDataCell.rowgroup.uid !== dataCell.rowgroup.uid ) ) ) || 6446 ( reverseTblParsing && ( dataCell.col.type !== 2 ) || ( previousDataCell && 6447 previousDataCell.col.type !== 1 && 6448 previousDataCell.col.groupstruct.uid !== dataCell.col.groupstruct.uid ) ) ) { 6449 break; 6450 } 6451 6452 // Gets the value 6453 header = !reverseTblParsing ? dataCell.row.header : dataCell.col.header; 6454 6455 cellValue = optionsCharts.getcellvalue( !reverseTblParsing ? 6456 currentDataGroupVector.cell[ rIndex ].elem : 6457 currentDataGroupVector.datacell[ rIndex ].elem ); 6458 6459 dataSeries.push( 6460 [ 6461 valuePoint, 6462 typeof cellValue === "object" ? 6463 cellValue[ 0 ] : 6464 cellValue 6465 ] 6466 ); 6467 6468 valuePoint += header[ header.length - 1 ].flotDelta; 6469 6470 break; 6471 } 6472 6473 pieQuaterFlotSeries = { }; 6474 6475 // Get the setting from the associative cell header 6476 dataCell = !reverseTblParsing ? 6477 currentDataGroupVector.cell[ rIndex ] : 6478 currentDataGroupVector.datacell[ rIndex ]; 6479 header = !reverseTblParsing ? 6480 dataCell.col.header : 6481 dataCell.row.header; 6482 header = header[ header.length - 1 ]; 6483 6484 // Apply any preset 6485 pieQuaterFlotSeries = applyPreset( defaultsOptions.series, $( header.elem ), "flot" ); 6486 6487 // Set the data issue from the table 6488 pieQuaterFlotSeries.data = dataSeries; 6489 pieQuaterFlotSeries.label = ( !reverseTblParsing ? 6490 $( currentDataGroupVector.dataheader[ currentDataGroupVector.dataheader.length - 1 ].elem ).text() : 6491 $( currentDataGroupVector.header[ currentDataGroupVector.header.length - 1 ].elem ).text() ); 6492 6493 // Add the series 6494 allSeries.push( pieQuaterFlotSeries ); 6495 } 6496 6497 // Create a sub Figure or use the main one 6498 if ( currentRowGroup.row.length === 1 && 6499 ( currentRowGroup.row[ 0 ].header[ 0 ].elem.innerHTML === captionHtml || 6500 currentRowGroup.row[ 0 ].header.length === 0 ) ) { 6501 6502 $placeHolder = $wetChartContainer; 6503 $placeHolder.css( { 6504 height: optionsCharts.height, 6505 width: optionsCharts.width 6506 } ); 6507 6508 } else { 6509 6510 header = !reverseTblParsing ? dataCell.row.header : dataCell.col.header; 6511 6512 figurehtml = "<figure><figcaption>" + 6513 header[ header.length - 1 ].elem.innerHTML + 6514 "</figcaption>" + htmlPlaceHolder + "</figure>"; 6515 6516 $wetChartContainer.append( $( figurehtml ) ); 6517 6518 $placeHolder = $( "div:last()", $wetChartContainer ); 6519 } 6520 6521 // Create the graphic 6522 $.plot( $placeHolder, allSeries, optionFlot ); 6523 6524 if ( !optionsCharts.legendinline ) { 6525 6526 // Move the legend under the graphic 6527 $( ".legend", $placeHolder ).appendTo( $wetChartContainer ); 6528 } 6529 6530 allSeries = []; 6531 } 6532 6533 if ( optionsCharts.nolegend ) { 6534 6535 // Remove the legend 6536 $( ".legend", $wetChartContainer ).remove(); 6537 } 6538 if ( !optionsCharts.legendinline ) { 6539 6540 // Fix the legend that appear under the graphic 6541 $( ".legend > div", $wetChartContainer ).remove(); 6542 $( ".legend > table", $wetChartContainer ).removeAttr( "style" ).addClass( "font-small" ); 6543 $( ".legend", $placeHolder ).appendTo( $imgContainer ); 6544 } 6545 6546 // Remove any "pieLabel" ids set by the flotPie.js plugin at line #457 6547 $( ".pieLabel" ).removeAttr( "id" ); 6548 6549 if ( !optionsCharts.noencapsulation ) {
6550 wrapTableIntoDetails(); 6551 } 6552 6553 return; 6554 } 6555 6556 if ( !reverseTblParsing ) { 6557 6558 // If normal parsing 6559 dataGroup = currentRowGroup; 6560 rIndex = ( parsedData.colgroup[ 0 ].type === 1 ? 6561 parsedData.colgroup[ 1 ].col.length : 6562 parsedData.colgroup[ 0 ].col.length ) - 1; 6563 chartslabels = horizontalLabels( parsedData ); 6564 } else { 6565 6566 // If reverse parsing 6567 dataGroup = parsedData.colgroup[ 0 ].type === 1 ? 6568 parsedData.colgroup[ 1 ] : 6569 parsedData.colgroup[ 0 ]; 6570 rIndex = currentRowGroup.row.length - 1; 6571 chartslabels = verticalLabels( parsedData ); 6572 } 6573 6574 // Add the labels at the Flot options 6575 optionFlot.xaxis.ticks = chartslabels; 6576 6577 dataGroupVector = !reverseTblParsing ? dataGroup.row : dataGroup.col; 6578 6579 // Count the number of bar charts, 6580 for ( i = 0, iLength = dataGroupVector.length; i !== iLength; i += 1 ) { 6581 currentDataGroupVector = dataGroupVector[ i ]; 6582 currDataVector = currentDataGroupVector.header[ currentDataGroupVector.header.length - 1 ]; 6583 6584 // Apply any preset 6585 currVectorOptions = applyPreset( defaultsOptions.series, $( currDataVector.elem ), "flot" ); 6586 6587 if ( currVectorOptions.bars || ( optionFlot.bars && !currVectorOptions.lines ) ) { 6588 6589 // Count number of bars, this number is use to calculate the bar width. 6590 nbBarChart += 1; 6591 6592 // Set a default setting specially for bar charts 6593 if ( !currVectorOptions.bars ) { 6594 currVectorOptions.bars = { show: true, barWidth: 0.9 }; 6595 } 6596 6597 // Set a default order for orderBars flot plugin 6598 if ( !currVectorOptions.bars.order ) { 6599 currVectorOptions.bars.order = nbBarChart; 6600 } 6601 } 6602 6603 // cache the compiled setting 6604 currDataVector.chartOption = currVectorOptions; 6605 } 6606 6607 // First rowgroup assume is a data row group. 6608 // For all row.... 6609 for ( i = 0, iLength = dataGroupVector.length; i !== iLength; i += 1 ) { 6610 dataSeries = []; 6611 datacolgroupfound = 0; 6612 valuePoint = 0; 6613 currDataVector = dataGroupVector[ i ]; 6614 6615 currVectorOptions = currDataVector.header[ currDataVector.header.length - 1 ].chartOption; 6616 6617 // For each cells 6618 for ( j = 0, jLength = currDataVector.cell.length; j !== jLength; j += 1 ) { 6619 6620 dataCell = currDataVector.cell[ j ]; 6621 6622 if ( datacolgroupfound > 1 && dataCell.col.groupstruct.type !== 2 ) { 6623 break; 6624 } 6625 6626 if ( ( !reverseTblParsing && dataCell.col.groupstruct.type === 2 ) || 6627 ( reverseTblParsing && dataCell.row.rowgroup.type === 2 ) ) { 6628 6629 // Gets the value 6630 header = !reverseTblParsing ? dataCell.col.header : dataCell.row.header; 6631 6632 cellValue = optionsCharts.getcellvalue( dataCell.elem ); 6633 6634 // Add the data point 6635 dataSeries.push( 6636 [ 6637 valuePoint, 6638 typeof cellValue === "object" ? 6639 cellValue[ 0 ] : 6640 cellValue 6641 ] 6642 ); 6643 valuePoint += header[ header.length - 1 ].flotDelta; 6644 datacolgroupfound += 1; 6645 } 6646 } 6647 6648 currVectorOptions.data = dataSeries; 6649 currVectorOptions.label = $( currDataVector.header[ currDataVector.header.length - 1 ].elem ).text(); 6650 6651 if ( currVectorOptions.bars ) { 6652 6653 // Adjust the bars width 6654 currVectorOptions.bars.barWidth = currVectorOptions.bars.barWidth * ( 1 / nbBarChart ); 6655 } 6656 6657 allSeries.push( currVectorOptions ); 6658 6659 } 6660 6661 if ( optionFlot.bars ) { 6662 6663 // Adjust the bars width 6664 optionFlot.bars.barWidth = optionFlot.bars.barWidth * ( 1 / nbBarChart ); 6665 } 6666 6667 // WET Charts Placeholder 6668 $placeHolder = createContainer( true ); 6669 6670 // Maximum width 6671 $placeHolder.css( "width", "100%" ); 6672 6673 // Create the graphic 6674 $.plot( $placeHolder, allSeries, optionFlot ); 6675 6676 if ( !optionsCharts.legendinline ) { 6677 6678 // Move the legend under the graphic 6679 $( ".legend > div", $placeHolder ).remove(); 6680 $( ".legend > table", $placeHolder ).removeAttr( "style" ).addClass( "font-small" ); 6681 $placeHolder.css( "height", "auto" ); 6682 } 6683 if ( optionsCharts.nolegend ) { 6684 6685 // Remove the legend 6686 $( ".legend", $placeHolder ).remove(); 6687 } 6688 6689 if ( !optionsCharts.noencapsulation ) { 6690 wrapTableIntoDetails(); 6691 } 6692 6693 $( "canvas:eq(1)", $placeHolder ).css( "position", "static" ); 6694 $( "canvas:eq(0)", $placeHolder ).css( "width", "100%" ); 6695 6696 $elm.trigger( "wb-updated" + selector ); 6697 }, 6698 6699 /** 6700 * @method init 6701 * @param {jQuery Event} event Event that triggered this handler 6702 */ 6703 init = function( event ) { 6704 6705 // Start initialization 6706 // returns DOM object = proceed with init 6707 // returns undefined = do not proceed with init (e.g., already initialized) 6708 var elm = wb.init( event, componentName, selector ), 6709 settings = window[ componentName ], 6710 elmId, modeJS, deps; 6711 6712 if ( elm ) { 6713 elmId = elm.id; 6714 modeJS = wb.getMode() + ".js"; 6715 deps = [ 6716 "site!deps/jquery.flot" + modeJS, 6717 "site!deps/jquery.flot.pie" + modeJS, 6718 "site!deps/jquery.flot.canvas" + modeJS, 6719 "site!deps/jquery.flot.orderBars" + modeJS, 6720 "site!deps/tableparser" + modeJS 6721 ]; 6722 6723 //TODO: Revist this in the new plugin structure 6724 if ( settings && settings.plugins ) { 6725 deps = deps.concat( settings.plugins ); 6726 } 6727 6728 // Only initialize the i18nText once 6729 if ( !i18nText ) { 6730 i18n = wb.i18n; 6731 i18nText = { 6732 tableMention: i18n( "hyphen" ) + i18n( "tbl-txt" ), 6733 tableFollowing: i18n( "hyphen" ) + i18n( "tbl-dtls" ),
6734 slicelegend: i18n( "chrt-cmbslc" ) 6735 }; 6736 } 6737 6738 // Load the required dependencies 6739 Modernizr.load( { 6740 6741 // For loading multiple dependencies 6742 load: deps, 6743 complete: function() { 6744 var $elm = $( "#" + elmId ); 6745 6746 // Let's parse the table 6747 $elm.trigger( tableParsingEvent ); 6748 6749 // Identify that initialization has completed 6750 wb.ready( $elm, componentName ); 6751 } 6752 } ); 6753 } 6754 }; 6755 6756// Bind the init event of the plugin 6757$document.on( "timerpoke.wb " + initEvent + " " + tableParsingCompleteEvent, selector, function( event ) { 6758 var eventType = event.type, 6759 elm = event.target; 6760 6761 switch ( eventType ) { 6762 6763 /* 6764 * Init 6765 */ 6766 case "timerpoke": 6767 case "wb-init": 6768 init( event ); 6769 break; 6770 6771 /* 6772 * Data table parsed 6773 */ 6774 case "parsecomplete": 6775 if ( event.currentTarget === elm ) { 6776 createCharts( $( elm ) ); 6777 } 6778 break; 6779 } 6780 6781 /* 6782 * Since we are working with events we want to ensure that we are being passive about our control, 6783 * so returning true allows for events to always continue 6784 */ 6785 return true; 6786} ); 6787 6788// Add the timer poke to initialize the plugin 6789wb.add( selector ); 6790 6791} )( jQuery, window, document, wb ); 6792 6793/** 6794 * @title WET-BOEW Collapsible alerts plugin 6795 * @overview Collapsible alerts (details/summary) 6796 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 6797 * @author WET community 6798 */ 6799( function( $, window, wb ) { 6800"use strict"; 6801 6802/* 6803 * Variable and function definitions. 6804 * These are global to the event - meaning that they will be initialized once per page, 6805 * not once per instance of event on the page. 6806 */ 6807var componentName = "wb-collapsible", 6808 selector = "details.alert", 6809 initEvent = "wb-init." + componentName, 6810 $document = wb.doc, 6811 key, 6812 6813 /** 6814 * @method init 6815 * @param {jQuery Event} event Event that triggered the function call 6816 */ 6817 init = function( event ) { 6818 6819 // Start initialization 6820 // returns DOM object = proceed with init 6821 // returns undefined = do not proceed with init (e.g., already initialized) 6822 var details = wb.init( event, componentName, selector ), 6823 $details; 6824 6825 if ( details ) { 6826 $details = $( details ); 6827 6828 key = "alert-collapsible-state-" + details.getAttribute( "id" ); 6829 6830 try { 6831 if ( localStorage.getItem( key ) ) { 6832 6833 // Set open/closed state for existing localStorage keys 6834 if ( localStorage.getItem( key ) === "open" ) { 6835 details.setAttribute( "open", "open" ); 6836 details.className += " open"; 6837 } else if ( localStorage.getItem( key ) === "closed" ) { 6838 details.removeAttribute( "open" ); 6839 details.className = details.className.replace( " open", "" ); 6840 } 6841 6842 } else { 6843 6844 // Set new localStorage values 6845 if ( details.hasAttribute( "open" ) ) { 6846 localStorage.setItem( key, "open" ); 6847 } else { 6848 localStorage.setItem( key, "closed" ); 6849 } 6850 6851 } 6852 } catch ( e ) { 6853 /* swallow error */} 6854 6855 // Identify that initialization has completed 6856 wb.ready( $details, componentName ); 6857 } 6858 }; 6859 6860// Bind the init event of the plugin 6861$document.on( "timerpoke.wb " + initEvent, selector, init ); 6862 6863// Do not bind events if details polyfill is active 6864if ( Modernizr.details ) { 6865 6866 // Bind the the event handlers of the plugin 6867 $document.on( "click keydown toggle." + componentName, selector + " summary", function( event ) { 6868 var which = event.which, 6869 currentTarget = event.currentTarget, 6870 isClosed, 6871 details; 6872 6873 // Ignore middle/right mouse buttons and wb-toggle enhanced summary elements (except for toggle) 6874 if ( ( !which || which === 1 ) && 6875 ( currentTarget.className.indexOf( "wb-toggle" ) === -1 || 6876 ( event.type === "toggle" && event.namespace === componentName ) ) ) { 6877 6878 details = currentTarget.parentNode; 6879 isClosed = details.getAttribute( "open" ) === null; 6880 key = "alert-collapsible-state-" + details.getAttribute( "id" ); 6881 6882 if ( isClosed ) { 6883 try { 6884 localStorage.setItem( key, "open" ); 6885 } catch ( e ) { 6886 /* swallow error */} 6887 } else { 6888 try { 6889 localStorage.setItem( key, "closed" ); 6890 } catch ( e ) { 6891 /* swallow error */} 6892 } 6893 } else if ( which === 13 || which === 32 ) { 6894 event.preventDefault(); 6895 $( currentTarget ).trigger( "click" ); 6896 } 6897 6898 /* 6899 * Since we are working with events we want to ensure that we are being passive about our control, 6900 * so returning true allows for events to always continue 6901 */ 6902 return true; 6903 } ); 6904} 6905 6906// Add the timer poke to initialize the plugin
6907wb.add( selector ); 6908 6909} )( jQuery, window, wb ); 6910 6911/** 6912 * @title WET-BOEW Country Content 6913 * @overview A basic AjaxLoader wrapper that inserts AJAXed in content based on a visitors country as resolved by freegeoip.app 6914 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 6915 * @author @nschonni 6916 */ 6917( function( $, window, wb ) { 6918"use strict"; 6919 6920/* 6921 * Variable and function definitions. 6922 * These are global to the plugin - meaning that they will be initialized once 6923 * per page, not once per instance of plugin on the page. So, this is a good 6924 * place to define variables that are common to all instances of the plugin on a 6925 * page. 6926 */ 6927var componentName = "wb-ctrycnt", 6928 selector = "[data-ctrycnt]", 6929 initEvent = "wb-init." + componentName, 6930 $document = wb.doc, 6931 6932 /** 6933 * @method init 6934 * @param {jQuery Event} event Event that triggered this handler 6935 */ 6936 init = function( event ) { 6937 6938 // Start initialization 6939 // returns DOM object = proceed with init 6940 // returns undefined = do not proceed with init (e.g., already initialized) 6941 var elm = wb.init( event, componentName, selector ), 6942 $elm, url; 6943 6944 if ( elm ) { 6945 $elm = $( elm ); 6946 url = $elm.data( "ctrycnt" ); 6947 6948 $.when( getCountry() ).then( function( countryCode ) { 6949 6950 if ( countryCode === "" ) { 6951 6952 // Leave default content since we couldn't find the country 6953 return; 6954 } else { 6955 6956 // @TODO: Handle bad country values or any whitelist of countries. 6957 } 6958 6959 url = url.replace( "{country}", countryCode.toLowerCase() ); 6960 6961 $elm.load( url, function() { 6962 6963 // Identify that initialization has completed 6964 wb.ready( $elm, componentName ); 6965 } ); 6966 } ); 6967 } 6968 }, 6969 getCountry = function() { 6970 var dfd = $.Deferred(), 6971 countryCode = localStorage.getItem( "countryCode" ); 6972 6973 // Couldn"t find a value in the session 6974 if ( countryCode === null ) { 6975 6976 // From https://github.com/aFarkas/webshim/blob/master/src/shims/geolocation.js#L89-L127 6977 $.ajax( { 6978 url: "https://api.country.is/", 6979 dataType: "json", 6980 cache: true, 6981 success: function( data ) { 6982 if ( data ) { 6983 countryCode = data.country; 6984 try { 6985 localStorage.setItem( "countryCode", countryCode ); 6986 } catch ( error ) { 6987 6988 /* swallow error */ 6989 } 6990 } 6991 6992 dfd.resolve( countryCode ); 6993 }, 6994 error: function() { 6995 dfd.reject( "" ); 6996 } 6997 } ); 6998 } else { 6999 dfd.resolve( countryCode ); 7000 } 7001 7002 return dfd.promise(); 7003 }; 7004 7005// Bind the init event of the plugin 7006$document.on( "timerpoke.wb " + initEvent, selector, init ); 7007 7008// Add the timer poke to initialize the plugin 7009wb.add( selector ); 7010 7011} )( jQuery, window, wb ); 7012 7013/** 7014 * @title WET-BOEW Data Ajax [data-ajax-after], [data-ajax-append], 7015 * [data-ajax-before], [data-ajax-prepend] and [data-ajax-replace] 7016 * @overview A basic AjaxLoader wrapper that inserts AJAXed-in content 7017 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 7018 * @author WET Community 7019 */ 7020( function( $, window, wb ) { 7021"use strict"; 7022 7023/* 7024 * Variable and function definitions. 7025 * These are global to the plugin - meaning that they will be initialized once 7026 * per page, not once per instance of plugin on the page. So, this is a good 7027 * place to define variables that are common to all instances of the plugin on a 7028 * page. 7029 */ 7030var componentName = "wb-data-ajax", 7031 shortName = "wb-ajax", 7032 selectors = [ 7033 "[data-ajax-after]", 7034 "[data-ajax-append]", 7035 "[data-ajax-before]", 7036 "[data-ajax-prepend]", 7037 "[data-ajax-replace]", 7038 "[data-" + shortName + "]" 7039 ], 7040 ajaxTypes = [ 7041 "before", 7042 "replace", 7043 "after", 7044 "append", 7045 "prepend" 7046 ], 7047 selectorsLength = selectors.length, 7048 selector = selectors.join( "," ), 7049 initEvent = "wb-init." + componentName, 7050 updateEvent = "wb-update." + componentName, 7051 contentUpdatedEvent = "wb-contentupdated", 7052 $document = wb.doc, 7053 s, 7054 7055 /** 7056 * @method init 7057 * @param {jQuery Event} event Event that triggered this handler 7058 * @param {string} ajaxType The type of AJAX operation, either after, append, before or replace 7059 */ 7060 init = function( event ) { 7061 7062 // Start initialization 7063 // returns DOM object = proceed with init 7064 // returns undefined = do not proceed with init (e.g., already initialized) 7065 var ajxInfo = getAjxInfo( event.target ), 7066 ajaxType = ajxInfo.type, 7067 elm = wb.init( event, componentName + "-" + ajaxType, selector ); 7068 7069 if ( elm && ajxInfo.url ) { 7070 7071 ajax.call( this, event, ajxInfo ); 7072 7073 // Identify that initialization has completed 7074 wb.ready( $( elm ), componentName, [ ajaxType ] ); 7075 } 7076 }, 7077 7078 ajax = function( event, ajxInfo ) { 7079 var elm = event.target, 7080 $elm = $( elm ), 7081 settings = window[ componentName ], 7082 url, 7083 fetchObj, 7084 urlParts; 7085 7086 if ( !ajxInfo ) { 7087 ajxInfo = getAjxInfo( elm ); 7088 } 7089 url = ajxInfo.url; 7090 fetchObj = { 7091 url: url, 7092 nocache: ajxInfo.nocache, 7093 nocachekey: ajxInfo.nocachekey 7094 }; 7095 7096 // Detect CORS requests 7097 if ( settings && ( url.substr( 0, 4 ) === "http" || url.substr( 0, 2 ) === "//" ) ) { 7098 urlParts = wb.getUrlParts( url ); 7099 if ( ( wb.pageUrlParts.protocol !== urlParts.protocol || wb.pageUrlParts.host !== urlParts.host ) && ( !Modernizr.cors || settings.forceCorsFallback ) ) { 7100 if ( typeof settings.corsFallback === "function" ) { 7101 fetchObj.dataType = "jsonp"; 7102 fetchObj.jsonp = "callback"; 7103 fetchObj = settings.corsFallback( fetchObj ); 7104 } 7105 } 7106 } 7107 7108 $elm.trigger( { 7109 type: "ajax-fetch.wb", 7110 fetch: fetchObj 7111 } ); 7112 }, 7113 7114 // Get Info and return { "url": "the/ajax/URL", "atype" } 7115 getAjxInfo = function( elm ) { 7116 var ajaxType, 7117 len = ajaxTypes.length, 7118 i, url, dtAttr, nocache, nocachekey; 7119 7120 for ( i = 0; i !== len; i += 1 ) { 7121 ajaxType = ajaxTypes[ i ]; 7122 url = elm.getAttribute( "data-ajax-" + ajaxType ); 7123 if ( url ) { 7124 break; 7125 } 7126 } 7127 7128 if ( !url ) { 7129 dtAttr = wb.getData( $( elm ), shortName ); 7130
7131 // Abort the init when called on an invalid element (related to #8058) 7132 if ( !dtAttr ) { 7133 return {}; 7134 } 7135 7136 ajaxType = dtAttr.type; 7137 if ( ajaxTypes.indexOf( ajaxType ) === -1 ) { 7138 throw "Invalid ajax type"; 7139 } 7140 7141 url = getURL( dtAttr.url, dtAttr.httpref ); 7142 if ( !url ) { 7143 return { "type": ajaxType }; 7144 } 7145 7146 nocache = dtAttr.nocache; 7147 nocachekey = dtAttr.nocachekey; 7148 } 7149 7150 return { 7151 "url": url, 7152 "type": ajaxType, 7153 "nocache": nocache, 7154 "nocachekey": nocachekey 7155 }; 7156 }, 7157 7158 // Return url for conditional display if regexp match http refer 7159 getURL = function( url, referer ) { 7160 var refers, httpRef, regHttpRef, 7161 i, i_len; 7162 7163 if ( referer ) { 7164 if ( !$.isArray( referer ) ) { 7165 refers = []; 7166 refers.push( referer ); 7167 } else { 7168 refers = referer; 7169 } 7170 7171 httpRef = window.document.referrer; 7172 i_len = refers.length; 7173 for ( i = 0; i !== i_len; i += 1 ) { 7174 regHttpRef = new RegExp( refers[ i ] ); 7175 if ( regHttpRef.test( httpRef ) ) { 7176 if ( $.isArray( url ) && url.length === i_len ) { 7177 return url[ i ]; 7178 } else { 7179 return url; 7180 } 7181 } 7182 } 7183 } else { 7184 return url; 7185 } 7186 return ""; 7187 }, 7188 7189 ajxFetched = function( elm, fetchObj ) { 7190 var $elm = $( elm ), 7191 ajxInfo = getAjxInfo( elm ), 7192 ajaxType = ajxInfo.type, 7193 content, jQueryCaching, 7194 settings = wb.getData( $( elm ), shortName ) || {}, 7195 doEncode = settings.encode, 7196 hasSelector = fetchObj.hasSelector; 7197 7198 // ajax-fetched event 7199 content = fetchObj.response; 7200 if ( content && content.length > 0 ) { 7201 7202 // If the fetched content need to be encoded 7203 if ( doEncode && hasSelector ) { 7204 content = content.html().replaceAll( "<", "<" ); 7205 } else if ( doEncode && !hasSelector ) { 7206 content = fetchObj.xhr.responseText.replaceAll( "<", "<" ); 7207 } 7208 7209 //Prevents the force caching of nested resources 7210 jQueryCaching = jQuery.ajaxSettings.cache; 7211 jQuery.ajaxSettings.cache = true; 7212 7213 // "replace" is the only event that doesn't map to a jQuery function 7214 if ( ajaxType === "replace" ) { 7215 $elm.html( content ); 7216 } else { 7217 $elm[ ajaxType ]( content ); 7218 } 7219 7220 //Resets the initial jQuery caching setting 7221 jQuery.ajaxSettings.cache = jQueryCaching; 7222 7223 $elm.trigger( contentUpdatedEvent, { "ajax-type": ajaxType, "content": content } ); 7224 } 7225 }; 7226 7227$document.on( "timerpoke.wb " + initEvent + " " + updateEvent + " ajax-fetched.wb", selector, function( event ) { 7228 var eventTarget = event.target; 7229 7230 switch ( event.type ) { 7231 7232 case "timerpoke": 7233 case "wb-init": 7234 init( event ); 7235 break; 7236 case "wb-update": 7237 ajax( event ); 7238 break; 7239 default: 7240 7241 // Filter out any events triggered by descendants 7242 if ( event.currentTarget === eventTarget ) { 7243 ajxFetched( eventTarget, event.fetch ); 7244 } 7245 } 7246 7247 /* 7248 * Since we are working with events we want to ensure that we are being 7249 * passive about our control, so returning true allows for events to always 7250 * continue 7251 */ 7252 return true; 7253} ); 7254 7255// Re-run WET for elements that have just been loaded if WET is already done initializing 7256$document.on( contentUpdatedEvent, function( event ) { 7257 if ( !wb.isDisabled ) { 7258 let updtElm = event.target; 7259 7260 $( updtElm ) 7261 .find( wb.allSelectors ) 7262 .addClass( "wb-init" ) 7263 .filter( ":not(#" + updtElm.id + " .wb-init .wb-init)" ) 7264 .trigger( "timerpoke.wb" ); 7265 } 7266} ); 7267 7268// Add the timerpoke to initialize the plugin 7269for ( s = 0; s !== selectorsLength; s += 1 ) { 7270 wb.add( selectors[ s ] ); 7271} 7272 7273} )( jQuery, window, wb ); 7274 7275/** 7276 * Web Experience Toolkit (WET) / Boîte à outils de l'expérience Web (BOEW) 7277 * @title Data Fusion Query 7278 * @overview Map a query parameter value into an input value 7279 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 7280 * @author @duboisp 7281 * 7282 */ 7283( function( document, $, wb ) { 7284"use strict"; 7285 7286/** 7287 * Variable and function definitions. 7288 * These are global to the plugin - meaning that they will be initialized once per page, 7289 * not once per instance of plugin on the page. So, this is a good place to define 7290 * variables that are common to all instances of the plugin on a page. 7291 */
7292var componentName = "wb-data-fusion-query", 7293 selector = "[data-fusion-query][name]", 7294 initEvent = "wb-init." + componentName, 7295 7296 7297 /** 7298 * @method init 7299 * @param {jQuery Event} event Event that triggered the function call 7300 */ 7301 init = function( event ) { 7302 7303 // Start initialization 7304 // returns DOM object = proceed with init 7305 // returns undefined = do not proceed with init (e.g., already initialized) 7306 var elm = wb.init( event, componentName, selector ), 7307 $elm, 7308 inputName, 7309 queryParamValue; 7310 7311 if ( elm ) { 7312 $elm = $( elm ); 7313 7314 // Retrieve the query parameter value and set it on the input 7315 inputName = $elm.attr( "name" ); 7316 queryParamValue = wb.pageUrlParts.params[ inputName ]; 7317 if ( queryParamValue ) { 7318 $elm.val( queryParamValue.replace( /\+/g, " " ) ); 7319 } 7320 7321 // Identify that initialization has completed 7322 wb.ready( $elm, componentName ); 7323 } 7324 }; 7325 7326// Bind the init event of the plugin 7327wb.doc.on( "timerpoke.wb " + initEvent, selector, init ); 7328 7329// Add the timer poke to initialize the plugin 7330wb.add( selector ); 7331 7332} )( document, jQuery, wb ); 7333 7334/** 7335 * @title WET-BOEW Data InView 7336 * @overview A simplified data-attribute driven plugin that responds to moving in and out of the viewport. 7337 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 7338 * @author WET Community 7339 */ 7340( function( $, window, wb ) { 7341"use strict"; 7342 7343/* 7344 * Variable and function definitions. 7345 * These are global to the plugin - meaning that they will be initialized once per page, 7346 * not once per instance of plugin on the page. So, this is a good place to define 7347 * variables that are common to all instances of the plugin on a page. 7348 */ 7349var componentName = "wb-inview", 7350 selector = "." + componentName, 7351 initEvent = "wb-init" + selector, 7352 scrollEvent = "scroll" + selector, 7353 $document = wb.doc, 7354 $window = wb.win, 7355 $elms = $(), 7356 7357 /** 7358 * @method init 7359 * @param {jQuery Event} event Event that triggered this handler 7360 */ 7361 init = function( event ) { 7362 7363 // Start initialization 7364 // returns DOM object = proceed with init 7365 // returns undefined = do not proceed with init (e.g., already initialized) 7366 var elm = wb.init( event, componentName, selector ), 7367 $elm; 7368 7369 if ( elm ) { 7370 $elm = $( elm ); 7371 $elms = $elms.add( $elm ); 7372 7373 // Allow other plugins to run first 7374 setTimeout( function() { 7375 onInView( $elm ); 7376 7377 // Identify that initialization has completed 7378 wb.ready( $elm, componentName ); 7379 }, 1 ); 7380 } 7381 }, 7382 7383 /** 7384 * @method onInView 7385 * @param {jQuery DOM element} $elm The plugin element 7386 */ 7387 onInView = function( $elm ) { 7388 var elementWidth = $elm.outerWidth(), 7389 elementHeight = $elm.outerHeight(), 7390 scrollTop = $window.scrollTop(), 7391 scrollBottom = scrollTop + $window.height(), 7392 scrollRight = $window.scrollLeft() + elementWidth, 7393 x1 = $elm.offset().left, 7394 x2 = x1 + elementWidth, 7395 y1 = $elm.offset().top, 7396 y2 = y1 + elementHeight, 7397 oldViewState = $elm.attr( "data-inviewstate" ), 7398 inView = ( scrollBottom < y1 || scrollTop > y2 ) || ( scrollRight < x1 || scrollRight > x2 ), 7399 7400 // this is a bit of a play on true/false to get the desired effect. In short this variable depicts 7401 // the view state of the element 7402 // all - the whole element is in the viewport 7403 // partial - part of the element is in the viewport 7404 // none - no part of the element is in the viewport 7405 viewState = ( scrollBottom > y2 && scrollTop < y1 ) ? "all" : inView ? "none" : "partial", 7406 $dataInView = $( "#" + $elm.attr( "data-inview" ) ), 7407 show; 7408 7409 // Remove any element that no longer exists in the DOM 7410 if ( elementWidth === 0 || elementHeight === 0 ) { 7411 $elms = $elms.not( $elm ); 7412 $dataInView.addClass( "user-closed" ); 7413 $dataInView.trigger( { 7414 type: ( "close" ), 7415 namespace: "wb-overlay", 7416 noFocus: true 7417 } ); 7418 7419 return; 7420 } 7421 7422 // Link the overlay close button to the dismiss action if the inview content is dismissable 7423 if ( $elm.hasClass( "wb-dismissable" ) ) { 7424 if ( $dataInView.hasClass( "wb-overlay" ) ) { 7425 $dataInView.children( ".overlay-close" ).on( "click", function( event ) { 7426 var which = event.which; 7427 7428 // Ignore middle/right mouse buttons 7429 if ( !which || which === 1 ) { 7430 $elm.parent().siblings( ".content-dismiss" ).trigger( "click" ); 7431 } 7432 } ); 7433 } 7434 } 7435 7436 // Only if the view state has changed 7437 if ( viewState !== oldViewState ) { 7438
7439 // Show on "partial"/"none" (default) or just "none" (requires "show-none" class) 7440 show = inView || ( $elm.hasClass( "show-none" ) ? false : viewState === "partial" ); 7441 7442 $elm.attr( "data-inviewstate", viewState ); 7443 7444 if ( $dataInView.length !== 0 ) { 7445 7446 // Keep closed if the user closed the inView result 7447 if ( !$dataInView.hasClass( "user-closed" ) ) { 7448 if ( $dataInView.hasClass( "wb-overlay" ) ) { 7449 if ( !oldViewState ) { 7450 $dataInView.addClass( "outside-off" ); 7451 } 7452 $dataInView.trigger( { 7453 type: ( show ? "open" : "close" ), 7454 namespace: "wb-overlay", 7455 noFocus: true 7456 } ); 7457 } else { 7458 if ( !$dataInView.attr( "data-hasPlayed" ) ) { 7459 $dataInView.toggleClass( "in", !show ); 7460 if ( !$dataInView.hasClass( "in-only" ) ) { 7461 $dataInView.toggleClass( "out", show ); 7462 } 7463 } 7464 if ( wb.isReady && viewState === "all" && $dataInView.hasClass( "in-only" ) ) { 7465 $dataInView.attr( "data-hasPlayed", "true" ); 7466 } 7467 } 7468 } 7469 } 7470 7471 // Trigger an event on the element identifying that the view state has changed 7472 // (e.g., "all.wb-inview", "partial.wb-inview", "none.wb-inview") 7473 $elm.trigger( viewState + selector ); 7474 } 7475 }; 7476 7477// Bind the init event of the plugin 7478$document.on( "timerpoke.wb " + initEvent + " " + scrollEvent, selector, function( event ) { 7479 var eventTarget = event.target, 7480 eventType = event.type; 7481 7482 switch ( eventType ) { 7483 case "timerpoke": 7484 case "wb-init": 7485 init( event ); 7486 break; 7487 7488 case "scroll": 7489 7490 // Filter out any events triggered by descendants 7491 if ( event.currentTarget === eventTarget ) { 7492 onInView( $( eventTarget ) ); 7493 } 7494 break; 7495 } 7496 7497 /* 7498 * Since we are working with events we want to ensure that we are being passive about our control, 7499 * so returning true allows for events to always continue 7500 */ 7501 return true; 7502} ); 7503 7504$window.on( "scroll scrollstop", function() { 7505 $elms.trigger( scrollEvent ); 7506} ); 7507 7508$document.on( "txt-rsz.wb win-rsz-width.wb win-rsz-height.wb", function() { 7509 $elms.trigger( scrollEvent ); 7510} ); 7511 7512$document.on( "refresh.wb", function() { 7513 $elms.each( function() { 7514 onInView( $( this ) ); 7515 } ); 7516} ); 7517 7518// Add the timer poke to initialize the plugin 7519wb.add( selector ); 7520 7521} )( jQuery, window, wb ); 7522 7523/** 7524 * @title WET-BOEW Data Picture 7525 * @overview Event driven port of the Picturefill library: https://github.com/scottjehl/picturefill 7526 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 7527 * @author @patheard 7528 */ 7529( function( $, window, wb ) { 7530"use strict"; 7531 7532/* 7533 * Variable and function definitions. 7534 * These are global to the plugin - meaning that they will be initialized once per page, 7535 * not once per instance of plugin on the page. So, this is a good place to define 7536 * variables that are common to all instances of the plugin on a page. 7537 */ 7538var imgClass, 7539 componentName = "wb-pic", 7540 selector = "[data-pic]", 7541 initEvent = "wb-init." + componentName, 7542 picturefillEvent = "picfill." + componentName, 7543 $document = wb.doc, 7544 7545 /** 7546 * @method init 7547 * @param {jQuery Event} event Event that triggered this handler 7548 */ 7549 init = function( event ) { 7550 7551 // Start initialization 7552 // returns DOM object = proceed with init 7553 // returns undefined = do not proceed with init (e.g., already initialized) 7554 var elm = wb.init( event, componentName, selector ), 7555 $elm; 7556 7557 if ( elm ) { 7558 $elm = $( elm ); 7559 7560 // Store the class attribute of the plugin element. It 7561 // will be added to the image created by the plugin. 7562 imgClass = $elm.data( "class" ) || ""; 7563 7564 $elm.trigger( picturefillEvent ); 7565 7566 // Identify that initialization has completed 7567 wb.ready( $elm, componentName ); 7568 } 7569 }, 7570 7571 /** 7572 * Updates the image displayed according to media queries. 7573 * This is the logic ported from Picturefill. 7574 * @method picturefill 7575 * @param {DOM element} elm The element containing the images to be updated 7576 */ 7577 picturefill = function( elm ) { 7578 var matches = [], 7579 img = elm.getElementsByTagName( "img" )[ 0 ], 7580 sources = elm.getElementsByTagName( "span" ), 7581 i, len, matchedElm, media; 7582 7583 // Loop over the data-media elements and find matching media queries 7584 for ( i = 0, len = sources.length; i !== len; i += 1 ) { 7585 media = sources[ i ].getAttribute( "data-media" ); 7586 if ( !media || Modernizr.mq( media ) ) { 7587 matches.push( sources[ i ] ); 7588 } 7589 } 7590 7591 // If a media query match was found, add the image to the page 7592 if ( matches.length !== 0 ) { 7593 matchedElm = matches.pop(); 7594 if ( !img ) { 7595 img = $document[ 0 ].createElement( "img" ); 7596 img.alt = elm.getAttribute( "data-alt" ); 7597 img.className = imgClass; 7598 } 7599 img.src = matchedElm.getAttribute( "data-src" ); 7600 matchedElm.appendChild( img ); 7601 7602 // No match and an image exists: delete it 7603 } else if ( img ) { 7604 img.parentNode.removeChild( img ); 7605 } 7606 7607 // Identify that the picture has been updated 7608 $( elm ).trigger( "wb-updated." + componentName ); 7609 }; 7610 7611// Bind the init event of the plugin 7612$document.on( "timerpoke.wb " + initEvent + " " + picturefillEvent, selector, function( event ) { 7613 var eventTarget = event.target, 7614 eventType = event.type; 7615 7616 switch ( eventType ) { 7617 case "timerpoke": 7618 case "wb-init": 7619 init( event ); 7620 break; 7621 7622 case "picfill": 7623 7624 // Filter out any events triggered by descendants 7625 if ( event.currentTarget === eventTarget ) { 7626 picturefill( eventTarget ); 7627 } 7628 break; 7629 } 7630} ); 7631 7632// Handles window resize so images can be updated as new media queries match 7633$document.on( "txt-rsz.wb win-rsz-width.wb win-rsz-height.wb", function() { 7634 $( selector ).trigger( picturefillEvent ); 7635} ); 7636 7637// Add the timer poke to initialize the plugin
7638wb.add( selector ); 7639 7640} )( jQuery, window, wb ); 7641 7642/** 7643 * @title WET-BOEW Details closed on small screen 7644 * @overview Closes details on defined viewport and down if they were not engaged, default is small 7645 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 7646 * @author @thomasgohard 7647 */ 7648( function( $, wb ) { 7649"use strict"; 7650 7651/* 7652* Variable and function definitions. 7653* These are global to the plugin - meaning that they will be initialized once per page, 7654* not once per instance of plugin on the page. So, this is a good place to define 7655* variables that are common to all instances of the plugin on a page. 7656*/ 7657var componentName = "wb-details-close", 7658 selector = ".provisional." + componentName, 7659 initEvent = "wb-init" + selector, 7660 $document = wb.doc, 7661 views = [ "xxs", "xs", "sm", "md", "lg", "xl" ], 7662 viewsClass = [ "xxsmallview", "xsmallview", "smallview", "mediumview", "largeview", "xlargeview" ], 7663 breakpoint, 7664 7665 /** 7666 * @method init 7667 * @param {jQuery Event} event Event that triggered the function call 7668 */ 7669 init = function( event ) { 7670 7671 // Start initialization 7672 // returns DOM object = proceed with init 7673 // returns undefined = do not proceed with init (e.g., already initialized) 7674 var elm = wb.init( event, componentName, selector ), 7675 $elm, i; 7676 7677 if ( elm ) { 7678 $elm = $( elm ); 7679 7680 // Get the plugin JSON configuration set on attribute data-wb-details-close 7681 // Will define one set settings for all .wb-details-close on the page 7682 breakpoint = $elm.data( "breakpoint" ) || "sm"; 7683 7684 // reset breakpoint if config is passed 7685 if ( views.length === viewsClass.length ) { 7686 i = views.indexOf( breakpoint ); 7687 viewsClass = viewsClass.slice( 0, i + 1 ); 7688 } 7689 7690 hideOnBreakpoint(); 7691 7692 // Identify that initialization has completed 7693 wb.ready( $elm, componentName ); 7694 } 7695 }, 7696 7697 /** 7698 * Toggle details depending on breakpoint 7699 * @method hideOnBreakpoint 7700 * @param {jQuery DOM element | jQuery Event} $elm Element targetted by this plugin, which is the details 7701 */ 7702 hideOnBreakpoint = function() { 7703 var $elm = $( selector ), 7704 viewsSelector = "html." + viewsClass.join( ", html." ); 7705 7706 // If within the targetted views, keep details closed 7707 if ( $( viewsSelector ).length ) { 7708 $elm.removeAttr( "open" ); 7709 } else { 7710 7711 // If not, keep opened 7712 $elm.attr( "open", "" ); 7713 } 7714 }; 7715 7716// Bind the init event of the plugin 7717$document.on( "timerpoke.wb " + initEvent, selector, init ); 7718 7719// Add the timer poke to initialize the plugin 7720wb.add( selector ); 7721 7722} )( jQuery, wb ); 7723 7724/** 7725 * @title WET-BOEW Dismissable content plugin 7726 * @overview Enables content to be dismissed 7727 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 7728 * @author @thomasgohard 7729 */ 7730( function( $, window, wb ) { 7731"use strict"; 7732 7733/* 7734 * Variable and function definitions. 7735 * These are global to the event - meaning that they will be initialized once per page, 7736 * not once per instance of event on the page. 7737 */ 7738var componentName = "wb-dismissable", 7739 selector = "." + componentName, 7740 initEvent = "wb-init." + componentName, 7741 containerClass = "wb-dismissable-container", 7742 wrapperClass = "wb-dismissable-wrapper", 7743 dismissClass = "content-dismiss", 7744 idKey = "dismissable-item-id", 7745 $document = wb.doc, 7746 i18n, i18nText, 7747 7748 /** 7749 * @method init 7750 * @param {jQuery Event} event Event that triggered the function call 7751 */ 7752 init = function( event ) { 7753 7754 // Start initialization 7755 // returns DOM object = proceed with init 7756 // returns undefined = do not proceed with init (e.g., already initialized) 7757 var elm = wb.init( event, componentName, selector ), 7758 itemId, dismissedState, contentContainer, contentWrapper, dismissButton; 7759 7760 if ( elm ) { 7761 7762 // Only initialize the i18nText once 7763 if ( !i18nText ) { 7764 i18n = wb.i18n; 7765 i18nText = { 7766 dismiss: i18n( "dismiss" ) 7767 }; 7768 } 7769 7770 // Give the dismissable element a unique ID 7771 if ( elm.hasAttribute( "id" ) ) { 7772 itemId = elm.getAttribute( "id" ); 7773 7774 // Ignore an ID assigned by wb 7775 if ( itemId.indexOf( "wb-auto-" ) === 0 ) { 7776 itemId = undefined; 7777 } 7778 } 7779 if ( itemId === undefined ) { 7780 itemId = wb.hashString( wb.stripWhitespace( elm.innerHTML ) ); 7781 } 7782 7783 dismissedState = getDismissedState( itemId ); 7784 7785 if ( dismissedState === "true" ) { 7786 7787 // Remove the element if it has been dismissed 7788 if ( elm.parentNode ) { 7789 elm.parentNode.removeChild( elm ); 7790 } 7791 } else { 7792 $( elm ).wrap( "<div class='" + wrapperClass + "'>" ); 7793 contentWrapper = elm.parentNode; 7794 7795 $( contentWrapper ).wrap( "<div class='" + containerClass + "'>" ); 7796 contentContainer = contentWrapper.parentNode; 7797
7798 dismissButton = "<button type='button' class='mfp-close " + dismissClass + 7799 "' title='" + i18nText.dismiss + "'>×<span class='wb-inv'> " + 7800 i18nText.dismiss + "</span></button>"; 7801 $( contentContainer ).append( dismissButton ); 7802 7803 contentContainer.setAttribute( "data-" + idKey, itemId ); 7804 } 7805 7806 // Identify that initialization has completed 7807 wb.ready( $document, componentName ); 7808 } 7809 }, 7810 7811 getDismissedState = function( id ) { 7812 var dismissState = localStorage.getItem( id ); 7813 7814 if ( dismissState === null ) { 7815 return false; 7816 } 7817 7818 return dismissState; 7819 }, 7820 7821 dismissContent = function( elm ) { 7822 localStorage.setItem( elm.getAttribute( "data-" + idKey ), true ); 7823 elm.parentNode.removeChild( elm ); 7824 $document.trigger( "refresh.wb" ); 7825 }; 7826 7827// Bind the init event of the plugin 7828$document.on( "timerpoke.wb " + initEvent, selector, init ); 7829 7830// Handler for clicking on the dismiss button 7831$document.on( "click", "." + dismissClass, function( event ) { 7832 var elm = event.currentTarget, 7833 which = event.which; 7834 7835 // Ignore middle/right mouse buttons 7836 if ( !which || which === 1 ) { 7837 dismissContent( elm.parentNode ); 7838 } 7839} ); 7840 7841// Add the timer poke to initialize the plugin 7842wb.add( selector ); 7843 7844} )( jQuery, window, wb ); 7845 7846/** 7847 * @title eqht 7848 * @overview Provide ability to have equal height containers and nested containers inside a WET-BOEW grid 7849 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 7850 * @author @luc-bertrand-hrsdc 7851 */ 7852( function( $, window, wb ) { 7853"use strict"; 7854 7855/* 7856 * Variable and function definitions. 7857 * These are global to the plugin - meaning that they will be initialized once per page, 7858 * not once per instance of plugin on the page. So, this is a good place to define 7859 * variables that are common to all instances of the plugin on a page. 7860 */ 7861var componentName = "wb-eqht-grd", 7862 selector = "." + componentName + " .eqht-trgt", 7863 initEvent = "wb-init" + selector, 7864 $document = wb.doc, 7865 7866 /** 7867 * @method init 7868 * @param {jQuery Event} event Event that triggered the function call 7869 */ 7870 init = function( event ) { 7871 7872 // Start initialization 7873 // returns DOM object = proceed with init 7874 // returns undefined = do not proceed with init (e.g., already initialized) 7875 var elm = wb.init( event, componentName, selector ), 7876 $elm, 7877 $eqhtParents; 7878 7879 if ( elm ) { 7880 $elm = $( elm ); 7881 $elm.addClass( "hght-inhrt" ); 7882 $eqhtParents = $elm.parentsUntil( "[class*='" + componentName + "']" ); 7883 $eqhtParents.addClass( "hght-inhrt" ); 7884 7885 // Identify that initialization has completed 7886 wb.ready( $elm, componentName ); 7887 } 7888 }; 7889 7890// Bind the init event of the plugin 7891$document.on( "timerpoke.wb " + initEvent, selector, init ); 7892 7893// Add the timer poke to initialize the plugin 7894wb.add( selector ); 7895 7896} )( jQuery, window, wb ); 7897 7898/** 7899 * @title WET-BOEW Responsive equal height 7900 * @overview Sets the same height for all elements in a container that are rendered on the same baseline (row). Adapted from https://codepen.io/micahgodbolt/pen/FgqLc. 7901 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 7902 * @author @thomasgohard 7903 */ 7904( function( $, window, wb ) { 7905"use strict"; 7906 7907/* 7908 * Variable and function definitions. 7909 * These are global to the plugin - meaning that they will be initialized once per page, 7910 * not once per instance of plugin on the page. So, this is a good place to define 7911 * variables that are common to all instances of the plugin on a page. 7912 */ 7913var componentName = "wb-eqht", 7914 selector = "." + componentName, 7915 $document = wb.doc, 7916 eventTimerpoke = "timerpoke.wb", 7917 initEvent = "wb-init" + selector, 7918 vAlignCSS = "vertical-align", 7919 vAlignDefault = "top", 7920 contentUpdatedEvent = "wb-contentupdated", 7921 minHeightCSS = "min-height", 7922 minHeightDefault = "0", 7923 cssValueSeparator = ":", 7924 cssPropertySeparator = ";", 7925 regexCSSValue = " ?[^;]+", 7926 regexVAlign = new RegExp( vAlignCSS + cssValueSeparator + " ?" + regexCSSValue + cssPropertySeparator + "?", "i" ), 7927 regexMinHeight = new RegExp( minHeightCSS + cssValueSeparator + " ?" + regexCSSValue + cssPropertySeparator + "?", "i" ), 7928 7929 /** 7930 * @method init 7931 * @param {jQuery Event} event Event that triggered the function call 7932 */ 7933 init = function( event ) { 7934 7935 // Start initialization 7936 // returns DOM object = proceed with init 7937 // returns undefined = do not proceed with init (e.g., already initialized) 7938 var elm = wb.init( event, componentName, selector ); 7939 7940 if ( elm ) { 7941 7942 // Remove the event handler since only want init fired once per page (not per element) 7943 $document.off( eventTimerpoke, selector ); 7944 7945 onResize(); 7946 7947 // Identify that initialization has completed 7948 wb.ready( $document, componentName ); 7949 } 7950 }, 7951 7952 /** 7953 * Re-equalise any time the window/document or a child element of 'selector' is resized. 7954 * @method onResize 7955 */ 7956 onResize = function() { 7957 var $elm, $children, $anchor, currentChild, childCSS, i, j, 7958 $elms = $( selector ), 7959 row = [], 7960 rowTop = -1, 7961 currentChildTop = -1, 7962 currentChildHeight = -1, 7963 tallestHeight = -1; 7964 7965 for ( i = $elms.length - 1; i !== -1; i -= 1 ) { 7966 $elm = $elms.eq( i ); 7967 $children = $elm.find( ".eqht-trgt" ); 7968 if ( !$children.length ) { 7969 $children = $elm.children(); 7970 } 7971 7972 // Reinitialize the row at the beginning of each section of equal height 7973 row = []; 7974 7975 $anchor = detachElement( $elm ); 7976 for ( j = $children.length - 1; j !== -1; j -= 1 ) { 7977 currentChild = $children[ j ]; 7978 childCSS = currentChild.style.cssText.toLowerCase(); 7979 7980 //Ensure the CSS string ends by a seperator 7981 if ( childCSS.length > 0 && childCSS.substr( childCSS.length - 1 ) !== cssPropertySeparator ) { 7982 childCSS += cssPropertySeparator; 7983 } 7984 7985 // Ensure all children that are on the same baseline have the same 'top' value. 7986 if ( childCSS.indexOf( vAlignCSS ) !== -1 ) { 7987 childCSS = childCSS.replace( regexVAlign, vAlignCSS + cssValueSeparator + vAlignDefault + cssPropertySeparator ); 7988 } else { 7989 childCSS += " " + vAlignCSS + cssValueSeparator + vAlignDefault + cssPropertySeparator; 7990 } 7991 7992 // Remove any previously set min height 7993 if ( childCSS.indexOf( minHeightCSS ) !== -1 ) { 7994 childCSS = childCSS.replace( regexMinHeight, minHeightCSS + cssValueSeparator + minHeightDefault + cssPropertySeparator ); 7995 } else { 7996 childCSS += " " + minHeightCSS + cssValueSeparator + minHeightDefault + cssPropertySeparator; 7997 } 7998 7999 currentChild.style.cssText = childCSS; 8000 $children.eq( j ).data( minHeightCSS, minHeightDefault ); 8001 } 8002 $elm = reattachElement( $anchor ); 8003 8004 // set the top offset and tallest height to the first element 8005 rowTop = $children[ 0 ] ? $children[ 0 ].getBoundingClientRect().top + window.pageYOffset : 0; 8006 tallestHeight = $children[ 0 ] ? $children[ 0 ].offsetHeight : 0; 8007 8008 // first, the loop MUST be from start to end to work. 8009 for ( j = 0; j < $children.length; j++ ) { 8010 currentChild = $children[ j ]; 8011 8012 currentChildTop = currentChild.getBoundingClientRect().top + window.pageYOffset; 8013 currentChildHeight = currentChild.offsetHeight; 8014 8015 // if the current element is visible...
8016 // note: hidden elements need to be excluded since they have a different top offset than visible ones 8017 if ( currentChildHeight ) { 8018 8019 // as soon as we find an element not on this row (not the same top offset)... 8020 if ( currentChildTop !== rowTop ) { 8021 8022 // we need to equalize each item in that row to align the next row 8023 equalize( row, tallestHeight ); 8024 8025 // since the elements of the previous row was equalized 8026 // we need to get the new top offset of the current element 8027 currentChildTop = currentChild.getBoundingClientRect().top + window.pageYOffset; 8028 8029 // reset the row, rowTop and tallestHeight 8030 row.length = 0; 8031 rowTop = currentChildTop; 8032 tallestHeight = currentChildHeight; 8033 } 8034 8035 tallestHeight = Math.max( currentChildHeight, tallestHeight ); 8036 row.push( $children.eq( j ) ); 8037 } 8038 } 8039 8040 // equalize the last row 8041 equalize( row, tallestHeight ); 8042 8043 // Identify that the height equalization was updated 8044 $document.trigger( "wb-updated" + selector ); 8045 } 8046 }, 8047 8048 /** 8049 * @method equalize 8050 * @param {array} row the array of items to be equalized 8051 * @param {int} tallestHeight the talest height to use to equalize 8052 */ 8053 equalize = function( row, tallestHeight ) { 8054 for ( var i = 0; i < row.length; i++ ) { 8055 8056 // added a +1 because some floated element got stuck if the 8057 // shortest element was the last element in the row 8058 var minHeight = tallestHeight + 1; 8059 row[ i ][ 0 ].style.minHeight = minHeight + "px"; 8060 } 8061 }, 8062 8063 /** 8064 * @method detachElement 8065 * @param {jQuery object} $elm The element to detach 8066 * @returns {object} The detached element 8067 */ 8068 detachElement = function( $elm ) { 8069 var $prev = $elm.prev(), 8070 $next = $elm.next(), 8071 $parent = $elm.parent(); 8072 8073 if ( $prev.length ) { 8074 $elm.data( { anchor: $prev, anchorRel: "prev" } ); 8075 } else if ( $next.length ) { 8076 $elm.data( { anchor: $next, anchorRel: "next" } ); 8077 } else if ( $parent.length ) { 8078 $elm.data( { anchor: $parent, anchorRel: "parent" } ); 8079 } 8080 8081 return $elm.detach(); 8082 }, 8083 8084 /** 8085 * @method reattachElement 8086 * @param {jQuery object} $elm The element to reattach 8087 * @returns {object} The reattached element 8088 */ 8089 reattachElement = function( $elm ) { 8090 var $anchor = $elm.data( "anchor" ), 8091 anchorRel = $elm.data( "anchorRel" ); 8092 8093 switch ( anchorRel ) { 8094 case "prev": 8095 $anchor.after( $elm ); 8096 break; 8097 case "next": 8098 $anchor.before( $elm ); 8099 break; 8100 case "parent": 8101 $anchor.append( $elm ); 8102 break; 8103 } 8104 8105 return $elm; 8106 }; 8107 8108// Bind the init event of the plugin 8109$document.on( eventTimerpoke + " " + initEvent, selector, init ); 8110 8111// Handle text and window resizing 8112$document.on( "txt-rsz.wb win-rsz-width.wb win-rsz-height.wb " + contentUpdatedEvent + " wb-updated.wb-tables wb-update" + selector, onResize ); 8113 8114// Add the timer poke to initialize the plugin 8115wb.add( selector ); 8116 8117} )( jQuery, window, wb ); 8118 8119/** 8120* @title WET-BOEW Exit script plugin 8121* @overview Plugin redirects users to non secure site 8122* @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 8123* @author @ipaksc 8124*/ 8125( function( $, window, wb, crypto ) { 8126"use strict"; 8127var componentName = "wb-exitscript", 8128 selector = "." + componentName, 8129 initEvent = "wb-init" + selector, 8130 $document = wb.doc, 8131 exiturlparam = componentName + "-urlparam", 8132 keyForKeyHolder = componentName + "key", 8133 moDalId = componentName + "-modal", 8134 i18n, 8135 i18nDict = { 8136 en: { 8137 "msgboxHeader": "Warning", 8138 "exitMsg": "You are about to leave a secure site, do you wish to continue?", 8139 "targetWarning": "The link will open in a new browser window.", 8140 "yesBtn": "Yes", 8141 "cancelBtn": "Cancel" 8142 8143 }, 8144 fr: { 8145 "msgboxHeader": "Avertissement", 8146 "exitMsg": "Vous êtes sur le point de quitter un site sécurisé. Voulez-vous continuer?", 8147 "targetWarning": "Le lien s'ouvrira dans une nouvelle fenêtre de navigateur.", 8148 "yesBtn": "Oui", 8149 "cancelBtn": "Annuler" 8150 8151 } 8152 }, 8153 8154 /** 8155 * @method init 8156 * @param {jQuery Event} event Event that triggered the function call 8157 */ 8158 init = function( event ) { 8159 var elm = wb.init( event, componentName, selector ), 8160 settings, 8161 queryString = window.location.search,
8162 urlParams = new URLSearchParams( queryString ), 8163 counterInUrl = wb.string.fromHexString( urlParams.get( "exturl" ) ), 8164 encryptedUrl = localStorage.getItem( componentName ), 8165 jwt = JSON.parse( localStorage.getItem( keyForKeyHolder ) ), 8166 $elm; 8167 if ( elm ) { 8168 $elm = $( elm ); 8169 settings = $.extend( 8170 true, 8171 window[ componentName ], 8172 wb.getData( $elm, componentName ) 8173 8174 ); 8175 8176 $elm.data( componentName, settings ); 8177 8178 if ( settings.url && crypto ) { 8179 8180 crypto.subtle.generateKey( 8181 { 8182 name: "AES-CTR", 8183 length: 256 8184 }, 8185 true, 8186 [ "encrypt", "decrypt" ] 8187 ).then( function( keyToEncryp ) { 8188 8189 var enc, messageEncoded, counter; 8190 8191 // Save the key in the anchor 8192 crypto.subtle.exportKey( "jwk", keyToEncryp ) 8193 .then( function( exportedJwtKey ) { 8194 elm[ keyForKeyHolder ] = exportedJwtKey; 8195 } ); 8196 8197 // Encrypt the URL 8198 enc = new TextEncoder(); 8199 messageEncoded = enc.encode( elm.href ); 8200 counter = crypto.getRandomValues( new Uint8Array( 16 ) ); 8201 crypto.subtle.encrypt( 8202 { 8203 name: "AES-CTR", 8204 counter: counter, 8205 length: 64 8206 }, 8207 keyToEncryp, 8208 messageEncoded 8209 ).then( function( ciphertext ) { 8210 elm[ componentName ] = ciphertext; 8211 } ); 8212 8213 // Change the link URL by passing the counter as a key 8214 $elm.attr( "href", settings.url + "?exturl=" + wb.string.toHexString( counter ) ); 8215 } ); 8216 8217 } 8218 8219 i18n = i18nDict[ wb.lang || "en" ]; 8220 8221 // This conditional statement for a middle static exit page, to retrieve the URL to the non-secure site. 8222 if ( $elm.hasClass( exiturlparam ) && encryptedUrl !== null && jwt !== null ) { 8223 8224 crypto.subtle.importKey( 8225 "jwk", 8226 jwt, 8227 { 8228 name: "AES-CTR", 8229 length: 256 8230 }, 8231 true, 8232 [ "decrypt" ] 8233 ).then( function( key ) { 8234 8235 crypto.subtle.decrypt( 8236 { 8237 name: "AES-CTR", 8238 counter: counterInUrl, 8239 length: 64 8240 }, 8241 key, 8242 wb.string.base64ToArrayBuffer( encryptedUrl ) 8243 ).then( function( decrypted ) { 8244 8245 var dec = new TextDecoder(), 8246 urlToRedirect = dec.decode( decrypted ); 8247 8248 // Check if the decrypted message is an valid URL and silently fail if the pattern don't match 8249 if ( urlToRedirect.match( /^(http|https):\/\//g ) ) { 8250 elm.outerHTML = "<a href='" + urlToRedirect + "'>" + urlToRedirect + "</a>"; 8251 } 8252 8253 } ); 8254 } ); 8255 8256 } 8257 8258 // Remove the plugin data and ensure it is removed from the localstorage 8259 localStorage.removeItem( componentName ); 8260 localStorage.removeItem( keyForKeyHolder ); 8261 8262 wb.ready( $elm, componentName ); 8263 } 8264 }; 8265 8266$document.on( "click", selector, function( event ) { 8267 8268 var elm = event.currentTarget, 8269 $elm = $( elm ), 8270 wrapper, 8271 targetAttribute = "", 8272 moDal = document.createDocumentFragment(), 8273 tpl = document.createElement( "div" ), 8274 settings = $elm.data( componentName ),
8275 msgboxHeader = i18n.msgboxHeader, 8276 yesBtn = i18n.yesBtn, 8277 cancelBtn = i18n.cancelBtn, 8278 exitMsg = i18n.exitMsg, 8279 targetWarning = i18n.targetWarning; 8280 8281 if ( settings.i18n ) { 8282 msgboxHeader = settings.i18n.msgboxHeader || i18n.msgboxHeader; 8283 yesBtn = settings.i18n.yesBtn || i18n.yesBtn; 8284 cancelBtn = settings.i18n.cancelBtn || i18n.cancelBtn; 8285 exitMsg = settings.i18n.exitMsg || i18n.exitMsg; 8286 targetWarning = settings.i18n.targetWarning || i18n.targetWarning; 8287 } 8288 8289 if ( !settings.url ) { 8290 8291 event.preventDefault(); 8292 } 8293 8294 if ( this.hasAttribute( "target" ) ) { 8295 targetAttribute = "target='" + this.getAttribute( "target" ) + "'"; 8296 } else { 8297 targetAttribute = "target='" + targetAttribute + "'"; 8298 } 8299 8300 if ( this.getAttribute( "target" ) === "_blank" ) { 8301 exitMsg = exitMsg + " " + targetWarning; 8302 } 8303 8304 if ( document.getElementById( moDalId ) ) { 8305 document.getElementById( moDalId ).remove(); 8306 8307 } 8308 8309 if ( !settings.url ) { 8310 tpl.innerHTML = "<section id='" + moDalId + "' " + "class='mfp-hide modal-dialog modal-content overlay-def'>" + 8311 "<header class='modal-header'><h2 class='modal-title'>" + msgboxHeader + "</h2></header>" + 8312 "<div class='modal-body'>" + 8313 "<p>" + exitMsg + "</p>" + 8314 "</div>" + 8315 "<div class='modal-footer'>" + 8316 "<ul class='list-inline text-center'>" + 8317 "<li><a class='btn btn-default pull-right popup-modal-dismiss'" + targetAttribute + " href='" + this.getAttribute( "href" ) + "'>" + yesBtn + "</a></li>" + 8318 "<li><button class='btn btn-primary popup-modal-dismiss pull-left'>" + cancelBtn + "</button></li>" + 8319 "</ul></div></section>"; 8320 moDal.appendChild( tpl ); 8321 wrapper = moDal.firstChild; 8322 wrapper = wrapper.firstChild; 8323 document.body.appendChild( wrapper ); 8324 8325 $( wrapper ).trigger( "open.wb-lbx", [ 8326 [ { 8327 src: "#" + moDalId, 8328 type: "inline" 8329 } ], 8330 8331 true 8332 8333 ] ); 8334 8335 } else if ( crypto && this[ componentName ] ) { 8336 8337 // Save to localstorage, the plugin init will ensure this data is only used once 8338 localStorage.setItem( componentName, wb.string.arrayBufferToBase64( this[ componentName ] ) ); 8339 localStorage.setItem( keyForKeyHolder, JSON.stringify( this[ keyForKeyHolder ] ) ); 8340 } 8341 8342} ); 8343 8344// Bind the init event of the plugin 8345$document.on( "timerpoke.wb " + initEvent, selector, init ); 8346 8347// Add the timer poke to initialize the plugin 8348wb.add( selector ); 8349 8350} )( jQuery, window, wb, crypto ); 8351 8352/** 8353* @title WET-BOEW Facebook embedded page 8354* @overview Helps with implementing Facebook embedded pages. 8355* @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 8356* @author @pjackson28 8357*/ 8358( function( $, window, wb ) { 8359"use strict"; 8360 8361/* 8362* Variable and function definitions. 8363* These are global to the plugin - meaning that they will be initialized once per page, 8364* not once per instance of plugin on the page. So, this is a good place to define 8365* variables that are common to all instances of the plugin on a page. 8366*/ 8367var componentName = "wb-facebook", 8368 selector = "." + componentName, 8369 initEvent = "wb-init" + selector, 8370 $document = wb.doc, 8371 fbinited = false, 8372 8373 /** 8374 * @method init 8375 * @param {jQuery Event} event Event that triggered the function call 8376 */ 8377 init = function( event ) { 8378 8379 // Start initialization 8380 // returns DOM object = proceed with init 8381 // returns undefined = do not proceed with init (e.g., already initialized) 8382 var ele = wb.init( event, componentName, selector ), 8383 protocol = wb.pageUrlParts.protocol; 8384 8385 if ( ele ) { 8386 Modernizr.load( 8387 { 8388 load: [ ( protocol.indexOf( "http" ) === -1 ? "http:" : protocol ) + "//connect.facebook.net/" + wb.lang + "_US/sdk.js" ], 8389 complete: function() { 8390 if ( !fbinited ) { 8391 window.FB.init( { 8392 version: "v2.4" 8393 } ); 8394 fbinited = true; 8395 } 8396 8397 window.FB.XFBML.parse( ele[ 0 ] ); 8398 wb.ready( $( ele ), componentName ); 8399 } 8400 } 8401 ); 8402 } 8403 }; 8404 8405$document.on( "timerpoke.wb " + initEvent, selector, init ); 8406 8407// Add the timer poke to initialize the plugin 8408wb.add( selector ); 8409 8410} )( jQuery, window, wb ); 8411 8412/** 8413 * @title WET-BOEW Favicon Plugin 8414 * @overview Provides the ability to add and update a page's favicons 8415 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 8416 * @author @patheard 8417 * 8418 * This plugin provides the ability to add and update the favicon's on a web page. Its default behaviour is to add a mobile favicon to web pages that have a favicon defined by a `<link rel='icon'>` element. 8419 * 8420 * The mobile favicon's file name, rel, path and sizes can be set with data attributes on the `<link rel='icon'/>`: 8421 * 8422 * -**data-filename:** filename of the mobile favicon (defaults to "favicon-mobile.png"). This will be appended to the favicon's path. 8423 * -**data-path:** path to the mobile favicon (defaults to using the same path as the shortcut icon). 8424 * -**data-rel:** rel attribute of the mobile favicon (defaults to "apple-touch-icon"). 8425 * -**data-sizes:** sizes attribute of the mobile favicon (defaults to "57x57 72x72 114x114 144x144 150x150"). 8426 * 8427 * For example, the following overides the rel and file name attributes of the mobile favicon: 8428 * 8429 * <link href="favion.ico" rel='icon' data-rel="apple-touch-icon-precomposed" data-filename="my-mobile-favicon.ico"/> 8430 */ 8431( function( $, document, wb ) { 8432"use strict"; 8433 8434/* 8435 * Variable and function definitions. 8436 * These are global to the plugin - meaning that they will be initialized once per page, 8437 * not once per instance of plugin on the page. So, this is a good place to define 8438 * variables that are common to all instances of the plugin on a page. 8439 */
8440var componentName = "wb-favicon", 8441 selector = "link[rel='icon']", 8442 initEvent = "wb-init." + componentName, 8443 updatedEvent = "wb-updated." + componentName, 8444 mobileEvent = "mobile." + componentName, 8445 iconEvent = "icon." + componentName, 8446 $document = wb.doc, 8447 8448 /* 8449 * Plugin users can override these defaults by setting attributes on the html elements that the 8450 * selector matches. 8451 * For example, adding the attribute data-option1="false", will override option1 for that plugin instance. 8452 */ 8453 defaults = { 8454 filename: "favicon-mobile.png", 8455 path: null, 8456 rel: "apple-touch-icon", 8457 sizes: "57x57 72x72 114x114 144x144 150x150" 8458 }, 8459 8460 /** 8461 * @method init 8462 * @param {jQuery Event} event Event that triggered the function call 8463 */ 8464 init = function( event ) { 8465 8466 // Start initialization 8467 // returns DOM object = proceed with init 8468 // returns undefined = do not proceed with init (e.g., already initialized) 8469 var elm = wb.init( event, componentName, selector, true ), 8470 $favicon, settings; 8471 8472 if ( elm ) { 8473 $favicon = $( elm ); 8474 8475 // Merge default settings with overrides from the selected plugin element. 8476 settings = $.extend( {}, defaults, $favicon.data() ); 8477 8478 $favicon.trigger( mobileEvent, settings ); 8479 8480 // Identify that initialization has completed 8481 wb.ready( $document, componentName ); 8482 } 8483 }, 8484 8485 /** 8486 * Adds, or updates, the mobile favicon on a page. Mobile favicons are identified by the 8487 * `apple` prefix in the `<link>` elements rel attribute. 8488 * @method mobile 8489 * @param {DOM element} favicon Favicon element 8490 * @param {jQuery Event} event Event that triggered this handler 8491 * @param {Object} data Key-value data object passed with the event 8492 */ 8493 mobile = function( favicon, event, data ) { 8494 var faviconPath, 8495 faviconMobile = $( "link[rel^='apple']" ), 8496 isFaviconMobile = faviconMobile.length !== 0; 8497 8498 // Create the mobile favicon if it doesn't exist 8499 if ( !isFaviconMobile ) { 8500 var lnk = document.createElement( "link" ); 8501 lnk.setAttribute( "rel", data.rel ); 8502 lnk.setAttribute( "sizes", data.sizes ); 8503 lnk.setAttribute( "class", componentName ); 8504 8505 document.head.appendChild( lnk ); 8506 8507 faviconMobile = $( lnk ); 8508 } 8509 8510 // Only add/update a mobile favicon that was created by the plugin 8511 if ( faviconMobile.hasClass( componentName ) ) { 8512 faviconPath = data.path !== null ? data.path : getPath( favicon.getAttribute( "href" ) ); 8513 faviconMobile.attr( "href", faviconPath + data.filename ); 8514 8515 if ( !isFaviconMobile ) { 8516 favicon.parentNode.insertBefore( faviconMobile[ 0 ], favicon ); 8517 } 8518 } 8519 8520 $document.trigger( updatedEvent, [ "mobile" ] ); 8521 }, 8522 8523 /** 8524 * Updates the the page's shortcut icon 8525 * @method icon 8526 * @param {DOM element} favicon Favicon element 8527 * @param {jQuery Event} event Event that triggered this handler 8528 * @param {Object} data Key-value data object passed with the event 8529 */ 8530 icon = function( favicon, event, data ) { 8531 var faviconPath = data.path !== null ? data.path : getPath( favicon.getAttribute( "href" ) ); 8532 favicon.setAttribute( "href", faviconPath + data.filename ); 8533 8534 $document.trigger( updatedEvent, [ "icon" ] ); 8535 }, 8536 8537 /** 8538 * Given a full file path, returns the path without the filename 8539 * @method getPath 8540 * @param {string} filepath The full path to file, including filename 8541 * @returns {string} The path to the file 8542 */ 8543 getPath = function( filepath ) { 8544 return filepath.substring( 0, filepath.lastIndexOf( "/" ) + 1 ); 8545 }; 8546 8547// Bind the init event 8548$document.on( "timerpoke.wb " + initEvent, selector, init ); 8549 8550// Bind the mobile and icon events 8551$document.on( mobileEvent + " " + iconEvent, selector, function( event, data ) { 8552 var eventTarget = event.target; 8553 8554 // Filter out any events triggered by descendants 8555 if ( event.currentTarget === eventTarget ) { 8556 switch ( event.type ) { 8557 case "mobile": 8558 mobile( eventTarget, event, data ); 8559 break; 8560 8561 case "icon": 8562 icon( eventTarget, event, data ); 8563 break; 8564 } 8565 } 8566 8567 /* 8568 * Since we are working with events we want to ensure that we are being passive about our control, 8569 * so returning true allows for events to always continue 8570 */ 8571 return true; 8572} ); 8573 8574// Add the timer poke to initialize the plugin
8575wb.add( selector ); 8576 8577} )( jQuery, document, wb ); 8578 8579/** 8580 * @title WET-BOEW Feeds 8581 * @overview Aggregates and displays entries from one or more Web feeds. 8582 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 8583 * @author @pjackson28 8584 */ 8585( function( $, window, wb, undef ) { 8586"use strict"; 8587 8588/* 8589 * Variable and function definitions. 8590 * These are global to the plugin - meaning that they will be initialized once per page, 8591 * not once per instance of plugin on the page. So, this is a good place to define 8592 * variables that are common to all instances of the plugin on a page. 8593 */ 8594var componentName = "wb-feeds", 8595 selector = "." + componentName, 8596 feedLinkSelector = "li > a", 8597 initEvent = "wb-init" + selector, 8598 $document = wb.doc, 8599 patt = /\\u([\d\w]{4})/g, 8600 8601 /** 8602 * @object Templates 8603 * @properties {function} 8604 * @param {object} requires a entry object of various ATOM based properties 8605 * @returns {string} modified string with appropiate markup/format for a entry object 8606 */ 8607 Templates = { 8608 8609 /** 8610 * [fickr template] 8611 * @param {entry object} data 8612 * @return {string} HTML string for creating a photowall effect 8613 */ 8614 flickr: function( data ) { 8615 var media = data.media.m, 8616 flickrData = { 8617 title: data.title, 8618 thumbnail: media.replace( "_m.", "_s." ), 8619 image: media.replace( "_m", "" ), 8620 description: data.description.replace( /^\s*<p>(.*?)<\/p>\s*<p>(.*?)<\/p>/i, "" ) 8621 }; 8622 8623 // due to CORS we cannot default to simple ajax pulls of the image. We have to inline the content box 8624 return "<li><a class='feed-flickr' href='#' data-flickr='" + 8625 wb.escapeAttribute( JSON.stringify( flickrData ) ) + "'><img src='" + flickrData.thumbnail + "' alt='" + 8626 wb.escapeAttribute( flickrData.title ) + "' title='" + wb.escapeAttribute( flickrData.title ) + 8627 "' class='img-responsive'/></a></li>"; 8628 }, 8629 8630 /** 8631 * [Youtube template] 8632 * @param {entry object} data 8633 * @return {string} HTML string for creating a photowall effect 8634 */ 8635 youtube: function( data ) { 8636 var youtubeDate = { 8637 title: data.title, 8638 videoId: data.id 8639 }; 8640 8641 // Due to CORS we cannot default to simple ajax pulls of the image. We have to inline the content box 8642 return "<li class='col-md-4 col-sm-6'><button class='btn btn-lnk feed-youtube' data-youtube='" + 8643 wb.escapeAttribute( JSON.stringify( youtubeDate ) ) + "'><img src='" + 8644 wb.pageUrlParts.protocol + "//img.youtube.com/vi/" + youtubeDate.videoId + "/mqdefault.jpg' alt='" + 8645 wb.escapeAttribute( youtubeDate.title ) + "' title='" + wb.escapeAttribute( youtubeDate.title ) + 8646 "' class='img-responsive' /></button></li>"; 8647 }, 8648 8649 /** 8650 * [pinterest template] 8651 * @param {entry object} data 8652 * @return {string} HTML string of formatted using a simple list / anchor view 8653 */ 8654 pinterest: function( data ) { 8655 var content = fromCharCode( data.description ).replace( /<a href="\/pin[^"]*"><img ([^>]*)><\/a>([^<]*)(<a .*)?/, "<a href='" + 8656 data.link + "'><img alt='' class='center-block' $1><br/>$2</a>$3" ); 8657 return "<li class='media'>" + content + 8658 ( data.publishedDate !== "" ? " <small class='small feeds-date'><time>" + 8659 wb.date.toDateISO( data.publishedDate, true ) + "</time></small>" : "" ) + "</li>"; 8660 }, 8661 8662 /** 8663 * [generic template] 8664 * @param {entry object} data 8665 * @return {string} HTML string of formatted using a simple list / anchor view 8666 */ 8667 generic: function( data ) { 8668 var title = data.title; 8669 8670 if ( typeof( title ) === "object" ) { 8671 if ( title.content ) { 8672 title = title.content; 8673 } else if ( title.type === "xhtml" && title.div ) { 8674 title = title.div.content; 8675 } 8676 } 8677 return "<li><a href='" + data.link + "'>" + title + "</a><br />" + 8678 ( data.publishedDate !== "" ? " <small class='feeds-date'><time>" + 8679 wb.date.toDateISO( data.publishedDate, true ) + "</time></small>" : "" ) + "</li>"; 8680 } 8681 }, 8682 8683 /** 8684 * Helper function that returns the string representaion of a unicode character 8685 * @method decode 8686 * @param {regex} match unicode pattern 8687 * @param {string} code string where unicode is needed to be converted 8688 * @return {string} unicode string character 8689 */ 8690 decode = function( match, code ) { 8691 return String.fromCharCode( parseInt( code, 16 ) ); 8692 }, 8693 8694 /**
8695 * Helper wrapper function that performs unicode decodes on a string 8696 * @method fromCharCode 8697 * @param {string} s string to sanitize with escaped unicode characters 8698 * @return {string} sanitized string 8699 */ 8700 fromCharCode = function( s ) { 8701 return s.replace( patt, decode ); 8702 }, 8703 8704 /** 8705 * Helper function that returns a class-based set limit on plugin instances 8706 * @method getLimit 8707 * @param {DOM object} elm The element to search for a class of the form limit-5 8708 * @return {number} 0 if none found, which means the plugin default 8709 */ 8710 getLimit = function( elm ) { 8711 var count = elm.className.match( /\blimit-\d+/ ); 8712 if ( !count ) { 8713 return 0; 8714 } 8715 return Number( count[ 0 ].replace( /limit-/i, "" ) ); 8716 }, 8717 8718 /** 8719 * @method init 8720 * @param {jQuery Event} event Event that triggered the function call 8721 */ 8722 init = function( event ) { 8723 8724 // Start initialization 8725 // returns DOM object = proceed with init 8726 // returns undefined = do not proceed with init (e.g., already initialized) 8727 var elm = wb.init( event, componentName, selector ), 8728 fetch, url, $content, limit, feeds, fType, last, i, callback, fElem, fIcon, youtubeData, $elm; 8729 if ( elm ) { 8730 $elm = $( elm ); 8731 $content = $elm.find( ".feeds-cont" ); 8732 limit = getLimit( elm ); 8733 feeds = $content.find( feedLinkSelector ); 8734 last = feeds.length - 1; 8735 8736 // Lets bind some variables to the node to ensure safe ajax thread counting 8737 8738 $content.data( "toProcess", feeds.length ) 8739 .data( "feedLimit", limit ) 8740 .data( "entries", [] ); 8741 8742 for ( i = last; i !== -1; i -= 1 ) { 8743 fElem = feeds.eq( i ); 8744 fIcon = fElem.find( "> img" ); 8745 8746 fetch = { 8747 dataType: "jsonp", 8748 timeout: 10000 8749 }; 8750 8751 if ( fElem.attr( "data-ajax" ) ) { 8752 8753 if ( fElem.attr( "href" ).indexOf( "flickr" ) !== -1 ) { 8754 fType = "flickr"; 8755 callback = "jsoncallback"; 8756 $content.data( componentName + "-postProcess", [ ".wb-lbx" ] ); 8757 } else { 8758 fType = "generic"; 8759 callback = false; 8760 fetch.dataType = "json"; 8761 } 8762 8763 // We need a Gallery so lets add another plugin 8764 // #TODO: Lightbox review for more abstraction we should not have to add a wb.add() for overlaying 8765 fetch.url = fElem.attr( "data-ajax" ); 8766 fetch.jsonp = callback; 8767 } else if ( fElem.attr( "data-youtube" ) ) { 8768 youtubeData = wb.getData( fElem, "youtube" ); 8769 8770 $content.data( componentName + "-postProcess", [ ".wb-lbx", ".wb-mltmd" ] ); 8771 8772 if ( youtubeData.playlist ) { 8773 fElem.trigger( { 8774 type: "data-ready.wb-feeds", 8775 feedsData: youtubeData.playlist 8776 }, { 8777 feedType: "youtube", 8778 _content: $content 8779 } ); 8780 } 8781 8782 } else { 8783 8784 url = fElem.attr( "href" ); 8785 fetch.dataType = "xml"; 8786 fetch.url = url; 8787 8788 // Let's bind the template to the Entries 8789 if ( url.indexOf( "pinterest.com" ) > -1 ) { 8790 fType = "pinterest"; 8791 } else { 8792 fType = "generic"; 8793 } 8794 } 8795 8796 fetch.jsonp = callback; 8797 8798 fetch.context = { 8799 fIcon: ( fIcon.length !== 0 ) ? fIcon.attr( "src" ) : "", 8800 feedType: fType, 8801 _content: $content 8802 }; 8803 8804 fElem.trigger( { 8805 type: "ajax-fetch.wb", 8806 fetch: fetch 8807 } ); 8808 } 8809 } 8810 }, 8811 8812 /** 8813 * Process Feed/JSON Entries for CORS Enabled 8814 * @method corsEntry 8815 */ 8816 corsEntry = function( xmlDoc, limit ) { 8817 var entries = xmlDoc.getElementsByTagName( "entry" ).length, 8818 arr_entry = [], 8819 corsObj = {}, 8820 jsonString = JSON.stringify( xmlToJson( xmlDoc ) ), 8821 jsonObj = JSON.parse( jsonString ), 8822 i, iCache; 8823 if ( limit > entries || limit === 0 || limit === null ) { 8824 limit = entries; 8825 } 8826 if ( entries === 1 ) { 8827 iCache = jsonObj.feed.entry; 8828 corsObj = { 8829 title: iCache.title[ "#text" ], 8830 link: ( iCache.link ? iCache.link[ "@attributes" ].href : iCache.id[ "#text" ] ), 8831 updated: iCache.updated[ "#text" ] 8832 }; 8833 arr_entry.push( corsObj ); 8834 } else if ( entries ) { 8835 for ( i = 0; i < limit; i++ ) { 8836 iCache = jsonObj.feed.entry[ i ]; 8837 corsObj = { 8838 title: iCache.title[ "#text" ], 8839 link: ( iCache.link ? iCache.link[ "@attributes" ].href : iCache.id[ "#text" ] ), 8840 updated: iCache.updated[ "#text" ] 8841 }; 8842 arr_entry.push( corsObj ); 8843 } 8844 } 8845 return arr_entry; 8846 }, 8847 8848 /** 8849 * Process XML to JSON 8850 * @method xmlToJson 8851 * @param {xml} 8852 */ 8853 xmlToJson = function( xml ) { 8854 8855 var obj = {}, 8856 i, iCache, nodeName, old, 8857 xmlAttributes, xmlChildNodes, 8858 xmlNodeType = xml.nodeType; 8859 8860 if ( xmlNodeType === 1 ) { 8861 xmlAttributes = xml.attributes; 8862 if ( xmlAttributes.length ) { 8863 obj[ "@attributes" ] = {}; 8864 for ( i = 0; i < xmlAttributes.length; i++ ) { 8865 iCache = xmlAttributes.item( i ); 8866 obj[ "@attributes" ][ iCache.nodeName ] = iCache.nodeValue; 8867 } 8868 } 8869 } else if ( xmlNodeType === 3 ) { 8870 obj = xml.nodeValue; 8871 } 8872 8873 if ( xml.hasChildNodes() ) { 8874 xmlChildNodes = xml.childNodes; 8875 for ( i = 0; i < xmlChildNodes.length; i++ ) { 8876 iCache = xmlChildNodes.item( i ); 8877 nodeName = iCache.nodeName; 8878 if ( typeof( obj[ nodeName ] ) === "undefined" ) { 8879 obj[ nodeName ] = xmlToJson( iCache ); 8880 } else { 8881 if ( typeof( obj[ nodeName ].push ) === "undefined" ) { 8882 old = obj[ nodeName ]; 8883 obj[ nodeName ] = []; 8884 obj[ nodeName ].push( old ); 8885 } 8886 obj[ nodeName ].push( xmlToJson( iCache ) ); 8887 } 8888 } 8889 } 8890 return obj; 8891 }, 8892 8893 /** 8894 * Process Feed/JSON Entries 8895 * @method processEntries 8896 * @param {data} JSON formatted data to process 8897 * @return {string} of HTML output 8898 */ 8899 processEntries = function( data ) { 8900 var items = data, 8901 entries = [], 8902 icon = this.fIcon, 8903 $content = this._content, 8904 toProcess = $content.data( "toProcess" ), 8905 i, len; 8906 8907 len = items.length; 8908 for ( i = 0; i !== len; i += 1 ) { 8909 items[ i ].fIcon = icon; 8910 8911 if ( items[ i ].publishedDate === undef ) { 8912 items[ i ].publishedDate = ( items[ i ].published || items[ i ].pubDate || items[ i ].updated || "" ); 8913 } 8914 8915 var link = items[ i ].link; 8916 8917 if ( link && link.href ) { 8918 items[ i ].link = link.href; 8919 } 8920 8921 entries.push( items[ i ] ); 8922 } 8923 8924 // lets merge with latest entries 8925 entries = $.merge( entries, $content.data( "entries" ) ); 8926 8927 if ( toProcess === 1 ) { 8928 parseEntries( entries, $content.data( "feedLimit" ), $content, this.feedType ); 8929 return 0; 8930 } 8931 8932 toProcess -= 1; 8933 $content.data( { 8934 "toProcess": toProcess, 8935 "entries": entries 8936 } ); 8937 8938 return toProcess; 8939 }, 8940 8941 /** 8942 * Parses the results from a JSON request and appends to an element 8943 * @method parseEntries 8944 * @param {object} entries Results from a JSON request. 8945 * @param {integer} limit Limit on the number of results to append to the element. 8946 * @param {jQuery DOM element} $elm Element to which the elements will be appended. 8947 * @return {url} The URL for the JSON request 8948 */ 8949 parseEntries = function( entries, limit, $elm, feedtype ) { 8950 var cap = ( limit > 0 && limit < entries.length ? limit : entries.length ), 8951 result = "", 8952 compare = wb.date.compare, 8953 $details = $elm.closest( "details" ), 8954 activate = true, 8955 feedContSelector = ".feeds-cont", 8956 hasVisibilityHandler = "vis-handler", 8957 i, sorted, sortedEntry, $tabs; 8958 8959 if ( feedtype !== "youtube" ) { 8960 sorted = entries.sort( function( a, b ) { 8961 return compare( b.publishedDate, a.publishedDate ); 8962 } ); 8963 } else { 8964 sorted = entries; 8965 } 8966 8967 for ( i = 0; i !== cap; i += 1 ) { 8968 sortedEntry = sorted[ i ]; 8969 result += Templates[ feedtype ]( sortedEntry ); 8970 } 8971 $elm.data( componentName + "-result", result ); 8972 8973 // Check to see if feed should be activated (only if visible) 8974 // and add handler to determine visibility 8975 if ( $details.length !== 0 ) { 8976 if ( $details.attr( "role" ) === "tabpanel" ) { 8977 if ( $details.attr( "aria-hidden" ) === "true" ) { 8978 activate = false;
8979 $elm.empty().addClass( "waiting" ); 8980 $tabs = $details.closest( ".wb-tabs" ); 8981 if ( !$tabs.hasClass( hasVisibilityHandler ) ) { 8982 $tabs 8983 .on( "wb-updated.wb-tabs", function( event, $newPanel ) { 8984 var $feedCont = $newPanel.find( feedContSelector ); 8985 if ( !$feedCont.hasClass( "feed-active" ) ) { 8986 activateFeed( $feedCont ); 8987 } 8988 } ) 8989 .addClass( hasVisibilityHandler ); 8990 } 8991 } 8992 } else if ( !$details.attr( "open" ) ) { 8993 activate = false; 8994 $elm.empty().addClass( "waiting" ); 8995 $details 8996 .children( "summary" ) 8997 .on( "click.wb-feeds", function( event ) { 8998 var $summary = $( event.currentTarget ).off( "click.wb-feeds" ); 8999 activateFeed( $summary.parent().find( feedContSelector ) ); 9000 } ); 9001 } 9002 } 9003 9004 if ( activate ) { 9005 activateFeed( $elm ); 9006 } 9007 9008 return true; 9009 }, 9010 9011 /** 9012 * Activates feed results view 9013 * @method activateFeed 9014 * @param = {jQuery object} $elm Feed container 9015 */ 9016 activateFeed = function( $elm ) { 9017 var result = $elm.data( componentName + "-result" ), 9018 postProcess = $elm.data( componentName + "-postProcess" ), 9019 i, postProcessSelector; 9020 9021 if ( !result ) { 9022 return; 9023 } 9024 9025 $elm.empty() 9026 .removeClass( "waiting" ) 9027 .addClass( "feed-active" ) 9028 .append( result ); 9029 9030 if ( postProcess ) { 9031 for ( i = postProcess.length - 1; i !== -1; i -= 1 ) { 9032 postProcessSelector = postProcess[ i ]; 9033 $elm.find( postProcessSelector ) 9034 .trigger( "wb-init" + postProcessSelector ); 9035 } 9036 } 9037 9038 // Identify that the feed has now been displayed 9039 $elm.trigger( "wb-feed-ready" + selector ); 9040 }; 9041 9042$document.on( "ajax-fetched.wb data-ready.wb-feeds", selector + " " + feedLinkSelector, function( event, context ) { 9043 var eventTarget = event.target, 9044 data, response, responseRaw, 9045 $emlRss, limit, results; 9046 9047 // Filter out any events triggered by descendants 9048 if ( event.currentTarget === eventTarget ) { 9049 $emlRss = $( eventTarget ).parentsUntil( selector ).parent(); 9050 switch ( event.type ) { 9051 case "ajax-fetched": 9052 responseRaw = event.fetch.response; 9053 if ( typeof responseRaw === "string" ) { 9054 response = JSON.parse( responseRaw ); // Assuming we have fetch a JSON document, try to parse it. 9055 } else { 9056 response = responseRaw.get( 0 ); // fetched an HTML or XML document which has been parsed by jQuery and sanitized by DomPurify 9057 } 9058 if ( response.documentElement ) { 9059 limit = getLimit( $emlRss[ Object.keys( $emlRss )[ 0 ] ] ); 9060 data = corsEntry( response, limit ); 9061 } else if ( response.query ) { 9062 results = response.query.results; 9063 if ( !results ) { 9064 data = results.item; // Flicker feeds 9065 if ( !Array.isArray( data ) ) { 9066 data = [ data ]; 9067 } 9068 } else { 9069 data = []; 9070 } 9071 } else { 9072 data = ( response.responseData ) ? response.responseData.feed.entries : response.items || response.feed.entry; 9073 } 9074 break; 9075 default: 9076 data = event.feedsData; 9077 } 9078 9079 // Identify that initialization has completed 9080 // if there are no entries left to process 9081 if ( processEntries.apply( context, [ data ] ) === 0 ) { 9082 wb.ready( $( eventTarget ).closest( selector ), componentName ); 9083 } 9084 } 9085} ); 9086 9087$document.on( "click", selector + " .feed-youtube", function( event ) { 9088 var youTubeOverlaySelector = "#wb-feeds-youtube-lbx", 9089 $youTubeOverlay = $( youTubeOverlaySelector ), 9090 youtubeData = wb.getData( event.currentTarget, "youtube" ), 9091 videoUrl = wb.pageUrlParts.protocol + "//www.youtube.com/watch?v=" + youtubeData.videoId, 9092 videoSource = "<figure class='wb-mltmd'><video title='" + youtubeData.title + "'>" + 9093 "<source type='video/youtube' src='" + videoUrl + "'></source>" + 9094 "</video><figcaption><p>" + youtubeData.title + "</p>" + 9095 "</figcaption></figure>"; 9096 9097 if ( $youTubeOverlay.length === 0 ) { 9098 $youTubeOverlay = $( "<section id='wb-feeds-youtube-lbx' class='mfp-hide modal-dialog modal-content overlay-def'>" + 9099 "<header class='modal-header'><h2 class='modal-title'>" + youtubeData.title + "</h2></header>" + 9100 "<div class='modal-body'>" + 9101 videoSource + 9102 "</div></section>" ).insertAfter( "main" ); 9103 } else { 9104 9105 //Modify lightbox 9106 $youTubeOverlay.find( ".modal-title" ).text( youtubeData.title ); 9107 $youTubeOverlay.find( ".modal-body" ).empty().append( videoSource ); 9108 } 9109 9110 //Temporary fix until lightbox auto initialize the multimedia player 9111 $youTubeOverlay.find( ".wb-mltmd" ).trigger( "wb-init.wb-mltmd" ); 9112 9113 $( document ).trigger( "open.wb-lbx", [ { 9114 src: youTubeOverlaySelector, 9115 type: "inline" 9116 } ] ); 9117} ); 9118 9119$document.on( "click", selector + " .feed-flickr", function( event ) { 9120 var flickrOverlaySelector = "#wb-feeds-flick-lbx", 9121 $flickrOverlay = $( flickrOverlaySelector ), 9122 flickrData = wb.getData( event.currentTarget, "flickr" ), 9123 body = "<img src='" + flickrData.image + "' class='thumbnail center-block' alt='" + flickrData.title + "' /><span>" + 9124 flickrData.description + "</span>"; 9125 9126 if ( $flickrOverlay.length === 0 ) { 9127 $flickrOverlay = $( "<section id='wb-feeds-flick-lbx' class='mfp-hide modal-dialog modal-content overlay-def'>" + 9128 "<header class='modal-header'><h2 class='modal-title'>" + flickrData.title + "</h2></header>" + 9129 "<div class='modal-body'>" + body + "</div></section>" ).insertAfter( "main" ); 9130 } else { 9131 9132 //Modify lightbox 9133 $flickrOverlay.find( ".modal-title" ).text( flickrData.title ); 9134 $flickrOverlay.find( ".modal-body" ).empty().append( body ); 9135 } 9136 9137 $( document ).trigger( "open.wb-lbx", [ { 9138 src: flickrOverlaySelector, 9139 type: "inline" 9140 } ] ); 9141} ); 9142 9143// Bind the init event to the plugin 9144$document.on( "timerpoke.wb " + initEvent, selector, init ); 9145 9146// Add the timer poke to initialize the plugin
9147wb.add( selector ); 9148 9149} )( jQuery, window, wb ); 9150 9151( function( $, window, wb ) { 9152"use strict"; 9153 9154var componentName = "wb-filter", 9155 selector = "." + componentName, 9156 initEvent = "wb-init" + selector, 9157 $document = wb.doc, 9158 filterClass = "wb-fltr-out", 9159 tagFilterClass = "wb-tgfltr-out", 9160 notFilterClassSel = ":not(." + filterClass + "):not(." + tagFilterClass + ")", 9161 inputClass = "wb-fltr-inpt", 9162 dtNameFltrArea = "wbfltrid", 9163 selectorInput = "." + inputClass, 9164 defaults = { 9165 std: { 9166 selector: "li" 9167 }, 9168 grp: { 9169 selector: "li", 9170 section: ">section" 9171 }, 9172 tbl: { 9173 selector: "tr", 9174 section: ">tbody" 9175 }, 9176 tblgrp: { 9177 selector: " th:not([scope])" + notFilterClassSel, 9178 hdnparentuntil: "tbody", 9179 section: ">tbody" 9180 } 9181 }, 9182 i18n, i18nText, 9183 wait, 9184 9185 init = function( event ) { 9186 var elm = wb.init( event, componentName, selector ), 9187 $elm, elmTagName, filterUI, prependUI, 9188 settings, setDefault, 9189 itemsObserver, 9190 inptId, totalEntries, 9191 secSelector, 9192 uiTemplate, uiInpt, uiInfo, 9193 uiNbItems, uiTotal, uiInfoID; 9194 9195 if ( elm ) { 9196 $elm = $( elm ); 9197 elmTagName = elm.nodeName; 9198 uiInfoID = elm.id + "-info"; 9199 9200 if ( [ "DIV", "SECTION", "ARTICLE" ].indexOf( elmTagName ) >= 0 ) { 9201 setDefault = defaults.grp; 9202 prependUI = true; 9203 } else if ( elmTagName === "TABLE" ) { 9204 if ( $elm.find( "tbody" ).length > 1 ) { 9205 setDefault = defaults.tblgrp; 9206 } else { 9207 setDefault = defaults.tbl; 9208 } 9209 } else { 9210 setDefault = defaults.std; 9211 } 9212 9213 settings = $.extend( true, {}, setDefault, window[ componentName ], wb.getData( $elm, componentName ) ); 9214 $elm.data( settings ); 9215 9216 if ( !i18nText ) { 9217 i18n = wb.i18n; 9218 i18nText = { 9219 filter_label: i18n( "fltr-lbl" ), 9220 fltr_info: i18n( "fltr-info" ) 9221 }; 9222 } 9223 9224 Modernizr.addTest( "stringnormalize", "normalize" in String ); 9225 Modernizr.load( { 9226 test: Modernizr.stringnormalize, 9227 nope: [ 9228 "site!deps/unorm" + wb.getMode() + ".js" 9229 ] 9230 } ); 9231 9232 if ( !elm.id ) { 9233 elm.id = wb.getId(); 9234 } 9235 9236 if ( settings.uiTemplate ) { 9237 uiTemplate = document.querySelector( settings.uiTemplate ); 9238 uiInpt = uiTemplate.querySelector( "input[type=search]" ); 9239 9240 if ( uiInpt ) { 9241 uiInfo = uiTemplate.querySelector( ".wb-fltr-info" ); 9242 9243 uiInpt.classList.add( inputClass ); 9244 uiInpt.setAttribute( "data-" + dtNameFltrArea, elm.id ); 9245 uiInpt.setAttribute( "aria-controls", elm.id ); 9246 9247 if ( uiInfo ) { 9248 uiInfoID = uiInfo.id || uiInfoID; 9249 uiInfo.id = uiInfoID; 9250 uiInfo.setAttribute( "role", "status" ); 9251 } 9252 } else { 9253 console.error( componentName + ": " + "an <input type=\"search\"> is required in your UI template." ); 9254 } 9255 9256 if ( settings.source ) { 9257 console.warn( componentName + ": " + "the 'source' option is not compatible with the 'uiTemplate' option. If both options are defined, only 'uiTemplate' will be registered." ); 9258 } 9259 } else { 9260 inptId = elm.id + "-inpt"; 9261 filterUI = $( "<div class=\"input-group\">" + 9262 "<label for=\"" + inptId + "\" class=\"input-group-addon\"><span class=\"glyphicon glyphicon-filter\" aria-hidden=\"true\"></span> " + i18nText.filter_label + "</label>" + 9263 "<input id=\"" + inptId + "\" class=\"form-control " + inputClass + "\" data-" + dtNameFltrArea + "=\"" + elm.id + "\" aria-controls=\"" + elm.id + "\" type=\"search\">" + 9264 "</div>" + 9265 "<p role=\"status\" id=\"" + uiInfoID + "\">" + i18nText.fltr_info + "</p>" ); 9266 9267 if ( settings.source ) { 9268 $( settings.source ).prepend( filterUI ); 9269 } else if ( prependUI ) { 9270 $elm.prepend( filterUI ); 9271 } else { 9272 $elm.before( filterUI ); 9273 } 9274 } 9275 9276 secSelector = ( settings.section || "" ) + " "; 9277 totalEntries = $elm.find( secSelector + settings.selector ).length; 9278 uiNbItems = document.querySelector( "#" + uiInfoID + " [data-nbitem]" ); 9279 uiTotal = document.querySelector( "#" + uiInfoID + " [data-total]" ); 9280 9281 if ( uiNbItems ) { 9282 uiNbItems.textContent = totalEntries; 9283 9284 itemsObserver = new MutationObserver( function() { 9285 uiNbItems.textContent = $elm.find( secSelector + settings.selector + notFilterClassSel ).length; 9286 } ); 9287 9288 itemsObserver.observe( elm, { attributes: true, subtree: true } ); 9289 } 9290 9291 if ( uiTotal ) { 9292 uiTotal.textContent = totalEntries; 9293 } 9294 9295 wb.ready( $elm, componentName ); 9296 } 9297 }, 9298 9299 /* 9300 * Takes in the text from the filter box 9301 * Returns: 9302 * An array of search words 9303 * Special characters are escaped 9304 * Double and single quotes removed 9305 */ 9306 filterQueryParser = function( filter ) { 9307
9308 // Pattern to seperate the filter text into "words" 9309 var pattern = /[^\s"]+|"([^"]*)"/gi; 9310 9311 // Make strings safe again for regex 9312 // Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions 9313 filter = filter.replace( /[.*+?^${}()|[\]\\]/g, "\\$&" ); 9314 9315 // Apply the word match pattern and return 9316 return filter.match( pattern ); 9317 }, 9318 9319 /** 9320 * Build the Regular Expression that we are going 9321 * to use to filter content 9322 * This involves identifying the type of search being 9323 * applied and then breaking up the search string into 9324 * words 9325 */ 9326 buildSearchFilterRegularExp = function( filterType, filter ) { 9327 9328 var words, wordRegExFilter = filter, 9329 i, i_len; 9330 9331 switch ( filterType ) { 9332 9333 case "and": 9334 words = filterQueryParser( filter ); 9335 if ( words ) { 9336 wordRegExFilter = ".*"; 9337 i_len = words.length; 9338 for ( i = 0; i < i_len; i++ ) { 9339 wordRegExFilter = wordRegExFilter + ( "(?=.*" + words[ i ] + ")" ); 9340 } 9341 } 9342 break; 9343 9344 case "or": // If one word fall back on default 9345 words = filterQueryParser( filter ); 9346 if ( words ) { 9347 wordRegExFilter = words.join( "|" ); 9348 } 9349 break; 9350 9351 default: 9352 break; 9353 9354 } 9355 9356 return new RegExp( wordRegExFilter, "i" ); 9357 9358 }, 9359 9360 filter = function( $field, $elm, settings ) { 9361 var unAccent = function( str ) { 9362 return str.normalize( "NFD" ).replace( /[\u0300-\u036f]/g, "" ); 9363 }, 9364 filter = unAccent( $field.val().trim() ), 9365 fCallBack = settings.filterCallback, 9366 secSelector = ( settings.section || "" ) + " ", 9367 hndParentSelector = settings.hdnparentuntil, 9368 $items = $elm.find( secSelector + settings.selector ), 9369 itemsLength = $items.length, 9370 i, $item, text, searchFilterRegularExp; 9371 9372 $elm.find( "." + filterClass ).removeClass( filterClass ); 9373 9374 searchFilterRegularExp = buildSearchFilterRegularExp( settings.filterType, filter ); 9375 9376 for ( i = 0; i < itemsLength; i += 1 ) { 9377 $item = $items.eq( i ); 9378 text = unAccent( $item.text() ); 9379 9380 if ( !searchFilterRegularExp.test( text ) ) { 9381 if ( hndParentSelector ) { 9382 $item = $item.parentsUntil( hndParentSelector ); 9383 } 9384 $item.addClass( filterClass ); 9385 } 9386 } 9387 9388 if ( !fCallBack || typeof fCallBack !== "function" ) { 9389 fCallBack = filterCallback; 9390 } 9391 fCallBack.apply( this, arguments ); 9392 9393 $elm.trigger( "wb-contentupdated" ); 9394 }, 9395 filterCallback = function( $field, $elm, settings ) { 9396 var $sections = $elm.find( settings.section ), 9397 sectionsLength = $sections.length, 9398 fndSelector = notFilterClassSel + settings.selector, 9399 s, $section; 9400 9401 for ( s = 0; s < sectionsLength; s += 1 ) { 9402 $section = $sections.eq( s ); 9403 if ( $section.find( fndSelector ).length === 0 ) { 9404 $section.addClass( filterClass ); 9405 } 9406 } 9407 }; 9408 9409$document.on( "keyup", selectorInput, function( event ) { 9410 var target = event.target, 9411 $input = $( target ), 9412 $elm = $( "#" + $input.data( dtNameFltrArea ) ); 9413 9414 if ( wait ) { 9415 clearTimeout( wait ); 9416 } 9417 wait = setTimeout( filter.bind( this, $input, $elm, $elm.data() ), 250 ); 9418 9419} ); 9420 9421$document.on( "timerpoke.wb " + initEvent, selector, init ); 9422
9423wb.add( selector ); 9424} )( jQuery, window, wb ); 9425 9426/** 9427 * @title WET-BOEW Footnotes 9428 * @overview Provides a consistent, accessible way of handling footnotes across websites. 9429 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 9430 * @author @EricDunsworth 9431 */ 9432( function( $, window, wb ) { 9433"use strict"; 9434 9435/* 9436 * Variable and function definitions. 9437 * These are global to the plugin - meaning that they will be initialized once per page, 9438 * not once per instance of plugin on the page. So, this is a good place to define 9439 * variables that are common to all instances of the plugin on a page. 9440 */ 9441var componentName = "wb-fnote", 9442 selector = "." + componentName, 9443 modFlag = "data-" + componentName, 9444 initEvent = "wb-init" + selector, 9445 setFocusEvent = "setfocus.wb", 9446 $document = wb.doc, 9447 9448 /** 9449 * @method init 9450 * @param {jQuery Event} event Event that triggered the function call 9451 */ 9452 init = function( event ) { 9453 9454 // Start initialization 9455 // returns DOM object = proceed with init 9456 // returns undefined = do not proceed with init (e.g., already initialized) 9457 var elm = wb.init( event, componentName, selector ), 9458 $elm, footnoteDd, footnoteDt, i, len, dd, dt; 9459 9460 if ( elm ) { 9461 $elm = $( elm ); 9462 footnoteDd = elm.getElementsByTagName( "dd" ); 9463 footnoteDt = elm.getElementsByTagName( "dt" ); 9464 9465 // Set initial event handlers for return to referrer links 9466 len = footnoteDd.length; 9467 for ( i = 0; i !== len; i += 1 ) { 9468 dd = footnoteDd[ i ]; 9469 dt = footnoteDt[ i ]; 9470 dd.setAttribute( "tabindex", "-1" ); 9471 dt.id = dd.id + "-dt"; 9472 } 9473 9474 // Remove "first/premier/etc"-style text from certain footnote return links (via the child spans that hold those bits of text) 9475 $elm.find( "dd p.fn-rtn a span span" ).remove(); 9476 9477 // Listen for footnote reference links that get clicked 9478 $document.on( "click", "main :not(" + selector + ") sup a.fn-lnk", function( event ) { 9479 var eventTarget = event.target, 9480 which = event.which, 9481 refId, $refLinkDest; 9482 9483 // Ignore middle/right mouse button 9484 if ( !which || which === 1 ) { 9485 refId = "#" + wb.jqEscape( eventTarget.getAttribute( "href" ).substring( 1 ) ); 9486 $refLinkDest = $document.find( refId ); 9487 9488 $refLinkDest.find( "p.fn-rtn a" ) 9489 .attr( "href", "#" + eventTarget.parentNode.id ) 9490 .attr( modFlag, true ); 9491 9492 // Assign focus to $refLinkDest 9493 $refLinkDest.trigger( setFocusEvent ); 9494 return false; 9495 } 9496 } ); 9497 9498 // Listen for footnote return links that get clicked 9499 $document.on( "click", selector + " dd p.fn-rtn a", function( event ) { 9500 var which = event.which, 9501 $elmTarget = $( event.target ), 9502 ref, 9503 refId, 9504 refIdSrc, 9505 refIdDashIdx, 9506 searchRefId; 9507 9508 // Ignore middle/right mouse button 9509 if ( !which || which === 1 ) { 9510 ref = $elmTarget.attr( "href" ); 9511 9512 // Focus on associated referrer link (if the return link points to an ID) 9513 if ( ref.charAt( 0 ) === "#" ) { 9514 refId = wb.jqEscape( ref.substring( 1 ) ); 9515 9516 // When first clicked, ensure we send the user on the first instance when the id follow the recommend pattern 9517 refIdDashIdx = refId.indexOf( "-" ); 9518 if ( refIdDashIdx !== -1 && !$elmTarget.attr( modFlag ) ) { 9519 searchRefId = refId.substring( 0, refIdDashIdx + 1 ); 9520 refIdSrc = wb.jqEscape( $( "sup[id^='" + searchRefId + "']:first()" ).attr( "id" ) ); 9521 if ( !refIdSrc || refId !== refIdSrc ) { 9522 console.warn( componentName + " - Relink first reference of " + ref + " for #" + refIdSrc ); 9523 refId = refIdSrc; 9524 $elmTarget 9525 .attr( "href", "#" + refId ) 9526 .attr( modFlag, true ); 9527 } 9528 } 9529 9530 // Assign focus to the link 9531 $document.find( "#" + refId + " a" ).trigger( setFocusEvent ); 9532 return false; 9533 } 9534 } 9535 } ); 9536 9537 // Identify that initialization has completed 9538 wb.ready( $elm, componentName ); 9539 } 9540 }; 9541 9542// Bind the init event of the plugin 9543$document.on( "timerpoke.wb " + initEvent, selector, init ); 9544 9545// Add the timer poke to initialize the plugin 9546wb.add( selector ); 9547 9548} )( jQuery, window, wb ); 9549 9550/** 9551 * @title WET-BOEW Form validation 9552 * @overview Provides generic validation and error message handling for Web forms. 9553 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 9554 * @author @pjackson28 9555 */ 9556( function( $, window, document, wb ) { 9557"use strict"; 9558 9559/* 9560* Variable and function definitions. 9561* These are global to the plugin - meaning that they will be initialized once per page, 9562* not once per instance of plugin on the page. So, this is a good place to define 9563* variables that are common to all instances of the plugin on a page. 9564*/ 9565var componentName = "wb-frmvld", 9566 selector = "." + componentName, 9567 initEvent = "wb-init" + selector, 9568 setFocusEvent = "setfocus.wb", 9569 $document = wb.doc, 9570 idCount = 0, 9571 i18n, i18nText, 9572 9573 defaults = { 9574 hdLvl: "h2", 9575 ignore: ":hidden" 9576 }, 9577 9578 /** 9579 * @method init 9580 * @param {jQuery Event} event Event that triggered the function call 9581 */ 9582 init = function( event ) { 9583 9584 // Start initialization 9585 // returns DOM object = proceed with init 9586 // returns undefined = do not proceed with init (e.g., already initialized) 9587 var eventTarget = wb.init( event, componentName, selector ), 9588 elmId, modeJS; 9589 9590 if ( eventTarget ) { 9591 elmId = eventTarget.id; 9592 9593 // Ensure there is a unique id on the element 9594 if ( !elmId ) { 9595 elmId = componentName + "-id-" + idCount; 9596 idCount += 1; 9597 eventTarget.id = elmId; 9598 } 9599 9600 // Read the selector node for parameters 9601 modeJS = wb.getMode() + ".js"; 9602 9603 // Only initialize the i18nText once 9604 if ( !i18nText ) { 9605 i18n = wb.i18n; 9606 i18nText = { 9607 colon: i18n( "colon" ), 9608 hyphen: i18n( "hyphen" ), 9609 error: i18n( "err" ), 9610 errorFound: i18n( "err-fnd" ), 9611 errorsFound: i18n( "errs-fnd" ), 9612 formNotSubmitted: i18n( "frm-nosubmit" ), 9613 errorCorrect: i18n( "err-correct" ) 9614 }; 9615 } 9616 9617 Modernizr.load( { 9618 load: [ "site!deps/jquery.validate" + modeJS ], 9619 testReady: function() { 9620 return ( $.validator ); 9621 }, 9622 complete: function() { 9623 Modernizr.load( { 9624 9625 // For loading multiple dependencies 9626 load: [ "site!deps/additional-methods" + modeJS ], 9627 testReady: function() { 9628 return ( $.validator.methods.bic ); 9629 }, 9630 complete: function() { 9631 var $elm = $( "#" + elmId ), 9632 $form = $elm.find( "form" ), 9633 formDOM = $form.get( 0 ), 9634 formId = $form.attr( "id" ), 9635 labels = formDOM.getElementsByTagName( "label" ), 9636 submitted = false, 9637 errorFormId = "errors-" + ( !formId ? "default" : formId ), 9638 settings = $.extend( 9639 true, 9640 {}, 9641 defaults, 9642 window[ componentName ], 9643 wb.getData( $elm, componentName ) 9644 ), 9645 summaryHeading = settings.hdLvl, 9646 i, len, validator; 9647 9648 if ( wb.lang === "fr" ) { 9649 9650 // alphanumeric regex is changed to allow french characters; 9651 $.validator.addMethod( "alphanumeric", function( value, element ) { 9652 return this.optional( element ) || /^[a-zà âçéèêëîïôûùüÿæÅ0-9_]+$/i.test( value ); 9653 }, "Letters, numbers, and underscores only please." ); 9654 9655 // error french text is adjusted to remove the word "spaces" 9656 $.extend( $.validator.messages, {
9657 alphanumeric: "Veuillez fournir seulement des lettres, nombres et soulignages." 9658 } ); 9659 } 9660 9661 // Append the aria-live region (for provide message updates to screen readers) 9662 $elm.append( "<div class='arialive wb-inv' aria-live='polite' aria-relevant='all'></div>" ); 9663 9664 // Add space to the end of the labels (so separation between label and error when CSS turned off) 9665 len = labels.length; 9666 for ( i = 0; i !== len; i += 1 ) { 9667 labels[ i ].innerHTML += " "; 9668 } 9669 9670 // Hide "required" label text in older forms from screen readers 9671 // Prevents redundant "required" announcements on semantically-required fields whose labels mention they're required 9672 $form.find( "strong.required:not([aria-hidden='true'])" ).each( function() { 9673 const $requiredText = $( this ), 9674 $label = $requiredText.closest( "label" ), 9675 fieldId = $label.attr( "for" ); 9676 let $field = fieldId ? $( "#" + fieldId ) : $label.find( ":input" ).first(); 9677 9678 // If the label's field has yet to be found, look for fields that refer to the label via aria-labelledby 9679 if ( !$field.length ) { 9680 const labelId = $label.attr( "id" ) || $requiredText.closest( "id" ); 9681 $field = $form.find( "[aria-labelledby~='" + labelId + "']:input" ).first(); 9682 } 9683 9684 // Hide the "required" text if its field is semantically-required 9685 if ( $field.is( "[required], [aria-required='true']" ) ) { 9686 $requiredText.attr( "aria-hidden", "true" ); 9687 } 9688 } ); 9689 9690 // The jQuery validation plug-in in action 9691 validator = $form.validate( { 9692 meta: "validate", 9693 focusInvalid: false, 9694 ignore: settings.ignore, 9695 9696 // Set the element which will wrap the inline error messages 9697 errorElement: "strong", 9698 9699 // Location for the inline error messages 9700 // In this case we will place them in the associated label element 9701 errorPlacement: function( $error, $element ) { 9702 var type = $element.attr( "type" ), 9703 group = $element.attr( "data-rule-require_from_group" ), 9704 $fieldset, $legend; 9705 9706 $error.data( "element-id", $element.attr( "id" ) ); 9707 if ( type ) { 9708 type = type.toLowerCase(); 9709 if ( type === "radio" || type === "checkbox" ) { 9710 $fieldset = $element.closest( "fieldset" ); 9711 if ( $fieldset.length !== 0 ) { 9712 $legend = $fieldset.find( "legend" ).first(); 9713 if ( $legend.length !== 0 && $fieldset.find( "input[name='" + $element.attr( "name" ) + "']" ) !== 1 ) { 9714 $error.appendTo( $legend ); 9715 return; 9716 } 9717 } 9718 } 9719 } 9720 9721 if ( group ) { 9722 $fieldset = $element.closest( "fieldset" ); 9723 if ( $fieldset.length !== 0 ) { 9724 $legend = $fieldset.find( "legend" ).first(); 9725 if ( $legend.length !== 0 && $fieldset.find( "input[name='" + $element.attr( "name" ) + "']" ) !== 1 ) { 9726 var $strong = $legend.find( "strong.error" ), 9727 id = $legend.attr( "id" ); 9728 9729 if ( $strong.length > 0 ) { 9730 $strong.remove(); 9731 } 9732 9733 if ( !id ) { 9734 id = "required-group-" + idCount; 9735 idCount += 1; 9736 9737 $legend.attr( "id", id ); 9738 } 9739 9740 $error.data( "element-id", id ); 9741 $error.attr( "id", id ); 9742 $error.appendTo( $legend ); 9743 9744 return; 9745 } 9746 } 9747 } 9748 9749 //Std If we have a label and the input field is inside the label 9750 // need to add a css-implicite-input 9751 if ( $form.find( "label" ).find( ".wb-server-error + input.css-implicite-input[name='" + $element.attr( "name" ) + "']" ).length > 0 ) { 9752 $error.insertBefore( $form.find( ".wb-server-error + input.css-implicite-input[name='" + $element.attr( "name" ) + "']" ) ); 9753 return; 9754 } 9755 9756 $error.appendTo( $form.find( "label[for='" + $element.attr( "id" ) + "']" ) ); 9757 return; 9758 }, 9759 9760 // Create our error summary that will appear before the form 9761 showErrors: function( errorMap ) { 9762 this.defaultShowErrors(); 9763 var $errors = $form.find( ".wb-server-error, strong.error" ).filter( ":not(:hidden)" ), 9764 $errorfields = $form.find( "input.error, select.error, textarea.error" ), 9765 prefixStart = "<span class='prefix'>" + i18nText.error + " ", 9766 prefixEnd = i18nText.colon + " </span>", 9767 separator = i18nText.hyphen, 9768 ariaLive = $form.closest( ".wb-frmvld" ).find( ".arialive" )[ 0 ], 9769 $summaryContainer, summary, key, i, len, $error, prefix, $fieldName, $f
9769ieldset, label, labelString; 9770 9771 // Correct the colouring of fields that are no longer invalid 9772 $form 9773 .find( ".has-error [aria-invalid=false]" ) 9774 .closest( ".has-error" ) 9775 .removeClass( "has-error" ); 9776 9777 if ( $errors.length !== 0 ) { 9778 9779 // Post process 9780 summary = "<" + summaryHeading + ">" + 9781 i18nText.formNotSubmitted + $errors.length + 9782 ( 9783 $errors.length !== 1 ? 9784 i18nText.errorsFound : 9785 i18nText.errorFound 9786 ) + "</" + summaryHeading + "><ul>"; 9787 $errorfields 9788 .closest( ".form-group" ) 9789 .addClass( "has-error" ); 9790 len = $errors.length; 9791 for ( i = 0; i !== len; i += 1 ) { 9792 $error = $errors.eq( i ); 9793 prefix = prefixStart + ( i + 1 ) + prefixEnd; 9794 $fieldName = $error.closest( "label" ).find( ".field-name" ); 9795 9796 // Try to find the field name in the legend (if one exists) 9797 if ( $fieldName.length === 0 ) { 9798 $fieldset = $error.closest( "fieldset" ); 9799 if ( $fieldset.length !== 0 ) { 9800 $fieldName = $fieldset.find( "legend .field-name" ); 9801 } 9802 } 9803 9804 $error.find( "span.prefix" ).detach(); 9805 9806 //Verify if it is a wb-server-error 9807 if ( $errors[ i ].classList.contains( "wb-server-error" ) ) { 9808 if ( $errors[ i ].id ) { 9809 var myParent = document.getElementById( $errors[ i ].id ).parentElement; 9810 if ( myParent === null ) { 9811 summary += "<li><a>" + prefix + ( $fieldName.length !== 0 ? $fieldName.html() + separator : "" ) + $error.text() + separator + i18nText.errorCorrect + "</a></li>"; 9812 } else { 9813 if ( myParent.hasAttribute( "for" ) && myParent.getAttribute( "for" ).length > 0 ) { 9814 summary += "<li><a href='#" + myParent.getAttribute( "for" ) + "'>" + prefix + ( $fieldName.length !== 0 ? $fieldName.html() + separator : "" ) + $error.text() + separator + i18nText.errorCorrect + "</a></li>"; 9815 } else { 9816 if ( myParent.getElementsByTagName( "input" )[ 0 ] && myParent.getElementsByTagName( "input" )[ 0 ].name.length > 0 ) { 9817 summary += "<li><a href='#" + myParent.getElementsByTagName( "input" )[ 0 ].id + "'>" + prefix + ( $fieldName.length !== 0 ? $fieldName.html() + separator : "" ) + $error.text() + separator + i18nText.errorCorrect + "</a></li>"; 9818 } else { 9819 if ( myParent.tagName === ( "LEGEND" ) && ( myParent.parentElement.getElementsByTagName( "input" )[ 0 ].type === "checkbox" || myParent.parentElement.getElementsByTagName( "input" )[ 0 ].type === "radio" && myParent.parentElement.getElementsByTagName( "input" )[ 0 ].name.length > 0 ) ) { 9820 summary += "<li><a href='#" + myParent.parentElement.getElementsByTagName( "input" )[ 0 ].id + "'>" + prefix + ( $fieldName.length !== 0 ? $fieldName.html() + separator : "" ) + $error.text() + separator + i18nText.errorCorrect + "</a></li>"; 9821 } else { 9822 summary += "<li><a>" + prefix + ( $fieldName.length !== 0 ? $fieldName.html() + separator : "" ) + $error.text() + separator + i18nText.errorCorrect + "</a></li>"; 9823 } 9824 } 9825 } 9826 } 9827 $error.html( "<strong>" + prefix + $error.text() + "</strong>" ); 9828 } 9829 } else { 9830 summary += "<li><a href='#" + $error.data( "element-id" ) + "'>" + prefix + ( $fieldName.length !== 0 ? $fieldName.html() + separator : "" ) + $error.text() + "</a></li>"; 9831 $error.html( "<span class='label label-danger'>" + prefix + $error.text() + "</span>" ); 9832 } 9833 } 9834 summary += "</ul>"; 9835 9836 if ( !submitted ) { 9837 9838 // Update the aria-live region as necessary 9839 i = 0; 9840 for ( key in errorMap ) { 9841 if ( Object.prototype.hasOwnProperty.call( errorMap, key ) ) { 9842 i += 1; 9843 break; 9844 } 9845 } 9846 if ( i !== 0 ) { 9847 len = $errors.length; 9848 for ( i = 0; i !== len; i += 1 ) { 9849 label = $errors[ i ].parentNode; 9850 if ( label.getAttribute( "for" ) === key ) { 9851 labelString = label.innerHTML; 9852 if ( labelString !== ariaLive.innerHTML ) { 9853 ariaLive.innerHTML = labelString; 9854 } 9855 break; 9856 } 9857 } 9858 } else if ( ariaLive.innerHTML.length !== 0 ) { 9859 ariaLive.innerHTML = ""; 9860 } 9861 } 9862 9863 // Delay updating the summary container in case a summary link was clicked 9864 setTimeout( function() { 9865 $summaryContainer = $form.find( "#" + errorFormId ); 9866 9867 // Output our error summary and place it in the error container 9868 // Create our container if one doesn't already exist 9869 if ( $summaryContainer.length === 0 ) { 9870 $summaryContainer = $( "<section id='" + errorFormId + "' class='alert alert-danger' tabindex='-1'>
9870" + summary + "</section>" ).prependTo( $form ); 9871 } else { 9872 $summaryContainer.empty().append( summary ); 9873 } 9874 9875 // Put focus on the error if the errors are generated by an attempted form submission 9876 if ( submitted ) { 9877 9878 // Assign focus to $summaryContainer 9879 $summaryContainer.trigger( setFocusEvent ); 9880 submitted = false; 9881 } 9882 }, 100 ); 9883 } else { 9884 9885 // Update the aria-live region as necessary 9886 if ( ariaLive.innerHTML.length !== 0 ) { 9887 ariaLive.innerHTML = ""; 9888 } 9889 $form.find( "#" + errorFormId ).detach(); 9890 } 9891 }, 9892 9893 /* End of showErrors() */ 9894 9895 invalidHandler: function() { 9896 submitted = true; 9897 } 9898 9899 } ); /* end of validate() */ 9900 9901 /* fixes validation issue (see PR #7913) */ 9902 $form.on( "change", "input[type=date], input[type=file], select", function() { 9903 $form.validate().element( this ); 9904 } ); 9905 9906 // Clear the form and remove error messages on reset 9907 $document.on( "click", selector + " input[type=reset]", function( event ) { 9908 var which = event.which, 9909 ariaLive; 9910 9911 // Ignore middle/right mouse buttons 9912 if ( !which || which === 1 ) { 9913 validator.resetForm(); 9914 $( "#" + errorFormId ).detach(); 9915 9916 ariaLive = $form.closest( ".wb-frmvld" ).find( ".arialive" )[ 0 ]; 9917 if ( ariaLive.innerHTML.length !== 0 ) { 9918 ariaLive.innerHTML = ""; 9919 } 9920 9921 // Correct the colouring of fields that are no longer invalid 9922 $form.find( ".has-error" ).removeClass( "has-error" ); 9923 } 9924 } ); 9925 9926 //Trigger validation on wb-server-error 9927 $form.find( ".wb-server-error" ).filter( ":not( :hidden )" ).parent().each( function() { 9928 if ( this.attributes.for && this.attributes.for.value.length > 0 ) { 9929 $form.validate().element( $( "[id =" + this.attributes.for.value + "]" ) ); 9930 } else if ( $( this ).find( "input" )[ 0 ] ) { 9931 $form.validate().element( $( this ).find( "input" )[ 0 ] ); 9932 } else if ( $( this ).next( ".radio, .checkbox" ).children( "label" ).children( "input" )[ 0 ] ) { 9933 if ( $( this ).find( $( this ).next( ".radio, .checkbox" ).children( "label" ).children( "input" )[ 0 ].id ) ) { 9934 $form.validate().element( $( this ).next( ".radio, .checkbox" ).children( "label" ).children( "input" )[ 0 ] ); 9935 } 9936 } else if ( $( this ).next( ".radio-inline, .checkbox-inline, .label-inline" ).children( "input" )[ 0 ] ) { 9937 if ( $( this ).find( $( this ).next( ".radio-inline, .checkbox-inline, .label-inline" ).children( "input" )[ 0 ].id ) ) { 9938 $form.validate().element( $( this ).next( ".radio-inline, .checkbox-inline, .label-inline" ).children( "input" )[ 0 ] ); 9939 } 9940 } 9941 } ); 9942 9943 // Tell the i18n file to execute to run any $.validator extends 9944 $form.trigger( "formLanguages.wb" ); 9945 9946 // Identify that initialization has completed 9947 wb.ready( $( eventTarget ), componentName ); 9948 } 9949 } ); 9950 } 9951 } ); 9952 } 9953 }; 9954 9955// Bind the init event of the plugin 9956$document.on( "timerpoke.wb " + initEvent, selector, init ); 9957 9958// Add the timer poke to initialize the plugin 9959wb.add( selector ); 9960 9961} )( jQuery, window, document, wb ); 9962 9963/* 9964 * @title WET-BOEW Geomap 9965 * @overview Displays a dynamic map over which information from additional sources can be overlaid. 9966 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 9967 * @author @pjackson28 9968 */ 9969( function( $, wb ) { 9970"use strict"; 9971 9972var componentName = "wb-geomap", 9973 selector = "." + componentName, 9974 initEvent = "wb-init" + selector, 9975 $document = wb.doc, 9976 9977 /** 9978 * @method init 9979 * @param {jQuery Event} event Event that triggered the function call 9980 */ 9981 init = function( event ) { 9982 9983 // Start initialization 9984 // returns DOM object = proceed with init 9985 // returns undefined = do not proceed with init (e.g., already initialized) 9986 var elm = wb.init( event, componentName, selector ), 9987 $elm, $map, $leg, modeJS; 9988 9989 if ( elm ) { 9990 $elm = $( elm ); 9991 $map = $elm.find( ".wb-geomap-map" ); 9992 $leg = $elm.find( ".wb-geomap-legend" ); 9993 9994 // Initialize the map height for the loader 9995 $map.height( $map.width() * 0.8 ); 9996 $map.append( "<div class='geomap-progress'><span class='wb-inv'>" + wb.i18n( "load" ) + "</span></div>" ); 9997 $leg.append( "<div class='skeleton-lgnd-1'><span class='skeleton-lgnd-3'></span></div>" + 9998 "<div class='skeleton-lgnd-2'><span class='skeleton-lgnd-3'></span></div>" + 9999 "<div class='skeleton-lgnd-2'><span class='skeleton-lgnd-3'></span></div>" + 10000 "<div class='skeleton-lgnd-2'><span class='skeleton-lgnd-3'></span></div>" + 10001 "<div class='skeleton-lgnd-1'><span class='skeleton-lgnd-3'></span></div>" ); 10002 10003 modeJS = wb.getMode() + ".js"; 10004 10005 Modernizr.load( [ { 10006 10007 // For loading multiple dependencies 10008 both: [ 10009 "site!deps/proj4" + modeJS, 10010 "site!deps/ol" + modeJS, 10011 "site!deps/geomap-lib" + modeJS 10012 ], 10013 complete: function() { 10014 $elm.trigger( "geomap.wb" ); 10015 } 10016 } ] ); 10017 } 10018 }; 10019 10020// Bind the init function to the timerpoke event 10021$document.on( "timerpoke.wb " + initEvent, selector, init ); 10022 10023// Add the timer poke to initialize the plugin
10024wb.add( selector ); 10025 10026} )( jQuery, wb ); 10027 10028/** 10029 * @title WET-BOEW JSON Fetch [ json-fetch ] 10030 * @overview Load and filter data from a JSON file 10031 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 10032 * @author @duboisp 10033 */ 10034/*global jsonpointer */ 10035( function( $, wb, DOMPurify ) { 10036"use strict"; 10037 10038/* 10039 * Variable and function definitions. 10040 * These are global to the plugin - meaning that they will be initialized once per page, 10041 * not once per instance of plugin on the page. So, this is a good place to define 10042 * variables that are common to all instances of the plugin on a page. 10043 */ 10044var $document = wb.doc, 10045 component = "json-fetch", 10046 fetchEvent = component + ".wb", 10047 jsonCache = { }, 10048 jsonCacheBacklog = { }, 10049 completeJsonFetch = function( callerId, refId, response, status, xhr, selector, fetchedOpts ) { 10050 if ( !window.jsonpointer ) { 10051 10052 // JSON pointer library is loaded but not executed in memory yet, we need to wait a tick before to continue 10053 setTimeout( function() { 10054 completeJsonFetch( callerId, refId, response, status, xhr, selector, fetchedOpts ); 10055 }, 100 ); 10056 return false; 10057 } 10058 if ( selector ) { 10059 try { 10060 response = jsonpointer.get( response, selector ); 10061 } catch ( ex ) { 10062 console.error( "JSON fetch - Bad JSON selector: " + selector ); 10063 console.error( response ); 10064 console.error( $( "#" + callerId ).get( 0 ) ); 10065 } 10066 } 10067 $( "#" + callerId ).trigger( { 10068 type: "json-fetched.wb", 10069 fetch: { 10070 response: response, 10071 status: status, 10072 xhr: xhr, 10073 refId: refId, 10074 fetchedOpts: fetchedOpts 10075 } 10076 }, this ); 10077 }; 10078 10079// Event binding 10080$document.on( fetchEvent, function( event ) { 10081 10082 var caller = event.element || event.target, 10083 fetchOpts = event.fetch || { url: "" }, 10084 urlParts = fetchOpts.url.split( "#" ), 10085 url = urlParts[ 0 ], 10086 fetchNoCache = fetchOpts.nocache, 10087 fetchNoCacheKey = fetchOpts.nocachekey || wb.cacheBustKey || "wbCacheBust", 10088 fetchNoCacheValue, 10089 fetchCacheURL, 10090 hashPart, 10091 datasetName, 10092 selector = urlParts[ 1 ] || false, 10093 callerId, refId = fetchOpts.refId, 10094 cachedResponse; 10095 10096 // Filter out any events triggered by descendants 10097 if ( caller === event.target || event.currentTarget === event.target ) { 10098 10099 if ( !caller.id ) { 10100 caller.id = wb.getId(); 10101 } 10102 callerId = caller.id; 10103 10104 if ( selector ) { 10105 10106 // If a Dataset Name exist let it managed by wb-jsonpatch plugin 10107 hashPart = selector.split( "/" ); 10108 datasetName = hashPart[ 0 ]; 10109 10110 // A dataset name must start with "[" character, if it is a letter, then follow JSON Schema (to be implemented) 10111 if ( datasetName.charCodeAt( 0 ) === 91 ) { 10112 10113 // Let the wb-jsonpatch plugin to manage it 10114 $( "#" + callerId ).trigger( { 10115 type: "postpone.wb-jsonmanager", 10116 postpone: { 10117 callerId: callerId, 10118 refId: refId, 10119 dsname: datasetName, 10120 selector: selector.substring( datasetName.length ) 10121 } 10122 } ); 10123 return; 10124 } 10125 fetchOpts.url = url; 10126 } 10127 10128 if ( fetchNoCache ) { 10129 if ( fetchNoCache === "nocache" ) { 10130 fetchNoCacheValue = wb.guid(); 10131 } else { 10132 fetchNoCacheValue = wb.sessionGUID(); 10133 } 10134 fetchCacheURL = fetchNoCacheKey + "=" + fetchNoCacheValue; 10135 10136 if ( url.indexOf( "?" ) !== -1 ) { 10137 url = url + "&" + fetchCacheURL; 10138 } else { 10139 url = url + "?" + fetchCacheURL; 10140 } 10141 fetchOpts.url = url; 10142 } 10143 10144 Modernizr.load( { 10145 load: "site!deps/jsonpointer" + wb.getMode() + ".js", 10146 complete: function() { 10147 10148 // Ensure this fetch has an URL. There is no URL when only using dataset name (a virtual JSON file). 10149 if ( !url ) { 10150 return; 10151 } 10152 10153 if ( !fetchOpts.nocache ) { 10154 cachedResponse = jsonCache[ url ]; 10155 10156 if ( cachedResponse ) { 10157 completeJsonFetch( callerId, refId, cachedResponse, "success", undefined, selector, fetchOpts ); 10158 return; 10159 } else { 10160 if ( !jsonCacheBacklog[ url ] ) { 10161 jsonCacheBacklog[ url ] = [ ]; 10162 } else { 10163 jsonCacheBacklog[ url ].push( { 10164 "callerId": callerId, 10165 "refId": refId, 10166 "selector": selector 10167 } ); 10168 return; 10169 } 10170 } 10171 } 10172 10173 // Ensure we only receive JSON data and don't allow jsonp 10174 // jQuery will raise an error if other data format is received 10175 fetchOpts.dataType = "json"; 10176 if ( fetchOpts.jsonp ) { 10177 fetchOpts.jsonp = false;
10178 } 10179 10180 // Sending Data 10181 if ( fetchOpts.data ) { 10182 try { 10183 fetchOpts.data = ( typeof fetchOpts.data === "string" ? fetchOpts.data : JSON.stringify( fetchOpts.data ) ); 10184 } catch ( err ) { 10185 throw "JSON fetch - Data being sent to server - " + err; 10186 } 10187 10188 fetchOpts.method = fetchOpts.method || "POST"; 10189 fetchOpts.contentType = fetchOpts.contentType || "application/json"; 10190 } 10191 10192 $.ajax( fetchOpts ) 10193 .done( function( response, status, xhr ) { 10194 var i, i_len, i_cache, backlog; 10195 10196 if ( !fetchOpts.nocache ) { 10197 try { 10198 jsonCache[ url ] = response; 10199 } catch ( error ) { 10200 return; 10201 } 10202 } 10203 10204 completeJsonFetch( callerId, refId, response, status, xhr, selector, fetchOpts ); 10205 10206 if ( jsonCacheBacklog[ url ] ) { 10207 backlog = jsonCacheBacklog[ url ]; 10208 10209 i_len = backlog.length; 10210 10211 for ( i = 0; i !== i_len; i += 1 ) { 10212 i_cache = backlog[ i ]; 10213 completeJsonFetch( i_cache.callerId, i_cache.refId, response, status, xhr, i_cache.selector, fetchOpts ); 10214 } 10215 } 10216 10217 } ) 10218 .fail( function( xhr, status, error ) { 10219 xhr.responseText = DOMPurify.sanitize( xhr.responseText ); 10220 $( "#" + callerId ).trigger( { 10221 type: "json-failed.wb", 10222 fetch: { 10223 xhr: xhr, 10224 status: status, 10225 error: error, 10226 refId: refId, 10227 fetchOpts: fetchOpts 10228 } 10229 }, this ); 10230 }, this ); 10231 } 10232 } ); 10233 } 10234} ); 10235 10236} )( jQuery, wb, DOMPurify ); 10237 10238/** 10239 * @title WET-BOEW Lightbox 10240 * @overview Helps build a photo gallery on a web page. 10241 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 10242 * @author @pjackson28 10243 */ 10244( function( $, DOMPurify, window, document, wb, undef ) { 10245"use strict"; 10246 10247/* 10248 * Variable and function definitions. 10249 * These are global to the plugin - meaning that they will be initialized once per page, 10250 * not once per instance of plugin on the page. So, this is a good place to define 10251 * variables that are common to all instances of the plugin on a page. 10252 */ 10253var componentName = "wb-lbx", 10254 selector = "." + componentName, 10255 initEvent = "wb-init" + selector, 10256 setFocusEvent = "setfocus.wb", 10257 dependenciesLoadedEvent = "deps-loaded" + selector, 10258 modalHideSelector = "#wb-tphp, body > header, body > main, body > footer", 10259 $document = wb.doc, 10260 callbacks, i18n, i18nText, 10261 10262 /** 10263 * @method init 10264 * @param {jQuery Event} event Event that triggered the function call 10265 */ 10266 init = function( event ) { 10267 10268 // Start initialization 10269 // returns DOM object = proceed with init 10270 // returns undefined = do not proceed with init (e.g., already initialized) 10271 var elm = wb.init( event, componentName, selector ), 10272 elmId; 10273 10274 if ( elm ) { 10275 elmId = elm.id; 10276 10277 // Ensure the dependencies are loaded first 10278 $document.one( dependenciesLoadedEvent, function() { 10279 var elm = document.getElementById( elmId ), 10280 $elm = $( elm ), 10281 settings = {}, 10282 firstLink; 10283 10284 if ( !elm ) { 10285 return; 10286 } 10287 10288 // TODO: Add swipe support 10289 10290 settings.callbacks = callbacks; 10291 10292 if ( elm.nodeName.toLowerCase() !== "a" ) { 10293 settings.delegate = "a:not(" + selector + "-skip)"; 10294 firstLink = elm.getElementsByTagName( "a" )[ 0 ]; 10295 10296 // Is the element a gallery? 10297 if ( elm.className.indexOf( "-gal" ) !== -1 ) { 10298 settings.gallery = { 10299 enabled: true 10300 }; 10301 } 10302 } else { 10303 firstLink = elm; 10304 } 10305 10306 if ( firstLink ) { 10307 if ( firstLink.getAttribute( "href" ).charAt( 0 ) === "#" ) { 10308 settings.type = "inline"; 10309 } else if ( firstLink.className.indexOf( "lbx-iframe" ) !== -1 ) { 10310 settings.type = "iframe"; 10311 } else if ( firstLink.getElementsByTagName( "img" ).length === 0 ) { 10312 settings.type = "ajax"; 10313 } else { 10314 settings.type = "image"; 10315 } 10316 10317 if ( elm.className.indexOf( "lbx-modal" ) !== -1 ) { 10318 settings.modal = true; 10319 } 10320 if ( elm.className.indexOf( "lbx-ajax" ) !== -1 ) { 10321 settings.type = "ajax"; 10322 } 10323 if ( elm.className.indexOf( "lbx-image" ) !== -1 ) { 10324 settings.type = "image"; 10325 } 10326 if ( elm.className.indexOf( "lbx-inline" ) !== -1 ) { 10327 settings.type = "inline"; 10328 } 10329 10330 // Extend the settings with window[ "wb-lbx" ] then data-wb-lbx 10331 settings = $.extend( 10332 true, 10333 settings, 10334 window[ componentName ], 10335 wb.getData( $elm, componentName ) 10336 ); 10337 $elm.magnificPopup( 10338 settings 10339 ).data( "wbLbxFilter", settings.filter ); 10340 } 10341 10342 // Identify that initialization has completed 10343 wb.ready( $elm, componentName ); 10344 } ); 10345
10346 // Load dependencies as needed 10347 setup(); 10348 } 10349 }, 10350 10351 /** 10352 * @method setup 10353 */ 10354 setup = function() { 10355 10356 // Only initialize the i18nText and callbacks once 10357 if ( !i18nText ) { 10358 i18n = wb.i18n; 10359 i18nText = { 10360 close: i18n( "close" ), 10361 oClose: i18n( "overlay-close" ), 10362 tClose: i18n( "overlay-close" ) + i18n( "space" ) + i18n( "esc-key" ), 10363 tLoading: i18n( "load" ), 10364 gallery: { 10365 tPrev: i18n( "prv-l" ), 10366 tNext: i18n( "nxt-r" ), 10367 tCounter: i18n( "lb-curr" ) 10368 }, 10369 image: { 10370 tError: i18n( "lb-img-err" ) + " (<a href=\"url%\">)" 10371 }, 10372 ajax: { 10373 tError: i18n( "lb-xhr-err" ) + " (<a href=\"url%\">)" 10374 } 10375 }; 10376 10377 callbacks = { 10378 open: function() { 10379 10380 // TODO: Better if dealt with upstream by Magnific popup 10381 var $item = this.currItem, 10382 $content = this.contentContainer, 10383 $wrap = this.wrap, 10384 $container = $wrap.find( ".mfp-container" ), 10385 $containerParent = $container.parent(), 10386 $modal = $wrap.find( ".modal-dialog" ), 10387 $buttons = $wrap.find( ".mfp-close, .mfp-arrow" ), 10388 len = $buttons.length, 10389 i, button; 10390 10391 createCloseButton( $modal ); 10392 10393 $document.find( "body" ).addClass( "wb-modal" ); 10394 $document.find( modalHideSelector ).attr( "aria-hidden", "true" ); 10395 for ( i = 0; i !== len; i += 1 ) { 10396 button = $buttons[ i ]; 10397 button.innerHTML += "<span class='wb-inv'> " + button.title + "</span>"; 10398 } 10399 10400 if ( $item.type === "image" ) { 10401 $content.find( ".mfp-bottom-bar" ).attr( "id", "lbx-title" ); 10402 } else { 10403 $content.attr( "role", "document" ); 10404 } 10405 10406 this.contentContainer.attr( "data-pgtitle", document.getElementsByTagName( "H1" )[ 0 ].textContent ); 10407 10408 trapTabbing( $wrap ); 10409 10410 if ( !$containerParent.is( "dialog" ) ) { 10411 $container.wrap( "<dialog class='mfp-container' open='open'></dialog>" ); 10412 } else { 10413 $containerParent.attr( "open", "open" ); 10414 } 10415 }, 10416 close: function() { 10417 $document.find( "body" ).removeClass( "wb-modal" ); 10418 $document.find( modalHideSelector ).removeAttr( "aria-hidden" ); 10419 this.wrap.find( "dialog" ).removeAttr( "open" ); 10420 10421 }, 10422 change: function() { 10423 var $item = this.currItem, 10424 $content = this.contentContainer, 10425 $el, $bottomBar, $source, $target, 10426 description, altTitleId, altTitle; 10427 10428 if ( $item.type === "image" ) { 10429 $el = $item.el; 10430 $target = $item.img; 10431 $bottomBar = $content.find( ".mfp-bottom-bar" ); 10432 10433 if ( $el ) { 10434 $source = $el.find( "img" ); 10435 $target.attr( "alt", $source.attr( "alt" ) ); 10436 10437 // Replicate aria-describedby if it exists 10438 description = $source.attr( "aria-describedby" ); 10439 if ( description ) { 10440 $target.attr( "aria-describedby", description ); 10441 } 10442 10443 // Replicate longdesc if it exists 10444 description = $source.attr( "longdesc" ); 10445 if ( description ) { 10446 $target.attr( "longdesc", description ); 10447 } 10448 10449 // Handle alternate titles 10450 altTitleId = $el.attr( "data-title" ); 10451 if ( altTitleId ) { 10452 altTitle = document.getElementById( altTitleId ); 10453 if ( altTitle !== null ) { 10454 $bottomBar.find( ".mfp-title" ).html( altTitle.innerHTML ); 10455 } 10456 } 10457 } else { 10458 $target.attr( "alt", $bottomBar.find( ".mfp-title" ).html() ); 10459 } 10460 } else { 10461 $content 10462 .find( ".modal-title, h1" ) 10463 .first() 10464 .attr( "id", "lbx-title" ); 10465 } 10466 10467 $content.attr( "aria-labelledby", "lbx-title" ); 10468 }, 10469 parseAjax: function( mfpResponse ) { 10470 var currItem = this.currItem, 10471 currEl = currItem.el, 10472 urlHash = currItem.src.split( "#" )[ 1 ], 10473 filter = currEl ? currEl.data( "wbLbxFilter" ) : undef, 10474 selector = filter || ( urlHash ? "#" + urlHash : false ), 10475 $response; 10476 10477 // Sanitize the response 10478 mfpResponse.data = DOMPurify.sanitize( mfpResponse.data ); 10479 10480 // Provide the ability to filter the AJAX response HTML 10481 // by the URL hash or a selector 10482 // TODO: Should be dealt with upstream by Magnific Popup 10483 if ( selector ) { 10484 $response = $( "<div>" + mfpResponse.data + "</div>" ).find( selector ); 10485 } else { 10486 $response = $( mfpResponse.data ); 10487 } 10488 createCloseButton( $response ); 10489 10490 $response 10491 .find( ".modal-title, h1" ) 10492 .first() 10493 .attr( "id", "lbx-title" ); 10494 10495 mfpResponse.data = $response; 10496 }, 10497 ajaxContentAdded: function() { 10498 trapTabbing( this.wrap ); 10499 } 10500 }; 10501 } 10502 10503 // Load Magnific Popup dependency and bind the init event handler 10504 Modernizr.load( { 10505 load: "site!deps/jquery.magnific-popup" + wb.getMode() + ".js", 10506 testReady: function() { 10507 return $.magnificPopup; 10508 },
10509 complete: function() { 10510 10511 // Set the dependency i18nText only once 10512 $.extend( true, $.magnificPopup.defaults, i18nText ); 10513 10514 $document.trigger( dependenciesLoadedEvent ); 10515 } 10516 } ); 10517 }, 10518 createCloseButton = function( $modal ) { 10519 if ( $modal !== null && $modal.hasClass( "modal-dialog" ) ) { 10520 var footer = $modal.find( ".modal-footer" ).first(), 10521 hasFooter = footer.length, 10522 hasButton = hasFooter && $( footer ).find( ".popup-modal-dismiss" ).length !== 0, 10523 closeClassFtr = "popup-modal-dismiss", 10524 closeTextFtr = i18nText.close, 10525 spanTextFtr = i18nText.oClose, 10526 overlayCloseFtr; 10527 10528 if ( !hasButton ) { 10529 if ( !hasFooter ) { 10530 footer = document.createElement( "div" ); 10531 footer.setAttribute( "class", "modal-footer" ); 10532 } 10533 10534 overlayCloseFtr = "<button type='button' class='btn btn-sm btn-primary pull-left " + closeClassFtr + 10535 "' title='" + spanTextFtr + "'>" + 10536 closeTextFtr + 10537 "<span class='wb-inv'>" + spanTextFtr + "</span></button>"; 10538 10539 $( footer ).append( overlayCloseFtr ); 10540 if ( !hasFooter ) { 10541 $( footer ).insertAfter( $modal.find( ".modal-body" ) ); 10542 } 10543 } 10544 } 10545 }, 10546 trapTabbing = function( $wrap ) { 10547 10548 $wrap.on( "keydown", function( e ) { 10549 if ( e.which === 9 ) { 10550 var tabbable = $wrap.find( ".mfp-container :tabbable:visible" ), 10551 firstTabbable = tabbable.first()[ 0 ], 10552 lastTabbable = tabbable.last()[ 0 ], 10553 currentFocus = $( document.activeElement )[ 0 ]; 10554 10555 if ( !e.shiftKey && currentFocus === lastTabbable ) { 10556 e.preventDefault(); 10557 firstTabbable.focus(); 10558 } else if ( e.shiftKey && ( currentFocus === firstTabbable || currentFocus === $wrap[ 0 ] ) ) { 10559 e.preventDefault(); 10560 lastTabbable.focus(); 10561 } 10562 } 10563 } ); 10564 }; 10565 10566// Bind the init event of the plugin 10567$document.on( "timerpoke.wb " + initEvent, selector, init ); 10568 10569// Handler for clicking on a same page link within the overlay to outside the overlay 10570$document.on( "click", ".mfp-wrap a[href^='#']", function( event ) { 10571 var which = event.which, 10572 eventTarget = event.currentTarget, 10573 $lightbox, linkTarget; 10574 10575 // Ignore middle/right mouse buttons 10576 if ( !which || which === 1 ) { 10577 $lightbox = $( eventTarget ).closest( ".mfp-wrap" ); 10578 linkTarget = document.getElementById( eventTarget.getAttribute( "href" ).substring( 1 ) ); 10579 10580 // Ignore same page links to within the overlay 10581 if ( linkTarget && !$.contains( $lightbox[ 0 ], linkTarget ) ) { 10582 10583 // Stop propagation of the click event 10584 if ( event.stopPropagation ) { 10585 event.stopImmediatePropagation(); 10586 } else { 10587 event.cancelBubble = true; 10588 } 10589 10590 // Close the overlay and set focus to the same page link 10591 $.magnificPopup.close(); 10592 $( linkTarget ).trigger( setFocusEvent ); 10593 } 10594 } 10595} ); 10596 10597// Event handler for closing a modal popup 10598$( document ).on( "click", ".popup-modal-dismiss", function( event ) { 10599 if ( !this.hasAttribute( "target" ) ) { 10600 event.preventDefault(); 10601 } 10602 10603 $.magnificPopup.close(); 10604} ); 10605 10606// Event handler for opening a popup without a link 10607$( document ).on( "open" + selector, function( event, items, modal, title, ajax ) { 10608 if ( event.namespace === componentName ) { 10609 var isGallery = items.length > 1, 10610 isModal = modal && !isGallery ? modal : false, 10611 titleSrc = title ? function() { 10612 return title[ $.magnificPopup.instance.index ]; 10613 } : "title"; 10614 10615 event.preventDefault(); 10616 10617 // Ensure the dependencies are loaded first 10618 $document.one( dependenciesLoadedEvent, function() { 10619 $.magnificPopup.open( { 10620 items: items, 10621 modal: isModal, 10622 gallery: { 10623 enabled: isGallery 10624 }, 10625 image: { 10626 titleSrc: titleSrc 10627 }, 10628 callbacks: callbacks, 10629 ajax: ajax 10630 } ); 10631 10632 wb.ready( undef, componentName ); 10633 } ); 10634 10635 // Load dependencies as needed 10636 setup(); 10637 } 10638} ); 10639 10640// Add the timer poke to initialize the plugin 10641wb.add( selector ); 10642 10643} )( jQuery, DOMPurify, window, document, wb ); 10644 10645/** 10646 * @title WET-BOEW Menu plugin 10647 * @overview A Menu plugin for WET 10648 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 10649 * @author WET community 10650 */ 10651( function( $, window, document, wb ) { 10652"use strict"; 10653 10654/* 10655 * Variable and function definitions. 10656 * These are global to the plugin - meaning that they will be initialized once per page, 10657 * not once per instance of plugin on the page. So, this is a good place to define 10658 * variables that are common to all instances of the plugin on a page. 10659 */
10660var componentName = "wb-menu", 10661 selector = "." + componentName, 10662 initEvent = "wb-init" + selector, 10663 breadcrumb = document.getElementById( "wb-bc" ), 10664 navCurrentEvent = "navcurr.wb", 10665 focusEvent = "setfocus.wb", 10666 detailsInitEvent = "wb-init.wb-details", 10667 menuItemSelector = "> a, > details > summary", 10668 $document = wb.doc, 10669 10670 // Used for half second delay on showing/hiding menus because of mouse hover 10671 hoverDelay = 500, 10672 menuCount = 0, 10673 globalTimeout, 10674 10675 /** 10676 * @method init 10677 * @param {jQuery Event} event Event that triggered the function call 10678 */ 10679 init = function( event ) { 10680 10681 // Start initialization 10682 // returns DOM object = proceed with init 10683 // returns undefined = do not proceed with init (e.g., already initialized) 10684 var elm = wb.init( event, componentName, selector ), 10685 $elm, ajaxFetch; 10686 10687 if ( elm ) { 10688 $elm = $( elm ); 10689 10690 // Ensure the container has an id attribute 10691 if ( !$elm.attr( "id" ) ) { 10692 $elm.attr( "id", componentName + "-" + menuCount ); 10693 } 10694 menuCount += 1; 10695 10696 // Lets test to see if we have any menus to fetch 10697 // This is required for backwards compatibility. In previous versions, the menu was not integrated witht he data ajax plugin. 10698 ajaxFetch = $elm.data( "ajax-fetch" ); 10699 if ( ajaxFetch ) { 10700 $elm.trigger( { 10701 type: "ajax-fetch.wb", 10702 fetch: { 10703 url: ajaxFetch 10704 } 10705 } ); 10706 } else { 10707 10708 //Enhance menus that don't rely on the data-ajax plugin 10709 ajaxFetch = $elm.data( "ajax-replace" ) || $elm.data( "ajax-append" ) || $elm.data( "ajax-prepend" ); 10710 if ( !ajaxFetch ) { 10711 onAjaxLoaded( $elm, $elm ); 10712 } 10713 } 10714 } 10715 }, 10716 10717 /** 10718 * Lets set some aria states and attributes 10719 * @method drizzleAria 10720 * @param {jQuery DOM elements} $elements The collection of elements 10721 */ 10722 drizzleAria = function( $elements ) { 10723 var length = $elements.length, 10724 $elm, $subMenu, i; 10725 10726 // Lets tweak for aria 10727 for ( i = 0; i !== length; i += 1 ) { 10728 $elm = $elements.eq( i ); 10729 $subMenu = $elm.siblings( "ul" ); 10730 10731 $elm.attr( { 10732 "aria-posinset": ( i + 1 ), 10733 "aria-setsize": length, 10734 role: "menuitem" 10735 } ); 10736 10737 // if there is a submenu lets put in the aria for it 10738 if ( $subMenu.length !== 0 ) { 10739 10740 $elm.attr( "aria-haspopup", "true" ); 10741 10742 $subMenu.attr( { 10743 "aria-expanded": "false", 10744 "aria-hidden": "true" 10745 } ); 10746 10747 // recurse into submenu 10748 drizzleAria( $subMenu.children( "li" ).find( menuItemSelector ) ); 10749 } 10750 } 10751 }, 10752 10753 /** 10754 * @method createCollapsibleSection 10755 * @return {string} 10756 */ 10757 createCollapsibleSection = function( section, sectionIndex, sectionsLength, $items, itemsLength ) { 10758 10759 // Use details/summary for the collapsible mechanism 10760 var k, $elm, elm, $item, $subItems, subItemsLength, 10761 $section = $( section ), 10762 posinset = "' aria-posinset='", 10763 menuitem = " role='menuitem' aria-setsize='", 10764 sectionHtml = "<li><details>" + "<summary class='mb-item" + 10765 ( $section.hasClass( "wb-navcurr" ) || $section.children( ".wb-navcurr" ).length !== 0 ? " wb-navcurr'" : "'" ) + 10766 menuitem + sectionsLength + posinset + ( sectionIndex + 1 ) + 10767 "' aria-haspopup='true'>" + $section.text() + "</summary>" + 10768 "<ul class='list-unstyled mb-sm' role='menu' aria-expanded='false' aria-hidden='true'>"; 10769 10770 // Convert each of the list items into WAI-ARIA menuitems 10771 for ( k = 0; k !== itemsLength; k += 1 ) { 10772 $item = $items.eq( k ); 10773 $elm = $item.find( menuItemSelector ); 10774 elm = $elm[ 0 ]; 10775 $subItems = $elm.parent().find( "> ul > li" ); 10776 subItemsLength = $subItems.length; 10777 10778 if ( elm && subItemsLength === 0 && elm.nodeName.toLowerCase() === "a" ) { 10779 sectionHtml += "<li>" + $item[ 0 ].innerHTML.replace( 10780 /(<a\s)/, 10781 "$1" + menuitem + itemsLength + 10782 posinset + ( k + 1 ) + 10783 "' tabindex='-1' " 10784 ) + "</li>"; 10785 } else { 10786 sectionHtml += createCollapsibleSection( elm, k, itemsLength, $subItems, $subItems.length ); 10787 } 10788 } 10789 10790 return sectionHtml + "</ul></details></li>"; 10791 }, 10792 10793 /** 10794 * @method createMobilePanelMenu 10795 * @param {array} allProperties Properties used to build the menu system 10796 * @return {string} 10797 */ 10798 createMobilePanelMenu = function( allProperties ) { 10799 var panel = "", 10800 sectionHtml, properties, sections, section, parent, $items, 10801 linkHtml, i, j, len, sectionsLength, itemsLength; 10802 10803 // Process the secondary and site menus 10804 len = allProperties.length; 10805 for ( i = 0; i !== len; i += 1 ) { 10806 properties = allProperties[ i ]; 10807 sectionHtml = ""; 10808 sections = properties[ 0 ]; 10809 sectionsLength = sections.length; 10810 for ( j = 0; j !== sectionsLength; j += 1 ) { 10811 section = sections[ j ]; 10812 $items = $( section.parentNode ).find( "> ul > li" ); 10813 itemsLength = $items.length; 10814 10815 // Collapsible section 10816 if ( itemsLength !== 0 ) { 10817 sectionHtml += createCollapsibleSection( section, j, sectionsLength, $items, itemsLength ); 10818 } else { 10819 parent = section.parentNode; 10820 10821 // Menu item without a section 10822 if ( parent.nodeName.toLowerCase() === "li" ) { 10823 linkHtml = parent.innerHTML; 10824 10825 // Non-list menu items without a section and that contain their own link 10826 } else if ( parent.getElementsByTagName( "a" )[ 0 ] === section.getElementsByTagName( "a" )[ 0 ] ) { 10827 linkHtml = section.innerHTML; 10828 10829 // Non-list menu item without a section and whose siblings contain a link 10830 } else { 10831 linkHtml = "<a href='" + 10832 parent.getElementsByTagName( "a" )[ 0 ].href + "'>" + 10833 section.innerHTML + "</a>"; 10834 } 10835 10836 // Convert the list item to a WAI-ARIA menuitem 10837 sectionHtml += "<li class='no-sect'>" + 10838 linkHtml.replace( 10839 /(<a\s)/, 10840 "$1 class='mb-item' " + "role='menuitem' aria-setsize='" + 10841 sectionsLength + "' aria-posinset='" + ( j + 1 ) + 10842 "' tabindex='-1' " 10843 ) + "</li>"; 10844 } 10845 } 10846 10847 // Create the panel section 10848 panel += "<nav role='navigation' typeof='SiteNavigationElement' id='" + 10849 properties[ 1 ] + "' class='" + properties[ 1 ] + " wb-menu wb-menu-inited'>" + 10850 "<h3>" + properties[ 2 ] + "</h3>" + 10851 "<ul class='list-unstyled mb-menu' role='menu'>" + 10852 sectionHtml + "</ul></nav>"; 10853 } 10854 10855 return panel.replace( /['"]?list-group-item['"]?/gi, "\"\"" ); 10856 }, 10857 10858 /** 10859 * @method onAjaxLoaded 10860 * @param {jQuery DOM element} $elm The plugin element 10861 * @param {jQuery DOM element}
10861 $ajaxResult The AJAXed in menu content to import 10862 */ 10863 onAjaxLoaded = function( $elm, $ajaxResult ) { 10864 var $info = $( "#wb-info" ), 10865 inner = function() { 10866 var $ajaxed = $ajaxResult && $ajaxResult.attr( "data-type" ) === "string" ? $ajaxResult : $elm, 10867 $menubar = $ajaxed.find( ".menu" ), 10868 $menu = $menubar.find( "> li > a" ), 10869 target = $elm.data( "trgt" ), 10870 $secnav = $( "#wb-sec" ), 10871 $language = $( "#wb-lng" ), 10872 search = document.getElementById( "wb-srch" ), 10873 panel = "", 10874 panelDOM = document.getElementById( target ), 10875 $panel = $( panelDOM ), 10876 allProperties = [], 10877 $navCurr, $menuItem, $langItems, len, i; 10878 10879 /* 10880 * Build the mobile panel 10881 */ 10882 10883 // Add search 10884 if ( search !== null ) { 10885 panel += "<section class='srch-pnl'>" + 10886 search.innerHTML 10887 .replace( /h2>/i, "h3>" ) 10888 .replace( /(for|id)="([^"]+)"/gi, "$1='$2-imprt'" ) + 10889 "</section>"; 10890 } 10891 10892 // Add active language offer 10893 if ( $language.length !== 0 ) { 10894 $langItems = $language.find( "li:not(.curr)" ); 10895 len = $langItems.length; 10896 panel += "<section class='lng-ofr'>" + 10897 "<h3>" + $language.children( "h2" ).html() + "</h3>" + 10898 "<ul class='list-inline'>"; 10899 for ( i = 0; i !== len; i += 1 ) { 10900 panel += $langItems[ i ].innerHTML 10901 .replace( /(<a\s.*<\/a>?)/, "<li>$1</li>" ); 10902 } 10903 panel += "</ul></section>"; 10904 } 10905 10906 // Create menu system 10907 if ( $secnav.length !== 0 || $menubar.length !== 0 || $info.length !== 0 ) { 10908 10909 // Add the secondary menu 10910 if ( $secnav.length !== 0 ) { 10911 allProperties.push( [ 10912 $secnav.find( "ul" ).filter( ":not(li > ul)" ).find( " > li > *:first-child" ).get(), 10913 "sec-pnl", 10914 $secnav.find( "h2" ).html() 10915 ] ); 10916 10917 if ( $secnav.find( ".wb-navcurr" ).length === 0 ) { 10918 10919 // Trigger the navcurrent plugin 10920 $secnav.trigger( navCurrentEvent, breadcrumb ); 10921 } 10922 } 10923 10924 // Add the site menu 10925 if ( $menubar.length !== 0 ) { 10926 10927 // Add the menubar role if it is missing 10928 if ( !$menubar.attr( "role" ) ) { 10929 $menubar.attr( "role", "menubar" ); 10930 } 10931 10932 allProperties.push( [ 10933 $menu.get(), 10934 "sm-pnl", 10935 $ajaxed.find( "h2" ).html() 10936 ] ); 10937 } 10938 10939 // Add the site information 10940 if ( $info.length !== 0 ) { 10941 allProperties.push( [ 10942 $info.find( "h3, a" ).not( "section a" ), 10943 "info-pnl", 10944 $info.find( "h2" ).html() 10945 ] ); 10946 10947 if ( $info.find( ".wb-navcurr" ).length === 0 ) { 10948 10949 // Trigger the navcurrent plugin 10950 $info.trigger( navCurrentEvent, breadcrumb ); 10951 } 10952 } 10953 10954 panel += createMobilePanelMenu( allProperties ); 10955 } 10956 10957 // Let's now populate the DOM since we have done all the work in a documentFragment 10958 panelDOM.innerHTML = "<header class='modal-header'><div class='modal-title'>" + 10959 document.getElementById( "wb-glb-mn" ) 10960 .getElementsByTagName( "h2" )[ 0 ] 10961 .innerHTML + 10962 "</div></header><div class='modal-body'>" + panel + "</div>"; 10963 panelDOM.className += " wb-overlay modal-content overlay-def wb-panel-r"; 10964 10965 // fix #8241 10966 if ( $.active > 0 ) { 10967 $( document ).ajaxStop( function() { 10968 initOverlay( $panel ); 10969 } ); 10970 } else { 10971 initOverlay( $panel ); 10972 } 10973 10974 /* 10975 * Build the regular mega menu 10976 */ 10977 $ajaxed 10978 .find( ":discoverable" ) 10979 .attr( "tabindex", "-1" ); 10980 10981 if ( $menu.length !== 0 ) { 10982 $menu[ 0 ].setAttribute( "tabindex", "0" ); 10983 drizzleAria( $menu ); 10984 $menu 10985 .filter( "[aria-haspopup=true]" ) 10986 .append( "<span class='expicon glyphicon glyphicon-chevron-down'></span>" ); 10987 } 10988 10989 // Replace elements 10990 $elm.html( $ajaxed.html() ); 10991 10992 // Trigger the navcurrent plugin 10993 setTimeout( function() { 10994 $elm.trigger( navCurrentEvent, breadcrumb ); 10995 $panel.find( "#sm-pnl" ).trigger( navCurrentEvent, breadcrumb ); 10996 10997 // Ensure that wb-navcurr is reflected in the top level 10998 $navCurr = $panel.find( ".wb-navcurr" ); 10999 len = $navCurr.length; 11000 for ( i = 0; i !== len; i += 1 ) { 11001 $menuItem = $navCurr.eq( i ); 11002 11003 // If not at the top level, then add wb-navcurr to the top level 11004 if ( !$menuItem.hasClass( ".mb-item" ) ) { 11005 $menuItem = $menuItem 11006 .closest( "details" ) 11007 .children( "summary" ) 11008 .addClass( "wb-navcurr" ); 11009 } 11010 } 11011 11012 // Open up the secondary menu if it has wb-navcurr and has a submenu 11013 $menuItem = $panel.find( "#sec-pnl .wb-navcurr.mb-item" ); 11014 if ( $menuItem.attr( "aria-haspopup" ) === "true" ) { 11015 $menuItem 11016 .trigger( "click" ) 11017 .parent() 11018 .prop( "open", "open" ); 11019 } 11020 11021 // Identify that initialization has completed 11022 wb.ready( $elm, componentName ); 11023 }, 1 ); 11024 }, 11025 $footerAjax = $info.find( "[data-ajax-replace],[data-ajax-append],[data-ajax-prepend]" ), 11026 footerAjaxLength = $footerAjax.length, 11027 ajaxCount = 0; 11028 11029 //Delay the execution the menu until any ajaxed footer content is in 11030 if ( footerAjaxLength === 0 ) { 11031 inner(); 11032 } else { 11033 $info.on( "wb-contentupdated ajax-failed.wb", function() { 11034 ajaxCount += 1; 11035 if ( ajaxCount === footerAjaxLength ) { 11036 inner(); 11037 } 11038 } ); 11039 } 11040 }, 11041 11042 // fix #8517 11043 /** 11044 * @method initOverlay 11045 * @param {jQuery object} $panel Current panel 11046 */ 11047 initOverlay = function( $panel ) { 11048 $panel 11049 .trigger( "wb-init.wb-overlay" ) 11050 .find( "summary" ) 11051 .attr( "tabindex", "-1" ) 11052 .trigger( detailsInitEvent ); 11053 $panel 11054 .find( ".mb-menu > li:first-child" ) 11055 .find( ".mb-item" ) 11056 .attr( "tabindex", "0" ); 11057 }, 11058 11059 /** 11060 * @method menuIncrement 11061 * @param {jQuery object} $menuItems Collection of of menu items to move between 11062 * @param {jQuery object} $current Current menu item 11063 * @param {integer} indexChange Requested relative change to the menu item index 11064 */ 11065 menuIncrement = function( $menuItems, $current, indexChange ) { 11066 var menuItemsLength = $menuItems.length, 11067 index = $menuItems.index( $current ) + indexChange; 11068 11069 // Correct out-of-range indexes 11070 index = index === menuItemsLength ? 0 : index === -1 ? menuItemsLength - 1 : index; 11071 11072 // Move to the new menu item 11073 $menuItems.eq( index ).trigger( focusEvent ); 11074 }, 11075 11076 /** 11077 * @method menuClose 11078 * @param {jQuery DOM element} $elm Parent of the element to close 11079 * @param {boolean} removeActive Whether or not to keep the active class 11080 */ 11081 menuClose = function( $elm, removeActive ) { 11082 $elm 11083 .removeClass( "sm-open" ) 11084 .children( ".open" ) 11085 .removeClass( "open" ) 11086 .attr( { 11087 "aria-hidden": "true", 11088 "aria-expanded": "false" 11089 } ) 11090 11091 // Close nested submenus 11092 .find( "details" ) 11093 .removeAttr( "open" ) 11094 .children( "ul" ) 11095 .attr( { 11096 "aria-hidden": "true", 11097 "aria-expanded": "false" 11098 } ); 11099 11100 if ( removeActive ) { 11101 $elm.removeClass( "active" ); 11102 } 11103 }, 11104 11105 /** 11106 * @method menuDisplay 11107 * @param {jQuery DOM element} $elm The plugin element 11108 * @param {jQuery event} menu The menu to display 11109 */ 11110 menuDisplay = function( $elm, menu ) { 11111 var menuLink = menu.children( "a" ); 11112 11113 menuClose( $elm.find( ".active" ), true ); 11114 11115 menu.addClass( "active" ); 11116 11117 // Ignore if doesn't have a submenu 11118 if ( menuLink.attr( "aria-haspopup" ) === "true" ) { 11119 11120 // Add the open state classes 11121 menu 11122 .addClass( "sm-open" ) 11123 .children( ".sm" ) 11124 .addClass( "open" ) 11125 .attr( { 11126 "aria-hidden": "false", 11127 "aria-expanded": "true" 11128 } ); 11129 } 11130 }, 11131 11132 /** 11133 * Searches for the next link that has link text starting with a specific letter 11134 * @method selectByLetter 11135 * @param {integer} charCode The charCode of the letter to search for 11136 * @param {DOM elements} links Collection of links to search 11137 */ 11138 selectByLetter = function( charCode, links ) { 11139 var len = links.length, 11140 keyChar = String.fromCharCode( charCode ), 11141 link, i; 11142 11143 for ( i = 0; i !== len; i += 1 ) { 11144 link = links[ i ]; 11145 if ( link.innerHTML.charAt( 0 ) === keyChar ) { 11146 $( link ).trigger( focusEvent ); 11147 return true; 11148 } 11149 } 11150 11151 return false;
11152 }; 11153 11154// Bind the events of the plugin 11155$document.on( "timerpoke.wb " + initEvent + " ajax-fetched.wb ajax-failed.wb", selector, function( event ) { 11156 11157 var eventType = event.type, 11158 elm, $elm; 11159 11160 switch ( eventType ) { 11161 case "ajax-fetched": 11162 case "ajax-failed": 11163 elm = event.target; 11164 11165 // Filter out any events triggered by descendants 11166 if ( event.currentTarget === elm ) { 11167 $elm = $( elm ); 11168 11169 // Only replace the menu if there isn't an error 11170 onAjaxLoaded( 11171 $elm, 11172 eventType === "ajax-fetched" ? event.fetch.pointer : $elm 11173 ); 11174 } 11175 return false; 11176 11177 case "timerpoke": 11178 case "wb-init": 11179 init( event ); 11180 break; 11181 } 11182 11183 /* 11184 * Since we are working with events we want to ensure that we are being passive about our control, 11185 * so returning true allows for events to always continue 11186 */ 11187 return true; 11188} ); 11189 11190$document.on( "mouseleave", selector + " .menu", function( event ) { 11191 var $currentTarget = $( event.currentTarget ); 11192 11193 // Clear the timeout for open/closing menus 11194 clearTimeout( globalTimeout ); 11195 11196 globalTimeout = setTimeout( function() { 11197 menuClose( $currentTarget.find( ".active" ), true ); 11198 }, hoverDelay ); 11199} ); 11200 11201// Prevent opening another menu if mouse re-enters already opened menu 11202$document.on( "mouseenter", selector + " .sm", function() { 11203 if ( $( this ).attr( "aria-expanded" ) === "true" ) { 11204 clearTimeout( globalTimeout ); 11205 } 11206} ); 11207 11208// Touchscreen "touches" on menubar items should close the submenu if it is open 11209$document.on( "click", selector + " .item[aria-haspopup=true]", function( event ) { 11210 var which = event.which, 11211 $this, $parent; 11212 11213 // Ignore middle and right mouse buttons 11214 if ( !which || which === 1 ) { 11215 event.preventDefault(); 11216 $this = $( this ); 11217 $parent = $this.parent(); 11218 11219 // Open the submenu if it is closed 11220 if ( !$parent.hasClass( "sm-open" ) ) { 11221 $this.trigger( "focusin" ); 11222 } 11223 } 11224} ); 11225 11226// Click on menu items with submenus should open and close those submenus 11227$document.on( "click", selector + " [role=menu] [aria-haspopup=true]", function( event ) { 11228 var menuItem = event.currentTarget, 11229 parent = menuItem.parentNode, 11230 submenu = parent.getElementsByTagName( "ul" )[ 0 ], 11231 isOpen = submenu.getAttribute( "aria-hidden" ) === "false", 11232 menuItemOffsetTop, menuContainer; 11233 11234 // Close any other open menus 11235 if ( !isOpen ) { 11236 $( parent ) 11237 .closest( "[role^='menu']" ) 11238 .find( "[aria-hidden=false]" ) 11239 .parent() 11240 .find( "[aria-haspopup=true]" ) 11241 .not( menuItem ) 11242 .trigger( "click" ); 11243 11244 // Ensure the opened menu is in view if in a mobile panel 11245 menuContainer = document.getElementById( "mb-pnl" ); 11246 menuItemOffsetTop = menuItem.offsetTop; 11247 if ( $.contains( menuContainer, menuItem ) && 11248 menuItemOffsetTop < menuContainer.scrollTop ) { 11249 11250 menuContainer.scrollTop = menuItemOffsetTop; 11251 } 11252 } 11253 11254 submenu.setAttribute( "aria-expanded", !isOpen ); 11255 submenu.setAttribute( "aria-hidden", isOpen ); 11256} ); 11257 11258// Clicks and touches outside of menus should close any open menus 11259$document.on( "click", function( event ) { 11260 var $openMenus, 11261 which = event.which; 11262 11263 // Ignore middle and right mouse buttons 11264 if ( event.type === "" || ( !which || which === 1 ) ) { 11265 $openMenus = $( selector + " .sm-open" ); 11266 if ( $openMenus.length !== 0 && 11267 $( event.target ).closest( selector ).length === 0 ) { 11268 11269 menuClose( $openMenus, true ); 11270 } 11271 } 11272} ); 11273 11274$document.on( "mouseover focusin", selector + " .item", function( event ) { 11275 var $elm = $( event.currentTarget ), 11276 $parent = $elm.parent(), 11277 $container = $parent.closest( selector ); 11278 11279 // Clear the timeout for open/closing menus 11280 clearTimeout( globalTimeout ); 11281 11282 if ( event.type === "focusin" ) { 11283 menuDisplay( $container, $parent ); 11284 } else { 11285 globalTimeout = setTimeout( function() { 11286 menuDisplay( $container, $parent ); 11287 }, hoverDelay ); 11288 } 11289} ); 11290 11291/* 11292 * Keyboard bindings 11293 */ 11294$document.on( "keydown", selector + " [role=menuitem]", function( event ) { 11295 var menuItem = event.currentTarget, 11296 which = event.which, 11297 $menuItem = $( menuItem ), 11298 hasPopup = $menuItem.attr( "aria-haspopup" ) === "true", 11299 $menu = $menuItem.parent().closest( "[role^='menu']" ), 11300 inMenuBar = $menu.attr( "role" ) === "menubar", 11301 $menuLink, $parentMenu, $parent, $subMenu, result, 11302 menuitemSelector, isOpen, menuItemOffsetTop, menuContainer; 11303 11304 // Define keycodes. (Make const when WET supports ES6) 11305 var TAB_KC = 9, 11306 ENTER_KC = 13, 11307 ESC_KC = 27, 11308 LEFT_KC = 37, 11309 UP_KC = 38, 11310 RIGHT_KC = 39, 11311 DOWN_KC = 40, 11312 SPACE_KC = 32; 11313 11314 if ( !( event.ctrlKey || event.altKey || event.metaKey ) ) { 11315 11316 // Tab key = Hide all sub-menus 11317 if ( which === TAB_KC ) { 11318 menuClose( $( selector + " .active" ), true ); 11319 11320 //Enter or spacebar on a link = follow the link and close menus 11321 } else if ( menuItem.nodeName === "A" && menuItem.hasAttribute( "href" ) && 11322 ( which === ENTER_KC || which === SPACE_KC ) ) { 11323 11324 event.preventDefault(); 11325 menuItem.click(); 11326 menuClose( $( selector + " .active" ), true ); 11327 11328 // Menu item is within a menu bar 11329 } else if ( inMenuBar ) { 11330 11331 // Left / right arrow = Previous / next menu item 11332 if ( which === LEFT_KC || which === RIGHT_KC ) { 11333 event.preventDefault(); 11334 menuIncrement( 11335 $menu.find( "> li > a" ), 11336 $menuItem, 11337 which === LEFT_KC ? -1 : 1 11338 ); 11339 11340 // Enter sub-menu 11341 } else if ( hasPopup && ( which === ENTER_KC || which === SPACE_KC || which === UP_KC || which === DOWN_KC ) ) { 11342 event.preventDefault(); 11343 $parent = $menuItem.parent(); 11344 $subMenu = $parent.find( ".sm" ); 11345 11346 // Open the submenu if it is not already open 11347 if ( !$subMenu.hasClass( "open" ) ) { 11348 menuDisplay( $menu.closest( selector ), $parent ); 11349 } 11350 11351 // Set focus on the first submenu item 11352 $subMenu.children( "li" ).eq( 0 ).find( menuItemSelector ).trigger( focusEvent ); 11353 11354 // Hide sub-menus and set focus 11355 } else if ( which === ESC_KC ) { 11356 event.preventDefault(); 11357 menuClose( $menu.closest( selector ).find( ".active" ), false ); 11358 11359 // Letters only 11360 } else if ( which > 64 && which < 91 ) { 11361 event.preventDefault(); 11362 selectByLetter( 11363 which, 11364 $menuItem.parent().find( "> ul > li > a, > ul > li > details > summary" ).get() 11365 ); 11366 } 11367 11368 // Menu item is not within a menu bar 11369 } else { 11370 menuitemSelector = menuItemSelector; 11371 11372 // Up / down arrow = Previous / next menu item 11373 if ( which === UP_KC || which === DOWN_KC ) { 11374 event.preventDefault(); 11375 menuIncrement( 11376 $menu.children( "li" ).find( menuitemSelector ), 11377 $menuItem, 11378 which === UP_KC ? -1 : 1 11379 ); 11380 11381 // Enter, space, or right arrow with a submenu 11382 } else if ( hasPopup && ( which === ENTER_KC || which === SPACE_KC || which === RIGHT_KC ) ) { 11383 $parent = $menuItem.parent(); 11384 11385 // Prevent handling by details.js polyfill 11386 event.stopImmediatePropagation(); 11387 event.preventDefault(); 11388 11389 // If the menu item is a summary element 11390 if ( menuItem.nodeName.toLowerCase( "summary" ) ) { 11391 isOpen = !!$parent.attr( "open" ); 11392 11393 // Close any other open menus 11394 if ( !isOpen ) { 11395 $( parent ) 11396 .closest( "[role^='menu']" ) 11397 .find( "[aria-hidden=false]" ) 11398 .parent() 11399 .find( "[aria-haspopup=true]" ) 11400 .not( menuItem ) 11401 .trigger( "click" ); 11402 11403 // Ensure the opened menu is in view if in a mobile panel 11404 menuContainer = document.getElementById( "mb-pnl" ); 11405 menuItemOffsetTop = menuItem.offsetTop; 11406 if ( $.contains( menuContainer, menuItem ) && 11407 menuItemOffsetTop < menuContainer.scrollTop ) { 11408 11409 menuContainer.scrollTop = menuItemOffsetTop; 11410 } 11411 11412 // Ensure the menu is opened or stays open 11413 $menuItem.trigger( "click" ); 11414 } 11415 11416 // Update the WAI-ARIA states and move focus to 11417 // the first submenu item 11418 $parent.children( "ul" ) 11419 .attr( { 11420 "aria-expanded": "true", 11421 "aria-hidden": "false" 11422 } ) 11423 .find( "[role=menuitem]:first" ) 11424 .trigger( "setfocus.wb" ); 11425 } 11426 11427 // Escape, left / right arrow without a submenu 11428 } else if ( which === ESC_KC || which === LEFT_KC || which === RIGHT_KC ) { 11429 $parent = $menu.parent(); 11430 $parentMenu = $parent.closest( "[role^='menu']" ); 11431 if ( which === LEFT_KC || which === RIGHT_KC ) { 11432 event.preventDefault(); 11433 } 11434 11435 // If the parent menu is a menubar 11436 if ( $parentMenu.attr( "role" ) === "menubar" ) { 11437 $menuLink = $menu.siblings( "a" ); 11438 11439 // Escape key = Close menu and return to menu bar item 11440 if ( which === ESC_KC ) { 11441 event.preventDefault(); 11442 $menuLink.trigger( focusEvent ); 11443 11444 // Close the menu but keep the referring link active 11445 setTimeout( function() { 11446 menuClose( $menuLink.parent(), false ); 11447 }, 100 ); 11448 11449 // Left / right key = Next / previous menu bar item 11450 } else if ( $parentMenu.attr( "role" ) === "menubar" ) { 11451 menuIncrement( 11452 $parentMenu.find( "> li > a" ), 11453 $menuLink, 11454 which === LEFT_KC ? -1 : 1 11455 ); 11456 } 11457 11458 // Escape or left arrow: Go up a level if there is a higher-level 11459 // menu or close the current submenu if there isn't 11460 } else if ( which !== RIGHT_KC ) { 11461 $subMenu = $parentMenu.length !== 0 ? $menu : $menuItem; 11462 11463 // There is a higher-level menu 11464 if ( $parentMenu.length !== 0 ) { 11465 event.preventDefault(); 11466 $menu.closest( "li" ) 11467 .find( menuitemSelector ) 11468 .trigger( "click" ) 11469 .trigger( "setfocus.wb" ); 11470 11471 // No higher-level menu but the current submenu is open 11472 } else if ( $menuItem.parent().children( "ul" ).attr( "aria-hidden" ) === "fal
11472se" ) { 11473 event.preventDefault(); 11474 $menuItem 11475 .trigger( "click" ) 11476 .trigger( "setfocus.wb" ); 11477 } 11478 } 11479 11480 // Select a menu item in the current menu by the first letter 11481 } else if ( which > 64 && which < 91 ) { 11482 event.preventDefault(); 11483 $parent = $menuItem.closest( "li" ); 11484 11485 // Try to find a match in the next siblings 11486 result = selectByLetter( 11487 which, 11488 $parent.nextAll().find( menuitemSelector ).get() 11489 ); 11490 11491 // If couldn't find a match, try the previous siblings 11492 if ( !result ) { 11493 result = selectByLetter( 11494 which, 11495 $parent.prevAll().find( menuitemSelector ).get() 11496 ); 11497 } 11498 } 11499 } 11500 } 11501} ); 11502 11503// Prevent Firefox from double-triggering menu behaviour 11504$document.on( "keyup", selector + " [role=menuitem]", function( event ) { 11505 event.preventDefault(); 11506 return false; 11507} ); 11508 11509// Close the mobile panel if switching to medium, large or extra large view 11510$document.on( "mediumview.wb largeview.wb xlargeview.wb", function() { 11511 var mobilePanel = document.getElementById( "mb-pnl" ); 11512 if ( mobilePanel && mobilePanel.getAttribute( "aria-hidden" ) === "false" ) { 11513 $( mobilePanel ).trigger( "close.wb-overlay" ); 11514 } 11515} ); 11516 11517// Add the timer poke to initialize the plugin 11518wb.add( selector ); 11519 11520} )( jQuery, window, document, wb ); 11521 11522/** 11523 * @title WET-BOEW Multimedia PLayer 11524 * @overview An accessible multimedia player for <audio> and <video> tags 11525 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 11526 * @author WET Community 11527 */ 11528/* globals YT */ 11529( function( $, DOMPurify, window, wb, undef ) { 11530"use strict"; 11531 11532/* Local scoped variables*/ 11533var componentName = "wb-mltmd", 11534 selector = "." + componentName, 11535 initEvent = "wb-init" + selector, 11536 ctrls = selector + " .wb-mm-ctrls", 11537 dispCtrls = selector + " .display," + ctrls, 11538 template, 11539 i18n, i18nText, 11540 youtubeReadyEvent = "ready.youtube", 11541 captionsLoadedEvent = "ccloaded" + selector, 11542 captionsLoadFailedEvent = "ccloadfail" + selector, 11543 captionsVisibleChangeEvent = "ccvischange" + selector, 11544 renderUIEvent = "renderui" + selector, 11545 initializedEvent = "inited" + selector, 11546 youtubeEvent = "youtube" + selector, 11547 resizeEvent = "resize" + selector, 11548 templateLoadedEvent = "templateloaded" + selector, 11549 cuepointEvent = "cuepoint" + selector, 11550 captionClass = "cc_on", 11551 multimediaEvents = [ 11552 "durationchange", 11553 "playing", 11554 "pause", 11555 "ended", 11556 "volumechange", 11557 "timeupdate", 11558 "waiting", 11559 "canplay", 11560 "seeked", 11561 "progress", 11562 captionsLoadedEvent, 11563 captionsLoadFailedEvent, 11564 captionsVisibleChangeEvent, 11565 cuepointEvent 11566 ].join( " " ), 11567 $document = wb.doc, 11568 $window = wb.win, 11569 11570 /** 11571 * @function init 11572 * @param {jQuery Event} event Event that triggered the function call 11573 */ 11574 init = function( event ) { 11575 11576 // Start initialization 11577 // returns DOM object = proceed with init 11578 // returns undefined = do not proceed with init (e.g., already initialized) 11579 var eventTarget = wb.init( event, componentName, selector ); 11580 11581 if ( eventTarget ) { 11582 11583 // Only initialize the i18nText once 11584 if ( !i18nText ) { 11585 11586 // YT workaround for when played inside a modal dialog, like with lightbox 11587 // 11588 // the issue is when the iFrame is moved, it reloads and then it doesn't emit the right event to 11589 // adjust the WET multimedia controller to represent its current state. 11590 // 11591 // This needs to be executed only once, that is why it is in the i18nText conditional 11592 window.addEventListener( "message", function( e ) { 11593 var data, frames, i, i_len, i_cache; 11594 11595 // Ensure we don't conflict with other postMessage listener 11596 try { 11597 data = JSON.parse( e.data ); 11598 11599 // Only for a specific YT message 11600 if ( data.event && data.event === "infoDelivery" && data.info && data.info.playerState ) { 11601 11602 // Find the iFrame and evaluate if it needs to be reposted 11603 frames = document.getElementsByTagName( "iframe" ); 11604 11605 i_len = frames.length; 11606 for ( i = 0; i < i_len; i++ ) { 11607 i_cache = frames[ i ]; 11608 if ( i_cache.dataset.L2 && i_cache.contentWindow === e.source ) { 11609 11610 // Prepare the event data and emulate the YT object for our event management need 11611 youTubeEvents.call( i_cache, { 11612 target: i_cache.parentElement.parentElement.object, 11613 data: data.info.playerState 11614 } ); 11615 break; 11616 } 11617 } 11618 } 11619 } catch ( err ) { 11620 11621 /* swallow error */ 11622 } 11623 } ); 11624 11625 // 11626 // END YT workaround 11627 // 11628 11629 i18n = wb.i18n; 11630 i18nText = { 11631 play: i18n( "mmp-play" ), 11632 pause: i18n( "pause" ), 11633 volume: i18n( "volume" ), 11634 cc_on: i18n( "cc", "on" ), 11635 cc_off: i18n( "cc", "off" ), 11636 cc_error: i18n( "cc-err" ), 11637 fs: i18n( "fs" ), 11638 mute_on: i18n( "mute", "on" ), 11639 mute_off: i18n( "mute", "off" ), 11640 duration: i18n( "dur" ), 11641 position: i18n( "pos" ) 11642 }; 11643 } 11644 11645 if ( template === undef ) { 11646 template = ""; 11647 $( eventTarget ).trigger( { 11648 type: "ajax-fetch.wb", 11649 fetch: {
11650 url: wb.getPath( "/assets" ) + "/mediacontrols.html" 11651 } 11652 } ); 11653 } else if ( template !== "" ) { 11654 $( eventTarget ).trigger( templateLoadedEvent ); 11655 } 11656 } 11657 }, 11658 11659 /* helper functions*/ 11660 11661 /** 11662 * @method formatTime 11663 * @description format a number of seconds to SMTPE Timecode format (HH:MM:SS.FF) 11664 * @param {Float} time The time to format 11665 * @returns {String} the formatted time 11666 */ 11667 formatTime = function( time ) { 11668 var index = 2, 11669 timecode = "", 11670 secondsIn, current, pad; 11671 11672 pad = function( number, digits ) { 11673 return new Array( Math.max( digits - String( number ).length + 1, 0 ) ).join( 0 ) + number; 11674 }; 11675 11676 time = Math.floor( time ); 11677 11678 //Loop to extract hours, minutes and seconds 11679 while ( index >= 0 ) { 11680 11681 //Get the number of seconds for the current iteration (hour, minute or second) 11682 secondsIn = Math.pow( 60, index ); 11683 current = Math.floor( time / secondsIn ); 11684 11685 if ( timecode !== "" ) { 11686 timecode += ":"; 11687 } 11688 11689 timecode += pad( current, 2 ); 11690 time -= secondsIn * current; 11691 index -= 1; 11692 } 11693 return timecode; 11694 }, 11695 11696 /** 11697 * @method parseTime 11698 * @description parse an SMTPE Timecode string (HH:MM:SS.FF) or duration (45s) and returns the number of seconds for the timecode 11699 * @param {String} time The timecode or duration string to parse 11700 * @returns {Float} the number of seconds in time 11701 */ 11702 parseTime = function( time ) { 11703 var i, parts, timeStringPortion, partLength, seconds; 11704 11705 if ( time !== undef ) { 11706 if ( time.charAt( time.length - 1 ) === "s" ) { 11707 11708 //Duration parsing 11709 return parseFloat( time.substring( 0, time.length - 1 ) ); 11710 } else { 11711 11712 //SMTPE Timecode Parsing 11713 parts = time.split( ":" ).reverse(); 11714 seconds = 0; 11715 11716 for ( i = 0, partLength = parts.length; i < partLength; i += 1 ) { 11717 timeStringPortion = i === 0 ? 11718 parseFloat( parts[ i ] ) : 11719 parseInt( parts[ i ], 10 ); 11720 seconds += timeStringPortion * Math.pow( 60, i ); 11721 } 11722 return seconds; 11723 } 11724 } 11725 return -1; 11726 }, 11727 11728 /* 11729 * Peformant micro templater 11730 * @credit: https://github.com/premasagar/tim/blob/master/tinytim.js 11731 * @todo: caching 11732 */ 11733 tmpl = ( function() { 11734 var start = "{{", 11735 end = "}}", 11736 11737 // e.g. config.person.name 11738 path = "[a-z0-9_$][\\.a-z0-9_]*", 11739 pattern = new RegExp( start + "\\s*(" + path + ")\\s*" + end, "gi" ); 11740 return function( template, data ) { 11741 11742 // Merge data into the template string 11743 return template.replace( pattern, function( tag, token ) { 11744 var path = token.split( "." ), 11745 len = path.length, 11746 lookup = data, 11747 i = 0; 11748 for ( ; i < len; i += 1 ) { 11749 lookup = lookup[ path[ i ] ]; 11750 11751 // Property not found 11752 if ( lookup === undef ) { 11753 throw "tim: '" + path[ i ] + "' not found in " + tag; 11754 } 11755 11756 // Return the required value 11757 if ( i === len - 1 ) { 11758 return lookup; 11759 } 11760 } 11761 } ); 11762 }; 11763 }() ), 11764 11765 /** 11766 * @method parseHtml 11767 * @description parse an HTML fragment and extract embed captions 11768 * @param {String} content The HTML fragment containing the captions 11769 * @returns {Array} An array of captions objects (ex: {text: "Caption", begin: 0, end :10}) 11770 */ 11771 parseHtml = function( content ) { 11772 var captions = [], 11773 captionSelector = ".wb-tmtxt", 11774 captionElements = content.find( captionSelector ), 11775 len = captionElements.length, 11776 i, captionElement, json, begin, end; 11777 11778 for ( i = 0; i !== len; i += 1 ) { 11779 captionElement = $( captionElements[ i ] ); 11780 begin = -1; 11781 end = -1; 11782 11783 if ( captionElement.attr( "data-begin" ) !== undef ) { 11784 begin = parseTime( captionElement.attr( "data-begin" ) ); 11785 end = captionElement.attr( "data-end" ) !== undef ? 11786 parseTime( captionElement.attr( "data-end" ) ) : 11787 parseTime( captionElement.attr( "data-dur" ) ) + begin; 11788 } else if ( captionElement.attr( "data" ) !== undef ) { 11789 json = captionElement.attr( "data" ) 11790 .replace( /(begin|dur|end)/g, "\"$1\"" ) 11791 .replace( /'/g, "\"" ); 11792 json = $.parseJSON( json ); 11793 begin = parseTime( json.begin ); 11794 end = json.end !== undef ? 11795 parseTime( json.end ) : 11796 parseTime( json.dur ) + begin; 11797 } 11798 11799 //Removes nested captions if an 11800 captionElement = captionElement.clone(); 11801 captionElement.find( captionSelector ).detach(); 11802 11803 captions[ captions.length ] = { 11804 text: captionElement.html(), 11805 begin: begin, 11806 end: end 11807 }; 11808 } 11809 return captions; 11810 }, 11811 11812 /** 11813 * @method parseXml 11814 * @description parse an TTML (Xml) document and extract captions 11815 * @param {String} content The TTML fragment containing the captions 11816 * @returns {Array} An array of captions objects (ex: {text: "Caption", begin: 0, end :10}) 11817 */ 11818 parseXml = function( content ) { 11819 var captions = [], 11820 captionSelector = "[begin]", 11821 parser = new DOMParser(), 11822 doc = parser.parseFromString( content, "application/xml" ), 11823 captionElements = doc.querySelectorAll( captionSelector ), 11824 len = captionElements.length, 11825 i, captionElement, begin, end; 11826 11827 for ( i = 0; i !== len; i += 1 ) { 11828 captionElement = captionElements[ i ]; 11829 11830 begin = parseTime( captionElement.getAttribute( "begin" ) + "" ); 11831 end = captionElement.hasAttribute( "end" ) ? 11832 parseTime( captionElement.getAttribute( "end" ) + "" ) : 11833 parseTime( captionElement.getAttribute( "dur" ) + "" ) + begin; 11834 11835 captions[ captions.length ] = { 11836 text: DOMPurify.sanitize( captionElement.textContent ), 11837 begin: begin, 11838 end: end 11839 }; 11840 } 11841 return captions; 11842 }, 11843 11844 /** 11845 * @method loadCaptionsExternal 11846 * @description Loads captions from an external source (HTML embed or TTML) 11847 * @param {Object} elm The jQuery object for the multimedia player loading the captions 11848 * @param {String} url The url for the captions resource to load 11849 * @fires ccloaded.wb-mltmd 11850 * @fires ccloadfail.wb-mltmd 11851 */ 11852 loadCaptionsExternal = function( elm, url ) { 11853 $.ajax( { 11854 url: url, 11855 dataType: "html", 11856 11857 //Filters out images and objects from the content to avoid loading them 11858 dataFilter: function( data ) { 11859 return data.replace( /<img|object [^>]*>/g, "" ); 11860 }, 11861 success: function( data ) { 11862 11863 var captionItems; 11864 11865 if ( data.indexOf( "<html" ) !== -1 ) { 11866 11867 // Sanitize the response 11868 captionItems = parseHtml( $( DOMPurify.sanitize( data, { WHOLE_DOCUMENT: true } ) ) ); 11869 } else { 11870 11871 // Response is sanitized in the XML parser function 11872 captionItems = parseXml( data ); 11873 } 11874 11875 if ( captionItems.length ) { 11876 elm.trigger( { 11877 type: captionsLoadedEvent, 11878 captions: captionItems 11879 } ); 11880 } else { 11881 elm.trigger( { 11882 type: captionsLoadFailedEvent 11883 } ); 11884 } 11885 }, 11886 error: function( response, textStatus, errorThrown ) { 11887 elm.trigger( { 11888 type: captionsLoadFailedEvent, 11889 error: errorThrown 11890 } ); 11891 } 11892 } ); 11893 }, 11894 11895 /** 11896 * @method loadCaptionsInternal 11897 * @description Loads same page captions emebed in HTML 11898 * @param {Object} elm The jQuery object for the multimedia player loading the captions 11899 * @param {Object}
11899 obj The jQUery object containing the captions 11900 * @fires ccloaded.wb-mltmd 11901 */ 11902 loadCaptionsInternal = function( elm, obj ) { 11903 var captionItems = parseHtml( obj ); 11904 11905 if ( captionItems.length ) { 11906 elm.trigger( { 11907 type: captionsLoadedEvent, 11908 captions: captionItems 11909 } ); 11910 } else { 11911 elm.trigger( { 11912 type: captionsLoadFailedEvent 11913 } ); 11914 } 11915 }, 11916 11917 /** 11918 * @method updateCaptions 11919 * @description Update the captions for a multimedia player (called from the timeupdate event of the HTML5 media API) 11920 * @param {Object} area The jQuery object for the element where captions are displayed 11921 * @param {Float} seconds The current time of the media (use to sync the captions) 11922 * @param {Object} captions The JavaScript object containing the captions 11923 */ 11924 updateCaptions = function( area, seconds, captions ) { 11925 var caption, i, 11926 captionsLength = captions.length; 11927 11928 // added   (non-breaking space) to prevent caption space from collapsing 11929 // Used .html() instead of .append for performance purposes 11930 // https://jsperf.com/jquery-append-vs-html-list-performance/2 11931 area.html( " " ); 11932 11933 for ( i = 0; i < captionsLength; i += 1 ) { 11934 caption = captions[ i ]; 11935 if ( seconds >= caption.begin && seconds <= caption.end ) { 11936 area.html( $( "<div>" + caption.text + "</div>" ) ); 11937 } 11938 } 11939 }, 11940 11941 /** 11942 * @method playerApi 11943 * @description Normalizes the calls to the HTML5 media API 11944 * @param {String} fn The function to call 11945 * @param {object} args The arguments to send to the function call 11946 */ 11947 playerApi = function( fn, args ) { 11948 var $this, method; 11949 11950 switch ( fn ) { 11951 case "play": 11952 try { 11953 this.object.play(); 11954 } catch ( ex ) { 11955 this.object.doPlay(); 11956 } 11957 break; 11958 case "pause": 11959 try { 11960 this.object.pause(); 11961 } catch ( ex ) { 11962 this.object.doPause(); 11963 } 11964 break; 11965 case "getCaptionsVisible": 11966 return $( this ).hasClass( captionClass ); 11967 case "setCaptionsVisible": 11968 $this = $( this ); 11969 if ( args ) { 11970 $this.addClass( captionClass ); 11971 } else { 11972 $this.removeClass( captionClass ); 11973 } 11974 $this.trigger( captionsVisibleChangeEvent ); 11975 break; 11976 case "fullscreen": 11977 if ( this.object.requestFullscreen ) { 11978 this.object.requestFullscreen(); 11979 } else if ( this.object.webkitRequestFullscreen ) { /* Safari */ 11980 this.object.webkitRequestFullscreen(); 11981 } else if ( this.object.msRequestFullscreen ) { /* IE11 */ 11982 this.object.msRequestFullscreen(); 11983 } 11984 break; 11985 case "getBuffering": 11986 return this.object.buffering || false; 11987 case "setBuffering": 11988 this.object.buffering = args; 11989 break; 11990 case "getPreviousTime": 11991 return this.object.previousTime; 11992 case "setPreviousTime": 11993 this.object.previousTime = args; 11994 break; 11995 default: 11996 method = fn.charAt( 3 ).toLowerCase() + fn.substr( 4 ); 11997 switch ( fn.substr( 0, 3 ) ) { 11998 case "get": 11999 return typeof this.object[ method ] !== "function" ? 12000 this.object[ method ] : 12001 this.object[ method ](); 12002 case "set": 12003 typeof this.object[ method ] !== "function" ? 12004 this.object[ method ] = args : 12005 this.object[ fn ]( args ); 12006 } 12007 } 12008 }, 12009 12010 /** 12011 * @method youTubeApi 12012 * @description Normalizes the calls to the YouTube API 12013 * @param {String} fn The function to call 12014 * @param {object} args The arguments to send to the function call 12015 */ 12016 youTubeApi = function( fn, args ) { 12017 var $media = $( this.object.getIframe() ), 12018 state; 12019 12020 switch ( fn ) { 12021 case "play": 12022 this.object.wasMutedPlay = this.object.isMuted(); 12023 return this.object.playVideo(); 12024 case "pause": 12025 return this.object.pauseVideo(); 12026 case "getPaused": 12027 state = this.object.getPlayerState(); 12028 return state === -1 || state === 0 || state === 2 || state === 5; 12029 case "getPlayed": 12030 return this.object.getPlayerState() > -1; 12031 case "getEnded": 12032 return this.object.getPlayerState() === 0; 12033 case "getDuration": 12034 return this.object.getDuration(); 12035 case "getCurrentTime": 12036 return this.object.getCurrentTime(); 12037 case "setCurrentTime": 12038 return this.object.seekTo( args, true ); 12039 case "fullscreen": 12040 return this.object.getIframe().requestFullscreen(); 12041 case "getMuted": 12042 if ( !this.object.playedOnce && this.object.wasMutedPlay ) { 12043 state = this.object.wasMutedPlay; 12044 this.object.playedOnce = true; 12045 return state; 12046 } else { 12047 return this.object.isMuted(); 12048 } 12049 case "setMuted": 12050 if ( args ) { 12051 this.object.mute(); 12052 } else {
12053 this.object.unMute(); 12054 } 12055 setTimeout( function() { 12056 $media.trigger( "volumechange" ); 12057 }, ( wb.isReady ? 50 : 500 ) ); 12058 break; 12059 case "getVolume": 12060 return this.object.getVolume() / 100; 12061 case "setVolume": 12062 this.object.setVolume( args * 100 ); 12063 setTimeout( function() { 12064 $media.trigger( "volumechange" ); 12065 }, 50 ); 12066 break; 12067 case "getCaptionsVisible": 12068 return $( this ).hasClass( captionClass ); 12069 case "setCaptionsVisible": 12070 if ( args ) { 12071 $( this ).addClass( captionClass ); 12072 try { 12073 this.object.loadModule( "cc" ); 12074 this.object.setOption( "cc", "track", { languageCode: this.object.getOption( "cc", "tracklist" )[ 0 ].languageCode } ); 12075 } catch ( e ) { 12076 this.object.loadModule( "captions" ); 12077 this.object.setOption( "captions", "track", { languageCode: this.object.getOption( "captions", "tracklist" )[ 0 ].languageCode } ); 12078 } 12079 } else { 12080 $( this ).removeClass( captionClass ); 12081 this.object.unloadModule( "cc" ); 12082 this.object.unloadModule( "captions" ); 12083 } 12084 $media.trigger( "ccvischange" ); 12085 } 12086 }, 12087 12088 /** 12089 * @method youTubeEvents 12090 * @description Youtube API event manager 12091 * @param {object} event The event object fior the triggered event 12092 */ 12093 youTubeEvents = function( event ) { 12094 var media = event.target.getIframe(), 12095 $media = $( media ), 12096 timeline = function() { 12097 $media.trigger( "timeupdate" ); 12098 }, 12099 $mltmPlayerElm, 12100 mltmPlayerElm, 12101 isMuted; 12102 12103 switch ( event.data ) { 12104 case null: // init 12105 $media 12106 .trigger( "canplay" ) 12107 .trigger( "durationchange" ); 12108 12109 // Put video on mute if the video is muted on init, run once 12110 $mltmPlayerElm = $media.parentsUntil( selector ).parent(); 12111 12112 // Mute the player, GUI 12113 if ( $mltmPlayerElm.data( "putMutedOnInit" ) ) { 12114 youTubeApi.call( $mltmPlayerElm.get( 0 ), "setMuted", true ); 12115 $mltmPlayerElm.data( "putMutedOnInit", false ); 12116 } 12117 break; 12118 case -1: 12119 event.target.unMute(); 12120 $media.trigger( "durationchange" ); 12121 break; 12122 case 0: 12123 $media.trigger( "ended" ); 12124 media.timeline = clearInterval( media.timeline ); 12125 break; 12126 case 1: // play 12127 12128 // Get the media player 12129 $mltmPlayerElm = $media.parentsUntil( selector ).parent(); 12130 mltmPlayerElm = $mltmPlayerElm.get( 0 ); 12131 12132 // Need to be muted here 12133 isMuted = mltmPlayerElm.player( "getMuted" ); 12134 12135 // Reset the close caption state when iframe was reloaded 12136 if ( media.dataset.L2 ) { 12137 youTubeApi.call( mltmPlayerElm, "setCaptionsVisible", $mltmPlayerElm.hasClass( captionClass ) ); 12138 } 12139 12140 // Play 12141 $media 12142 .trigger( "canplay" ) 12143 .trigger( "play" ) 12144 .trigger( "playing" ); 12145 12146 // Reset muted as needed because youtube onMute by default when playing 12147 if ( isMuted ) { 12148 youTubeApi.call( mltmPlayerElm, "setMuted", true ); 12149 } 12150 12151 media.timeline = setInterval( timeline, 250 ); 12152 break; 12153 case 2: 12154 $media.trigger( "pause" ); 12155 media.timeline = clearInterval( media.timeline ); 12156 break; 12157 case 3: 12158 media.timeline = clearInterval( media.timeline ); 12159 break; 12160 } 12161 }, 12162 12163 youTubeAPIReady = function() { 12164 var youTube = window.youTube; 12165 youTube.ready = true; 12166 $document.trigger( youtubeReadyEvent ); 12167 }, 12168 12169 onResize = function() { 12170 $( selector + " object, " + selector + " iframe, " + selector + " video" ).trigger( resizeEvent ); 12171 }; 12172 12173$document.on( "timerpoke.wb " + initEvent, selector, init ); 12174 12175$window.on( "resize", onResize ); 12176 12177$document.on( "ready", onResize ); 12178 12179$document.on( "ajax-fetched.wb " + templateLoadedEvent, selector, function( event ) { 12180 var $this = $( this ); 12181 12182 if ( event.type === "ajax-fetched" ) { 12183 template = event.fetch.pointer.html(); 12184 12185 //Notify all player waiting for the controls to load 12186 $this = $( selector ); 12187 } 12188 12189 $this.trigger( { 12190 type: initializedEvent 12191 } ); 12192} ); 12193 12194$document.on( initializedEvent, selector, function( event ) { 12195 if ( event.namespace === componentName ) { 12196 var $this = $( this ), 12197 $media = $this.children( "audio, video" ).eq( 0 ), 12198 captions = $media.children( "track[kind='captions']" ).attr( "src" ) || undef, 12199 id = $this.attr( "id" ), 12200 mId = $media.attr( "id" ) || id + "-md", 12201 type = $media.is( "audio" ) ? "audio" : "video", 12202 title = $media.attr( "title" ) || "", 12203 width = type === "video" ? $media.attr( "width" ) || $media.width() : 0, 12204 height = type === "video" ? $media.attr( "height" ) || $media.height() : 0,
12205 settings = wb.getData( $this, componentName ), 12206 data = $.extend( { 12207 media: $media, 12208 captions: captions, 12209 id: id, 12210 mId: mId, 12211 type: type, 12212 title: title, 12213 height: height, 12214 width: width 12215 }, i18nText ), 12216 media = $media.get( 0 ), 12217 youTube = window.youTube, 12218 url, 12219 i18n = wb.i18n; 12220 12221 if ( $media.attr( "id" ) === undef ) { 12222 $media.attr( "id", mId ); 12223 } 12224 12225 if ( settings !== undef ) { 12226 data.shareUrl = settings.shareUrl; 12227 data.fullscreen = settings.fullscreenBtn || false; 12228 } 12229 12230 $this.addClass( type ); 12231 12232 if ( $media.find( "[type='video/youtube']" ).length > 0 ) { 12233 12234 // lets tweak some variables and start the load sequence 12235 url = wb.getUrlParts( $this.find( "[type='video/youtube']" ).attr( "src" ) ); 12236 12237 // lets set the flag for the call back 12238 data.youTubeId = url.params.v ? url.params.v : url.pathname.substr( 1 ); 12239 12240 // Defaults config set on the video element 12241 data.isInitMuted = $media.get( 0 ).muted; 12242 12243 if ( youTube.ready === false ) { 12244 $document.one( youtubeReadyEvent, function() { 12245 $this.trigger( youtubeEvent, data ); 12246 } ); 12247 } else { 12248 $this.trigger( youtubeEvent, data ); 12249 } 12250 12251 // finally lets load safely 12252 return Modernizr.load( { 12253 load: "https://www.youtube.com/iframe_api", 12254 12255 //possible solution for multimedia and doaction conflict in corporate network 12256 complete: function() { 12257 12258 // Ensure that Youtube API is loading the iframe and if it fails, ensure that it will show a message, like accessing the web via our GC network. 12259 setTimeout( function() { 12260 var resources, arrIframesYt, $notifText; 12261 12262 resources = window.performance.getEntriesByType( "resource" ); 12263 12264 /* get all the iframe initiators that have the same YT url id */ 12265 arrIframesYt = resources.filter( function( obj ) { 12266 12267 return obj.initiatorType === "iframe" && obj.name.includes( data.youTubeId ); 12268 12269 } ); 12270 12271 12272 /* if none found, most probably wb is loaded in restricted network so wb.ready() is triggered for not preventing other wb components to load*/ 12273 if ( arrIframesYt.length < 1 ) { 12274 if ( !wb.isReady ) { 12275 12276 // show the video notification error 12277 $notifText = $( "<div aria-live='polite' class='pstn-lft-xs bg-dark text-white'><p class='mrgn-tp-md mrgn-rght-md mrgn-bttm-md mrgn-lft-md'>" + i18n( "msgYoutubeNotLoad" ) + "</p></div>" ); 12278 $this.prepend( $notifText ); 12279 data.notifyText = $notifText;
12280 wb.ready( $this, componentName ); 12281 } 12282 } 12283 }, 1000 ); 12284 12285 } 12286 } ); 12287 12288 } else if ( media.error === null && media.currentSrc !== "" && media.currentSrc !== undef ) { 12289 $this.trigger( renderUIEvent, [ type, data ] ); 12290 12291 // Identify that initialization has completed 12292 wb.ready( $this, componentName ); 12293 } 12294 } 12295} ); 12296 12297/* 12298 * Youtube Video mode Event 12299 */ 12300$document.on( youtubeEvent, selector, function( event, data ) { 12301 if ( event.namespace === componentName ) { 12302 var mId = data.mId, 12303 $this = $( event.currentTarget ), 12304 $media, ytPlayer; 12305 12306 ytPlayer = new YT.Player( mId, { 12307 videoId: data.youTubeId, 12308 playerVars: { 12309 autoplay: 0, 12310 controls: 0, 12311 origin: wb.pageUrlParts.host, 12312 modestbranding: 1, 12313 rel: 0, 12314 showinfo: 0, 12315 html5: 1, 12316 cc_load_policy: 1 12317 }, 12318 events: { 12319 onReady: function( event ) { 12320 if ( data.notifyText ) { 12321 data.notifyText.hide(); 12322 } 12323 onResize(); 12324 youTubeEvents( event ); 12325 if ( !wb.isReady ) { 12326 wb.ready( $this, componentName ); 12327 } 12328 }, 12329 onStateChange: youTubeEvents, 12330 onApiChange: function() { 12331 12332 //If captions were enabled before the module was ready, re-enable them 12333 var t = $this.get( 0 ); 12334 t.player( "setCaptionsVisible", t.player( "getCaptionsVisible" ) ); 12335 }, 12336 onError: function() { 12337 console.warn( "There is an issue loading the Youtube player" ); 12338 } 12339 } 12340 } ); 12341 12342 $this.addClass( "youtube" ); 12343 12344 $media = $this.find( "#" + mId ).attr( "tabindex", -1 ); 12345 12346 data.media = $media; 12347 data.ytPlayer = ytPlayer; 12348 12349 // The fullscreen button is not visible by default because there are no controls when in full screen. 12350 if ( data.fullscreen ) { 12351 $this.attr( "data-fullscreen-btn", true ); 12352 } 12353 12354 // Detect if the YT player reloads, like when magnific Popup show the modal, because it moves the iframe 12355 // and then the iframe gets refreshed and reloaded. So the issue is that the iframe stops emitting the event 12356 // needed to adjust the multimedia player controler, like the "onStateChange" event. 12357 $media.on( "load", function( evt ) { 12358 12359 var elm = evt.currentTarget, 12360 ds = elm.dataset; 12361
12362 // Do nothing on the first load and add a flag to indicate it is loaded a second time 12363 if ( ds.L1 ) { 12364 ds.L2 = true; 12365 } else { 12366 ds.L1 = true; 12367 } 12368 } ); 12369 12370 $this.trigger( renderUIEvent, [ "youtube", data ] ); 12371 } 12372} ); 12373 12374$document.on( renderUIEvent, selector, function( event, type, data ) { 12375 if ( event.namespace === componentName ) { 12376 var $this = $( event.currentTarget ), 12377 captionsUrl = wb.getUrlParts( data.captions ), 12378 currentUrl = wb.getUrlParts( window.location.href ), 12379 $media = data.media, 12380 $eventReceiver; 12381 12382 $media 12383 .after( tmpl( template, data ) ) 12384 .wrap( "<div class=\"display\"></div>" ); 12385 12386 $eventReceiver = $media.is( "object" ) ? $media.children( ":first-child" ) : $media; 12387 12388 // Create an adapter for the event management 12389 $eventReceiver.on( multimediaEvents, function( event ) { 12390 $this.trigger( event ); 12391 } ); 12392 12393 this.object = data.ytPlayer || $media.get( 0 ); 12394 this.player = ( data.ytPlayer ) ? youTubeApi : playerApi; 12395 12396 // Trigger the duration change for cases where the event was called before the event binding 12397 if ( type !== "youtube" && !isNaN( this.player( "getDuration" ) ) ) { 12398 $eventReceiver.trigger( "durationchange" ); 12399 } 12400 12401 // Load the progress polyfill if needed 12402 $this.find( "progress" ).trigger( "wb-init.wb-progress" ); 12403 12404 // Load the slider polyfill if needed 12405 $this.find( "input[type='range']" ).trigger( "wb-init.wb-slider" ); 12406 12407 // Create the share widgets if needed 12408 if ( data.shareUrl !== undef ) { 12409 $( "<div class='wb-share' data-wb-share='{\"type\": \"" + 12410 ( type === "audio" ? type : "video" ) + "\", \"title\": \"" + 12411 data.title.replace( /'/g, "'" ) + "\", \"url\": \"" + data.shareUrl + 12412 "\", \"pnlId\": \"" + data.id + "-shr\"}'></div>" ) 12413 .insertBefore( $media.parent() ) 12414 .trigger( "wb-init.wb-share" ); 12415 } 12416 12417 if ( data.isInitMuted ) { 12418 $this.data( "putMutedOnInit", true ); 12419 } else if ( !data.ytPlayer && this.object.muted ) { 12420 $media.trigger( "volumechange" ); 12421 } 12422 12423 if ( data.captions === undef ) { 12424 return 1; 12425 } 12426 12427 // Load the captions 12428 if ( currentUrl.absolute.replace( currentUrl.hash || "#", "" ) !== captionsUrl.absolute.replace( captionsUrl.hash || "#", "" ) ) { 12429 loadCaptionsExternal( $media, captionsUrl.absolute ); 12430 } else { 12431 loadCaptionsInternal( $media, $( "#" + wb.jqEscape( captionsUrl.hash.substring( 1 ) ) ) ); 12432 } 12433 12434 // The fullscreen button is not visible by default because there are no controls when in full screen. 12435 if ( data.fullscreen ) { 12436 $this.attr( "data-fullscreen-btn", true ); 12437 } 12438 } 12439} ); 12440 12441/* 12442 * UI Bindings 12443 */ 12444 12445$document.on( "click", selector, function( event ) { 12446 var $target = $( event.target ), 12447 className = $target.attr( "class" ) || ""; 12448 12449 // Ignore middle and right mouse buttons 12450 if ( event.which === 2 || event.which === 3 ) { 12451 return true; 12452 } 12453 12454 // Optimized multiple class tests to include child glyphicon because Safari was reporting the click event 12455 // from the child span not the parent button, forcing us to have to check for both elements 12456 // JSPerf for multiple class matching https://jsperf.com/hasclass-vs-is-stackoverflow/7 12457 if ( /playpause|-play|-pause|display/.test( className ) || $target.is( "object" ) || $target.is( "video" ) ) { 12458 this.player( "getPaused" ) || this.player( "getEnded" ) ? this.player( "play" ) : this.player( "pause" ); 12459 } else if ( /(^|\s)cc\b|-subtitles/.test( className ) && !$target.attr( "disabled" ) && !$target.parent().attr( "disabled" ) ) { 12460 this.player( "setCaptionsVisible", !this.player( "getCaptionsVisible" ) ); 12461 } else if ( /\bmute\b|-volume-(up|off)/.test( className ) ) { 12462 this.player( "setMuted", !this.player( "getMuted" ) ); 12463 } else if ( $target.is( "progress" ) || $target.hasClass( "progress" ) || $target.hasClass( "progress-bar" ) ) { 12464 this.player( "setCurrentTime", this.player( "getDuration" ) * ( ( event.pageX - $target.offset().left ) / $target.width() ) ); 12465 } else if ( /\brewind\b|-backward/.test( className ) ) { 12466 this.player( "setCurrentTime", this.player( "getCurrentTime" ) - this.player( "getDuration" ) * 0.05 ); 12467 } else if ( /\bfastforward\b|-forward/.test( className ) ) { 12468 this.player( "setCurrentTime", this.player( "getCurrentTime" ) + this.player( "getDuration" ) * 0.05 ); 12469 } else if ( className.includes( "cuepoint" ) ) { 12470 $( this ).trigger( { type: "cuepoint", cuepoint: $target.data( "cuepoint" ) } ); 12471 } else if ( /fullscreen|fs/.test( className ) ) { 12472 this.player( "fullscreen" ); 12473 } 12474} ); 12475 12476$document.on( "input change", selector, function( event ) { 12477 var target = event.target; 12478 12479 if ( $( target ).hasClass( "volume" ) ) { 12480 event.currentTarget.player( "setMuted", false ); 12481 event.currentTarget.player( "setVolume", target.value / 100 ); 12482 } 12483} ); 12484 12485$document.on( "keydown", dispCtrls, function( event ) { 12486 var playerTarget = event.currentTarget.parentNode, 12487 which = event.which, 12488 volume = 0, 12489 step = 0.05, 12490 $playerTarget = $( playerTarget ); 12491 12492 if ( !( event.ctrlKey || event.altKey || event.metaKey ) ) { 12493 switch ( which ) { 12494 case 32: 12495 12496 // Mute/unmute if focused on the mute/unmute button or volume input. 12497 if ( $( event.target ).hasClass( "mute" ) || event.target.nodeName === "INPUT" ) { 12498 $playerTarget.find( ".mute" ).trigger( "click" ); 12499 } else if ( $( event.target ).hasClass( "fs" ) ) { 12500 12501 // Enter full screen if focused on the full screen button 12502 $playerTarget.find( ".fs" ).trigger( "click" ); 12503 } else if ( $( event.target ).hasClass( "cc" ) ) { 12504 12505 // Show/hide captions if focused on the closed captions button. 12506 $playerTarget.find( ".cc" ).trigger( "click" ); 12507 } else { 12508 12509 // Play/pause if focused on anything else (i.e. the video itself, play/pause button or progress bar). 12510 $playerTarget.find( ".playpause" ).trigger( "click" ); 12511 } 12512 break; 12513 12514 case 37: 12515 playerTarget.player( "setCurrentTime", this.parentNode.player( "getCurrentTime" ) - this.parentNode.player( "getDuration" ) * 0.05 ); 12516 break; 12517 12518 case 39: 12519 playerTarget.player( "setCurrentTime", this.parentNode.player( "getCurrentTime" ) + this.parentNode.player( "getDuration" ) * 0.05 ); 12520 break; 12521 12522 case 38: 12523 volume = Math.round( playerTarget.player( "getVolume" ) * 100 ) / 100 + step; 12524 playerTarget.player( "setVolume", volume < 1 ? volume : 1 ); 12525 break; 12526 12527 case 40: 12528 volume = Math.round( playerTarget.player( "getVolume" ) * 100 ) / 100 - step; 12529 playerTarget.player( "setVolume", volume > 0 ? volume : 0 ); 12530 break; 12531 12532 default: 12533 return true; 12534 } 12535 return false;
12536 } 12537} ); 12538 12539$document.on( "keyup", ctrls, function( event ) { 12540 if ( event.which === 32 && !( event.ctrlKey || event.altKey || event.metaKey ) ) { 12541 12542 // Allows the spacebar to be used for play/pause without double triggering 12543 return false; 12544 } 12545} ); 12546 12547$document.on( "wb-activate", selector, function() { 12548 this.player( "play" ); 12549} ); 12550 12551$document.on( "closed.wb-overlay", ".wb-overlay", function( event ) { 12552 var mltmdPlayer = event.currentTarget.querySelector( selector ); 12553 if ( mltmdPlayer ) { 12554 mltmdPlayer.player( "pause" ); 12555 } 12556} ); 12557 12558$document.on( multimediaEvents, selector, function( event, simulated ) { 12559 var eventTarget = event.currentTarget, 12560 eventType = event.type, 12561 eventNamespace = event.namespace, 12562 $this = $( eventTarget ), 12563 invStart = "<span class='wb-inv'>", 12564 invEnd = "</span>", 12565 currentTime, $button, $slider, buttonData, isPlay, isMuted, isCCVisible, skipTo, volume; 12566 switch ( eventType ) { 12567 case "playing": 12568 case "pause": 12569 case "ended": 12570 isPlay = eventType === "playing"; 12571 $button = $this.find( ".playpause" ); 12572 buttonData = $button.data( "state-" + ( isPlay ? "off" : "on" ) ); 12573 if ( isPlay ) { 12574 $this.addClass( "playing" ); 12575 $this.find( ".progress" ).addClass( "active" ); 12576 } else { 12577 if ( eventType === "ended" ) { 12578 this.loading = clearTimeout( this.loading ); 12579 } 12580 $this.removeClass( "playing" ); 12581 } 12582 $button 12583 .attr( "title", buttonData ) 12584 .children( "span" ) 12585 .toggleClass( "glyphicon-play", !isPlay ) 12586 .toggleClass( "glyphicon-pause", isPlay ) 12587 .html( invStart + buttonData + invEnd ); 12588 break; 12589 12590 case "volumechange": 12591 isMuted = eventTarget.player( "getMuted" ); 12592 $button = $this.find( ".mute" ); 12593 buttonData = $button.data( "state-" + ( isMuted ? "off" : "on" ) ); 12594 volume = eventTarget.player( "getVolume" ) * 100; 12595 $button 12596 .attr( { 12597 title: buttonData, 12598 "aria-pressed": isMuted 12599 } ) 12600 .children( "span" ) 12601 .toggleClass( "glyphicon-volume-up", !isMuted ) 12602 .toggleClass( "glyphicon-volume-off", isMuted ) 12603 .html( invStart + buttonData + invEnd ); 12604 $slider = $this.find( "input[type='range']" ); 12605 $slider[ 0 ].value = isMuted ? 0 : volume; 12606 $slider.trigger( "wb-update.wb-slider" ); 12607 break; 12608 12609 case "timeupdate": 12610 currentTime = eventTarget.player( "getCurrentTime" ); 12611 $this.find( "progress" ) 12612 .attr( 12613 "value", 12614 Math.round( currentTime / eventTarget.player( "getDuration" ) * 1000 ) / 10 12615 ).trigger( "wb-update.wb-progress" ); 12616 12617 $this.find( ".wb-mm-tmln-crrnt span:nth-child(2)" ) 12618 .text( formatTime( currentTime ) ); 12619 12620 if ( $this.hasClass( captionClass ) && $.data( eventTarget, "captions" ) !== undef ) { 12621 updateCaptions( 12622 $this.find( ".wb-mm-cc" ), 12623 currentTime, 12624 $.data( eventTarget, "captions" ) 12625 ); 12626 } 12627 break; 12628 12629 case "durationchange": 12630 $this.find( ".wb-mm-tmln-ttl span:nth-child(2)" ) 12631 .text( formatTime( eventTarget.player( "getDuration" ) ) ); 12632 12633 // Skip to pointer from the querystring 12634 skipTo = wb.pageUrlParts.params[ event.target.id ]; 12635 if ( skipTo ) { 12636 skipTo = parseTime( skipTo ); 12637 eventTarget.player( "setCurrentTime", skipTo ); 12638 } 12639 break; 12640 12641 case "ccloaded": 12642 if ( eventNamespace === componentName ) { 12643 $.data( eventTarget, "captions", event.captions ); 12644 } 12645 break; 12646 12647 case "ccloadfail": 12648 if ( eventNamespace === componentName ) { 12649 if ( !$this.hasClass( "errmsg" ) ) { 12650 $this.addClass( "cc_on errmsg" ) 12651 .find( ".wb-mm-cc" ) 12652 .append( "<div>" + i18nText.cc_error + "</div>" ) 12653 .end() 12654 .find( ".cc" ) 12655 .attr( "disabled", "" ) 12656 .removeAttr( "aria-pressed" ); 12657 } 12658 } 12659 break; 12660 12661 case "ccvischange": 12662 if ( eventNamespace === componentName ) { 12663 isCCVisible = eventTarget.player( "getCaptionsVisible" ); 12664 $button = $this.find( ".cc" ); 12665 buttonData = $button.data( "state-" + ( isCCVisible ? "off" : "on" ) ); 12666 $button.attr( { 12667 title: buttonData, 12668 "aria-pressed": isCCVisible 12669 } ).children( "span" ).html( invStart + buttonData + invEnd ); 12670 } 12671 break; 12672 12673 case "waiting": 12674 if ( !simulated ) { 12675 $document.off( "progress", selector ); 12676 } 12677 this.loading = setTimeout( function() { 12678 $this.addClass( "waiting" ); 12679 }, 500 ); 12680 break; 12681 12682 case "canplay": 12683 case "seeked": 12684 this.loading = clearTimeout( this.loading ); 12685 $this.removeClass( "waiting" ); 12686 break; 12687 case "cuepoint": 12688 eventTarget.player( "setCurrentTime", parseTime( event.cuepoint ) ); 12689 break; 12690 } 12691} ); 12692 12693// Fallback for browsers that don't implement the waiting events 12694$document.on( "progress", selector, function( event ) { 12695 var eventTarget = event.currentTarget,
12696 $this = $( eventTarget ); 12697 12698 // Waiting detected 12699 if ( this.player( "getPaused" ) === false && this.player( "getCurrentTime" ) === this.player( "getPreviousTime" ) ) { 12700 if ( eventTarget.player( "getBuffering" ) === false ) { 12701 eventTarget.player( "setBuffering", true ); 12702 $this.trigger( "waiting", true ); 12703 } 12704 12705 // Waiting has ended 12706 } else if ( eventTarget.player( "getBuffering" ) === true ) { 12707 eventTarget.player( "setBuffering", false ); 12708 $this.trigger( "canplay", true ); 12709 } 12710 eventTarget.player( "setPreviousTime", eventTarget.player( "getCurrentTime" ) ); 12711} ); 12712 12713$document.on( resizeEvent, selector, function( event ) { 12714 if ( event.namespace === componentName ) { 12715 var media = event.target, 12716 $media = $( media ), 12717 ratio, newHeight; 12718 12719 if ( $( event.currentTarget ).hasClass( "video" ) ) { 12720 if ( media.videoWidth === 0 || media.videoWidth === undef ) { 12721 ratio = $media.attr( "height" ) / $media.attr( "width" ); 12722 12723 // Calculate the new height based on the specified ratio or assume a default 16:9 ratio 12724 newHeight = Math.round( $media.width() * ( !isNaN( ratio ) ? ratio : 0.5625 ) ); 12725 12726 $media.css( "height", newHeight + "px" ); 12727 } else { 12728 $media.css( "height", "" ); 12729 } 12730 } 12731 } 12732} ); 12733 12734window.onYouTubeIframeAPIReady = youTubeAPIReady; 12735 12736window.youTube = { 12737 ready: false 12738}; 12739 12740wb.add( selector ); 12741 12742} )( jQuery, DOMPurify, window, wb ); 12743 12744/** 12745 * @title WET-BOEW NavCurrent 12746 * @overview Identify URL in a navigation system that matches current page URL or a URL in the breadcrumb trail. Call by applying .trigger( "navcurr.wb", breadcrumb ) where the breadcrumb parameter is an optional object (DOM or jQuery) 12747 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 12748 * @author @pjackson28 12749 */ 12750( function( $, window, wb ) { 12751"use strict"; 12752 12753/* 12754 * Variable and function definitions. 12755 * These are global to the plugin - meaning that they will be initialized once per page, 12756 * not once per instance of plugin on the page. So, this is a good place to define 12757 * variables that are common to all instances of the plugin on a page. 12758 */ 12759var componentName = "wb-navcurr", 12760 selector = "." + componentName, 12761 $document = wb.doc, 12762 breadcrumbLinksArray, breadcrumbLinksUrlArray, 12763 12764 /** 12765 * @method init 12766 * @param {jQuery Event} event Event that triggered the function call 12767 * @param {jQuery DOM element | DOM element} breadcrumb Optional breadcrumb element 12768 * @param {string} classNameOverride Optional class name override (default is wb-navcurr) 12769 */ 12770 init = function( event, breadcrumb, classNameOverride ) { 12771 if ( event.namespace === "wb" ) { 12772 12773 // Start initialization 12774 // returns DOM object = proceed with init 12775 // returns undefined = do not proceed with init (e.g., already initialized) 12776 var menu = wb.init( event.target, componentName, selector ), 12777 menuLinks = menu.getElementsByTagName( "a" ), 12778 menuLinksArray = [], 12779 menuLinksUrlArray = [], 12780 windowLocation = window.location, 12781 pageUrl = windowLocation.hostname + windowLocation.pathname.replace( /^([^/])/, "/$1" ), 12782 pageUrlQuery = windowLocation.search, 12783 match = false, 12784 className = classNameOverride ? classNameOverride : componentName, 12785 child, len, i, j, link, linkHref, linkUrl, linkQuery, linkQueryLen, 12786 localBreadcrumbLinks, localBreadcrumbLinksArray, localBreadcrumbLinksUrlArray, 12787 localBreadcrumbQuery, localBreadcrumbLinkUrl; 12788 12789 if ( menu ) { 12790 12791 // Try to find a match with the page Url and cache link + Url for later if no match found 12792 // Perform the check and caching in reverse to go from more specific links to more general links 12793 for ( i = menuLinks.length - 1; i !== -1; i -= 1 ) { 12794 link = menuLinks[ i ]; 12795 linkHref = link.getAttribute( "href" ); 12796 if ( linkHref !== null ) { 12797 if ( linkHref.length !== 0 && linkHref.charAt( 0 ) !== "#" ) { 12798 linkUrl = link.hostname + link.pathname.replace( /^([^/])/, "/$1" ); 12799 linkQuery = link.search; 12800 linkQueryLen = linkQuery.length; 12801 if ( pageUrl.slice( -linkUrl.length ) === linkUrl && ( linkQueryLen === 0 || pageUrlQuery.slice( -linkQueryLen ) === linkQuery ) ) { 12802 match = true; 12803 break; 12804 } 12805 menuLinksArray.push( link ); 12806 menuLinksUrlArray.push( linkUrl ); 12807 } 12808 } 12809 } 12810 12811 // No page Url match found, try a breadcrumb link match instead 12812 if ( !match && breadcrumb ) { 12813 12814 // Check to see if the data has been cached already 12815 if ( !breadcrumbLinksArray ) { 12816 12817 // Pre-process the breadcrumb links 12818 localBreadcrumbLinksArray = []; 12819 localBreadcrumbLinksUrlArray = []; 12820 localBreadcrumbLinks = ( breadcrumb.jquery ? breadcrumb[ 0 ] : breadcrumb ).getElementsByTagName( "li" ); 12821 len = localBreadcrumbLinks.length; 12822 if ( len ) { 12823 link = localBreadcrumbLinks[ len - 1 ]; 12824 child = link.firstChild; 12825 linkHref = ( child && child.nodeName === "A" ) ? child.getAttribute( "href" ) : ""; 12826 if ( linkHref && linkHref.charAt( 0 ) !== "#" ) { 12827 localBreadcrumbLinksArray.push( child ); 12828 localBreadcrumbLinksUrlArray.push( child.hostname + child.pathname.replace( /^([^/])/, "/$1" ) ); 12829 } 12830 } 12831 12832 // Cache the data in case of more than one execution (e.g., site menu + secondary navigation) 12833 breadcrumbLinksArray = localBreadcrumbLinksArray; 12834 breadcrumbLinksUrlArray = localBreadcrumbLinksUrlArray; 12835 } else { 12836 12837 // Retrieve the cached data 12838 localBreadcrumbLinksArray = breadcrumbLinksArray; 12839 localBreadcrumbLinksUrlArray = breadcrumbLinksUrlArray; 12840 } 12841 12842 // Try to match each breadcrumb link 12843 len = menuLinksArray.length; 12844 for ( j = localBreadcrumbLinksArray.length - 1; j !== -1; j -= 1 ) { 12845 localBreadcrumbLinkUrl = localBreadcrumbLinksUrlArray[ j ]; 12846 localBreadcrumbQuery = localBreadcrumbLinksArray[ j ].search; 12847 12848 for ( i = 0; i !== len; i += 1 ) { 12849 link = menuLinksArray[ i ]; 12850 linkUrl = menuLinksUrlArray[ i ]; 12851 linkQuery = link.search; 12852 linkQueryLen = linkQuery.length; 12853 12854 if ( localBreadcrumbLinkUrl.slice( -linkUrl.length ) === linkUrl && ( linkQueryLen === 0 || localBreadcrumbQuery.slice( -linkQueryLen ) === linkQuery ) ) { 12855 match = true; 12856 break; 12857 } 12858 } 12859 if ( match ) { 12860 break; 12861 } 12862 } 12863 } 12864 12865 if ( match ) { 12866 link.className += " " + className; 12867 if ( menu.className.indexOf( "wb-menu" ) !== -1 && link.className.indexOf( "item" ) === -1 ) { 12868 $( link ).closest( ".sm" ).parent().children( "a" ).addClass( className ); 12869 } 12870 } 12871 12872 // Identify that initialization has completed 12873 wb.ready( $( menu ), componentName ); 12874 } 12875 } 12876 }; 12877 12878// Bind the navcurrent event of the plugin 12879$document.on( "navcurr.wb", init ); 12880 12881} )( jQuery, window, wb ); 12882 12883/** 12884 * @title WET-BOEW Overlay 12885 * @overview Provides multiple styles of overlays such as panels and pop-ups 12886 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 12887 * @author @thomasgohard, @pjackson28 12888 */ 12889( function( $, window, document, wb ) { 12890"use strict"; 12891 12892/* 12893 * Variable and function definitions. 12894 * These are global to the plugin - meaning that they will be initialized once per page, 12895 * not once per instance of plugin on the page. So, this is a good place to define 12896 * variables that are common to all instances of the plugin on a page. 12897 */
12898var componentName = "wb-overlay", 12899 selector = "." + componentName, 12900 initEvent = "wb-init" + selector, 12901 closeClass = "overlay-close", 12902 linkClass = "overlay-lnk", 12903 ignoreOutsideClass = "outside-off", 12904 OverlayOpenFlag = "wb-overlay-dlg", 12905 initialized = false, 12906 sourceLinks = {}, 12907 setFocusEvent = "setfocus.wb", 12908 $document = wb.doc, 12909 i18n, i18nText, 12910 12911 /** 12912 * @method init 12913 * @param {jQuery Event} event Event that triggered the function call 12914 */ 12915 init = function( event ) { 12916 12917 // Start initialization 12918 // returns DOM object = proceed with init 12919 // returns undefined = do not proceed with init (e.g., already initialized) 12920 var elm = wb.init( event, componentName, selector ), 12921 $elm, footer, $header, closeText, overlayClose; 12922 12923 if ( elm ) { 12924 $elm = $( elm ); 12925 12926 // Only initialize the i18nText once 12927 if ( !i18nText ) { 12928 i18n = wb.i18n; 12929 i18nText = { 12930 close: i18n( "close" ), 12931 colon: i18n( "colon" ), 12932 space: i18n( "space" ), 12933 esc: i18n( "esc-key" ), 12934 closeOverlay: i18n( closeClass ) 12935 }; 12936 } 12937 12938 // One left and right panels add close button 12939 var isPanel = ( $elm.attr( "class" ).indexOf( "wb-panel" ) > -1 ) ? true : false, 12940 isPopup = ( $elm.attr( "class" ).indexOf( "wb-popup" ) > -1 ) ? true : false; 12941 if ( isPanel || isPopup ) { 12942 footer = $elm.find( ".modal-footer" )[ 0 ]; 12943 12944 var hasFooter = ( footer && footer.length !== 0 ) ? true : false, 12945 hasButton = hasFooter && $( footer ).find( "." + closeClass ).length !== 0, 12946 closeClassFtr = ( $elm.hasClass( "wb-panel-l" ) ? "pull-right " : "pull-left " ) + closeClass, 12947 closeTextFtr = i18nText.close, 12948 spanTextFtr = i18nText.closeOverlay, 12949 overlayCloseFtr; 12950 12951 if ( !hasButton ) { 12952 if ( !hasFooter ) { 12953 footer = document.createElement( "div" ); 12954 footer.setAttribute( "class", "modal-footer" ); 12955 } 12956 12957 if ( isPopup ) { 12958 footer.style.border = "0"; 12959 } 12960 12961 overlayCloseFtr = "<button type='button' class='btn btn-sm btn-primary " + closeClassFtr + 12962 "' title='" + spanTextFtr + "'>" + 12963 closeTextFtr + 12964 "<span class='wb-inv'>" + spanTextFtr + "</span></button>"; 12965 12966 $( footer ).append( overlayCloseFtr ); 12967 if ( !hasFooter ) { 12968 $elm.append( footer ); 12969 } 12970 } 12971 } 12972 12973 // Add close button 12974 $header = $elm.find( ".modal-title" ); 12975 if ( $header.length !== 0 ) { 12976 closeText = i18nText.close + i18nText.colon + i18nText.space + 12977 $header.text() + i18nText.space + i18nText.esc; 12978 } else { 12979 closeText = i18nText.closeOverlay; 12980 } 12981 closeText = closeText.replace( "'", "'" ); 12982 overlayClose = "<button type='button' class='mfp-close " + closeClass + 12983 "' title='" + closeText + "'>×<span class='wb-inv'> " + 12984 closeText + "</span></button>"; 12985 12986 $elm.append( overlayClose ); 12987 elm.setAttribute( "aria-hidden", "true" ); 12988 12989 // Identify that initialization has completed 12990 initialized = true; 12991 wb.ready( $elm, componentName ); 12992 } 12993 }, 12994 12995 openOverlay = function( overlayId, noFocus ) {
12996 var $overlay = $( "#" + wb.jqEscape( overlayId ) ); 12997 12998 $overlay 12999 .addClass( "open" ) 13000 .attr( "role", "dialog" ) 13001 .attr( "aria-hidden", "false" ); 13002 13003 if ( $overlay.hasClass( "wb-popup-full" ) || $overlay.hasClass( "wb-popup-mid" ) ) { 13004 $overlay.attr( "data-pgtitle", document.getElementsByTagName( "H1" )[ 0 ].textContent ); 13005 $document.find( "body" ).addClass( OverlayOpenFlag ); 13006 } 13007 13008 if ( !noFocus ) { 13009 $overlay 13010 .scrollTop( 0 ) 13011 .trigger( setFocusEvent ); 13012 } 13013 13014 // Register the overlay if it wasn't previously registered 13015 // (only required when opening through an event) 13016 if ( !sourceLinks[ overlayId ] ) { 13017 setTimeout( function() { 13018 sourceLinks[ overlayId ] = null; 13019 }, 1 ); 13020 } 13021 13022 $overlay.trigger( "opened" + selector ); 13023 }, 13024 13025 closeOverlay = function( overlayId, noFocus, userClosed ) { 13026 var $overlay = $( "#" + overlayId ), 13027 sourceLink = sourceLinks[ overlayId ]; 13028 13029 $overlay 13030 .removeClass( "open" ) 13031 .removeAttr( "role" ) 13032 .attr( "aria-hidden", "true" ); 13033 13034 if ( $overlay.hasClass( "wb-popup-full" ) || $overlay.hasClass( "wb-popup-mid" ) ) { 13035 $document.find( "body" ).removeClass( OverlayOpenFlag ); 13036 } 13037 13038 if ( userClosed ) { 13039 $overlay.addClass( "user-closed" ); 13040 } 13041 13042 if ( !noFocus && sourceLink ) { 13043 13044 // Returns focus to the source link for the overlay 13045 $( sourceLink ).trigger( setFocusEvent ); 13046 } 13047 13048 // Delete the source link reference 13049 delete sourceLinks[ overlayId ]; 13050 13051 $overlay.trigger( "closed" + selector ); 13052 }; 13053 13054$document.on( "timerpoke.wb " + initEvent + " keydown open" + selector + 13055" close" + selector, selector, function( event ) { 13056 13057 var eventType = event.type, 13058 which = event.which, 13059 eventTarget = event.target, 13060 eventTurrentTarget = event.currentTarget, 13061 overlayId = eventTurrentTarget.id,
13062 overlay, $focusable, index, length; 13063 13064 switch ( eventType ) { 13065 case "timerpoke": 13066 case "wb-init": 13067 init( event ); 13068 break; 13069 13070 case "open": 13071 if ( eventTurrentTarget === eventTarget ) { 13072 openOverlay( overlayId, event.noFocus ); 13073 } 13074 break; 13075 13076 case "close": 13077 if ( eventTurrentTarget === eventTarget ) { 13078 closeOverlay( overlayId, event.noFocus ); 13079 } 13080 break; 13081 13082 default: 13083 overlay = document.getElementById( overlayId ); 13084 13085 switch ( which ) { 13086 13087 // Tab key 13088 case 9: 13089 13090 // No special tab handling when ignoring outside activity 13091 if ( overlay.className.indexOf( ignoreOutsideClass ) === -1 ) { 13092 $focusable = $( overlay ).find( ":focusable:not([tabindex='-1'])" ); 13093 length = $focusable.length; 13094 index = $focusable.index( event.target ) + ( event.shiftKey ? -1 : 1 ); 13095 13096 if ( index === -1 || index === length ) { 13097 event.preventDefault(); 13098 $focusable.eq( index === -1 ? length - 1 : 0 ) 13099 .trigger( setFocusEvent ); 13100 } 13101 } 13102 break; 13103 13104 // Escape key 13105 case 27: 13106 if ( !event.isDefaultPrevented() ) { 13107 closeOverlay( overlayId, false, true ); 13108 } 13109 break; 13110 } 13111 } 13112} ); 13113 13114// Handler for clicking on the close button of the overlay 13115$document.on( "click", "." + closeClass, function( event ) { 13116 var which = event.which; 13117 13118 // Ignore if not initialized and middle/right mouse buttons 13119 if ( initialized && ( !which || which === 1 ) ) { 13120 closeOverlay( 13121 $( event.currentTarget ).closest( selector ).attr( "id" ), 13122 false, 13123 true 13124 ); 13125 } 13126} ); 13127 13128// Handler for clicking on a source link for the overlay 13129$document.on( "click keydown", "." + linkClass, function( event ) { 13130 var which = event.which, 13131 sourceLink = event.currentTarget, 13132 overlayId = sourceLink.hash.substring( 1 ); 13133 13134 // Ignore if not initialized and middle/right mouse buttons 13135 if ( initialized && ( !which || which === 1 || which === 32 ) ) { 13136 event.preventDefault(); 13137 13138 // Introduce a delay to prevent outside activity detection 13139 setTimeout( function() { 13140 13141 // Stores the source link for the overlay 13142 sourceLinks[ overlayId ] = sourceLink; 13143 13144 // Opens the overlay 13145 openOverlay( overlayId ); 13146 }, 1 ); 13147 } 13148} ); 13149 13150// Handler for clicking on a same page link within the overlay to outside the overlay 13151$document.on( "click", selector + " a[href^='#']", function( event ) { 13152 var which = event.which, 13153 eventTarget = event.target, 13154 href, overlay, linkTarget; 13155 13156 // Ignore if not initialized and middle/right mouse buttons 13157 if ( initialized && ( !which || which === 1 ) ) { 13158 overlay = $( eventTarget ).closest( selector )[ 0 ]; 13159 href = eventTarget.getAttribute( "href" ); 13160 linkTarget = document.getElementById( href.substring( 1 ) ); 13161 13162 // Ignore same page links to within the overlay 13163 if ( href.length > 1 && !$.contains( overlay, linkTarget ) ) { 13164 13165 // Stop propagation of the click event 13166 if ( event.stopPropagation ) { 13167 event.stopImmediatePropagation(); 13168 } else { 13169 event.cancelBubble = true; 13170 } 13171 13172 // Close the overlay and set focus to the same page link 13173 closeOverlay( overlay.id, true ); 13174 $( linkTarget ).trigger( setFocusEvent ); 13175 } 13176 } 13177} ); 13178 13179// Outside activity detection 13180$document.on( "click focusin", "body", function( event ) { 13181 var eventTarget = event.target, 13182 which = event.which, 13183 overlayId, overlay; 13184 13185 // Ignore if not initialized and middle/right mouse buttons 13186 if ( initialized && ( !which || which === 1 ) ) { 13187 13188 // Close any overlays with outside activity 13189 for ( overlayId in sourceLinks ) { 13190 overlay = document.getElementById( overlayId ); 13191 if ( overlay && overlay.getAttribute( "aria-hidden" ) === "false" && 13192 eventTarget.id !== overlayId && 13193 overlay.className.indexOf( ignoreOutsideClass ) === -1 && 13194 !$.contains( overlay, eventTarget ) ) { 13195 13196 // Close the overlay 13197 closeOverlay( overlayId ); 13198 } 13199 } 13200 } 13201} ); 13202 13203// Ensure any element in focus outside an overlay is visible 13204$document.on( "keyup", function( ) { 13205 var elmInFocus, elmInFocusRect, focusAreaBelow, focusAreaAbove, 13206 overlayId, overlay, overlayRect; 13207 13208 // Ignore if not initialized 13209 if ( initialized ) { 13210 elmInFocus = document.activeElement; 13211 elmInFocusRect = elmInFocus.getBoundingClientRect(); 13212 focusAreaBelow = 0; 13213 focusAreaAbove = window.innerHeight; 13214 13215 // Ensure that at least one overlay is visible, and that the element in focus is not an overlay, 13216 // a child of an overlay, or the body element 13217 if ( $.isEmptyObject( sourceLinks ) || elmInFocus.className.indexOf( componentName ) !== -1 || 13218 $( elmInFocus ).parents( selector ).length !== 0 || elmInFocus === document.body ) { 13219 return; 13220 } 13221 13222 // Determine the vertical portion of the viewport that is not obscured by an overlay 13223 for ( overlayId in sourceLinks ) {
13224 overlay = document.getElementById( overlayId ); 13225 if ( overlay && overlay.getAttribute( "aria-hidden" ) === "false" ) { 13226 overlayRect = overlay.getBoundingClientRect(); 13227 if ( overlay.className.indexOf( "wb-bar-t" ) !== -1 ) { 13228 focusAreaBelow = Math.max( overlayRect.bottom, focusAreaBelow ); 13229 } else if ( overlay.className.indexOf( "wb-bar-b" ) !== -1 ) { 13230 focusAreaAbove = Math.min( overlayRect.top, focusAreaAbove ); 13231 } 13232 } 13233 } 13234 13235 // Ensure the element in focus is visible 13236 // TODO: Find a solution for when there isn't enough page to scoll up or down 13237 if ( elmInFocusRect.top < focusAreaBelow ) { 13238 13239 // Scroll down till the top of the element is visible 13240 window.scrollBy( 0, focusAreaBelow - elmInFocusRect.top ); 13241 } else if ( elmInFocusRect.bottom > focusAreaAbove ) { 13242 13243 // Scroll up till the bottom of the element is visible 13244 window.scrollBy( 0, elmInFocusRect.bottom - focusAreaAbove ); 13245 } 13246 } 13247} ); 13248 13249// Add the timer poke to initialize the plugin 13250wb.add( selector ); 13251 13252} )( jQuery, window, document, wb ); 13253 13254/** 13255 * @title WET-BOEW Tag filter 13256 * @overview Filter based content tagging 13257 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 13258 * @author @duboisp 13259 */ 13260( function( $, window, document, wb ) { 13261"use strict"; 13262 13263let i18n, i18nText; 13264 13265const componentName = "wb-paginate", 13266 selector = ".provisional." + componentName, 13267 initEvent = "wb-init" + selector, 13268 $document = wb.doc, 13269 filterClass = "wb-fltr-out", 13270 pgFilterOutClass = "wb-pgfltr-out", 13271 tagFilterClass = "wb-tgfltr-out", 13272 pagerClass = "wb-paginate-pager", 13273 pageData = "data-pagination-idx", 13274 notFilterClassSel = ":not(." + filterClass + "):not(." + tagFilterClass + ")", 13275 defaults = { 13276 lst: { 13277 selector: "li" 13278 }, 13279 grp: { 13280 selector: "> *" 13281 }, 13282 tbl: { 13283 selector: "tr", 13284 section: ":scope > tbody" 13285 }, 13286 itemsPerPage: 10 13287 }, 13288 13289 init = function( event ) { 13290 const elm = wb.init( event, componentName, selector ); 13291 13292 if ( elm ) { 13293 var $elm = $( elm ), 13294 paginationElm, 13295 elmTagName = elm.nodeName, 13296 setDefault, 13297 uiTargetElm; 13298 13299 if ( !i18nText ) { 13300 i18n = wb.i18n; 13301 i18nText = { 13302 prv: i18n( "prv" ), 13303 nxt: i18n( "nxt" ) 13304 }; 13305 } 13306 13307 // Setup configurations 13308 switch ( elmTagName ) { 13309 case "UL": 13310 setDefault = defaults.lst; 13311 break; 13312 case "TABLE": 13313 setDefault = defaults.tbl; 13314 break; 13315 default: 13316 setDefault = defaults.grp; 13317 break; 13318 } 13319 13320 elm.id = elm.id || wb.getId(); 13321 elm.pgSettings = $.extend( true, {}, setDefault, window[ componentName ], wb.getData( $elm, componentName ) ); 13322 elm.pgSettings.currPage = 1; 13323 elm.pgSettings.itemsPerPage = elm.pgSettings.itemsPerPage || defaults.itemsPerPage; 13324 elm.pgSettings.items = elm.querySelectorAll( ( elm.pgSettings.section || ":scope" ) + " " + elm.pgSettings.selector + notFilterClassSel ); 13325 13326 // Setup pagination container 13327 paginationElm = document.createElement( "div" ); 13328 paginationElm.id = componentName + "-" + elm.id; 13329 paginationElm.classList.add( pagerClass ); 13330 13331 // Add pagination container 13332 if ( elm.pgSettings.uiTarget ) { 13333 uiTargetElm = document.querySelector( elm.pgSettings.uiTarget ); 13334 uiTargetElm.appendChild( paginationElm ); 13335 } else if ( elm.pgSettings.section ) { 13336 if ( elmTagName === "UL" || elmTagName === "TABLE" ) { 13337 elm.after( paginationElm ); 13338 } else { 13339 elm.querySelector( elm.pgSettings.section ).after( paginationElm ); 13340 } 13341 } else { 13342 elm.after( paginationElm ); 13343 } 13344 13345 // Show/hide items and generate pagination 13346 updateItems( elm ); 13347 generateUI( elm ); 13348 13349 wb.ready( $( elm ), componentName ); 13350 } 13351 }, 13352 13353 // Set or reset pagination UI 13354 generateUI = function( elm ) { 13355 var paginationUI = "", 13356 currPage = elm.pgSettings.currPage, 13357 pagesCount = elm.pgSettings.pagesCount, 13358 paginationElm = document.querySelector( "#" + componentName + "-" + elm.id ), 13359 i = 1; 13360 13361 // Make sure the defined current page is not bigger than the total pages 13362 if ( currPage > pagesCount ) { 13363 currPage = pagesCount; 13364 } 13365 13366 // Only add pagination if there is more than one page 13367 if ( pagesCount > 1 ) { 13368 paginationUI = "<ol class=\"pagination\">"; 13369 13370 // Add Previous page button 13371 paginationUI += "<li" + ( i === currPage ? " class=\"disabled\"" : "" ) + "><a class=\"paginate-prev\" aria-controls=\"" + elm.id + "\" href=\"#" + elm.id + "\">" + i18nText.prv + "</a></li>"; 13372 13373 // Add pages buttons 13374 for ( i; i <= pagesCount; i++ ) { 13375 paginationUI += "<li class=\"" + returnItemClass( currPage, pagesCount, i ) + "\"" + ( i === currPage ? " aria-current=\"page\"" : "" ) + "><a href=\"#" + elm.id + "\" " + pageData + "=\"" + i + "\" aria-controls=\"" + elm.id + "\"><span class=\"wb-inv\">Page </span>" + i + "</a></li>"; 13376 } 13377 13378 // Add Next page button 13379 paginationUI += "<li" + ( i === currPage ? " class=\"disabled\"" : "" ) + "><a class=\"paginate-next\" aria-controls=\"" + elm.id + "\" href=\"#" + elm.id + "\">" + i18nText.nxt + "</a></li>"; 13380 13381 paginationUI += "</ol>"; 13382 } 13383 13384 // Insert HTML 13385 paginationElm.innerHTML = paginationUI; 13386 }, 13387 13388 // Show/hide items to reflect current page 13389 updateItems = function( elm ) { 13390 let currPage = elm.pgSettings.currPage, 13391 items = elm.pgSettings.items, 13392 itemsPerPage = elm.pgSettings.itemsPerPage; 13393
13394 items.forEach( function( item, index ) { 13395 if ( ( index < ( itemsPerPage * currPage ) ) && ( index >= ( itemsPerPage * currPage ) - itemsPerPage ) ) { 13396 item.classList.remove( pgFilterOutClass ); 13397 } else { 13398 item.classList.add( pgFilterOutClass ); 13399 } 13400 } ); 13401 13402 elm.pgSettings.pagesCount = Math.ceil( items.length / itemsPerPage ); 13403 }, 13404 13405 // Update pagination to reflect current page 13406 goToPage = function( elm ) { 13407 let paginationElm = document.querySelector( "#" + componentName + "-" + elm.id ), 13408 pageItems = paginationElm.querySelectorAll( "li" ), 13409 itemClass, 13410 pageLink, 13411 currPage = elm.pgSettings.currPage, 13412 pagesCount = elm.pgSettings.pagesCount; 13413 13414 pageItems.forEach( function( pageItem, i ) { 13415 pageLink = pageItem.querySelector( "a" ); 13416 13417 if ( pageLink.classList.contains( "paginate-prev" ) ) { 13418 if ( currPage > 1 ) { 13419 pageItem.classList.remove( "disabled" ); 13420 } else { 13421 pageItem.classList.add( "disabled" ); 13422 } 13423 } else if ( pageLink.classList.contains( "paginate-next" ) ) { 13424 if ( currPage < pagesCount ) { 13425 pageItem.classList.remove( "disabled" ); 13426 } else { 13427 pageItem.classList.add( "disabled" ); 13428 } 13429 } else { 13430 pageItem.className = ""; 13431 pageItem.removeAttribute( "aria-current" ); 13432 13433 itemClass = returnItemClass( currPage, pagesCount, i ); 13434 13435 if ( i === currPage ) { 13436 pageItem.setAttribute( "aria-current", "page" ); 13437 } 13438 13439 pageItem.className = itemClass; 13440 } 13441 } ); 13442 }, 13443 13444 // Return the list item classname 13445 returnItemClass = function( currPage, pagesCount, i ) { 13446 let itemClass = ""; 13447 13448 if ( currPage > 1 && currPage < pagesCount ) { 13449 if ( Math.abs( currPage - i ) > 1 ) { 13450 itemClass += "hidden-xs hidden-sm"; 13451 13452 if ( Math.abs( currPage - i ) > 2 ) { 13453 itemClass += " hidden-md"; 13454 } 13455 } 13456 } else { 13457 if ( Math.abs( currPage - i ) > 2 ) { 13458 itemClass += "hidden-xs hidden-sm"; 13459 13460 if ( Math.abs( currPage - i ) > 4 ) { 13461 itemClass += " hidden-md"; 13462 } 13463 } 13464 } 13465 13466 if ( pagesCount > 10 ) { 13467 if ( currPage <= 5 ) { 13468 if ( i > 10 ) { 13469 itemClass += " hidden"; 13470 } 13471 } else if ( ( currPage > 5 ) && ( currPage < pagesCount - 5 ) ) { 13472 if ( ( i < currPage - 4 ) || ( i > currPage + 5 ) ) { 13473 itemClass += " hidden"; 13474 } 13475 } else { 13476 if ( i <= pagesCount - 10 ) { 13477 itemClass += " hidden"; 13478 } 13479 } 13480 } 13481 13482 if ( i === currPage ) { 13483 itemClass += " active"; 13484 } 13485 13486 return itemClass; 13487 }; 13488 13489// When a filter is updated 13490$document.on( "click", "." + pagerClass + " a", function() { 13491 let elm = document.querySelector( "#" + this.getAttribute( "aria-controls" ) ), 13492 pageDest = ( ( this.getAttribute( pageData ) ) * 1 ) || elm.pgSettings.currPage; 13493 13494 if ( this.classList.contains( "paginate-next" ) ) { 13495 pageDest++; 13496 } else if ( this.classList.contains( "paginate-prev" ) ) { 13497 pageDest--; 13498 } 13499 13500 if ( pageDest !== elm.pgSettings.currPage ) { 13501 elm.pgSettings.currPage = pageDest; 13502 13503 updateItems( elm ); 13504 goToPage( elm ); 13505 } 13506} ); 13507 13508// Resets items and pagination 13509$document.on( "wb-contentupdated", selector, function() { 13510 this.pgSettings.currPage = 1; 13511 this.pgSettings.items = this.pgSettings.items = this.querySelectorAll( ( this.pgSettings.section || ":scope" ) + " " + this.pgSettings.selector + notFilterClassSel ); 13512 13513 updateItems( this ); 13514 generateUI( this ); 13515} ); 13516 13517$document.on( "timerpoke.wb " + initEvent, selector, init ); 13518
13519wb.add( selector ); 13520 13521} )( jQuery, window, document, wb ); 13522 13523/** 13524 * @title WET-BOEW Prettify Plugin 13525 * @overview Wrapper for Google Code Prettify library: https://github.com/google/code-prettify 13526 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 13527 * @author @patheard 13528 */ 13529/* 13530 * Syntax highlighting of source code snippets in an html page using [google-code-prettify](https://github.com/google/code-prettify). 13531 * 13532 * 1. Apply `class="prettyprint"` to a `pre` or `code` element to apply syntax highlighting. Alternatively use `class="all-pre"` to apply syntax highlighting to all `pre` elements on the page. 13533 * 2. Apply `class="linenums"` to a `pre` or `code` element to add line numbers. Alternatively use `class="all-linenums"` to all applicable `pre` elements. Specify the starting number by adding `linenums:#` before `linenums`. 13534 * 3. Add extra language support by applying `class="lang-*"` to each applicable `pre` or `code` element. Supported language syntax CSS classes are as follows: 13535 * - lang-apollo 13536 * - lang-clj 13537 * - lang-css 13538 * - lang-dart 13539 * - lang-go 13540 * - lang-hs 13541 * - lang-lisp 13542 * - lang-lua 13543 * - lang-ml 13544 * - lang-n 13545 * - lang-proto 13546 * - lang-scala 13547 * - lang-sql 13548 * - lang-tex 13549 * - lang-vb 13550 * - lang-vhdl 13551 * - lang-wiki 13552 * - lang-xq 13553 * - lang-yaml 13554 */ 13555( function( $, window, wb ) { 13556"use strict"; 13557 13558/* 13559 * Variable and function definitions. 13560 * These are global to the plugin - meaning that they will be initialized once per page, 13561 * not once per instance of plugin on the page. So, this is a good place to define 13562 * variables that are common to all instances of the plugin on a page. 13563 */ 13564var componentName = "wb-prettify", 13565 selector = "." + componentName, 13566 initEvent = "wb-init" + selector, 13567 prettyPrintEvent = "prettyprint" + selector, 13568 $document = wb.doc, 13569 13570 /* 13571 * Plugin users can override these defaults by setting attributes on the html elements that the 13572 * selector matches. 13573 */ 13574 defaults = { 13575 linenums: false, 13576 allpre: false 13577 }, 13578 13579 /** 13580 * @method init 13581 * @param {jQuery Event} event Event that triggered the function call 13582 */ 13583 init = function( event ) { 13584 13585 // Start initialization 13586 // returns DOM object = proceed with init 13587 // returns undefined = do not proceed with init (e.g., already initialized) 13588 var elm = wb.init( event, componentName, selector ), 13589 modeJS = wb.getMode() + ".js", 13590 deps = [ "site!deps/prettify" + modeJS ], 13591 $elm, classes, settings, i, len, $pre; 13592 13593 if ( elm ) { 13594 $elm = $( elm ); 13595 classes = elm.className.split( " " ); 13596 13597 // Merge default settings with overrides from the selected plugin element. There may be more than one, so don't override defaults globally! 13598 settings = $.extend( {}, defaults, $elm.data() ); 13599 13600 // Check the element for `lang-*` syntax CSS classes 13601 for ( i = 0, len = classes.length; i !== len; i += 1 ) { 13602 if ( classes[ i ].indexOf( "lang-" ) === 0 ) { 13603 deps.push( "site!deps/" + classes[ i ] + modeJS ); 13604 } 13605 } 13606
13607 // CSS class overides of settings 13608 settings.allpre = settings.allpre || $elm.hasClass( "all-pre" ); 13609 settings.linenums = settings.linenums || $elm.hasClass( "linenums" ); 13610 13611 // Apply global settings 13612 if ( settings.allpre || settings.linenums ) { 13613 $pre = $document.find( "pre" ); 13614 if ( settings.allpre ) { 13615 $pre.addClass( "prettyprint" ); 13616 } 13617 if ( settings.linenums ) { 13618 $pre.filter( ".prettyprint" ).addClass( "linenums" ); 13619 } 13620 } 13621 13622 // Load the required dependencies and prettify the code once finished 13623 Modernizr.load( { 13624 load: deps, 13625 complete: function() { 13626 $document.trigger( prettyPrintEvent ); 13627 } 13628 } ); 13629 } 13630 }, 13631 13632 prettifyDone = function() { 13633 13634 // Identify that initialization has completed 13635 wb.ready( $document, componentName ); 13636 }, 13637 13638 /* 13639 * Invoke the Google pretty print library if it has been initialized 13640 * @method prettyprint 13641 */ 13642 prettyprint = function( event ) { 13643 if ( event.namespace === componentName && 13644 typeof window.prettyPrint === "function" ) { 13645 13646 window.prettyPrint( prettifyDone ); 13647 } 13648 }; 13649 13650// Bind the plugin events 13651$document 13652 .on( "timerpoke.wb " + initEvent, selector, init ) 13653 .on( prettyPrintEvent, prettyprint ); 13654 13655// Add the timer poke to initialize the plugin 13656wb.add( selector ); 13657 13658} )( jQuery, window, wb ); 13659 13660/** 13661 * @title WET-BOEW Resize 13662 * @overview Text and window resizing event handler 13663 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 13664 * @author @pjackson28 13665 */ 13666( function( $, window, document, wb ) { 13667"use strict"; 13668 13669/* 13670 * Variable and function definitions. 13671 * These are global to the plugin - meaning that they will be initialized once per page, 13672 * not once per instance of plugin on the page. So, this is a good place to define 13673 * variables that are common to all instances of the plugin on a page. 13674 */ 13675var componentName = "wb-rsz", 13676 selector = "#" + componentName, 13677 initEvent = "wb-init" + selector, 13678 $document = wb.doc, 13679 sizes = [], 13680 events = [ 13681 "txt-rsz.wb", 13682 "win-rsz-width.wb", 13683 "win-rsz-height.wb" 13684 ], 13685 13686 // Breakpoint names and lower pixel limits 13687 breakpoints = { 13688 xxsmallview: 0, 13689 xsmallview: 480, 13690 smallview: 768, 13691 mediumview: 992, 13692 largeview: 1200, 13693 xlargeview: 1600 13694 }, 13695 resizeTest, currentView, 13696 13697 /** 13698 * @method init 13699 * @param {jQuery Event} event Event that triggered the function call 13700 */ 13701 init = function( event ) { 13702 13703 // Start initialization 13704 // returns DOM object = proceed with init 13705 // returns undefined = do not proceed with init (e.g., already initialized) 13706 var elm = wb.init( event, componentName, selector ), 13707 localResizeTest; 13708 13709 if ( elm ) { 13710 13711 // Set up the DOM element used for resize testing 13712 localResizeTest = document.createElement( "span" ); 13713 localResizeTest.innerHTML = " "; 13714 localResizeTest.setAttribute( "id", componentName ); 13715 document.body.appendChild( localResizeTest ); 13716 resizeTest = localResizeTest; 13717 13718 // Get a snapshot of the current sizes 13719 sizes = [ 13720 localResizeTest.offsetHeight, 13721 window.innerWidth || $document.width(), 13722 window.innerHeight || $document.height() 13723 ]; 13724 13725 // Determine the current view 13726 viewChange( sizes[ 1 ] ); 13727 13728 // Identify that initialization has completed 13729 wb.ready( $document, componentName ); 13730 } 13731 }, 13732 13733 viewChange = function( viewportWidth ) { 13734 var breakpoint, viewName; 13735 13736 // Check for a change between views 13737 for ( breakpoint in breakpoints ) { 13738 13739 // Determine the current view 13740 if ( viewportWidth < breakpoints[ breakpoint ] ) { 13741 break; 13742 } else { 13743 viewName = breakpoint; 13744 } 13745 } 13746 13747 // Determine if the current view is different than the previous view 13748 if ( viewName !== currentView ) { 13749 13750 // Change the breakpoint class on the html element 13751 wb.html 13752 .removeClass( currentView || "" ) 13753 .addClass( viewName ); 13754 13755 // Update the current view 13756 currentView = viewName; 13757 13758 // Trigger the view event 13759 $document.trigger( viewName + ".wb" ); 13760 } 13761 }, 13762 13763 /** 13764 * Tests for text size, window width and window height changes and triggers an event when a change is found 13765 * @method test 13766 */ 13767 test = function() { 13768 var currentSizes = [ 13769 resizeTest.offsetHeight, 13770 window.innerWidth || $document.width(), 13771 window.innerHeight || $document.height() 13772 ], 13773 len = currentSizes.length, 13774 i; 13775 13776 // Check for a viewport or text size change 13777 for ( i = 0; i !== len; i += 1 ) { 13778 if ( currentSizes[ i ] !== sizes[ i ] ) { 13779 13780 // Change detected so trigger related event 13781 $document.trigger( events[ i ], currentSizes ); 13782 13783 // Check for a view change 13784 viewChange( currentSizes[ 1 ] ); 13785 } 13786 } 13787 sizes = currentSizes; 13788 13789 return; 13790 }; 13791 13792// Bind the init event to the plugin 13793$document.on( initEvent, init ); 13794 13795// Re-test on each timerpoke 13796$document.on( "timerpoke.wb", selector, test ); 13797 13798// Initialize the resources 13799$document.trigger( initEvent ); 13800 13801// Add the timer poke to initialize the plugin
13802wb.add( selector ); 13803 13804} )( jQuery, window, document, wb ); 13805 13806/** 13807 * @title WET-BOEW Session Timeout 13808 * @overview Helps Web asset owners to provide session timeout and inactivity timeout functionality. 13809 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 13810 * @author @patheard 13811 */ 13812( function( $, DOMPurify, window, document, wb ) { 13813"use strict"; 13814 13815/* 13816 * Variable and function definitions. 13817 * These are global to the plugin - meaning that they will be initialized once per page, 13818 * not once per instance of plugin on the page. So, this is a good place to define 13819 * variables that are common to all instances of the plugin on a page. 13820 */ 13821var $modal, $modalLink, countdownInterval, i18n, i18nText, 13822 $document = wb.doc, 13823 componentName = "wb-sessto", 13824 selector = "." + componentName, 13825 confirmClass = componentName + "-confirm", 13826 initEvent = "wb-init" + selector, 13827 resetEvent = "reset" + selector, 13828 keepaliveEvent = "keepalive" + selector, 13829 inactivityEvent = "inactivity" + selector, 13830 dataAttr = componentName, 13831 13832 /* 13833 * Plugin users can override these defaults by setting attributes on the html elements that the 13834 * selector matches. 13835 * For example, adding the attribute data-option1="false", will override option1 for that plugin instance. 13836 */ 13837 defaults = { 13838 inactivity: 1200000, /* default inactivity period 20 minutes */ 13839 reactionTime: 180000, /* default confirmation period of 3 minutes */ 13840 sessionalive: 1200000, /* default keepalive period of 20 minutes */ 13841 refreshCallbackUrl: null, /* refresh callback if using AJAX keepalive (no default) */ 13842 logouturl: "./", /* logout URL once the session has expired */ 13843 refreshOnClick: true, /* refresh session if user clicks on the page */ 13844 refreshLimit: 120000, /* default period of 2 minutes (ajax calls happen only once during this period) */ 13845 method: "POST", /* the request method to use */ 13846 additionalData: null, /* additional data to send with the request */ 13847 refreshCallback: function( response ) { /* callback function used to check the server response */ 13848 return response.replace( /\s/g, "" ) === "true"; 13849 } 13850 }, 13851 13852 /** 13853 * @function init 13854 * @param {jQuery Event} event Event that triggered the function call 13855 */ 13856 init = function( event ) { 13857 13858 // Start initialization 13859 // returns DOM object = proceed with init 13860 // returns undefined = do not proceed with init (e.g., already initialized) 13861 var elm = wb.init( event, componentName, selector ), 13862 $elm, settings, onReady; 13863 13864 if ( elm ) { 13865 $elm = $( elm ); 13866 13867 // For backwards compatibility where data-wet-boew was used instead of data-wb-sessto 13868 if ( !$elm.attr( "data-" + componentName ) ) { 13869 dataAttr = "wet-boew"; 13870 } 13871 13872 // Merge default settings with overrides from the plugin element 13873 // and save back to the element for future reference 13874 settings = $.extend( {}, defaults, window[ componentName ], $elm.data( dataAttr ) ); 13875 $elm.data( dataAttr, settings ); 13876 13877 // Only initialize the i18nText once 13878 if ( !i18nText ) { 13879 i18n = wb.i18n; 13880 i18nText = { 13881 buttonContinue: i18n( "st-btn-cont" ), 13882 buttonEnd: i18n( "st-btn-end" ), 13883 buttonSignin: i18n( "tmpl-signin" ), 13884 timeoutBegin: i18n( "st-to-msg-bgn" ), 13885 timeoutEnd: i18n( "st-to-msg-end" ), 13886 timeoutTitle: i18n( "st-msgbx-ttl" ), 13887 timeoutAlready: i18n( "st-alrdy-to-msg" ) 13888 }; 13889 } 13890 13891 onReady = function() { 13892 13893 // Setup the refresh on click behaviour 13894 initRefreshOnClick( $elm, settings ); 13895 13896 // Initialize the keepalive and inactive timeouts of the plugin 13897 $elm.trigger( resetEvent, settings ); 13898 13899 // Identify that initialization has completed 13900 wb.ready( $elm, componentName ); 13901 }; 13902 13903 // Create the modal dialog 13904 initModalDialog( onReady ); 13905 } 13906 }, 13907 13908 /** 13909 * Initializes a timeout that triggers an event 13910 * @function initEventTimeout 13911 * @param {jQuery DOM Element} $elm Element to trigger the event on 13912 * @param {string} eventName Name of the event to trigger on setTimeout 13913 * @param {mixed} time Time to wait before triggering the event 13914 * @param {Object} settings Key-value object 13915 */ 13916 initEventTimeout = function( $elm, eventName, time, settings ) { 13917 var duration = parseTime( time ); 13918 13919 // Clear any existing timeout for the event 13920 clearTimeout( $elm.data( eventName ) ); 13921 13922 // Create the new timeout that will trigger the event 13923 $elm.data( eventName, setTimeout( function() { 13924 $elm.trigger( eventName, settings ); 13925 }, duration ) ); 13926 }, 13927 13928 /** 13929 * Creates the modal dialog element, appends to the <body> and initializes the lightbox plugin 13930 * that is used to create the dialog behaviour. 13931 * @function initModalDialog 13932 */ 13933 initModalDialog = function( callback ) { 13934 var modalID = "#" + componentName + "-modal", 13935 child, modal, temp; 13936 13937 if ( $document.find( modalID ).length === 0 ) { 13938 modal = document.createDocumentFragment(); 13939 temp = document.createElement( "div" ); 13940 13941 // Create the modal dialog. A temp <div> element is used so that its innerHTML can be set as a string. 13942 temp.innerHTML = "<a class='wb-lbx lbx-modal mfp-hide' href='#" + componentName + "-modal'>" + i18nText.timeoutTitle + "</a>" + 13943 "<section id='" + componentName + "-modal' class='mfp-hide modal-dialog modal-content overlay-def'>" + 13944 "<header class='modal-header'><h2 class='modal-title'>" + i18nText.timeoutTitle + "</h2></header>" + 13945 "<div class='modal-body'></div>" + 13946 "<div class='modal-footer'></div>" + 13947 "</section>"; 13948 13949 // Get the temporary <div>'s top level children and append to the fragment 13950 while ( ( child = temp.firstChild ) !== null ) { 13951 modal.appendChild( child ); 13952 } 13953 document.body.appendChild( modal ); 13954 13955 $modal = $document.find( modalID ); 13956 13957 // Get object references to the modal and its triggering link 13958 $modalLink = $modal.prev() 13959 .one( "wb-ready.wb-lbx", callback ) 13960 .trigger( "wb-init.wb-lbx" ); 13961 } else { 13962 callback(); 13963 } 13964 }, 13965 13966 /** 13967 * Initialize the refresh on click keepalive behaviour. This will cause a `keepalive.wb-sessto` 13968 * event to be triggered when the document is clicked, limited by the settings.refreshLimit value. 13969 * @function initRefreshOnClick 13970 * @param {jQuery DOM Element} $elm DOM element to trigger the event on 13971 * @param {Object} settings Key-value object that will be passed when event is triggered. 13972 */ 13973 initRefreshOnClick = function( $elm, settings ) { 13974 if ( settings.refreshOnClick ) { 13975 $document.on( "click", function( event ) { 13976 var className = event.target.className, 13977 lastActivity, currentTime; 13978 13979 // Ignore clicks when the modal dialog is open 13980 if ( ( !className || className.indexOf( confirmClass ) === -1 ) && 13981 $( ".mfp-ready ." + confirmClass ).length === 0 ) { 13982 13983 lastActivity = $elm.data( "lastActivity" ); 13984 currentTime = getCurrentTime(); 13985 if ( !lastActivity || ( currentTime - lastActivity ) > settings.refreshLimit ) { 13986 $elm 13987 .trigger( resetEvent, settings ) 13988 .trigger( keepaliveEvent, settings ); 13989 $elm.data( "lastActivity", currentTime ); 13990 } 13991 } 13992 } ); 13993 } 13994 }, 13995 13996 /** 13997 * Keepalive session event handler. Sends the POST request to determine if the session is still alive. 13998 * @function keepalive 13999 * @param {jQuery Event} event `keepalive.wb-sessto` event that triggered the function call 14000 * @param {Object} settings Key-value object 14001 */ 14002 keepalive = function( event, settings ) { 14003 var $elm = $( event.target ); 14004 if ( settings.refreshCallbackUrl !== null ) { 14005 $.ajax( { 14006 url: settings.refreshCallbackUrl, 14007 data: settings.additionalData, 14008 dataType: "text", 14009 method: settings.method, 14010 success: function( response ) { 14011 14012 // Sanitize the response 14013 response = DOMPurify.sanitize( response ); 14014 14015 // Session is valid 14016 if ( response && settings.refreshCallback( response ) ) { 14017 $elm.trigger( resetEvent, settings ); 14018 14019 // Session has timed out - let the user know they need to sign in again 14020 } else { 14021 14022 // End the inactivity timeouts since the session is already kaput 14023 clearTimeout( $elm.data( inactivityEvent ) ); 14024 clearTimeout( $elm.data( keepaliveEvent ) ); 14025 14026 openModal( { 14027 body: "<p>" + i18nText.timeoutAlready + "</p>", 14028 buttons: $( "<button type='button' class='" + confirmClass + 14029 " btn btn-primary popup-modal-dismiss'>" + i18nText.buttonSignin + "</button>" ) 14030 .data( "logouturl", settings.logouturl ) 14031 } ); 14032 } 14033 } 14034 } ); 14035 } 14036 }, 14037 14038 /** 14039 * Inactivity check event handler. Displays the modal dialog to allow the user to confirm their activity. 14040 * @function inactivity 14041 * @param {jQuery Event} event `inactivity.wb-sessto` event that triggered the function call 14042 * @param {Object} settings Key-value object 14043 */ 14044 inactivity = function( event, settings ) { 14045 var $buttonContinue, $buttonEnd, 14046 time = getTime( settings.reactionTime ), 14047 startTime = getCurrentTime(), 14048 timeoutBegin = i18nText.timeoutBegin 14049 .replace( "#min#", "<span class='min'>" + time.minutes + "</span>" ) 14050 .replace( "#sec#", "<span class='sec'>" + time.seconds + "</span>" ), 14051 buttonStart = "<button type='button' class='", 14052 buttonEnd = "</button>"; 14053 14054 // Clear the keepalive timeout to avoid double firing of requests 14055 clearInterval( $( event.target ).data( keepaliveEvent ) ); 14056 14057 $buttonContinue = $( buttonStart + confirmClass + 14058 " btn btn-primary popup-modal-dismiss'>" + i18nText.buttonContinue + buttonEnd ) 14059 .data( settings ) 14060 .data( "start", startTime ); 14061 $buttonEnd = $( buttonStart + confirmClass + " btn btn-default'>" + 14062 i18nText.buttonEnd + buttonEnd ) 14063 .data( "logouturl", settings.logouturl ); 14064
14065 openModal( { 14066 body: "<p>" + timeoutBegin + "<br />" + i18nText.timeoutEnd + "</p>", 14067 buttons: [ $buttonContinue, $buttonEnd ], 14068 open: function() { 14069 var $minutes = $modal.find( ".min" ), 14070 $seconds = $modal.find( ".sec" ), 14071 endDuration = settings.reactionTime; 14072 14073 countdownInterval = setInterval( function() { 14074 if ( countdown( $minutes, $seconds, startTime, endDuration ) ) { 14075 clearInterval( countdownInterval ); 14076 14077 // Let the user know their session has timed out 14078 $modal.find( "p" ).text( i18nText.timeoutAlready ); 14079 $buttonContinue.text( i18nText.buttonSignin ); 14080 $buttonEnd.hide(); 14081 } 14082 }, 500 ); 14083 } 14084 } ); 14085 }, 14086 14087 /** 14088 * Initialize the inactivity and keepalive timeouts of the plugin 14089 * @function reset 14090 * @param {jQuery Event} event `reset.wb-sessto` event that triggered the function call 14091 * @param {Object} settings Key-value object 14092 */ 14093 reset = function( event, settings ) { 14094 var $elm = $( event.target ); 14095 14096 initEventTimeout( $elm, inactivityEvent, settings.inactivity, settings ); 14097 if ( settings.refreshCallbackUrl !== null ) { 14098 initEventTimeout( $elm, keepaliveEvent, settings.sessionalive, settings ); 14099 } 14100 }, 14101 14102 /** 14103 * Checks if the user wants to keep their session alive. 14104 * @function inactivity 14105 * @param {jQuery Event} event `confirm.wb-sessto` event that triggered the function call 14106 */ 14107 confirm = function( event ) { 14108 var elm = event.target, 14109 $elm = $( elm ), 14110 settings = $elm.data(); 14111 14112 event.preventDefault(); 14113 $.magnificPopup.close(); 14114 clearInterval( countdownInterval ); 14115 14116 // User wants their session maintained 14117 if ( settings.start !== undefined && ( getCurrentTime() - settings.start ) <= settings.reactionTime ) { 14118 $( selector ) 14119 .trigger( resetEvent, settings ) 14120 .trigger( keepaliveEvent, settings ); 14121 14122 // Negative confirmation or the user took too long; logout 14123 } else { 14124 window.location.href = settings.logouturl; 14125 } 14126 }, 14127 14128 /** 14129 * Add the modal dialog's content and display it to the user 14130 * @function openModal 14131 * @param {Object} data Key-value object 14132 */ 14133 openModal = function( data ) { 14134 14135 // Detach the modal to prevent reflows while updating the element 14136 $modal = $modal.detach(); 14137 $modal.find( ".modal-body" ).html( data.body ); 14138 $modal.find( ".modal-footer" ).empty().append( data.buttons ); 14139 14140 // Re-attach the modal and open the dialog 14141 $modal = $modal.insertAfter( $modalLink ); 14142 $modalLink.magnificPopup( "open" ); 14143 14144 // Execute the open callback if it exists 14145 if ( data.open ) { 14146 data.open(); 14147 } 14148 }, 14149 14150 /** 14151 * Returns the current time in milliseconds 14152 * @function getCurrentTime 14153 * @returns {integer} Current time in milliseconds 14154 */ 14155 getCurrentTime = function() { 14156 return ( new Date() ).getTime(); 14157 }, 14158 14159 /** 14160 * Parses a time value into a milliseconds integer value. 14161 * @function parseTime 14162 * @param {Mixed} value The time value to parse (integer or string) 14163 * @returns {integer} Millisecond integer value parsed from the time value 14164 */ 14165 parseTime = function( value ) { 14166 var result, num, mult, 14167 powers = { 14168 ms: 1, 14169 cs: 10, 14170 ds: 100, 14171 s: 1000, 14172 das: 10000, 14173 hs: 100000, 14174 ks: 1000000 14175 }; 14176 14177 if ( value == null ) { //eslint-disable-line no-eq-null 14178 return null; 14179 } 14180 14181 result = /^([0-9]+(?:\.[0-9]*)?)\s*(.*s)?$/.exec( $.trim( value.toString() ) ); 14182 if ( result[ 2 ] ) { 14183 num = parseFloat( result[ 1 ] ); 14184 mult = powers[ result[ 2 ] ] || 1; 14185 return num * mult; 14186 } 14187 return value; 14188 }, 14189 14190 /** 14191 * Converts a millisecond value into minutes and seconds 14192 * @function getTime 14193 * @param {integer} milliseconds The time value in milliseconds 14194 * @returns {Object} An object with a seconds and minutes property 14195 */ 14196 getTime = function( milliseconds ) { 14197 var time = { minutes: "", seconds: "" }; 14198 14199 if ( milliseconds != null ) { //eslint-disable-line no-eq-null 14200 time.minutes = parseInt( ( milliseconds / ( 1000 * 60 ) ) % 60, 10 ); 14201 time.seconds = parseInt( ( milliseconds / 1000 ) % 60, 10 ); 14202 } 14203 return time; 14204 }, 14205 14206 /** 14207 * Given 2 elements representing minutes and seconds, decrement their time value by 1 second 14208 * @function countdown 14209 * @param {jQuery DOM Element} $minutes Element that contains the minute value 14210 * @param {jQuery DOM Element} $seconds Element that contains the second value 14211 * @param { integer } startTime The time value of when the countdown started in milliseconds 14212 * @param { integer } endDuration The time value of the duration of the countdown in milliseconds 14213 * @returns {boolean} Is the countdown finished? 14214 */ 14215 countdown = function( $minutes, $seconds, startTime, endDuration ) { 14216 var newTime = getTime( endDuration - ( getCurrentTime() - startTime ) ); 14217 14218 // Update the DOM elements 14219 $minutes.text( newTime.minutes ); 14220 $seconds.text( newTime.seconds ); 14221 14222 return newTime.minutes <= 0 && newTime.seconds <= 0; 14223 }; 14224 14225// Bind the plugin events 14226$document.on( "timerpoke.wb " + initEvent + " " + keepaliveEvent + " " + 14227inactivityEvent + " " + resetEvent, selector, function( event, settings ) { 14228 14229 var eventType = event.type; 14230 14231 switch ( eventType ) { 14232 case "timerpoke": 14233 case "wb-init": 14234 init( event ); 14235 break; 14236 14237 case "keepalive": 14238 keepalive( event, settings ); 14239 break; 14240 14241 case "inactivity": 14242 inactivity( event, settings ); 14243 break; 14244 14245 case "reset": 14246 reset( event, settings ); 14247 break; 14248 } 14249} ); 14250 14251$document.on( "click", "." + confirmClass, confirm ); 14252 14253// Add the timer poke to initialize the plugin
14254wb.add( selector ); 14255 14256} )( jQuery, DOMPurify, window, document, wb ); 14257 14258/** 14259 * @title WET-BOEW Share widget 14260 * @overview Facilitates sharing Web content on social media platforms. 14261 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 14262 * @author @pjackson28 14263 */ 14264( function( $, window, document, wb ) { 14265"use strict"; 14266 14267/* 14268 * Variable and function definitions. 14269 * These are global to the plugin - meaning that they will be initialized once per page, 14270 * not once per instance of plugin on the page. So, this is a good place to define 14271 * variables that are common to all instances of the plugin on a page. 14272 */ 14273var componentName = "wb-share", 14274 selector = "." + componentName, 14275 initEvent = "wb-init" + selector, 14276 shareLink = "shr-lnk", 14277 panelCount = 0, 14278 $document = wb.doc, 14279 i18n, i18nText, 14280 14281 /* 14282 * Plugin users can override these defaults by setting attributes on the html elements that the 14283 * selector matches. 14284 */ 14285 defaults = { 14286 hdLvl: "h2", 14287 14288 // Supported types are: "page", "video" and "audio" 14289 type: "page", 14290 14291 // For custom types 14292 // custType = " this comment" results in "Share this comment" 14293 custType: "", 14294 14295 url: wb.pageUrlParts.href, 14296 title: document.title || $document.find( "h1:first" ).text(), 14297 14298 pnlId: "", 14299 lnkClass: "", 14300 img: "", 14301 desc: "", 14302 14303 // For filtering the sites that area displayed and controlling the order 14304 // they are displayed. Empty array displays all sites in the default order. 14305 // Otherwise, it displays the sites in the order in the array using the 14306 // keys used by the sites object. 14307 filter: [], 14308 14309 sites: { 14310 14311 // The definitions of the available bookmarking sites, in URL use 14312 // '{u}' for the page URL, '{t}' for the page title, {i} for the image, and '{d}' for the description 14313 blogger: { 14314 name: "Blogger", 14315 url: "https://www.blogger.com/blog_this.pyra?t=&u={u}&n={t}" 14316 }, 14317 diigo: { 14318 name: "Diigo", 14319 url: "https://www.diigo.com/post?url={u}&title={t}" 14320 }, 14321 facebook: { 14322 name: "Facebook", 14323 url: "https://www.facebook.com/sharer.php?u={u}&t={t}" 14324 }, 14325 gmail: { 14326 name: "Gmail", 14327 url: "https://mail.google.com/mail/?view=cm&fs=1&tf=1&to=&su={t}&body={u}%0A{d}" 14328 }, 14329 linkedin: { 14330 name: "LinkedIn®", 14331 url: "https://www.linkedin.com/shareArticle?mini=true&url={u}&title={t}&ro=false&summary={d}&source=" 14332 }, 14333 myspace: { 14334 name: "MySpace", 14335 url: "https://www.myspace.com/Modules/PostTo/Pages/?u={u}&t={t}" 14336 }, 14337 pinterest: { 14338 name: "Pinterest", 14339 url: "https://www.pinterest.com/pin/create/button/?url={u}&media={i}&description={t}" 14340 }, 14341 reddit: { 14342 name: "reddit", 14343 url: "https://reddit.com/submit?url={u}&title={t}" 14344 }, 14345 tinyurl: { 14346 name: "TinyURL", 14347 url: "https://tinyurl.com/create.php?url={u}" 14348 }, 14349 tumblr: { 14350 name: "tumblr", 14351 url: "https://www.tumblr.com/share/link?url={u}&name={t}&description={d}" 14352 }, 14353 twitter: { 14354 name: "Twitter", 14355 url: "https://twitter.com/intent/tweet?text={t}&url={u}" 14356 }, 14357 yahoomail: { 14358 name: "Yahoo! Mail", 14359 url: "https://compose.mail.yahoo.com/?to=&subject={t}&body={u}%0A{d}" 14360 }, 14361 whatsapp: { 14362 name: "Whatsapp", 14363 url: "https://api.whatsapp.com/send?text={t}%0A{d}%0A{u}" 14364 } 14365 } 14366 }, 14367 14368 /** 14369 * @method init 14370 * @param {jQuery Event} event Event that triggered the function call 14371 */ 14372 init = function( event ) { 14373 14374 // Start initialization 14375 // returns DOM object = proceed with init 14376 // returns undefined = do not proceed with init (e.g., already initialized) 14377 var elm = wb.init( event, componentName, selector ), 14378 sites, heading, settings, panel, link, $share, $elm, 14379 pageHref, pageTitle, pageImage, pageDescription, 14380 siteProperties, url, shareText, id, pnlId, regex, 14381 filter, i, len, keys, key; 14382 14383 if ( elm ) { 14384 14385 // Only initialize the i18nText once 14386 if ( !i18nText ) { 14387 i18n = wb.i18n; 14388 i18nText = { 14389 shareText: i18n( "shr-txt" ), 14390 page: i18n( "shr-pg" ), 14391 video: i18n( "shr-vid" ), 14392 audio: i18n( "shr-aud" ), 14393 disclaimer: i18n( "shr-disc" ), 14394 email: i18n( "email" ) 14395 }; 14396 14397 // Add an email mailto option 14398 defaults.sites.email = { 14399 name: i18nText.email, 14400 url: "mailto:?subject={t}&body={u}%0A{d}", 14401 isMailto: true 14402 }; 14403 } 14404 14405 $elm = $( elm ); 14406 settings = $.extend( 14407 true, 14408 {}, 14409 defaults, 14410 window[ componentName ], 14411 wb.getData( $elm, componentName ) 14412 ); 14413 sites = settings.sites; 14414 filter = settings.filter; 14415 heading = settings.hdLvl; 14416 14417 shareText = i18nText.shareText + ( settings.custType.length !== 0 ? settings.custType : i18nText[ settings.type ] ); 14418 pnlId = settings.pnlId; 14419 id = "shr-pg" + ( pnlId.length !== 0 ? "-" + pnlId : panelCount ); 14420 pageHref = encodeURIComponent( settings.url ); 14421 14422 regex = /'|'|'/g; 14423 pageTitle = encodeURIComponent( settings.title ) 14424 .replace( regex, "%27" ); 14425 pageImage = encodeURIComponent( settings.img ); 14426 pageDescription = encodeURIComponent( settings.desc ) 14427 .replace( regex, "%27" ); 14428 14429 // Don't create the panel for the second link (class="link-only") 14430 if ( elm.className.indexOf( "link-only" ) === -1 ) { 14431 panel = "<section id='" + id + "' class='shr-pg mfp-hide modal-dialog modal-content overlay-def" + 14432 "'><header class='modal-header'><" + heading + " class='modal-title'>" + 14433 shareText + "</" + heading + "></header><div class='modal-body'>" + 14434 "<ul class='list-unstyled colcount-xs-2'>"; 14435 14436 // If there is no filter array of site keys, then generate an array of site keys 14437 if ( !filter || filter.length === 0 ) { 14438 keys = []; 14439 for ( key in sites ) { 14440 if ( Object.prototype.hasOwnProperty.call( sites, key ) ) { 14441 keys.push( key ); 14442 } 14443 } 14444 } else { 14445 keys = filter; 14446 } 14447 14448 // i18n-friendly sort of the site keys 14449 keys.sort( function( x, y ) { 14450 return wb.normalizeDiacritics( x ).localeCompare( wb.normalizeDiacritics( y ) ); 14451 } ); 14452 len = keys.length; 14453 14454 // Generate the panel 14455 for ( i = 0; i !== len; i += 1 ) { 14456 key = keys[ i ]; 14457 siteProperties = sites[ key ]; 14458 url = siteProperties.url 14459 .replace( /\{u\}/, pageHref ) 14460 .replace( /\{t\}/, pageTitle ) 14461 .replace( /\{i\}/, pageImage ) 14462 .replace( /\{d\}/, pageDescription ); 14463 panel += "<li><a href='" + url + "' class='" + shareLink + 14464 " " + ( siteProperties.isMailto ? "email" : key ) + 14465 " btn btn-default' target='_blank' rel='noreferrer noopener'>" + 14466 siteProperties.name + "</a></li>"; 14467 } 14468 14469 panel += "</ul><p class='col-sm-12 shr-dscl'>" + i18nText.disclaimer + 14470 "</p><div class='clearfix'></div></div></section>"; 14471 panelCount += 1; 14472 } 14473 link = "<a href='#" + id + "' aria-controls='" + id + 14474 "' class='shr-opn wb-lbx " + settings.lnkClass + 14475 "'><span class='glyphicon glyphicon-share'></span>" + 14476 shareText + "</a>"; 14477 14478 $share = $( ( panel ? panel : "" ) + link ); 14479 14480 $elm.append( $share ); 14481 14482 $share 14483 .trigger( "wb-init.wb-lbx" ); 14484 14485 // Identify that initialization has completed 14486 wb.ready( $elm, componentName ); 14487 } 14488 }; 14489 14490// Bind the init event of the plugin 14491$document.on( "timerpoke.wb " + initEvent, selector, init ); 14492 14493// Add the timer poke to initialize the plugin
14494wb.add( selector ); 14495 14496} )( jQuery, window, document, wb ); 14497 14498/** 14499 * @title WET-BOEW step form 14500 * @overview Provide ability for a form to be broken into steps. 14501 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 14502 * @author @kodecount 14503 */ 14504( function( $, window, document, wb ) { 14505"use strict"; 14506 14507/* 14508 * Variable and function definitions. 14509 * These are global to the plugin - meaning that they will be initialized once per page, 14510 * not once per instance of plugin on the page. So, this is a good place to define 14511 * variables that are common to all instances of the plugin on a page. 14512 */ 14513var componentName = "wb-steps", 14514 selector = ".provisional." + componentName, 14515 initEvent = "wb-init" + selector, 14516 $document = wb.doc, 14517 i18n, i18nText, 14518 btnPrevious, btnNext, btnSubmit, 14519 14520 /** 14521 * @method init 14522 * @param {jQuery Event} evt Event that triggered the function call 14523 */ 14524 init = function( evt ) { 14525 14526 // Start initialization 14527 // returns DOM object = proceed with init 14528 // returns undefined = do not proceed with init (e.g., already initialized) 14529 var elm = wb.init( evt, componentName, selector ); 14530 14531 if ( elm ) { 14532 14533 // Ensure there is a unique id on the element 14534 if ( !elm.id ) { 14535 elm.id = wb.getId(); 14536 } 14537 14538 // Only initialize the i18nText once 14539 if ( !i18nText ) { 14540 i18n = wb.i18n; 14541 i18nText = { 14542 prv: i18n( "prv" ), 14543 nxt: i18n( "nxt" ) 14544 }; 14545 } 14546 14547 /* 14548 * Variable and function definitions 14549 * These will be initialized once per instance of plugin. 14550 */ 14551 var form = elm.getElementsByTagName( "FORM" )[ 0 ], 14552 fieldsets = ( form ) ? $( form ).children( "fieldset" ) : 0, 14553 hasStepsInitialized; 14554 14555 // Initialize navigation buttons 14556 btnPrevious = createStepsButton( "prev", "mrgn-rght-sm mrgn-bttm-md", i18nText.prv ); 14557 btnNext = createStepsButton( "next", "mrgn-bttm-md", i18nText.nxt ); 14558 btnSubmit = form.querySelector( "input[type=submit], button[type=submit]" ); 14559 btnSubmit.classList.add( "mrgn-bttm-md" ); 14560 14561 /* 14562 * Determines if html is correctly formatted and initialize all fieldsets/legend combinations into steps. 14563 */ 14564 for ( var i = 0, len = fieldsets.length; i < len; i++ ) { 14565 14566 /* 14567 * Variable and function definitions 14568 * These well be initialized once per instance of each fieldset. 14569 * Determines the following business rules: 14570 * -Only allow steps if elements are in proper order fieldset -> legend -> div 14571 * -Only allow NEXT button on first step 14572 * -Only allow final SUBMIT button on last step 14573 */ 14574 var fieldset = fieldsets[ i ], 14575 isFirstFieldset = ( i === 0 ) ? true : false, 14576 isLastFieldset = ( i === ( len - 1 ) ) ? true : false, 14577 legend = fieldset.firstElementChild, 14578 div = ( legend && legend.tagName === "LEGEND" ) ? legend.nextElementSibling : false, 14579 buttonGroup = document.createElement( "div" ), 14580 wrapper = document.createElement( "div" ), 14581 buttonGroupClassList = buttonGroup.classList, 14582 divClassList = div.classList; 14583 14584 buttonGroupClassList.add( "buttons" ); 14585 fieldset.parentNode.insertBefore( wrapper, fieldset ); 14586 wrapper.appendChild( fieldset ); 14587 wrapper.classList.add( "steps-wrapper" ); 14588 14589 if ( div && div.tagName === "DIV" ) { 14590 var btnClone; 14591 hasStepsInitialized = true; 14592 14593 if ( !isFirstFieldset ) { 14594 btnClone = btnPrevious.cloneNode( true ); 14595 setStepsBtnEvent( btnClone ); 14596 buttonGroup.appendChild( btnClone ); 14597 wrapper.appendChild( buttonGroup ); 14598 } 14599 14600 if ( !isLastFieldset ) { 14601 btnClone = btnNext.cloneNode( true ); 14602 setStepsBtnEvent( btnClone ); 14603 buttonGroup.appendChild( btnClone ); 14604 } else { 14605 buttonGroup.appendChild( btnSubmit ); 14606 } 14607 14608 wrapper.appendChild( buttonGroup ); 14609 14610 fieldset.classList.add( "wb-tggle-fildst" ); 14611 divClassList.add( "hidden" ); 14612 buttonGroupClassList.add( "hidden" ); 14613 14614 if ( isFirstFieldset ) { 14615 legend.classList.add( "wb-steps-active" ); 14616 btnClone.classList.remove( "hidden" ); 14617 divClassList.remove( "hidden" ); 14618 buttonGroupClassList.remove( "hidden" ); 14619 } 14620 } 14621 } 14622 14623 /* 14624 * if steps has initialized hide any precreated submit or reset buttons 14625 */ 14626 if ( form && hasStepsInitialized ) { 14627 $( form ).children( "input" ).hide(); 14628 wb.ready( $( elm ), componentName ); 14629 } 14630 } 14631 }, 14632 14633 /** 14634 * @method createStepsButton 14635 * @param {string var} tagName, {string var} type, {boolean var} isPrimary, {string var} style, {string var} text 14636 * @returns {Object} A ready-to-use button element 14637 */ 14638 createStepsButton = function( type, style, text ) { 14639 var control = document.createElement( "BUTTON" ); 14640 14641 // set default attributes 14642 control.className = ( type === "prev" ? "btn btn-md btn-default" : "btn btn-md btn-primary" ) + " " + style; 14643 control.setAttribute( "type", "button" ); 14644 control.innerHTML = text; 14645 14646 return control; 14647 }, 14648 14649 /** 14650 * @method setStepsBtnEvent 14651 * @param {JavaScript element} elm 14652 */ 14653 setStepsBtnEvent = function( elm ) { 14654 elm.addEventListener( "click", function( evt ) { 14655 evt.preventDefault(); 14656 var classes = ( this.className ) ? this.className : false, 14657 isNext = ( classes && classes.indexOf( "btn-primary" ) > -1 ), 14658 isFormValid = true, 14659 parentElement = this.parentElement, 14660 parentParentElement = parentElement.parentElement, 14661 parentPreviousClassList = parentElement.previousElementSibling.classList; 14662 14663 // confirm if form is valid 14664 if ( isNext && jQuery.validator && jQuery.validator !== "undefined" ) { 14665 isFormValid = $( "#" + parentParentElement.parentElement.id ).valid(); 14666 } 14667 14668 // continue if valid 14669 if ( isFormValid ) { 14670 showSteps( parentParentElement, isNext ); 14671 if ( isNext ) { 14672 parentPreviousClassList.remove( "wb-steps-error" ); 14673 } 14674 } else if ( isNext && !isFormValid ) { 14675 parentPreviousClassList.add( "wb-steps-error" ); 14676 } 14677 } ); 14678 }, 14679 14680 /** 14681 * @method showSteps 14682 * @param {JavaScript element} elm and {boolean var} isNext 14683 */ 14684 showSteps = function( elm, isNext ) { 14685 var fieldsetElement = elm.getElementsByTagName( "FIELDSET" )[ 0 ], 14686 fields = fieldsetElement.getElementsByTagName( "div" )[ 0 ], 14687 legend = fieldsetElement.getElementsByTagName( "legend" )[ 0 ], 14688 buttonGroup = elm.querySelector( "div.buttons" ), 14689 fieldset; 14690 14691 if ( elm ) { 14692 fields.classList.add( "hidden" ); 14693 buttonGroup.classList.add( "hidden" ); 14694 14695 if ( legend ) { 14696 legend.classList.remove( "wb-steps-active" ); 14697 } 14698 14699 fieldset = ( !isNext ) ? elm.previousElementSibling : elm.nextElementSibling; 14700 if ( fieldset ) { 14701 legend = fieldset.getElementsByTagName( "LEGEND" )[ 0 ]; 14702 elm = fieldset.getElementsByTagName( "DIV" )[ 0 ]; 14703 buttonGroup = fieldset.querySelector( "div.buttons" ); 14704 if ( legend ) { 14705 legend.classList.add( "wb-steps-active" ); 14706 legend.tabIndex = 0; 14707 legend.focus(); 14708 legend.tabIndex = -1; 14709 } 14710 if ( elm ) { 14711 elm.classList.remove( "hidden" ); 14712 } 14713 if ( buttonGroup ) { 14714 buttonGroup.classList.remove( "hidden" ); 14715 } 14716 } 14717 } 14718 }; 14719 14720// Bind the init event of the plugin 14721$document.on( "timerpoke.wb " + initEvent, selector, init ); 14722 14723// Add the timer poke to initialize the plugin
14724wb.add( selector ); 14725 14726} )( jQuery, window, document, wb ); 14727 14728/** 14729 * @title WET-BOEW Tables 14730 * @overview Integrates the DataTables plugin into WET providing searching, sorting, filtering, pagination and other advanced features for tables. 14731 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 14732 * @author @jeresiv 14733 */ 14734/*jshint scripturl:true*/ 14735( function( $, window, wb ) { 14736"use strict"; 14737 14738/* 14739 * Variable and function definitions. 14740 * These are global to the plugin - meaning that they will be initialized once per page, 14741 * not once per instance of plugin on the page. So, this is a good place to define 14742 * variables that are common to all instances of the plugin on a page. 14743 */ 14744var componentName = "wb-tables", 14745 selector = "." + componentName, 14746 initEvent = "wb-init" + selector, 14747 $document = wb.doc, 14748 idCount = 0, 14749 i18n, i18nText, defaults, 14750 14751 /** 14752 * @method init 14753 * @param {jQuery Event} event Event that triggered the function call 14754 */ 14755 init = function( event ) { 14756 14757 // Start initialization 14758 // returns DOM object = proceed with init 14759 // returns undefined = do not proceed with init (e.g., already initialized) 14760 var elm = wb.init( event, componentName, selector ), 14761 elmId; 14762 14763 if ( elm ) { 14764 elmId = elm.id; 14765 14766 // Ensure there is a unique id on the element 14767 if ( !elmId ) { 14768 elmId = componentName + "-id-" + idCount; 14769 idCount += 1; 14770 elm.id = elmId; 14771 } 14772 14773 // Only initialize the i18nText once 14774 if ( !i18nText ) { 14775 i18n = wb.i18n; 14776 i18nText = { 14777 aria: { 14778 sortAscending: i18n( "sortAsc" ), 14779 sortDescending: i18n( "sortDesc" ) 14780 }, 14781 emptyTable: i18n( "emptyTbl" ), 14782 info: i18n( "infoEntr" ), 14783 infoEmpty: i18n( "infoEmpty" ), 14784 infoFiltered: i18n( "infoFilt" ), 14785 lengthMenu: i18n( "lenMenu" ), 14786 loadingRecords: i18n( "load" ), 14787 paginate: { 14788 first: i18n( "first" ), 14789 last: i18n( "last" ), 14790 next: i18n( "nxt" ), 14791 previous: i18n( "prv" ), 14792 page: i18n( "page" ) 14793 }, 14794 processing: i18n( "process" ), 14795 search: i18n( "filter" ), 14796 thousands: i18n( "info1000" ), 14797 zeroRecords: i18n( "infoEmpty" ), 14798 tblFilterInstruction: i18n( "tbFilterInst" ) 14799 }; 14800 } 14801 14802 defaults = { 14803 asStripeClasses: [], 14804 language: i18nText, 14805 dom: "<'top'fil>rt<'bottom'p><'clear'>" 14806 }; 14807 14808 Modernizr.load( { 14809 load: [ "site!deps/jquery.dataTables" + wb.getMode() + ".js" ], 14810 testReady: function() { 14811 return ( $.fn.dataTable && $.fn.dataTable.version ); 14812 }, 14813 complete: function() { 14814 var $elm = $( "#" + elmId ), 14815 dataTableExt = $.fn.dataTableExt, 14816 settings = wb.getData( $elm, componentName ) || {}; 14817 14818 // Explicitly deactivate the paging for the filterEmphasis provisional feature/styling when not configured 14819 if ( $elm.hasClass( "provisional" ) && $elm.hasClass( "filterEmphasis" ) ) { 14820 settings.paging = settings.paging ? settings.paging : false; 14821 } 14822 14823 /* 14824 * Extend sorting support 14825 */ 14826 $.extend( dataTableExt.type.order, { 14827 14828 // Enable internationalization support in the sorting 14829 "html-pre": function( a ) { 14830 return wb.normalizeDiacritics( 14831 !a ? "" : a.replace ? 14832 a.replace( /<.*?>/g, "" ).toLowerCase() : a + "" 14833 ); 14834 }, 14835 "string-case-pre": function( a ) { 14836 return wb.normalizeDiacritics( a ); 14837 }, 14838 "string-pre": function( a ) { 14839 return wb.normalizeDiacritics( a ); 14840 }, 14841 14842 // Formatted number sorting 14843 "formatted-num-asc": function( a, b ) { 14844 return wb.formattedNumCompare( a, b ); 14845 }, 14846 "formatted-num-desc": function( a, b ) { 14847 return wb.formattedNumCompare( b, a ); 14848 } 14849 } ); 14850 14851 // Create the DataTable object 14852 $elm.dataTable( $.extend( true, {}, defaults, window[ componentName ], settings ) ); 14853 } 14854 } ); 14855 } 14856 }, 14857 updatePaginationMarkup = function( $pagination, setFocusOnId ) { 14858 14859 var ol = document.createElement( "OL" ), 14860 li = document.createElement( "LI" ), 14861 paginate_buttons = $pagination.find( ".paginate_button" ), 14862 navFocusOnId; 14863 14864 if ( $pagination.length === 0 ) { 14865 return; 14866 } 14867 14868 // Set the element to get the focus upon navigation 14869 navFocusOnId = setFocusOnId || $pagination.get( 0 ).id; 14870 14871 // Update Pagination List 14872 for ( var i = 0; i < paginate_buttons.length; i++ ) { 14873 var item = li.cloneNode( true ); 14874 item.appendChild( paginate_buttons[ i ] ); 14875 ol.appendChild( item ); 14876 } 14877 14878 ol.className = "pagination mrgn-tp-0 mrgn-bttm-0"; 14879 $pagination.empty(); 14880 $pagination.append( ol ); 14881 14882 // Update the aria-pressed properties on the pagination buttons 14883 // Should be pushed upstream to DataTables 14884 $pagination.find( ".paginate_button" ) 14885 .attr( {
14886 "href": "#" + navFocusOnId 14887 } ) 14888 14889 // This is required to override the datatable.js (v1.10.13) behavior to cancel the event propagation on anchor element. 14890 .on( "keypress", function( evn ) { 14891 if ( evn.keyCode === 13 ) { 14892 window.location = evn.target.href; 14893 } 14894 } ) 14895 14896 .not( ".previous, .next" ) 14897 .attr( "aria-pressed", "false" ) 14898 .html( function( index, oldHtml ) { 14899 return "<span class='wb-inv'>" + i18nText.paginate.page + " </span>" + oldHtml; 14900 } ) 14901 .filter( ".current" ) 14902 .attr( "aria-pressed", "true" ); 14903 }; 14904 14905// Bind the init event of the plugin 14906$document.on( "timerpoke.wb " + initEvent, selector, init ); 14907 14908// Handle the draw.dt event 14909$document.on( "draw.dt", selector, function( event, settings ) { 14910 var $elm = $( event.target ), 14911 pagination = $elm.next( ".bottom" ).find( "div:first-child" ), 14912 pagination_top = $elm.prevAll( ".top" ).find( "div.dataTables_paginate" ), 14913 paginate_buttons = $elm.next( ".bottom" ).find( ".paginate_button" ), 14914 pbLength = paginate_buttons.length, 14915 pHasLF = pagination.find( ".last, .first" ).length === 2, 14916 pHasPN = pagination.find( ".previous, .next" ).length === 2; 14917 14918 // Handle sorting/ordering 14919 var order = $elm.dataTable( { "retrieve": true } ).api().order(); 14920 $elm.find( "th" ).each( function( index ) { 14921 var $th = $( this ), 14922 $btn = $th.find( "button" ); 14923 if ( order && order.length && order[ 0 ][ 0 ] === index ) { 14924 var label = ( order[ 0 ][ 1 ] === "desc" ) ? i18nText.aria.sortAscending : i18nText.aria.sortDescending; 14925 label = $btn.text() + label; 14926 $btn.attr( "title", label ); 14927 } 14928 $th.removeAttr( "aria-label" ); 14929 } ); 14930 14931 // Determine if Pagination required 14932 if ( 14933 pbLength === 1 || 14934 ( 14935 pbLength === 3 && 14936 ( 14937 pHasLF || 14938 pHasPN 14939 ) 14940 ) || 14941 ( 14942 pbLength === 5 && 14943 pHasLF && 14944 pHasPN 14945 ) 14946 ) { 14947 pagination.addClass( "hidden" ); 14948 pagination_top.addClass( "hidden" ); 14949 } else { 14950 14951 // Make sure Pagination is visible 14952 pagination.removeClass( "hidden" ); 14953 pagination_top.removeClass( "hidden" ); 14954 14955 updatePaginationMarkup( pagination, $elm.get( 0 ).id ); 14956 updatePaginationMarkup( pagination_top ); 14957 } 14958 14959 // Identify that the table has been updated 14960 $elm.trigger( "wb-updated" + selector, [ settings ] ); 14961} ); 14962 14963// Identify that initialization has completed 14964$document.on( "init.dt", selector, function( event ) { 14965 var $elm = $( event.target ), 14966 settings = $.extend( true, {}, defaults, window[ componentName ], wb.getData( $elm, componentName ) ); 14967 14968 // Handle sorting/ordering 14969 var ordering = ( settings && settings.ordering === false ) ? false : true; 14970 if ( ordering ) { 14971 $elm.find( "thead th" ).each( function() { 14972 var $th = $( this ), 14973 label = ( $th.attr( "aria-sort" ) === "ascending" ) ? i18nText.aria.sortDescending : i18nText.aria.sortAscending; 14974 if ( ( $th.attr( "data-orderable" ) !== "false" ) && !( $th.hasClass( "sorting_disabled" ) ) ) { 14975 $th.html( "<button type='button' aria-controls='" + $th.attr( "aria-controls" ) + "' title='" + $th.text().replace( /'/g, "'" ) + label + "'>" + $th.html() + "<span class='sorting-cnt'><span class='sorting-icons' aria-hidden='true'></span></span></button>" ); 14976 $th.removeAttr( "aria-label tabindex aria-controls" ); 14977 } 14978 } ); 14979 $elm.attr( "aria-label", i18nText.tblFilterInstruction ); 14980 } 14981 14982 // Apply the filter emphasis style 14983 if ( $elm.hasClass( "provisional" ) && $elm.hasClass( "filterEmphasis" ) ) { 14984 $elm.parent().addClass( "provisional filterEmphasis" ); 14985 } 14986 14987 wb.ready( $( event.target ), componentName ); 14988} ); 14989 14990// Handle the draw.dt event 14991$document.on( "submit", ".wb-tables-filter", function( event ) { 14992 14993 event.preventDefault(); 14994 14995 var $form = $( this ), 14996 $datatable = $( "#" + $form.data( "bind-to" ) ).dataTable( { "retrieve": true } ).api(), 14997 $toNumber = function stringToNumber( number ) { 14998 number = number.replace( /[^0-9\-,.]+/g, "" ); 14999 if ( /[,]\d{1,2}$/.test( number ) ) { 15000 number = number.replace( /(\d{2})$/, ".$1" ); 15001 } 15002 number = number.replace( /,/g, "" ); 15003 return parseFloat( number ); 15004 }, 15005 $isDate = function isDate( date ) { 15006 return date instanceof Date && !isNaN( date ); 15007 }; 15008 15009 // Lets reset the search 15010 $datatable.search( "" ).columns().search( "" ); 15011 15012 // Lets loop throug all options 15013 var $prevCol = -1, $cachedVal = ""; 15014 $form.find( "[name]" ).each( function() { 15015 var $elm = $( this ), 15016 $val = $elm.val(), 15017 $value = "", 15018 $regex = "", 15019 $column = parseInt( $elm.attr( "data-column" ), 10 ), 15020 $isAopts = $elm.data( "aopts" ), 15021 $aoptsSelector = "[data-aopts*='\"column\": \"" + $column + "\"']:checked", 15022 $aopts = $( $aoptsSelector ), 15023 $aoType = ( $aopts && $aopts.data( "aopts" ) ) ? $aopts.data( "aopts" ).type.toLowerCase() : "", 15024 $fData; 15025 15026 // Ignore the advanced options fields 15027 if ( $isAopts ) { 15028 return; 15029 } 15030 15031 // Verifies if filtering the same column 15032 if ( $column !== $prevCol || $prevCol === -1 ) { 15033 $cachedVal = ""; 15034 } 15035 $prevCol = $column; 15036 15037 // Filters based on input type 15038 if ( $elm.is( "select" ) ) { 15039 $value = $elm.find( "option:selected" ).val(); 15040 } else if ( $elm.is( "input[type='number']" ) ) { 15041 var $minNum, $maxNum = null; 15042 15043 // Retain minimum number (always the first number input) 15044 if ( $cachedVal === "" ) { 15045 $cachedVal = parseFloat( $val ); 15046 $cachedVal = ( $cachedVal ) ? $cachedVal : "-0"; 15047 } else { 15048 $maxNum = parseFloat( $val ); 15049 $maxNum = ( $maxNum ) ? $maxNum : "-0"; 15050 } 15051 $minNum = $cachedVal; 15052 15053 //Number filtering logic needs to be reviewed in order to remove the "-0" value (issue #9235) 15054 // Generates a list of numbers (within the min and max number) 15055 if ( !isNaN( $minNum ) && !isNaN( $maxNum ) ) { 15056 $fData = $datatable.column( $column ).data().filter( function( obj ) { 15057 var $num = $toNumber( obj.toString() ); 15058 15059 if ( !isNaN( $num ) ) { 15060 if ( $aoType === "and" ) {
15061 if ( $cachedVal !== $maxNum && $cachedVal !== "-0" && $maxNum !== "0" && $num >= $minNum && $num <= $maxNum ) { 15062 return true; 15063 } 15064 } else { 15065 if ( $maxNum === null ) { // only one input number 15066 return $minNum === "-0" || $minNum === $num; 15067 } else if ( $maxNum === $cachedVal && $cachedVal === "-0" ) { // both are empty 15068 return true; 15069 } else if ( $maxNum !== "-0" && $minNum === $maxNum && $num === $maxNum ) { // min and max are the same 15070 return true; 15071 } else if ( $maxNum === "-0" && $num >= $minNum ) { // max number is missing 15072 return true; 15073 } else if ( $cachedVal === "-0" && $num <= $maxNum ) { // min number is missing 15074 return true; 15075 } else if ( $cachedVal !== "-0" && $num >= $minNum && $num <= $maxNum ) { // min and max are present 15076 return true; 15077 } 15078 } 15079 } 15080 return false; 15081 } ); 15082 $fData = $fData.join( "|" ); 15083 15084 // If no numbers match set as -0, so no results return 15085 $value = ( $fData ) ? $fData : "-0"; 15086 $regex = "(" + $value.replace( / |\s/g, "\\s" ).replace( /\$/g, "\\$" ) + ")"; 15087 } 15088 } else if ( $elm.is( "input[type='date']" ) && $val ) { 15089 var $minDate, $maxDate; 15090 15091 // Retain minimum date (always the first date input) 15092 if ( $cachedVal === "" ) { 15093 $cachedVal = new Date( $val ); 15094 $cachedVal.setDate( $cachedVal.getDate() + 1 ); 15095 $cachedVal.setHours( 0, 0, 0, 0 ); 15096 } 15097 $minDate = $cachedVal; 15098 15099 // Maximum date is always the current selected date 15100 $maxDate = new Date( $val ); 15101 $maxDate.setDate( $maxDate.getDate() + 1 ); 15102 $maxDate.setHours( 23, 59, 59, 999 ); 15103 15104 // Generates a list of date strings (within the min and max date) 15105 $fData = $datatable.column( $column ).data().filter( function( obj ) { 15106 var $date = obj.replace( /[0-9]{2}\s[0-9]{2}:/g, function( e ) { 15107 return e.replace( /\s/g, "T" ); 15108 } ); 15109 15110 if ( !$date.includes( "T" ) ) { 15111 $date = $date + "T00:00:00"; 15112 } 15113 $date = new Date( $date ); 15114 $date.setHours( 0, 0, 0, 0 ); 15115 15116 if ( !$isDate( $minDate ) || !$isDate( $maxDate ) || !$isDate( $date ) ) { 15117 return; 15118 } 15119 return ( $date >= $minDate && $date <= $maxDate ); 15120 } ); 15121 $fData = $fData.join( "|" ); 15122 15123 // If no dates match set as element value, so no results return 15124 $value = ( $fData ? $fData : $val ); 15125 } else if ( $elm.is( ":checkbox" ) ) { 15126 15127 // Verifies if checkbox is checked before setting value 15128 if ( $elm.is( ":checked" ) ) { 15129 if ( $aoType === "both" ) { 15130 $cachedVal += "(?=.*\\b" + $val + "\\b)"; 15131 } else { 15132 $cachedVal += ( $cachedVal.length > 0 ) ? "|" : ""; 15133 $cachedVal += $val; 15134 } 15135 15136 $value = $cachedVal; 15137 $value = $value.replace( /\s/g, "\\s*" ); 15138 15139 // Adjust regex based on advanced options 15140 switch ( $aoType ) { 15141 case "both": 15142 $regex = "(" + $value + ").*"; 15143 break; 15144 case "either": 15145 $regex = "^(" + $value + ")$"; 15146 break; 15147 case "and": 15148 $regex = ( $value.indexOf( "|" ) > -1 ) ? "^(" + $value + "|[,\\s])(" + $value + "|[,\\s])+$" : "(" + $value + ")"; 15149 break; 15150 case "any": 15151 default: 15152 $regex = "(" + $value + ")"; 15153 break; 15154 } 15155 } 15156 } else { 15157 $value = $val; 15158 } 15159 15160 if ( $value ) { 15161 15162 // Verifies if regex was preset, if not preset use 'contains value' as default 15163 if ( !$regex ) { 15164 if ( $elm[ 0 ].getAttribute( "data-exact" ) ) { 15165 $regex = "^" + $value + "$"; 15166 } else { 15167 $value = $value.replace( /\s/g, "\\s*" ); 15168 $regex = "(" + $value + ")"; 15169 } 15170 } 15171 15172 $datatable.column( $column ).search( $regex, true ); 15173 } 15174 } ); 15175 $datatable.draw(); 15176 return false; 15177} ); 15178 15179$document.on( "click", ".wb-tables-filter [type='reset']", function( event ) { 15180 event.preventDefault(); 15181 15182 var $form = $( this ).closest( ".wb-tables-filter" ), 15183 $datatable = $( "#" + $form.data( "bind-to" ) ).dataTable( { "retrieve": true } ).api(); 15184 15185 $datatable.search( "" ).columns().search( "" ).draw(); 15186 15187 $form.find( "select" ).prop( "selectedIndex", 0 ); 15188 $form.find( "input:checkbox, input:radio" ).prop( "checked", false ); 15189 $form.find( ":input" ).not( ":button, :submit, :reset, :hidden, :checkbox, :radio" ).val( "" ); 15190 return false;
15191} ); 15192 15193// Add the timer poke to initialize the plugin 15194wb.add( selector ); 15195 15196} )( jQuery, window, wb ); 15197 15198/** 15199 * @title WET-BOEW Tabbed interface 15200 * @overview Dynamically stacks multiple sections of content, transforming them into a tabbed interface. 15201 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 15202 * @author WET Community 15203 */ 15204( function( $, window, wb ) { 15205"use strict"; 15206 15207/* 15208 * Variable and function definitions. 15209 * These are global to the plugin - meaning that they will be initialized once per page, 15210 * not once per instance of plugin on the page. So, this is a good place to define 15211 * variables that are common to all instances of the plugin on a page. 15212 */ 15213var componentName = "wb-tabs", 15214 selector = "." + componentName, 15215 initEvent = "wb-init" + selector, 15216 shiftEvent = "wb-shift" + selector, 15217 selectEvent = "wb-select" + selector, 15218 updatedEvent = "wb-updated" + selector, 15219 setFocusEvent = "setfocus.wb", 15220 controls = selector + " ul[role=tablist] a, " + selector + " ul[role=tablist] .tab-count", 15221 initialized = false, 15222 tabsAccordionClass = "tabs-acc", 15223 nestedTglPanelSelector = "> .tabpanels > details > .tgl-panel", 15224 activePanel = "-activePanel", 15225 activateEvent = "click keydown", 15226 pagePath = wb.pageUrlParts.pathname + "#", 15227 $document = wb.doc, 15228 i18n, i18nText, 15229 15230 // Includes "smallview", "xsmallview" and "xxsmallview" 15231 smallViewPattern = "smallview", 15232 isSmallView, oldIsSmallView, 15233 15234 defaults = { 15235 excludePlay: false, 15236 interval: 6, 15237 updateHash: false, 15238 ignoreSession: false 15239 }, 15240 15241 /** 15242 * @method init 15243 * @param {jQuery Event} event Event that triggered the function call 15244 */ 15245 init = function( event ) { 15246 15247 // Start initialization 15248 // returns DOM object = proceed with init 15249 // returns undefined = do not proceed with init (e.g., already initialized) 15250 var elm = wb.init( event, componentName, selector ), 15251 isCarousel = true, 15252 open = "open", 15253 hashTargetLen = 0, 15254 $panels, $tablist, activeId, $openPanel, $elm, elmId, $hashTarget, 15255 settings, $panel, i, len, tablist, isOpen, hashFocus, 15256 newId, positionY, groupClass, $tabPanels, openByHash; 15257 15258 if ( elm ) { 15259 $elm = $( elm ); 15260 15261 // For backwards compatibility. Should be removed in WET v4.1 15262 if ( $elm.children( ".tabpanels" ).length === 0 ) { 15263 $elm.children( "[role=tabpanel], details" ).wrapAll( "<div class='tabpanels'></div>" ); 15264 } 15265 15266 $panels = $elm.find( "> .tabpanels > [role=tabpanel], > .tabpanels > details" ); 15267 $tablist = $elm.children( "[role=tablist]" ); 15268 isCarousel = $tablist.length !== 0; 15269 15270 // If a carousel contains only 1 panel, remove its controls, visually-hide its thumbnails and prevent it from attempting to play 15271 if ( isCarousel && $panels.length === 1 ) { 15272 15273 $elm.removeClass( "show-thumbs playing" ); 15274 $elm.addClass( "exclude-controls" ); 15275 } 15276 15277 activeId = wb.jqEscape( wb.pageUrlParts.hash.substring( 1 ) ); 15278 hashFocus = activeId.length !== 0; 15279 $openPanel = hashFocus ? $panels.filter( "#" + activeId ) : undefined; 15280 openByHash = $openPanel && $openPanel.length !== 0; 15281 elmId = elm.id; 15282 15283 settings = $.extend( 15284 true, 15285 {}, 15286 defaults, 15287 { 15288 interval: $elm.hasClass( "slow" ) ? 15289 9 : $elm.hasClass( "fast" ) ? 15290 3 : defaults.interval, 15291 excludeControls: $elm.hasClass( "exclude-controls" ), 15292 excludePlay: $elm.hasClass( "exclude-play" ), 15293 updateHash: $elm.hasClass( "update-hash" ), 15294 playing: $elm.hasClass( "playing" ), 15295 ignoreSession: $elm.hasClass( "ignore-session" ) 15296 }, 15297 window[ componentName ], 15298 wb.getData( $elm, componentName ) 15299 ); 15300 15301 try { 15302 15303 // If the panel was not set by URL hash 15304 if ( !openByHash ) { 15305 if ( hashFocus ) { 15306 $hashTarget = $panels.find( "#" + activeId ); 15307 hashTargetLen = $hashTarget.length; 15308 } 15309 15310 // If the anchor target is within a panel, then open that panel 15311 if ( hashTargetLen !== 0 ) { 15312 activeId = $hashTarget.closest( "[role=tabpanel]" ).attr( "id" ); 15313 15314 // Attempt to retrieve active panel from sessionStorage 15315 } else { 15316 if ( !settings.ignoreSession ) { 15317 activeId = sessionStorage.getItem( pagePath + elmId + activePanel ); 15318 } 15319 } 15320 15321 if ( activeId ) { 15322 $openPanel = $panels.filter( "#" + activeId ); 15323 } 15324 15325 // If the panel was set by URL hash, then store in sessionStorage 15326 } else { 15327 if ( !settings.ignoreSession ) { 15328 try { 15329 sessionStorage.setItem( pagePath + elmId + activePanel, activeId ); 15330 } catch ( error ) { 15331 15332 /* swallow error */ 15333 } 15334 } 15335 } 15336 } catch ( error ) { 15337 15338 /* swallow error */ 15339 } 15340 15341 // Determine the current view 15342 isSmallView = document.documentElement.className.indexOf( smallViewPattern ) !== -1; 15343 15344 // Only initialize the i18nText once 15345 if ( !i18nText ) { 15346 i18n = wb.i18n; 15347 i18nText = { 15348 prev: i18n( "prv" ), 15349 next: i18n( "nxt" ), 15350 play: i18n( "tab-play" ), 15351 rotStart: i18n( "tab-rot" ).on, 15352 rotStop: i18n( "tab-rot" ).off, 15353 space: i18n( "space" ), 15354 hyphen: i18n( "hyphen" ), 15355 pause: i18n( "pause" ), 15356 tabCount: i18n( "lb-curr" ) 15357 }; 15358 } 15359 15360 // Build the tablist and enhance the panels as needed for details/summary 15361 if ( !isCarousel ) { 15362 $elm.addClass( tabsAccordionClass ); 15363 groupClass = elmId + "-grp"; 15364 $tabPanels = $elm.children( ".tabpanels" ); 15365 $panels = $tabPanels.children( "details" ); 15366 len = $panels.length; 15367 15368 $tabPanels.detach(); 15369 15370 // Ensure there is only one panel open 15371 // Order of priority is hash, open property, first details 15372 if ( !$openPanel || $openPanel.length === 0 ) { 15373 $openPanel = $panels.filter( "[open]" ).first(); 15374 if ( $openPanel.length === 0 ) { 15375 $openPanel = $panels.eq( 0 ); 15376 } 15377 } 15378 $panels.removeAttr( open ); 15379 $openPanel.attr( open, open ); 15380 15381 // Hide the tablist in small view and the summary elements in large view 15382 tablist = "<ul role='tablist' aria-live='off' class='generated'>"; 15383 15384 for ( i = 0; i !== len; i += 1 ) { 15385 $panel = $panels.eq( i ); 15386 $panel 15387 .addClass( groupClass ) 15388 .html( 15389 $panel.html() 15390 .replace( /(<\/summary>)/i, "$1<div class='tgl-panel'>" ) + 15391 "</div>" 15392 ); 15393 15394 newId = $panel.attr( "id" ); 15395 if ( !newId ) { 15396 newId = wb.getId(); 15397 $panel.attr( "id", newId ); 15398 } 15399 isOpen = !!$panel.attr( open ); 15400 15401 if ( isSmallView ) { 15402 if ( !Modernizr.details ) { 15403 $panel.toggleClass( "open", isOpen ); 15404 } 15405 } else { 15406 $panel.attr( { 15407 role: "tabpanel", 15408 open: open 15409 } ); 15410 $panel.addClass( ( Modernizr.details ? "" : open + " " ) + 15411 "fade " + ( isOpen ? "in" : "noheight out wb-inv" ) ); 15412 } 15413 15414 tablist += "<li" + ( isOpen ? " class='active'" : "" ) + 15415 "><a id='" + newId + "-lnk' href='#" + newId + "'>" + 15416 $panel.children( "summary" ).html() + "</a></li>"; 15417 } 15418 15419 $tablist = $( tablist + "</ul>" ); 15420 $tabPanels.find( "> details > summary" ) 15421 .addClass( "wb-toggle tgl-tab" ) 15422 .attr( "data-toggle", "{\"parent\": \"#" + elmId + 15423 "\", \"group\": \"." + groupClass + "\"}" ); 15424 15425 $elm 15426 .prepend( $tablist ) 15427 .append( $tabPanels ) 15428 .trigger( "wb-init.wb-toggle" ); 15429 } else if ( $openPanel && $openPanel.length !== 0 ) { 15430 $panels.filter( ".in" ) 15431 .addClass( "out noheight" ) 15432 .removeClass( "in" ); 15433 $openPanel 15434 .addClass( "in" ) 15435 .removeClass( "out noheight" ); 15436 $tablist.find( ".active" ) 15437 .removeClass( "active" ); 15438 $tablist.find( "a" ) 15439 .filter( "[href$='" + activeId + "']" ) 15440 .parent() 15441 .addClass( "active" ); 15442 } 15443 15444 drizzleAria( $panels, $tablist ); 15445 15446 if ( isCarousel ) { 15447 15448 // Returns true if the tabs should be rotating automatically 15449 if ( createControls( $tablist, settings ) ) { 15450 15451 // Register this specific tabs instance for timerpoke.wb events 15452 wb.add( "#" + elmId + selector ); 15453 } 15454 } 15455 15456 // If focus is being set by the URL hash, then ensure the tabs are 15457 // not above the top of the viewport (if the panel was the target),
15458 // or the anchor is not above the top of viewport (if the anchor was 15459 // the target) 15460 if ( hashFocus ) { 15461 15462 // Need a slight delay to allow for the reflow 15463 setTimeout( function() { 15464 if ( openByHash ) { 15465 positionY = $tablist.offset().top; 15466 } else if ( hashTargetLen !== 0 ) { 15467 positionY = $hashTarget.offset().top; 15468 } else { 15469 positionY = -1; 15470 } 15471 15472 if ( positionY !== -1 && positionY < document.body.scrollTop ) { 15473 document.body.scrollTop = positionY; 15474 } 15475 }, 1 ); 15476 } 15477 15478 $elm.data( { 15479 "wb-tabs": { 15480 panels: $panels, 15481 tablist: $tablist, 15482 settings: settings, 15483 ctime: 0 15484 } 15485 } ); 15486 15487 initialized = true; 15488 onResize( $elm ); 15489 15490 // Update the URL hash if needed 15491 if ( settings.updateHash ) { 15492 updateHash( $openPanel[ 0 ] ); 15493 } 15494 15495 // Identify that initialization has completed 15496 wb.ready( $elm, componentName ); 15497 } 15498 }, 15499 15500 /** 15501 * @method onTimerPoke 15502 * @param {jQuery DOM element} $elm The plugin element 15503 */ 15504 onTimerPoke = function( $elm ) { 15505 var data = $elm.data( componentName ),
15506 delayCurrent = parseFloat( data.ctime ) + 0.5; 15507 15508 // Check if we need to rotate panels 15509 if ( parseFloat( data.settings.interval ) <= delayCurrent ) { 15510 $elm.trigger( shiftEvent ); 15511 delayCurrent = 0; 15512 } 15513 15514 data.ctime = delayCurrent; 15515 $elm.data( componentName, data ); 15516 }, 15517 15518 /** 15519 * @method createControls 15520 * @param {jQuery DOM element} $tablist The plugin element 15521 * @param {object} settings Settings for the tabs instance 15522 * @returns {boolean} Whether or not the tabs should be rotating initially 15523 */ 15524 createControls = function( $tablist, settings ) { 15525 var prevText = i18nText.prev, 15526 nextText = i18nText.next, 15527 spaceText = i18nText.space, 15528 excludeControls = settings.excludeControls, 15529 excludePlay = settings.excludePlay, 15530 isPlaying = !excludeControls && !excludePlay && settings.playing, 15531 state = isPlaying ? i18nText.pause : i18nText.play, 15532 hidden = isPlaying ? i18nText.rotStop : i18nText.rotStart, 15533 glyphiconStart = "<span class='glyphicon glyphicon-", 15534 wbInvStart = "<span class='wb-inv'>", 15535 tabsToggleStart = "<li class='control ", 15536 btnMiddle = "' href='javascript:;' role='button' title='", 15537 btnEnd = "</span></a></li> ", 15538 iconState = glyphiconStart + ( isPlaying ? "pause" : "play" ) + "'></span>", 15539 $tabs = $tablist.find( "[role=tab]" ), 15540 currentIndex = $tabs.index( $tabs.filter( "[aria-selected=true]" ) ) + 1, 15541 i18nTabCount = i18nText.tabCount, 15542 firstReplaceIndex = i18nTabCount.indexOf( "%" ), 15543 lastReplaceIndex = i18nTabCount.lastIndexOf( "%" ) + 1, 15544 prevControl = tabsToggleStart + "prv'><a class='prv" + btnMiddle + 15545 prevText + "'>" + glyphiconStart + "chevron-left'></span>" + 15546 wbInvStart + prevText + btnEnd, 15547 tabCount = tabsToggleStart + " tab-count' tabindex='0'><div>" + 15548 i18nTabCount.substring( 0, firstReplaceIndex ) + 15549 "<div class='curr-count'>" + 15550 i18nTabCount.substring( firstReplaceIndex, lastReplaceIndex ) 15551 .replace( "%curr%", "<span class='curr-index'>" + currentIndex + "</span>" ) 15552 .replace( "%total%", $tabs.length ) + 15553 "</div>" + i18nTabCount.substring( lastReplaceIndex ) + 15554 "</div></li>", 15555 nextControl = tabsToggleStart + "nxt'><a class='nxt" + btnMiddle + 15556 nextText + "'>" + glyphiconStart + "chevron-right'></span>" + 15557 wbInvStart + nextText + btnEnd, 15558 playControl = tabsToggleStart + "plypause'><a class='plypause" + 15559 btnMiddle + state + "'>" + iconState + " <span>" + state + 15560 "</span>" + wbInvStart + spaceText + i18nText.hyphen + spaceText + 15561 hidden + btnEnd; 15562 15563 if ( !excludeControls ) { 15564 $tablist.prepend( prevControl + tabCount + nextControl ); 15565 } 15566 15567 if ( !excludeControls && !excludePlay ) { 15568 $tablist.append( playControl ); 15569 } 15570 $tablist.find( "a[role=button]" ).attr( "href", "javascript:;" ); 15571 15572 return isPlaying; 15573 }, 15574 15575 /** 15576 * @method drizzleAria 15577 * @param {jQuery DOM element} $panels Tabpanel groupings 15578 * @param {jQuery DOM element} $tablist Pointers to the groupings 15579 */ 15580 drizzleAria = function( $panels, $tabList ) { 15581 15582 // Let's process the elements for aria 15583 var panels = $panels.get(), 15584 tabCounter = panels.length - 1, 15585 listItems = $tabList.children().get(), 15586 listCounter = listItems.length - 1, 15587 isDetails = $panels[ 0 ].nodeName.toLowerCase() === "details", 15588 isActive, item, link, panelId, activeFound; 15589 15590 for ( ; tabCounter !== -1; tabCounter -= 1 ) { 15591 item = panels[ tabCounter ]; 15592 isActive = item.className.indexOf( "out" ) === -1; 15593 15594 if ( !isDetails || !isSmallView ) { 15595 item.setAttribute( "aria-hidden", isActive ? "false" : "true" ); 15596 item.setAttribute( "aria-expanded", isActive ? "true" : "false" ); 15597 } 15598 item.setAttribute( "aria-labelledby", item.id + "-lnk" ); 15599 } 15600 15601 activeFound = false; 15602 for ( ; listCounter !== -1; listCounter -= 1 ) { 15603 item = listItems[ listCounter ]; 15604 item.setAttribute( "role", "presentation" ); 15605 15606 isActive = item.className.indexOf( "active" ) !== -1; 15607 if ( isActive ) { 15608 activeFound = true; 15609 } else if ( listCounter === 0 && !activeFound ) { 15610 isActive = true; 15611 item.className += " active"; 15612 } 15613 15614 link = item.getElementsByTagName( "a" )[ 0 ]; 15615 panelId = link.getAttribute( "href" ).substring( 1 ); 15616 15617 link.tabIndex = isActive ? "0" : "-1"; 15618 link.setAttribute( "role", "tab" ); 15619 link.setAttribute( "aria-selected", isActive ? "true" : "false" ); 15620 link.setAttribute( "aria-controls", panelId ); 15621 link.id = panelId + "-lnk"; 15622 } 15623 $tabList.attr( "aria-live", "off" ); 15624 }, 15625 15626 /** 15627 * @method updateHash 15628 * @param {DOM element} elm Tabpanel to be referenced in the URL hash 15629 */ 15630 updateHash = function( elm ) { 15631 var elmId = elm.id; 15632 15633 wb.ignoreHashChange = true;
15634 elm.id += "-off"; 15635 window.location.hash = elmId; 15636 elm.id = elmId; 15637 wb.ignoreHashChange = false; 15638 }, 15639 15640 updateNodes = function( $panels, $controls, $next, $control ) { 15641 var $tabs = $controls.find( "[role=tab]" ), 15642 newIndex = $tabs.index( $control ) + 1, 15643 $currPanel = $panels.filter( ".in" ), 15644 $container = $next.closest( selector ), 15645 tabSettings = $container.data( componentName ).settings, 15646 mPlayers = $currPanel.find( ".wb-mltmd-inited" ).get(), 15647 mPlayersLen = mPlayers.length, 15648 mPlayer, i, j, last; 15649 15650 // Handle the direction of the slide transitions 15651 if ( $currPanel[ 0 ].className.indexOf( "slide" ) !== -1 ) { 15652 i = $panels.index( $currPanel ); 15653 j = $panels.index( $next ); 15654 last = $panels.length - 1; 15655 15656 $panels.toggleClass( 15657 "reverse", 15658 ( i > j && ( i !== last || j !== 0 ) ) || ( i === 0 && j === last ) 15659 ); 15660 } 15661 15662 $currPanel 15663 .removeClass( "in" ) 15664 .addClass( "out noheight" ) 15665 .attr( { 15666 "aria-hidden": "true", 15667 "aria-expanded": "false" 15668 } ); 15669 15670 // Pause all multimedia players in the current panel 15671 for ( i = 0; i !== mPlayersLen; i += 1 ) { 15672 mPlayer = mPlayers[ i ]; 15673 if ( mPlayer.player ) { 15674 mPlayer.player( "pause" ); 15675 } 15676 } 15677 15678 $next 15679 .removeClass( "out noheight" ) 15680 .addClass( "in" ) 15681 .attr( { 15682 "aria-hidden": "false", 15683 "aria-expanded": "true" 15684 } ); 15685 15686 $controls 15687 .find( ".active" ) 15688 .removeClass( "active" ) 15689 .children( "a" ) 15690 .attr( { 15691 "aria-selected": "false", 15692 tabindex: "-1" 15693 } ); 15694 15695 // Update the Item x of n 15696 $controls 15697 .find( ".curr-index" ) 15698 .html( newIndex ); 15699 15700 $control 15701 .attr( { 15702 "aria-selected": "true", 15703 tabindex: "0" 15704 } ) 15705 .parent() 15706 .addClass( "active" ); 15707 15708 // Update sessionStorage with the current active panel 15709 if ( !tabSettings.ignoreSession ) { 15710 try { 15711 sessionStorage.setItem( 15712 pagePath + $container.attr( "id" ) + activePanel, 15713 $next.attr( "id" ) 15714 ); 15715 } catch ( error ) { 15716 15717 /* swallow error */ 15718 } 15719 } 15720 15721 // Update the URL hash if needed 15722 if ( tabSettings.updateHash ) { 15723 updateHash( $next[ 0 ] ); 15724 } 15725 15726 // Identify that the tabbed interface/carousel was updated 15727 $container.trigger( updatedEvent, [ $next ] ); 15728 }, 15729 15730 /** 15731 * @method onPick 15732 * @param {jQuery DOM element} $sldr The plugin element 15733 * @param {jQuery DOM element} $elm The selected link from the tablist 15734 */ 15735 onPick = function( $sldr, $elm ) { 15736 var data = $sldr.data( componentName ),
15737 $panels = data.panels, 15738 $controls = data.tablist, 15739 $next = $panels.filter( "#" + $elm.attr( "aria-controls" ) ); 15740 15741 updateNodes( $panels, $controls, $next, $elm ); 15742 }, 15743 15744 /** 15745 * @method onShift 15746 * @param (jQuery event} event Current event 15747 * @param {jQuery DOM element} $elm The plugin element 15748 */ 15749 onShift = function( event, $elm ) { 15750 var data = $elm.data( componentName ), 15751 $panels = data.panels, 15752 len = $panels.length, 15753 current = $elm.find( "> .tabpanels > .in" ).prevAll( "[role=tabpanel]" ).length, 15754 autoCycle = !event.shiftto, 15755 next = current > len ? 0 : current + ( autoCycle ? 1 : event.shiftto ); 15756 15757 onSelect( $panels[ ( next > len - 1 ) ? 0 : ( next < 0 ) ? len - 1 : next ].id, autoCycle ); 15758 }, 15759 15760 /** 15761 * @method onSelect 15762 * @param (string) id Id attribute of the panel 15763 * @param (boolean) autoCycle Whether change is caused by an auto cycle 15764 */ 15765 onSelect = function( id, autoCycle ) { 15766 var panelSelector = "#" + id, 15767 $panel = $( panelSelector ), 15768 $panelSelectorLink; 15769 15770 if ( isSmallView && $panel[ 0 ].nodeName.toLowerCase() === "details" ) { 15771 $panel.children( "summary" ).trigger( $panel.attr( "open" ) ? setFocusEvent : "click" ); 15772 } else { 15773 $panelSelectorLink = $( panelSelector + "-lnk" ); 15774 $panelSelectorLink.trigger( { 15775 type: "click", 15776 which: autoCycle ? undefined : 1 15777 } ); 15778 15779 // Don't change the focus if change is cause by an auto cycle 15780 if ( !autoCycle ) { 15781 $panelSelectorLink.trigger( setFocusEvent ); 15782 } 15783 } 15784 }, 15785 15786 /** 15787 * @method onCycle 15788 * @param {jQuery DOM element} $elm The plugin element 15789 * @param {integer} shifto The item to shift to 15790 */ 15791 onCycle = function( $elm, shifto ) { 15792 $elm.trigger( { 15793 type: shiftEvent, 15794 shiftto: shifto 15795 } ); 15796 }, 15797 15798 /** 15799 * @method onResize 15800 * @param {jQuery Object} $currentElm Element being initialized (only during initialization process). 15801 */ 15802 onResize = function( $currentElm ) { 15803 var $elms, $elm, $tabPanels, $details, $detailsElm, $tablist, 15804 $openDetails, openDetailsId, activeId, $summary, $panelElm, 15805 i, len, j, len2, viewChange, isInit, isActive; 15806 15807 if ( initialized ) { 15808 isSmallView = document.documentElement.className.indexOf( smallViewPattern ) !== -1; 15809 viewChange = isSmallView !== oldIsSmallView; 15810 isInit = $currentElm.length ? true : false; 15811 15812 if ( viewChange || isInit ) { 15813 $elms = isInit ? $currentElm : $( selector ); 15814 len = $elms.length; 15815 15816 for ( i = 0; i !== len; i += 1 ) { 15817 $elm = $elms.eq( i ); 15818 $tabPanels = $elm.children( ".tabpanels" ); 15819 $details = $tabPanels.children( "details" ); 15820 len2 = $details.length; 15821 15822 if ( $details.length !== 0 ) { 15823 $tabPanels.detach(); 15824 $summary = $details.children( "summary" ); 15825 $tablist = $elm.children( "ul" ); 15826 15827 if ( isSmallView ) { 15828 15829 // Switch to small view 15830 activeId = $tablist.find( ".active a" ).attr( "href" ).substring( 1 ); 15831 for ( j = 0; j !== len2; j += 1 ) { 15832 $detailsElm = $details.eq( j ); 15833 $panelElm = $detailsElm.children( ".tgl-panel" ); 15834 isActive = $detailsElm.attr( "id" ) === activeId; 15835 15836 $detailsElm 15837 .removeAttr( "role aria-expanded aria-hidden" ) 15838 .removeClass( "fade out noheight in" ) 15839 .toggleClass( "open", isActive ); 15840 15841 $panelElm 15842 .attr( "role", "tabpanel" ) 15843 .removeAttr( "aria-expanded" ) 15844 .removeAttr( "aria-hidden" ); 15845 15846 if ( isActive ) { 15847 $detailsElm.attr( "open", "open" ); 15848 } else { 15849 $detailsElm.removeAttr( "open" ); 15850 } 15851 15852 if ( !isInit ) { 15853 $detailsElm 15854 .children( "summary" ) 15855 .attr( { 15856 "aria-expanded": isActive, 15857 "aria-selected": isActive 15858 } ); 15859 } 15860 } 15861 } else if ( oldIsSmallView ) { 15862 15863 // Switch to large view 15864 $openDetails = $details.filter( "[open]" ); 15865 openDetailsId = $openDetails.attr( "id" ); 15866 15867 $openDetails = ( $openDetails.length === 0 ? $details : $openDetails ).eq( 0 ); 15868 15869 $details 15870 .attr( { 15871 role: "tabpanel", 15872 open: "open" 15873 } ) 15874 .not( $openDetails ) 15875 .addClass( "fade out noheight wb-inv" ) 15876 .attr( { 15877 "aria-hidden": "true", 15878 "aria-expanded": "false" 15879 } ); 15880 15881 $details.children( ".tgl-panel" ).removeAttr( "role" ); 15882 15883 $openDetails 15884 .addClass( "fade in" ) 15885 .attr( { 15886 "aria-hidden": "false", 15887 "aria-expanded": "true" 15888 } ); 15889 } 15890 15891 $summary.attr( "aria-hidden", !isSmallView ); 15892 $tablist.attr( "aria-hidden", isSmallView ); 15893 15894 $elm.append( $tabPanels ); 15895 15896 // Update the tablist role 15897 if ( isSmallView && !isInit ) { 15898 $elm.attr( "role", "tablist" ); 15899 } else if ( oldIsSmallView ) { 15900 $elm 15901 .removeAttr( "role" ) 15902 .find( nestedTglPanelSelector ).removeAttr( "role" ); 15903 15904 $elm.find( "> ul [href$='" + openDetailsId + "']" ).trigger( "click" ); 15905 } 15906 } 15907 } 15908 15909 // Need timeout to account for Toggle changes 15910 if ( isInit && !isSmallView && $elms.hasClass( tabsAccordionClass ) ) { 15911 setTimeout( function() { 15912 $elms 15913 .removeAttr( "role" ) 15914 .find( nestedTglPanelSelector ).removeAttr( "role" ); 15915 }, 1 ); 15916 } 15917 } 15918 15919 oldIsSmallView = isSmallView; 15920 } 15921 15922 if ( viewChange || isInit ) { 15923 15924 // Remove wb-inv from regular tabs that were used to prevent FOUC (after 300ms delay) 15925 setTimeout( function() { 15926 $( selector + " .tabpanels > details.wb-inv" ).removeClass( "wb-inv" ); 15927 }, 300 ); 15928 } 15929 }; 15930 15931// Bind the init event of the plugin 15932$document.on( "timerpoke.wb " + initEvent + " " + shiftEvent + " " + selectEvent, selector, function( event ) { 15933 var eventTarget = event.target, 15934 eventCurrentTarget = event.currentTarget, 15935 $elm; 15936 15937 // Filter out any events triggered by descendants 15938 if ( eventCurrentTarget === eventTarget ) { 15939 switch ( event.type ) { 15940 case "timerpoke": 15941 $elm = $( eventTarget ); 15942 if ( !$elm.hasClass( componentName + "-inited" ) ) { 15943 init( event ); 15944 } else if ( $elm.hasClass( "playing" ) ) { 15945 onTimerPoke( $elm ); 15946 } 15947 break; 15948 15949 /* 15950 * Init 15951 */ 15952 case "wb-init": 15953 init( event ); 15954 break; 15955 15956 /* 15957 * Change tab panels by a delta 15958 */ 15959 case "wb-shift": 15960 onShift( event, $( eventTarget ) ); 15961 break; 15962 15963 /* 15964 * Select a specific tab panel 15965 */ 15966 case "wb-select":
15967 onSelect( event.id ); 15968 break; 15969 } 15970 } 15971 15972 /* 15973 * Since we are working with events we want to ensure that we are being passive about our control, 15974 * so returning true allows for events to always continue 15975 */ 15976 return true; 15977} ); 15978 15979/* 15980 * Tabs, next, previous and play/pause 15981 */ 15982$document.on( activateEvent, controls, function( event ) { 15983 var which = event.which, 15984 elm = event.currentTarget, 15985 className = elm.className, 15986 spaceText = i18nText.space, 15987 $elm, $sldr, sldrId, plypause, buttonText, data, isPlaying, isPlayPause; 15988 15989 // No control, alt or meta keys and only left mouse button, enter key, 15990 // space bar, escape key and arrow keys 15991 if ( !( event.ctrlKey || event.altKey || event.metaKey ) && 15992 ( !which || which === 1 || which === 13 || which === 27 || 15993 which === 32 || ( which > 36 && which < 41 ) ) ) { 15994 15995 // Stop propagation of the activate event 15996 event.preventDefault(); 15997 if ( event.stopPropagation ) { 15998 event.stopImmediatePropagation(); 15999 } else { 16000 event.cancelBubble = true; 16001 } 16002 16003 $elm = $( elm ); 16004 $sldr = $elm.closest( selector ); 16005 sldrId = $sldr[ 0 ].id; 16006 isPlaying = $sldr.hasClass( "playing" ); 16007 isPlayPause = className.indexOf( "plypause" ) !== -1; 16008 16009 // Reset ctime to 0 16010 data = $sldr.data( componentName ); 16011 data.ctime = 0; 16012 $sldr.data( componentName, data ); 16013 16014 // Stop the slider from playing unless it is already stopped 16015 // and the play button is activated 16016 if ( ( isPlaying && which ) || ( isPlayPause && !( which > 36 && which < 41 ) ) ) { 16017 if ( isPlaying ) { 16018 wb.remove( "#" + sldrId + selector ); 16019 } else { 16020 wb.add( "#" + sldrId + selector ); 16021 } 16022 16023 $sldr.toggleClass( "playing" ); 16024 isPlaying = !isPlaying; 16025 buttonText = isPlaying ? i18nText.pause : i18nText.play; 16026 16027 plypause = $sldr.find( "a.plypause" )[ 0 ]; 16028 plypause.setAttribute( "title", buttonText ); 16029 plypause.innerHTML = "<span class='glyphicon glyphicon-" + 16030 ( isPlaying ? "pause" : "play" ) + "'></span> " + 16031 "<span>" + buttonText + "</span><span class='wb-inv'>" + 16032 spaceText + i18nText.hyphen + spaceText + 16033 ( isPlaying ? i18nText.rotStop : i18nText.rotStart ) + "</span>"; 16034 } 16035 16036 // Arrow keys 16037 if ( which > 36 ) { 16038 onCycle( $sldr, which < 39 ? -1 : 1 ); 16039 $sldr.find( "> [role=tablist] .active a" ).trigger( setFocusEvent ); 16040 16041 // Not the escape key 16042 } else if ( which !== 27 ) { 16043 16044 // If the target is a tab 16045 if ( elm.getAttribute( "role" ) === "tab" ) { 16046 16047 // Only change the tabpanel if the tab is not currently selected 16048 if ( elm.getAttribute( "aria-selected" ) !== "true" ) { 16049 onPick( $sldr, $elm ); 16050 } 16051 16052 // Put focus on the tab panel if the enter key or space bar are used 16053 if ( which === 13 || which === 32 ) { 16054 $sldr.find( elm.getAttribute( "href" ) ) 16055 .trigger( setFocusEvent ); 16056 } 16057 16058 // If the target is next, previous or tab count 16059 } else if ( !isPlayPause ) { 16060 onCycle( $sldr, className.indexOf( "prv" ) !== -1 ? -1 : 1 ); 16061 } 16062 } 16063 } 16064 16065 /* 16066 * Since we are working with events we want to ensure that we are being passive about our control, 16067 * so returning true allows for events to always continue 16068 */ 16069 return true; 16070} ); 16071 16072$document.on( activateEvent, selector + " [role=tabpanel]", function( event ) { 16073 var currentTarget = event.currentTarget, 16074 which = event.which, 16075 $container; 16076 16077 // Stop propagation of the click/keydown event 16078 if ( event.stopPropagation ) { 16079 event.stopImmediatePropagation(); 16080 } else { 16081 event.cancelBubble = true; 16082 } 16083 16084 // Ctrl + Up arrow 16085 if ( event.ctrlKey && event.which === 38 ) { 16086 16087 // Move focus to the tab or summary element 16088 if ( isSmallView ) { 16089 $( currentTarget ).prev().trigger( setFocusEvent ); 16090 } else { 16091 $( currentTarget ) 16092 .closest( selector ) 16093 .find( "[href$='#" + currentTarget.id + "']" ) 16094 .trigger( setFocusEvent ); 16095 } 16096 16097 // Left mouse button click or escape key 16098 } else if ( !which || which === 1 || which === 27 ) { 16099 $container = $( event.currentTarget ).closest( selector ); 16100 16101 // Stop the carousel 16102 if ( $container.hasClass( "playing" ) ) { 16103 $container.find( ".plypause" ).trigger( "click" ); 16104 } 16105 } 16106} ); 16107 16108// Handling for links to tabs from within a panel 16109$document.on( "click", selector + " [role=tabpanel] a", function( event ) { 16110 var currentTarget = event.currentTarget, 16111 href = currentTarget.getAttribute( "href" ), 16112 which = event.which, 16113 $tabpanels, $panel, $summary; 16114 16115 // Ignore middle and right mouse buttons 16116 if ( ( !which || which === 1 ) && href.charAt( 0 ) === "#" ) { 16117 $tabpanels = $( currentTarget ).closest( ".tabpanels" ); 16118 $panel = $tabpanels.children( "#" + wb.jqEscape( href.substring( 1 ) ) ); 16119 if ( $panel.length !== 0 ) { 16120 event.preventDefault(); 16121 $summary = $panel.children( "summary" ); 16122 if ( $summary.length !== 0 && $summary.attr( "aria-hidden" ) !== "true" ) { 16123 $summary.trigger( "click" ); 16124 } else { 16125 $tabpanels.parent().find( href + "-lnk" ).trigger( "click" ); 16126 } 16127 } 16128 } 16129} ); 16130 16131// These events only fire at the document level 16132$document.on( wb.resizeEvents, onResize ); 16133 16134$document.on( activateEvent, selector + " > .tabpanels > details > summary", function( event ) { 16135 var which = event.which, 16136 details = event.currentTarget.parentNode, 16137 $details, $container, tabSettings; 16138 16139 if ( !( event.ctrlKey || event.altKey || event.metaKey ) && 16140 ( !which || which === 1 || which === 13 || which === 32 ) ) { 16141 16142 $container = $( details.parentNode.parentNode ); 16143 $details = $( details ); 16144 tabSettings = $container.data( componentName ).settings; 16145 16146 // Update sessionStorage with the current active panel 16147 if ( !tabSettings.ignoreSession ) { 16148 try { 16149 sessionStorage.setItem( 16150 pagePath + $container.attr( "id" ) + activePanel, 16151 details.id 16152 ); 16153 } catch ( error ) { 16154 16155 /* swallow error */ 16156 } 16157 } 16158 16159 // Update the URL hash if needed 16160 if ( tabSettings.updateHash ) { 16161 updateHash( details ); 16162 } 16163 16164 // Identify that the tabbed interface accordion was updated 16165 // if the panel was not already open 16166 if ( !$details.attr( "open" ) ) { 16167 $container.trigger( updatedEvent, [ $details ] ); 16168 } 16169 } 16170} ); 16171 16172// Change the panel based upon an external link click 16173$document.on( "click", ".wb-tabs-ext", function( event ) { 16174 var which = event.which; 16175 16176 // Ignore middle and right mouse buttons 16177 if ( !which || which === 1 ) { 16178 event.preventDefault(); 16179 onSelect( event.currentTarget.getAttribute( "href" ).substring( 1 ) ); 16180 } 16181} ); 16182 16183// Add the timer poke to initialize the plugin
16184wb.add( selector ); 16185 16186} )( jQuery, window, wb ); 16187 16188/** 16189 * @title WET-BOEW Tag filter 16190 * @overview Filter based content tagging 16191 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 16192 * @author @duboisp 16193 */ 16194( function( $, window, document, wb ) { 16195"use strict"; 16196 16197let wait; 16198 16199const componentName = "wb-tagfilter", 16200 selector = ".provisional." + componentName, 16201 selectorCtrl = "." + componentName + "-ctrl", 16202 initEvent = "wb-init" + selector, 16203 $document = wb.doc, 16204 filterOutClass = "wb-fltr-out", 16205 tgFilterOutClass = "wb-tgfltr-out", 16206 itemsWrapperClass = "wb-tagfilter-items", 16207 noResultWrapperClass = "wb-tagfilter-noresult", 16208 16209 init = function( event ) { 16210 const elm = wb.init( event, componentName, selector ); 16211 16212 if ( elm ) { 16213 const filterControls = elm.querySelectorAll( selectorCtrl ), 16214 taggedItems = elm.querySelectorAll( "[data-wb-tags]" ), 16215 taggedItemsWrapper = elm.querySelector( "." + itemsWrapperClass ), 16216 noResultWrapper = elm.querySelector( "." + noResultWrapperClass ); 16217 16218 elm.items = []; 16219 elm.filters = {}; 16220 elm.activeFilters = []; 16221 16222 if ( taggedItemsWrapper ) { 16223 taggedItemsWrapper.id = taggedItemsWrapper.id || wb.getId(); // Ensure the element has an ID 16224 taggedItemsWrapper.setAttribute( "aria-live", "polite" ); 16225 } 16226 16227 // Handle filters 16228 if ( filterControls.length ) { 16229 elm.filters = buildFiltersObj( filterControls ); 16230 16231 filterControls.forEach( function( item ) { 16232 item.setAttribute( "aria-controls", taggedItemsWrapper.id ); 16233 } ); 16234 } 16235 16236 // Handle tagged items 16237 if ( taggedItems.length ) { 16238 elm.items = buildTaggedItemsArr( taggedItems ); 16239 } 16240 16241 // Add accessibility to no result element 16242 if ( noResultWrapper ) { 16243 noResultWrapper.setAttribute( "role", "status" ); 16244 } 16245 16246 // Update list of visible items (in case of predefined filters) 16247 update( elm ); 16248 16249 wb.ready( $( elm ), componentName ); 16250 } 16251 }, 16252 16253 // Add every tagged item to an array of objects with their DOM ID, list of associated tags, and default isMatched attribute 16254 buildTaggedItemsArr = function( taggedItems ) { 16255 let taggedItemsArr = []; 16256 16257 taggedItems.forEach( function( taggedItem ) { 16258 let tagsList = taggedItem.dataset.wbTags.split( " " ); 16259 16260 if ( !taggedItem.id ) { 16261 taggedItem.setAttribute( "id", wb.getId() ); 16262 } 16263 16264 taggedItemsArr.push( { 16265 id: taggedItem.id, 16266 tags: tagsList, 16267 isMatched: true, 16268 itemText: taggedItem.innerText.toLowerCase() 16269 } ); 16270 } ); 16271 16272 return taggedItemsArr; 16273 }, 16274 16275 // Build list of available filters using all filters grouped by filter name 16276 buildFiltersObj = function( filterControls ) { 16277 let filtersObj = {}; 16278 16279 filterControls.forEach( function( control ) { 16280 if ( !control.name ) { 16281 console.error( componentName + ": Filter controls require an attribute 'name'." ); 16282 } 16283 16284 switch ( control.type ) { 16285 case "checkbox": 16286 case "radio": 16287 if ( !( control.name in filtersObj ) ) { 16288 filtersObj[ control.name ] = [ ]; 16289 } 16290 16291 filtersObj[ control.name ].push( { 16292 isChecked: control.checked, 16293 type: control.type, 16294 value: control.value 16295 } ); 16296 16297 break; 16298 case "select-one": 16299 filtersObj[ control.name ] = [ { 16300 type: control.type, 16301 value: control.value 16302 } ]; 16303 break; 16304 } 16305 } ); 16306 16307 return filtersObj; 16308 }, 16309 16310 // Update array of active filters according to UI selected controls 16311 refineFilters = function( instance ) { 16312 instance.activeFilters = [ ]; // Clear active filters 16313 16314 for ( let filterGroupName in instance.filters ) { 16315 let filterGroup = instance.filters[ filterGroupName ], 16316 filterGroupChkCnt = filterGroup.filter( function( o ) { 16317 return o.isChecked === true; 16318 } ).length, 16319 filterGroupActiveFilters = [ ]; 16320 16321 switch ( filterGroup[ 0 ].type ) { 16322 case "checkbox": 16323 if ( filterGroupChkCnt > 0 ) { 16324 filterGroup.forEach( function( filterItem ) { 16325 if ( filterItem.isChecked ) { 16326 filterGroupActiveFilters.push( filterItem.value ); 16327 } 16328 } ); 16329 } 16330 break; 16331 16332 case "radio": 16333 if ( filterGroupChkCnt > 0 ) { 16334 for ( let filterItem of filterGroup ) { 16335 if ( filterItem.isChecked === true ) { 16336 if ( filterItem.value !== "" ) { 16337 filterGroupActiveFilters.push( filterItem.value ); 16338 } 16339 break; 16340 } 16341 } 16342 } else { 16343 console.warn( componentName + ": Radio button groups must have a default selected value. If you want to display all items, add an option called \"All\" with an empty value." ); 16344 } 16345 break; 16346 16347 case "select-one": 16348 if ( filterGroup[ 0 ].value !== "" ) { 16349 filterGroupActiveFilters.push( filterGroup[ 0 ].value ); 16350 } 16351 break; 16352 } 16353 16354 instance.activeFilters.push( filterGroupActiveFilters ); 16355 } 16356 }, 16357 16358 // Match tagged items to active filters and only return items that have an active filter in every filter group 16359 matchItemsToFilters = function( instance ) { 16360 let filtersGroups = instance.activeFilters.length; 16361
16362 instance.items.forEach( function( item ) { 16363 let matchCount = 0; 16364 16365 instance.activeFilters.forEach( function( filterGroup ) { 16366 if ( filterGroup.length === 0 ) { 16367 matchCount++; 16368 } else { 16369 let itemIncludesFilter = filterGroup.filter( function( f ) { 16370 return item.tags.includes( f ); 16371 } ).length; 16372 16373 if ( itemIncludesFilter ) { 16374 matchCount++; 16375 } 16376 } 16377 } ); 16378 16379 matchCount === filtersGroups ? item.isMatched = true : item.isMatched = false; 16380 } ); 16381 }, 16382 16383 // Update list of visible items according to their "isMatched" property 16384 updateDOMItems = function( instance ) { 16385 const updatedItemsList = instance.items.forEach( function( item ) { 16386 let domItem = instance.querySelector( "#" + item.id ), 16387 matched = item.isMatched; 16388 16389 if ( matched ) { 16390 if ( domItem.classList.contains( tgFilterOutClass ) ) { 16391 domItem.classList.remove( tgFilterOutClass ); 16392 } 16393 } else { 16394 if ( !domItem.classList.contains( tgFilterOutClass ) ) { 16395 domItem.classList.add( tgFilterOutClass ); 16396 } 16397 } 16398 } ); 16399 16400 return updatedItemsList; 16401 }, 16402 16403 // Utility method to update stored active filters, update stored items and update visibility of tagged items 16404 update = function( instance ) { 16405 refineFilters( instance ); 16406 matchItemsToFilters( instance ); 16407 updateDOMItems( instance ); 16408 16409 $( instance ).trigger( "wb-contentupdated", [ { source: componentName } ] ); 16410 }; 16411 16412// When a filter is updated 16413$document.on( "change", selectorCtrl, function( event ) { 16414 let control = event.currentTarget, 16415 filterType = control.type, 16416 filterName = control.name, 16417 filterValue = control.value, 16418 elm = control.closest( selector ), 16419 filterGroup = elm.filters[ filterName ]; 16420 16421 switch ( filterType ) { 16422 case "checkbox": 16423 16424 // Update virtual filter to the new state 16425 filterGroup.find( function( filter ) { 16426 return filter.value === filterValue; 16427 } ).isChecked = !!control.checked; 16428 break; 16429 16430 case "radio": 16431 16432 // Set all virtual radio items to unchecked 16433 filterGroup.forEach( function( filterItem ) { 16434 filterItem.isChecked = false; 16435 } ); 16436 16437 // Set selected radio button's associated virtual filter to checked 16438 filterGroup.find( function( filter ) { 16439 return filter.value === filterValue; 16440 } ).isChecked = true; 16441 break; 16442 16443 case "select-one": 16444 16445 // Update virtual filter to the new value 16446 filterGroup[ 0 ].value = filterValue; 16447 break; 16448 } 16449 16450 // Update list of visible items 16451 update( elm ); 16452} ); 16453 16454$document.on( "wb-contentupdated", selector, function( event, data ) { 16455 let that = this, 16456 supportsHas = window.getComputedStyle( document.documentElement ).getPropertyValue( "--supports-has" ); // Get "--supports-has" CSS property 16457 16458 // Reinitialize tagfilter if content on the page has been updated by another plugin 16459 if ( data.source !== componentName ) { 16460 if ( wait ) { 16461 clearTimeout( wait ); 16462 } 16463 16464 wait = setTimeout( function() { 16465 that.classList.remove( "wb-init", componentName + "-inited" ); 16466 $( that ).trigger( "wb-init." + componentName ); 16467 }, 100 ); 16468 } 16469 16470 // Show no result message if on Firefox -- Remove once Firefox supports ":has()" 16471 if ( supportsHas === "false" ) { 16472 let noResultItem = this.querySelector( "." + noResultWrapperClass ); 16473 16474 if ( noResultItem && this.items.length > 0 ) { 16475 let visibleItems = this.querySelectorAll( "." + itemsWrapperClass + " " + "[data-wb-tags]:not(." + tgFilterOutClass + ", ." + filterOutClass + ")" ); 16476 16477 if ( visibleItems.length < 1 ) { 16478 noResultItem.style.display = "block"; 16479 } else { 16480 noResultItem.style.display = "none"; 16481 } 16482 } 16483 } 16484} ); 16485 16486$document.on( "timerpoke.wb " + initEvent, selector, init ); 16487
16488wb.add( selector ); 16489 16490} )( jQuery, window, document, wb ); 16491 16492/** 16493 * @title WET-BOEW Text highlighting 16494 * @overview Automatically highlights certain words on a Web page. The highlighted words can be selected via the query string. 16495 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 16496 * @author @pjackson28 16497 */ 16498( function( $, window, document, wb ) { 16499"use strict"; 16500 16501/* 16502 * Variable and function definitions. 16503 * These are global to the plugin - meaning that they will be initialized once per page, 16504 * not once per instance of plugin on the page. So, this is a good place to define 16505 * variables that are common to all instances of the plugin on a page. 16506 */ 16507var componentName = "wb-txthl", 16508 selector = "." + componentName, 16509 initEvent = "wb-init" + selector, 16510 $document = wb.doc, 16511 16512 /** 16513 * @method init 16514 * @param {jQuery Event} event Event that triggered the function call 16515 */ 16516 init = function( event ) { 16517 16518 // Start initialization 16519 // returns DOM object = proceed with init 16520 // returns undefined = do not proceed with init (e.g., already initialized) 16521 var elm = wb.init( event, componentName, selector ), 16522 params = wb.pageUrlParts.params, 16523 searchCriteria, newText; 16524 16525 if ( elm ) { 16526 if ( event.txthl ) { 16527 searchCriteria = $.isArray( event.txthl ) ? event.txthl.join( "|" ) : event.txthl; 16528 } else if ( params && params.txthl ) { 16529 searchCriteria = decodeURIComponent( 16530 wb.pageUrlParts.params.txthl 16531 .replace( /^\s+|\s+$|\|+|"|\(|\)/g, "" ).replace( /\++/g, "|" ) 16532 ); 16533 } 16534 16535 if ( searchCriteria ) { 16536 16537 // Strip html 16538 var map = { "&": "&", "<": "<", ">": ">", "\"": """, "'": "'" }; 16539 searchCriteria = searchCriteria.replace( /[&<>"']/g, function( m ) { 16540 return map[ m ]; 16541 } ); 16542 16543 // Make sure that we're not checking for text within a tag; only the text outside of tags. 16544 searchCriteria = "(?=([^>]*<))([\\s'])?(" + searchCriteria + ")(?!>)"; 16545 16546 newText = elm.innerHTML.replace( new RegExp( searchCriteria, "gi" ), function( match, group1, group2, group3 ) { 16547 return ( !group2 ? "" : group2 ) + "<mark>" + group3 + "</mark>"; 16548 } ); 16549 elm.innerHTML = newText; 16550 } 16551 16552 // Identify that initialization has completed 16553 wb.ready( $( elm ), componentName ); 16554 } 16555 }; 16556 16557// Bind the init event of the plugin 16558$document.on( "timerpoke.wb " + initEvent, selector, init ); 16559 16560// Add the timer poke to initialize the plugin 16561wb.add( selector ); 16562 16563} )( jQuery, window, document, wb ); 16564 16565/** 16566 * @title WET-BOEW Toggle 16567 * @overview Plugin that allows a link to toggle elements between on and off states. 16568 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 16569 * @author @patheard 16570 */ 16571( function( $, window, wb ) { 16572"use strict"; 16573 16574/* 16575 * Variable and function definitions. 16576 * These are global to the plugin - meaning that they will be initialized once per page, 16577 * not once per instance of plugin on the page. So, this is a good place to define 16578 * variables that are common to all instances of the plugin on a page. 16579 */ 16580var componentName = "wb-toggle", 16581 selector = "." + componentName, 16582 selectorPanel = ".tgl-panel", 16583 selectorTab = ".tgl-tab", 16584 initEvent = "wb-init" + selector, 16585 toggleEvent = "toggle" + selector, 16586 toggledEvent = "toggled" + selector, 16587 setFocusEvent = "setfocus.wb", 16588 states = {}, 16589 $document = wb.doc, 16590 $window = wb.win, 16591 16592 defaults = { 16593 stateOn: "on", 16594 stateOff: "off" 16595 }, 16596 16597 /** 16598 * @method init 16599 * @param {jQuery Event} event Event that triggered the function call 16600 */ 16601 init = function( event ) { 16602 16603 // Start initialization 16604 // returns DOM object = proceed with init 16605 // returns undefined = do not proceed with init (e.g., already initialized) 16606 var link = wb.init( event, componentName, selector, true ), 16607 $link, data, persistState; 16608 16609 if ( link ) { 16610 16611 // Merge the elements settings with the defaults 16612 $link = $( link ); 16613 data = $.extend( {}, defaults, $link.data( "toggle" ) ); 16614 $link.data( "toggle", data ); 16615 16616 // Add aria attributes of the toggle element 16617 initAria( link, data ); 16618 16619 // Persist toggle state across page loads 16620 if ( data.persist ) { 16621 persistState = initPersist( $link, data ); 16622 } 16623 16624 // Toggle behaviour when the page is printed 16625 if ( data.print ) { 16626 initPrint( $link, data ); 16627 } 16628 16629 // Set the initial state if the state has been specified and 16630 // the persistent state has not been set 16631 if ( !persistState && data.state ) { 16632 setState( $link, data, data.state ); 16633 } 16634 16635 // Identify that initialization has completed 16636 wb.ready( $link, componentName ); 16637 } 16638 }, 16639 16640 /** 16641 * Initialize the aria attributes for a given toggle element 16642 * @param {DOM element} link The toggle element to initialize 16643 * @param {Object} data Simple key/value data object passed when the event was triggered 16644 */ 16645 initAria = function( link, data ) { 16646 var i, len, elm, elms, parent, tabs, tab, panel, isOpen, wrapper,
16647 ariaControls = "", 16648 hasOpen = false; 16649 16650 // Group toggle elements with a parent are assumed to be a tablist 16651 if ( data.group != null && data.parent != null ) { //eslint-disable-line no-eq-null 16652 parent = document.querySelector( data.parent ); 16653 16654 // Check that the tablist widget hasn't already been initialized 16655 if ( parent.getAttribute( "role" ) !== "tablist" ) { 16656 16657 // Only apply the tablist role if the parent is not the tabbed interface container 16658 // or the page is currently in "smallview", "xsmallview" or "xxsmallview" 16659 if ( parent.className.indexOf( "wb-tabs" ) === -1 || 16660 document.documentElement.className.indexOf( "smallview" ) !== -1 ) { 16661 parent.setAttribute( "role", "tablist" ); 16662 } 16663 16664 elms = parent.querySelectorAll( data.group ); 16665 tabs = parent.querySelectorAll( data.group + " " + selectorTab ); 16666 16667 // Set the tab and panel aria attributes 16668 for ( i = 0, len = elms.length; i !== len; i += 1 ) { 16669 elm = elms[ i ]; 16670 tab = tabs[ i ]; 16671 panel = elm.querySelector( selectorPanel ); 16672 16673 // Check if the element is toggled on based on the 16674 // open attribute or "on" CSS class 16675 isOpen = elm.nodeName.toLowerCase() === "details" ? 16676 !!elm.hasAttribute( "open" ) : 16677 ( " " + tab.className + " " ).indexOf( " " + data.stateOn + " " ); 16678 if ( isOpen ) { 16679 hasOpen = true; 16680 } 16681 16682 if ( !tab.getAttribute( "id" ) ) { 16683 tab.setAttribute( "id", wb.getId() ); 16684 } 16685 16686 //Details and summary don't support aria roles and some aria attribute that is why they are wrapped in a div 16687 if ( elm.nodeName.toLowerCase() === "details" && elm.parentNode.className.toLowerCase().indexOf( "accordion" ) > -1 ) { 16688 wrapper = document.createElement( "div" ); 16689 wrapper.classList.add( "tgl-tab" ); 16690 wrapper.setAttribute( "role", "tab" ); 16691 wrapper.setAttribute( "aria-selected", isOpen ); 16692 wrapper.setAttribute( "tabindex", isOpen ? "0" : "-1" ); 16693 wrapper.setAttribute( "aria-posinset", i + 1 ); 16694 wrapper.setAttribute( "aria-setsize", len ); 16695 parent.replaceChild( wrapper, elm ); 16696 wrapper.appendChild( elm ); 16697 } else { 16698 tab.setAttribute( "role", "tab" ); 16699 tab.setAttribute( "aria-selected", isOpen ); 16700 tab.setAttribute( "tabindex", isOpen ? "0" : "-1" ); 16701 tab.setAttribute( "aria-posinset", i + 1 ); 16702 tab.setAttribute( "aria-setsize", len ); 16703 } 16704 panel.setAttribute( "role", "tabpanel" ); 16705 panel.setAttribute( "aria-labelledby", tab.getAttribute( "id" ) ); 16706 panel.setAttribute( "aria-expanded", isOpen ); 16707 panel.setAttribute( "aria-hidden", !isOpen ); 16708 } 16709 16710 // No open panels so put the first summary in the tab order 16711 if ( !hasOpen ) { 16712 tabs[ 0 ].setAttribute( "tabindex", "0" ); 16713 } 16714 } 16715 16716 // Set the elements this link controls 16717 } else { 16718 elms = getElements( link, data ); 16719 for ( i = 0, len = elms.length; i !== len; i += 1 ) { 16720 elm = elms[ i ]; 16721 if ( !elm.id ) { 16722 elm.id = wb.getId(); 16723 } 16724 ariaControls += elm.id + " "; 16725 } 16726 link.setAttribute( "aria-controls", ariaControls.slice( 0, -1 ) ); 16727 } 16728 }, 16729 16730 /** 16731 * Initializes persistent behaviour of the toggle element 16732 * @param {jQuery Object} $link The toggle element to initialize 16733 * @param {Object} data Simple key/value data object passed when the event was triggered 16734 * @returns {string} Persistent state 16735 */ 16736 initPersist = function( $link, data ) { 16737 var state, 16738 link = $link[ 0 ]; 16739 16740 // Store the persistence type and key for later use 16741 data.persist = data.persist === "session" ? sessionStorage : localStorage; 16742 data.persistKey = componentName + ( data.group ? data.group : "" ) + link.id; 16743 16744 // If there's a saved toggle state, trigger the change to that state 16745 state = data.persist.getItem( data.persistKey ); 16746 if ( state ) { 16747 $link.trigger( toggleEvent, $.extend( {}, data, { type: state } ) ); 16748 } 16749 16750 return state; 16751 }, 16752 16753 /** 16754 * Initialize open on print behaviour of the toggle element 16755 * @param {jQuery Object} $link The toggle element to initialize 16756 * @param {Object} data Simple key/value data object passed when the event was triggered 16757 */ 16758 initPrint = function( $link, data ) { 16759 var mediaQuery, 16760 printEvent = "beforeprint"; 16761 16762 $window.on( printEvent, function() { 16763 $link.trigger( toggleEvent, $.extend( {}, data, { type: data.print } ) ); 16764 } ); 16765 16766 // Fallback for browsers that don't support print events 16767 if ( window.matchMedia ) { 16768 mediaQuery = window.matchMedia( "print" ); 16769 if ( mediaQuery.addListener ) { 16770 mediaQuery.addListener( function( query ) { 16771 if ( query.matches ) { 16772 $window.trigger( printEvent ); 16773 } 16774 } ); 16775 } 16776 } 16777 }, 16778 16779 /** 16780 * Click handler for the toggle links 16781 * @param {jQuery Event} event The event that triggered this invocation 16782 */ 16783 click = function( event ) { 16784 var $link = $( event.currentTarget ); 16785 16786 $link.trigger( toggleEvent, $link.data( "toggle" ) ); 16787 event.preventDefault(); 16788 16789 // Assign focus to eventTarget 16790 $link.trigger( setFocusEvent ); 16791 }, 16792 16793 /** 16794 * Toggles the elements a link controls between the on and off states. 16795 * @param {jQuery Event} event The event that triggered this invocation 16796 * @param {Object} data Simple key/value data object passed when the event was triggered 16797 */ 16798 toggle = function( event, data ) { 16799 if ( event.namespace === componentName ) { 16800 var dataGroup, key, $elmsGroup, 16801 isGroup = !!data.group, 16802 isPersist = !!data.persist, 16803 isTablist = isGroup && !!data.parent, 16804 link = event.currentTarget, 16805 $link = $( link ), 16806 stateFrom = getState( $link, data ), 16807 isToggleOn = stateFrom === data.stateOff, 16808 stateTo = isToggleOn ? data.stateOn : data.stateOff, 16809 $elms = isTablist ? $link.parent( data.group ) : getElements( link, data ); 16810 16811 // Group toggle behaviour: only one element in the group open at a time. 16812 if ( isGroup ) { 16813 16814 // Get the grouped elements using data.group as the CSS selector 16815 dataGroup = $.extend( {}, data, { selector: data.group } ); 16816 $elmsGroup = getElements( link, dataGroup ); 16817 16818 // Set the toggle state to "off". For tab lists, this is stored on the tab element 16819 setState( isTablist ? $( data.parent ).find( selectorTab ) : $elmsGroup, 16820 dataGroup, data.stateOff ); 16821 16822 // Toggle all grouped elements to "off" 16823 $elmsGroup.wb( "toggle", data.stateOff, data.stateOn ); 16824 $elmsGroup.trigger( toggledEvent, { 16825 isOn: false, 16826 isTablist: isTablist, 16827 elms: $elmsGroup 16828 } ); 16829 16830 // Remove all grouped persistence keys 16831 if ( isPersist ) { 16832 for ( key in data.persist ) { 16833 if ( key.indexOf( componentName + data.group ) === 0 ) { 16834 data.persist.removeItem( key ); 16835 } 16836 } 16837 } 16838 } 16839 16840 // Set the toggle state. For tab lists, this is set on the tab element 16841 setState( isTablist ? $link : $elms, data, stateTo ); 16842 16843 // Toggle all elements to the requested state 16844 $elms.wb( "toggle", stateTo, stateFrom ); 16845 $elms.trigger( toggledEvent, { 16846 isOn: isToggleOn, 16847 isTablist: isTablist, 16848 elms: $elms 16849 } ); 16850 16851 // Ensure that last focused element in the accordion remains keyboard focusable 16852 // whether it is collapsed or not 16853 if ( isGroup ) { 16854 $elms.find( "summary" ).attr( { "tabindex": "0" } ); 16855 } 16856 16857 // Store the toggle link's current state if persistence is turned on. 16858 // Try/catch is required to address exceptions thrown when using BB10 or 16859 // private browsing in iOS. 16860 if ( isPersist ) { 16861 try { 16862 data.persist.setItem( data.persistKey, stateTo ); 16863 } catch ( error ) { 16864 16865 /* swallow error */ 16866 } 16867 } 16868 } 16869 }, 16870 16871 /** 16872 * Sets the required property and attribute for toggling open/closed a details element 16873 * @param {jQuery Event} event The event that triggered this invocation 16874 * @param {Object} data Simple key/value data object passed when the event was triggered 16875 */ 16876 toggleDetails = function( event, data ) { 16877 if ( event.namespace === componentName && event.target === event.currentTarget ) { 16878 var top, 16879 isOn = data.isOn, 16880 $elms = data.elms, 16881 $this = $( this ), 16882 $detail = $this.is( "summary" ) ? $this.parent() : $this; 16883 16884 // Stop propagation of the toggleDetails event 16885 if ( event.stopPropagation ) { 16886 event.stopImmediatePropagation(); 16887 } else { 16888 event.cancelBubble = true; 16889 } 16890 16891 $detail.prop( "open", isOn ); 16892 16893 if ( data.isTablist ) { 16894 16895 // Set the required aria attributes 16896 $elms.find( selectorTab ).parents( selectorTab ).attr( { 16897 "aria-selected": isOn, 16898 tabindex: isOn ? "0" : "-1" 16899 } ); 16900 $elms.find( selectorPanel ).attr( { 16901 "aria-hidden": !isOn, 16902 "aria-expanded": isOn 16903 } ); 16904 16905 // Check that the top of the open element is in view. 16906 if ( isOn && $elms.length === 1 ) { 16907 top = $elms.offset().top; 16908 if ( top < $window.scrollTop() ) { 16909 $window.scrollTop( top ); 16910 } 16911 } 16912 } 16913 } 16914 }, 16915 16916 /** 16917 * Returns the elements a given toggle element controls. 16918 * @param {DOM element} link Toggle element that was clicked 16919 * @param {Object} data Simple key/value data object passed when the event was triggered 16920 * @returns {jQuery Object} DOM elements the toggle link controls 16921 */ 16922 getElements = function( link, data ) { 16923 var selector = data.selector || link, 16924 parent = data.parent || null; 16925 16926 return parent !== null ? $( parent ).find( selector ) : $( selector ); 16927 }, 16928 16929 /** 16930 * Gets the current toggle state of elements controlled by the given link. 16931 * @param {jQuery Object} $link Toggle link that was clicked 16932 * @param {Object} data Simple key/value data object passed when the event was triggered 16933 */ 16934 getState = function( $link, data ) { 16935 var parent = data.parent, 16936 selector = data.selector, 16937 type = data.type; 16938 16939 // Get opposite state of the type. Toggle reverses this 16940 // to the requested state. 16941 if ( type ) { 16942 return type === "on" ? data.stateOff : data.stateOn; 16943 16944 // <details> elements use the open attribute to determine state 16945 } else if ( $link[ 0 ].nodeName.toLowerCase() === "summary" ) { 16946 return $link.parent().attr( "open" ) ? data.stateOn : data.stateOff; 16947 16948 // When no selector, use the data attribute of the link 16949 } else if ( !selector ) { 16950 return $link.data( componentName + "-state" ) || data.stateOff; 16951 16952 // When toggling multiple <details> elements, state is "off" if any are collapsed 16953 } else if ( selector === "details" && !type ) { 16954 var anyCollapsed = false;
16955 getElements( $link, data ).each( function() { 16956 if ( !$( this ).attr( "open" ) ) { 16957 anyCollapsed = true; 16958 } 16959 } ); 16960 return anyCollapsed ? data.stateOff : data.stateOn; 16961 16962 // Get the current on/off state of the elements specified by the selector and parent 16963 } else if ( Object.prototype.hasOwnProperty.call( states, selector ) ) { 16964 return Object.prototype.hasOwnProperty.call( states[ selector ], parent ) ? 16965 states[ selector ][ parent ] : 16966 states[ selector ].all; 16967 } 16968 return data.stateOff; 16969 }, 16970 16971 /* 16972 * Sets the current toggle state of elements controlled by the given link. 16973 * @param {DOM element} link Toggle link that was clicked 16974 * @param {Object} data Simple key/value data object passed when the event was triggered 16975 * @param {String} state The current state of the elements: "on" or "off" 16976 */ 16977 setState = function( $elms, data, state ) { 16978 var prop, 16979 parent = data.parent, 16980 selector = data.selector, 16981 elmsState = states[ selector ]; 16982 16983 if ( selector ) { 16984 16985 // Check the selector object has been created 16986 if ( !elmsState ) { 16987 elmsState = { all: data.stateOff }; 16988 states[ selector ] = elmsState; 16989 } 16990 16991 // If there's a parent, set its state 16992 if ( parent ) { 16993 elmsState[ parent ] = state; 16994 16995 // No parent means set all states for the given selector. This is 16996 // because toggle links that apply to the entire DOM also affect 16997 // links that are restricted by parent. 16998 } else { 16999 for ( prop in elmsState ) { 17000 if ( Object.prototype.hasOwnProperty.call( elmsState, prop ) ) { 17001 elmsState[ prop ] = state; 17002 } 17003 } 17004 } 17005 } 17006 17007 // Store the state on the elements as well. This allows a link to toggle itself. 17008 $elms.data( componentName + "-state", state ); 17009 }; 17010 17011// Bind the plugin's events 17012$document.on( "timerpoke.wb " + initEvent + " " + toggleEvent + 17013" click", selector, function( event, data ) { 17014 17015 var eventType = event.type; 17016 17017 switch ( eventType ) { 17018 case "click": 17019 click( event ); 17020 break; 17021 17022 case "toggle": 17023 toggle( event, data ); 17024 break; 17025 17026 case "timerpoke": 17027 case "wb-init": 17028 init( event ); 17029 break; 17030 } 17031} ); 17032 17033$document.on( toggledEvent, "summary, details", toggleDetails ); 17034 17035// Keyboard handling for the accordion 17036$document.on( "keydown", selectorTab, function( event ) { 17037 var which = event.which, 17038 data, $elm, $parent, $group, $newPanel, index; 17039 17040 if ( !event.ctrlKey && which > 34 && which < 41 ) { 17041 event.preventDefault(); 17042 $elm = $( event.currentTarget ); 17043 data = $elm.data( "toggle" ); 17044 $parent = $document.find( data.parent ); 17045 $group = $parent.find( data.group ); 17046 index = $group.index( $elm.parent() ); 17047 17048 switch ( which ) { 17049 17050 // End 17051 case 35: 17052 $newPanel = $group.last(); 17053 break; 17054 17055 // Home 17056 case 36: 17057 $newPanel = $group.first(); 17058 break; 17059 17060 // Left / up arrow 17061 case 37: 17062 case 38: 17063 if ( index === 0 ) { 17064 $newPanel = $group.last(); 17065 } else { 17066 $newPanel = $group.eq( index - 1 ); 17067 } 17068 break; 17069 17070 // Right / down arrow 17071 case 39: 17072 case 40: 17073 if ( index === $group.length - 1 ) { 17074 $newPanel = $group.first(); 17075 } else { 17076 $newPanel = $group.eq( index + 1 ); 17077 } 17078 break; 17079 } 17080 17081 $newPanel 17082 .children( "summary" ) 17083 .trigger( setFocusEvent ); 17084 } 17085} ); 17086 17087$document.on( "keydown", selectorPanel, function( event ) { 17088 17089 // Ctrl + Up arrow 17090 if ( event.ctrlKey && event.which === 38 ) { 17091 17092 // Move focus to the summary element 17093 $( event.currentTarget ) 17094 .prev() 17095 .trigger( setFocusEvent ); 17096 } 17097} ); 17098 17099// Add the timer poke to initialize the plugin 17100wb.add( selector ); 17101 17102} )( jQuery, window, wb ); 17103 17104/** 17105 * @title WET-BOEW Twitter embedded timeline 17106 * @overview Helps with implementing Twitter embedded timelines. 17107 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 17108 * @author @pjackson28 17109 */ 17110( function( $, window, wb ) { 17111"use strict"; 17112 17113/* 17114 * Variable and function definitions. 17115 * These are global to the plugin - meaning that they will be initialized once per page, 17116 * not once per instance of plugin on the page. So, this is a good place to define 17117 * variables that are common to all instances of the plugin on a page. 17118 */
17119var componentName = "wb-twitter", 17120 selector = "." + componentName, 17121 initEvent = "wb-init" + selector, 17122 $document = wb.doc, 17123 17124 /** 17125 * @method init 17126 * @param {jQuery Event} event Event that triggered the function call 17127 */ 17128 init = function( event ) { 17129 17130 // Start initialization 17131 // returns DOM object = proceed with init 17132 // returns undefined = do not proceed with init (e.g., already initialized) 17133 var eventTarget = wb.init( event, componentName, selector ), 17134 protocol = wb.pageUrlParts.protocol; 17135 17136 if ( eventTarget ) { 17137 const twitterLink = eventTarget.firstElementChild; 17138 17139 // Ignore IE11 17140 // Note: Twitter's widget no longer supports it... 17141 if ( wb.ie11 ) { 17142 wb.ready( $( eventTarget ), componentName ); 17143 return; 17144 } 17145 17146 // If the plugin container's first child element is a Twitter link... 17147 if ( twitterLink && twitterLink.matches( "a.twitter-timeline" ) ) { 17148 const loadingDiv = document.createElement( "div" ); 17149 let observer; 17150 17151 // Add a loading icon below the link 17152 loadingDiv.className = "twitter-timeline-loading"; 17153 twitterLink.after( loadingDiv ); 17154 17155 // Remove the loading icon after the timeline widget appears 17156 // Note: Twitter's widget script removes "a.twitter-timeline" upon filling-in the timeline's content... at which point the loading icon is no longer useful 17157 observer = new MutationObserver( function( mutations ) { 17158 17159 // Check for DOM mutations 17160 mutations.forEach( function( mutation ) { 17161 17162 // Deal only with removed HTML nodes 17163 mutation.removedNodes.forEach( function( removedNode ) { 17164 17165 // If the removed node was a Twitter link, remove its adjacent loading icon and stop observing 17166 if ( removedNode === twitterLink && mutation.nextSibling === loadingDiv ) { 17167 loadingDiv.remove(); 17168 observer.disconnect(); 17169 } 17170 } ); 17171 } ); 17172 } ); 17173 17174 // Observe changes to the plugin container's direct child elements 17175 observer.observe( eventTarget, { 17176 childList: true 17177 } ); 17178 } 17179 17180 Modernizr.load( { 17181 load: ( protocol.indexOf( "http" ) === -1 ? "http:" : protocol ) + "//platform.twitter.com/widgets.js", 17182 complete: function() { 17183 17184 // Identify that initialization has completed 17185 wb.ready( $( eventTarget ), componentName ); 17186 } 17187 } ); 17188 } 17189 }; 17190 17191$document.on( "timerpoke.wb " + initEvent, selector, init ); 17192 17193// Add the timer poke to initialize the plugin 17194wb.add( selector ); 17195 17196} )( jQuery, window, wb ); 17197 17198/** 17199 * @title WET-BOEW Data Json [data-json-after], [data-json-append], 17200 * [data-json-before], [data-json-prepend], [data-json-replace], [data-json-replacewith] and [data-wb-json] 17201 * @overview Insert content extracted from JSON file. 17202 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 17203 * @author @duboisp 17204 */ 17205/*global jsonpointer */ 17206( function( $, window, wb ) { 17207"use strict"; 17208 17209/* 17210 * Variable and function definitions. 17211 * These are global to the plugin - meaning that they will be initialized once per page, 17212 * not once per instance of plugin on the page. So, this is a good place to define 17213 * variables that are common to all instances of the plugin on a page. 17214 */ 17215var componentName = "wb-data-json", 17216 shortName = "wb-json", 17217 selectors = [ 17218 "[data-json-after]", 17219 "[data-json-append]", 17220 "[data-json-before]", 17221 "[data-json-prepend]", 17222 "[data-json-replace]", 17223 "[data-json-replacewith]", 17224 "[data-" + shortName + "]" 17225 ], 17226 allowJsonTypes = [ "after", "append", "before", "prepend", "val" ], 17227 allowAttrNames = /(href|src|data-*|aria-*|role|pattern|min|max|step|low|high|lang|hreflang|action)/, 17228 allowPropNames = /(checked|selected|disabled|required|readonly|multiple|hidden)/, 17229 selectorsLength = selectors.length, 17230 selector = selectors.join( "," ), 17231 initEvent = "wb-init." + componentName, 17232 updateEvent = "wb-update." + componentName, 17233 contentUpdatedEvent = "wb-contentupdated", 17234 dataQueue = componentName + "-queue", 17235 $document = wb.doc, 17236 isExtensionRegistered, 17237 s, 17238 17239 /** 17240 * @method init 17241 * @param {jQuery Event} event Event that triggered this handler 17242 * @param {string} ajaxType The type of JSON operation, either after, append, before or replace 17243 */ 17244 init = function( event ) { 17245 17246 // Start initialization 17247 // returns DOM object = proceed with init 17248 // returns undefined = do not proceed with init (e.g., already initialized) 17249 var elm = wb.init( event, componentName, selector ), 17250 jsSettings = window[ componentName ] || { }, 17251 prop, 17252 $elm; 17253 17254 if ( elm ) { 17255 17256 var jsonCoreTypes = [ 17257 "before", 17258 "replace", 17259 "replacewith", 17260 "after", 17261 "append", 17262 "prepend" 17263 ], 17264 jsonType, jsondata, 17265 i, i_len = jsonCoreTypes.length, i_cache, 17266 lstCall = [], 17267 url; 17268 17269 $elm = $( elm ); 17270 17271 for ( i = 0; i !== i_len; i += 1 ) { 17272 jsonType = jsonCoreTypes[ i ]; 17273 url = elm.getAttribute( "data-json-" + jsonType ); 17274 if ( url !== null ) { 17275 lstCall.push( { 17276 type: jsonType, 17277 url: url 17278 } ); 17279 } 17280 } 17281 17282 // Extend but not overwrite the functionTest and the functionForOperand if some was added 17283 if ( !isExtensionRegistered ) { 17284 if ( jsSettings.functionForTest ) { 17285 for ( prop in jsSettings.functionForTest ) { 17286 if ( !functionForTest[ prop ] ) { 17287 functionForTest[ prop ] = jsSettings.functionForTest[ prop ]; 17288 } 17289 } 17290 } 17291 if ( jsSettings.functionForOperand ) { 17292 for ( prop in jsSettings.functionForOperand ) { 17293 if ( !functionForOperand[ prop ] ) { 17294 functionForOperand[ prop ] = jsSettings.functionForOperand[ prop ]; 17295 } 17296 } 17297 } 17298 isExtensionRegistered = true; 17299 } 17300 17301 // Identify that initialization has completed 17302 wb.ready( $elm, componentName ); 17303
17304 jsondata = wb.getData( $elm, shortName ); 17305 17306 if ( jsondata && jsondata.url ) { 17307 lstCall.push( jsondata ); 17308 } else if ( jsondata && $.isArray( jsondata ) ) { 17309 i_len = jsondata.length; 17310 for ( i = 0; i !== i_len; i += 1 ) { 17311 lstCall.push( jsondata[ i ] ); 17312 } 17313 } 17314 17315 // Save it to the dataJSON object. 17316 $elm.data( dataQueue, lstCall ); 17317 17318 i_len = lstCall.length; 17319 for ( i = 0; i !== i_len; i += 1 ) { 17320 i_cache = lstCall[ i ]; 17321 loadJSON( elm, i_cache.url, i, i_cache.nocache, i_cache.nocachekey, i_cache.data, i_cache.contenttype, i_cache.method ); 17322 } 17323 17324 } 17325 }, 17326 17327 loadJSON = function( elm, url, refId, nocache, nocachekey, data, contentType, method ) { 17328 var $elm = $( elm ), 17329 fetchObj = { 17330 url: url, 17331 refId: refId, 17332 nocache: nocache, 17333 nocachekey: nocachekey, 17334 data: data, 17335 contentType: contentType, 17336 method: method 17337 }; 17338 17339 $elm.trigger( { 17340 type: "json-fetch.wb", 17341 fetch: fetchObj 17342 } ); 17343 }, 17344 17345 17346 // Manage JSON value After the json data has been fetched. This function can deal with array. 17347 jsonFetched = function( event ) { 17348 17349 var elm = event.target, 17350 $elm = $( elm ), 17351 lstCall = $elm.data( dataQueue ), 17352 fetchObj = event.fetch, 17353 itmSettings = lstCall[ fetchObj.refId ], 17354 jsonType = itmSettings.type, 17355 attrname = itmSettings.prop || itmSettings.attr, 17356 showEmpty = itmSettings.showempty, 17357 content = fetchObj.response, 17358 typeOfContent = typeof content, 17359 jQueryCaching; 17360 17361 if ( showEmpty || typeOfContent !== "undefined" ) { 17362 17363 if ( showEmpty && typeOfContent === "undefined" ) { 17364 content = ""; 17365 } 17366 17367 //Prevents the force caching of nested resources 17368 jQueryCaching = jQuery.ajaxSettings.cache; 17369 jQuery.ajaxSettings.cache = true; 17370 17371 // "replace" and "replaceWith" doesn't map to a jQuery function 17372 if ( !jsonType ) { 17373 jsonType = "template"; 17374 applyTemplate( elm, itmSettings, content ); 17375 17376 // Trigger wet 17377 if ( itmSettings.trigger && !wb.isDisabled ) { 17378 $elm 17379 .find( wb.allSelectors ) 17380 .addClass( "wb-init" ) 17381 .filter( ":not(#" + elm.id + " .wb-init .wb-init)" ) 17382 .trigger( "timerpoke.wb" ); 17383 } 17384 } else if ( jsonType === "replace" ) { 17385 $elm.html( content ); 17386 } else if ( jsonType === "replacewith" ) { 17387 $elm.replaceWith( content ); 17388 } else if ( jsonType === "addclass" ) { 17389 $elm.addClass( content ); 17390 } else if ( jsonType === "removeclass" ) { 17391 $elm.removeClass( content ); 17392 } else if ( jsonType === "prop" && attrname && allowPropNames.test( attrname ) ) { 17393 $elm.prop( attrname, content ); 17394 } else if ( jsonType === "attr" && attrname && allowAttrNames.test( attrname ) ) { 17395 $elm.attr( attrname, content ); 17396 } else if ( typeof $elm[ jsonType ] === "function" && allowJsonTypes.indexOf( jsonType ) !== -1 ) { 17397 $elm[ jsonType ]( content ); 17398 } else { 17399 throw componentName + " do not support type: " + jsonType; 17400 } 17401 17402 //Resets the initial jQuery caching setting 17403 jQuery.ajaxSettings.cache = jQueryCaching; 17404 17405 $elm.trigger( contentUpdatedEvent, { "json-type": jsonType, "content": content } ); 17406 } 17407 }, 17408 17409 // Apply the template as per the configuration 17410 applyTemplate = function( elm, settings, content ) { 17411 17412 var elmClass = elm.className, 17413 dataTable, 17414 dataTableAddRow, 17415 template = settings.source ? document.querySelector( settings.source ) : elm.querySelector( "template" ); 17416 17417 // If combined with wb-tables plugin 17418 if ( elm.tagName === "TABLE" && elmClass.indexOf( "wb-tables" ) !== -1 ) { 17419 17420 // Wait for its initialization before to applyTemplate 17421 if ( elmClass.indexOf( "wb-tables-inited" ) === -1 ) { 17422 $( elm ).one( "wb-ready.wb-tables,init.dt", function( ) { 17423 applyTemplate( elm, settings, content ); 17424 } ); 17425 return; 17426 } 17427 17428 // Edge case, when both plugin are ready at the same time, just wait for the next tick 17429 if ( !$.fn.dataTable.isDataTable( elm ) && elmClass.indexOf( componentName + "-dtwait" ) === -1 ) { 17430 elm.classList.add( componentName + "-dtwait" ); 17431 setTimeout( function( ) { 17432 applyTemplate( elm, settings, content ); 17433 }, 50 ); 17434 return; 17435 } 17436 17437 dataTable = $( elm ).dataTable( { "retrieve": true } ).api(); 17438 dataTableAddRow = dataTable.row.add; 17439 settings.tobeclone = "tr"; // Only table row can be added 17440 } 17441 17442 if ( !template ) { 17443 return; 17444 } 17445 17446 // Needed when executing sub-template that wasn't polyfill, like in IE11 17447 if ( !template.content ) { 17448 wb.tmplPolyfill( template ); 17449 } 17450 17451 if ( !settings.streamline ) { 17452 dataIterator( elm, content, settings ); 17453 } else { 17454 processMapping( elm, elm, content, settings ); 17455 } 17456 17457 // Refresh the dataTable display 17458 if ( dataTableAddRow ) { 17459 dataTable.draw(); 17460 } 17461 }, 17462 17463 // Iterate over the dataset 17464 dataIterator = function( elm, content, mappingConfig, useClone ) { 17465 17466 var i, i_len, i_cache, 17467 elmAppendTo = elm, 17468 clone, template, 17469 dataTable, dataTableAddRow; 17470 17471 if ( mappingConfig.appendto ) { 17472 elmAppendTo = $( mappingConfig.appendto ).get( 0 ); 17473 } 17474 17475 // Connection with data table plugin 17476 if ( elm.tagName === "TABLE" && elm.className.indexOf( "wb-tables" ) !== -1 ) { 17477 dataTable = $( elm ).dataTable( { "retrieve": true } ).api(); 17478 dataTableAddRow = dataTable.row.add; 17479 mappingConfig.tobeclone = "tr"; 17480 } 17481 17482
17483 // if content is object, transform into array @id and @value 17484 if ( !$.isArray( content ) ) { 17485 if ( typeof content !== "object" ) { 17486 content = [ content ]; 17487 } else { 17488 content = $.map( content, function( val, index ) { 17489 if ( val && typeof val === "object" && !$.isArray( val ) ) { 17490 if ( !val[ "@id" ] ) { 17491 val[ "@id" ] = index; 17492 } 17493 } else { 17494 val = { 17495 "@id": index, 17496 "@value": val 17497 }; 17498 } 17499 return [ val ]; 17500 } ); 17501 } 17502 } 17503 i_len = content.length; 17504 17505 // Get the template to be iterated. 17506 if ( !useClone && mappingConfig.source ) { 17507 template = document.querySelector( mappingConfig.source ); 17508 } else if ( !useClone && mappingConfig.template ) { 17509 template = elm.querySelector( mappingConfig.template ); 17510 } else if ( !useClone ) { 17511 template = elm.querySelectorAll( ":scope > template" ); 17512 if ( template.length === 1 || template[ 0 ].attributes.length === 0 ) { 17513 17514 // Only when there is one choice or take the first one only if only there is no attribute set on the element 17515 template = template[ 0 ]; 17516 } else { 17517 17518 // let the mapping instructions to define which template to use 17519 template = false; 17520 } 17521 } 17522 17523 // Iterate the data array 17524 for ( i = 0; i < i_len; i += 1 ) { 17525 i_cache = content[ i ]; 17526 17527 17528 // If the data are filtered. This is deprecated and are only for backward compatible purpose 17529 if ( !filterPassJSON( i_cache, mappingConfig.filter, mappingConfig.filternot ) ) { 17530 continue; 17531 } 17532 17533 // Get the template (if applicable) 17534 if ( !clone && useClone ) { 17535 clone = useClone; 17536 } 17537 17538 // Create a clone if one unique template is found 17539 if ( !useClone && template && !mappingConfig.tobeclone ) { 17540 clone = template.content.cloneNode( true ); 17541 } else if ( !useClone && template ) { 17542 clone = template.content.querySelector( mappingConfig.tobeclone ).cloneNode( true ); 17543 } 17544 17545 // process the mapping, return value is the new clone object if applicable 17546 var tmpClone; 17547 tmpClone = processMapping( elm, clone, i_cache, mappingConfig ); 17548 17549 // Remove the template flag, to ensure we do reuse it for the subsequent iteration 17550 if ( tmpClone ) { 17551 delete mappingConfig.template; 17552 clone = tmpClone; 17553 } 17554 17555 // Add the clone object 17556 if ( dataTableAddRow ) { 17557 dataTableAddRow( $( clone ) ); // If wb-tables, use its API to add rows 17558 } else if ( !useClone && template ) { 17559 elmAppendTo.appendChild( clone ); 17560 } 17561 } 17562 17563 // Refresh the dataTable display (if applicable) 17564 if ( dataTableAddRow ) { 17565 dataTable.draw(); 17566 } 17567 17568 }, 17569 17570 // Check if the mapping are met or not 17571 canProcessMapping = function( content, mappingConfig ) { 17572 17573 var rawValue, value, 17574 testableData, 17575 operand = mappingConfig.operand || "softEq", 17576 operandOutcome; 17577 17578 if ( !mappingConfig.test ) { 17579 return; 17580 } 17581 17582 // Get the value to be tested 17583 try { 17584 rawValue = getRawValue( content, mappingConfig.assess || mappingConfig.value ); 17585 value = getValue( rawValue ); 17586 } catch ( ex ) { 17587 17588 // If this is an error, the path probably don't exist 17589 rawValue = undefined; 17590 value = undefined; 17591 } 17592 17593 // Get the function to use 17594 if ( !functionForTest[ mappingConfig.test ] ) { 17595 console.error( "The test function '" + mappingConfig.test + "' don't exist. Default to false test result" ); 17596 console.error( mappingConfig ); 17597 return false; 17598 } 17599 testableData = functionForTest[ mappingConfig.test ].call( mappingConfig, value, rawValue ); 17600 17601 // Run the operand 17602 if ( !functionForOperand[ operand ] ) { 17603 console.error( "The operand '" + operand + "' don't exist" ); 17604 console.error( mappingConfig ); 17605 operand = "softEq"; 17606 } 17607 operandOutcome = functionForOperand[ operand ].call( mappingConfig, testableData, mappingConfig.expect ); 17608 17609 // If not true, go next 17610 if ( !operandOutcome ) { 17611 return false; 17612 } 17613 17614 // Run mapping if satisfied 17615 return true; 17616 }, 17617 17618 // Special mapping typed function 17619 functionForTypedMapping = { 17620 17621 "rdf:Alt": function( elm, clone, content, mappingConfig ) { 17622 17623 var mapping = mappingConfig.mapping, 17624 i, i_cache, 17625 i_len = mapping.length, 17626 value = content; 17627 17628 for ( i = 0; i < i_len || i === 0; i += 1 ) { 17629 i_cache = mapping[ i ]; 17630 17631 if ( canProcessMapping( content, i_cache ) ) { 17632 17633 // Clone the object to avoid conflict when it is reused for other data 17634 i_cache = $.extend( true, {}, i_cache ); 17635 17636 // Remove the test, because it was checked 17637 delete i_cache.test; 17638 17639 // Navigate the content if specified 17640 if ( i_cache.value ) { 17641 value = getValue( content, i_cache.value ); 17642 } 17643 17644 // Process the mapping 17645 processMapping( elm, clone, value, i_cache ); 17646 17647 // End 17648 return; 17649 } 17650 } 17651 } 17652 }, 17653 17654 // Function called for testing the mapping condition, which can be extend via the js configuration 17655 functionForTest = { 17656 17657 "fn:isArray": function( value ) { 17658 return $.isArray( value ); 17659 }, 17660 17661 "fn:isLiteral": function( value ) { 17662 17663 // Check if the value are set under the JSON-LD parameter @value 17664 if ( value && value[ "@value" ] ) { 17665 value = value[ "@value" ]; 17666 } 17667 17668 if ( value && typeof value !== "object" ) { 17669 return true; 17670 } 17671 17672 return false;
17673 }, 17674 17675 "fn:getType": function( value, rawValue ) { 17676 17677 var tp = value[ "@type" ] || rawValue[ "@type" ]; 17678 17679 if ( tp === "@json" ) { 17680 return "rdf:JSON"; 17681 } else if ( $.isArray( tp ) && tp.indexOf( "@json" ) !== -1 ) { 17682 tp[ tp.indexOf( "@json" ) ] = "rdf:JSON"; 17683 } 17684 17685 if ( tp ) { 17686 return tp; 17687 } else { 17688 return typeof value; 17689 } 17690 }, 17691 17692 "fn:getValue": function( value ) { 17693 17694 return value; 17695 17696 }, 17697 17698 "fn:guessType": function( value, rawValue ) { 17699 17700 var guessType; 17701 17702 if ( !value ) { 17703 guessType = "undefined"; 17704 } else if ( value[ "@type" ] ) { 17705 guessType = value[ "@type" ]; 17706 } else if ( rawValue[ "@type" ] ) { 17707 guessType = rawValue[ "@type" ]; 17708 } else if ( value[ "@value" ] ) { 17709 17710 // Only if we are in JSON ld mode 17711 // Check if the value are set under the JSON-LD parameter @value 17712 value = value[ "@value" ]; 17713 } 17714 17715 // Edge case to convert the @json data type into its RDF form 17716 if ( guessType && guessType !== "undefined" ) { 17717 if ( guessType === "@json" ) { 17718 guessType = "rdf:JSON"; 17719 } else if ( $.isArray( guessType ) && guessType.indexOf( "@json" ) !== -1 ) { 17720 guessType[ guessType.indexOf( "@json" ) ] = "rdf:JSON"; 17721 } 17722 } 17723 17724 if ( !guessType ) { 17725 if ( typeof value === "string" && value.match( /^([a-z][a-z0-9+\-.]*):/i ) ) { 17726 guessType = [ "xsd:anyURI", "rdfs:Literal" ]; 17727 } else if ( typeof value === "string" ) { 17728 guessType = [ "xsd:string", "rdfs:Literal" ]; 17729 } else if ( typeof value === "boolean" ) { 17730 guessType = [ "xsd:boolean", "rdfs:Literal" ]; 17731 } else if ( typeof value === "number" ) { 17732 guessType = [ "xsd:double", "rdfs:Literal" ]; 17733 } else if ( typeof value === "undefined" ) { 17734 guessType = "undefined"; 17735 } else if ( $.isArray( value ) ) { 17736 guessType = "rdfs:Container"; 17737 } else { 17738 17739 // The type is a generic Object 17740 guessType = "rdfs:Resource"; 17741 } 17742 } 17743 17744 return guessType; 17745 } 17746 17747 }, 17748 17749 // Operand used to evaluate the testable output from functionForTest to determine if the mapping condition is met or not, which can be extend via the js configuration 17750 functionForOperand = { 17751 17752 "softEq": function( value, expect ) { 17753 var i, i_len; 17754 17755 if ( $.isArray( value ) && !$.isArray( expect ) && value.indexOf( expect ) !== -1 ) { 17756 return true; 17757 } else if ( $.isArray( value ) && $.isArray( expect ) ) { 17758 i_len = expect.length; 17759 for ( i = 0; i !== i_len; i++ ) { 17760 if ( value.indexOf( expect[ i ] ) ) { 17761 return true; 17762 } 17763 } 17764 } else if ( expect && value === expect ) { 17765 return true; 17766 } else if ( !expect && value ) { 17767 return true; 17768 } 17769 17770 return false; 17771 17772 }, 17773 17774 "eq": function( value, expect ) { 17775 17776 if ( _equalsJSON( value, expect ) ) { 17777 return true; 17778 } 17779 17780 return false; 17781 }, 17782 17783 "neq": function( value, expect ) { 17784 if ( !_equalsJSON( value, expect ) ) { 17785 return true; 17786 } 17787 17788 return false; 17789 }, 17790 17791 "in": function( value, expect ) { 17792 17793 var i; 17794 17795 if ( !expect ) { 17796 console.error( "Expected value is missing. Defaulting to false." ); 17797 console.error( this ); 17798 return false; 17799 } 17800 17801 if ( $.isArray( value ) && !$.isArray( expect ) && value.indexOf( expect ) !== -1 ) { 17802 return true; 17803 } else if ( $.isArray( value ) && $.isArray( expect ) ) { 17804 for ( i = 0; i !== expect.length; i++ ) { 17805 if ( value.indexOf( expect[ i ] ) ) { 17806 return true; 17807 } 17808 } 17809 } else if ( !$.isArray( value ) && $.isArray( expect ) && expect.indexOf( value ) !== -1 ) { 17810 return true; 17811 } else if ( value === expect ) { 17812 return true; 17813 } 17814 17815 return false; 17816 }, 17817 17818 "nin": function( value, expect ) { 17819 return !functionForOperand.in.call( this, value, expect ); 17820 } 17821 }, 17822 17823 // Mapping the data into a template or into a node 17824 processMapping = function( elm, clone, content, mappingConfig ) { 17825 17826 var j, j_cache, 17827 cached_node, cached_value, 17828 cached_value_is_HTML, cached_value_is_JSON, cached_value_is_IRI, 17829 queryAll = mappingConfig.queryall, 17830 selElements, 17831 mapping = mappingConfig.mapping, 17832 mapping_len, 17833 upstreamClone, template; 17834 17835 17836 // Is this mapping a special mapping type? 17837 if ( mappingConfig[ "@type" ] ) { 17838 functionForTypedMapping[ mappingConfig[ "@type" ] ].call( content, elm, clone, content, mappingConfig ); 17839 return; 17840 } 17841 17842 // Can we proceed? 17843 if ( mappingConfig.test && !canProcessMapping( content, mappingConfig ) ) { 17844 return; 17845 } 17846 17847 // Check if there is some mapping configuration 17848 if ( !mapping && !queryAll && !mappingConfig.template && typeof mapping !== "object" ) { 17849 return; 17850 } 17851 17852 // Clone mappingConfig to ensure it don't interfere with subsequent data iteration 17853 mappingConfig = $.extend( true, {}, mappingConfig ); 17854 17855 // If there is no clone, let use the element (parent) 17856 clone = clone || elm; 17857 17858 // If there is a "template" property, get the inner template 17859 if ( mappingConfig.template ) { 17860 template = clone.querySelector( mappingConfig.template ); 17861 17862 upstreamClone = clone; // Keep reference of the top clone 17863 17864 clone = template.content.cloneNode( true ); 17865 17866 // Ensure we don't recreated it if during a subsequent iteration 17867 delete mappingConfig.template; 17868 } 17869 17870 17871 // Is content an array? then iterate the content 17872 if ( $.isArray( content ) ) { 17873 17874 17875 dataIterator( clone, content, mappingConfig, clone ); 17876 17877 // Case of where a template is associated with this mapping action 17878 if ( template ) { 17879 if ( template.parentNode ) { 17880 17881 if ( !mappingConfig.append ) { 17882 template.parentNode.insertBefore( clone, template ); 17883 } else { 17884 template.parentNode.appendChild( clone ); 17885 } 17886 } else { 17887 upstreamClone.appendChild( clone ); 17888 } 17889 17890 return elm; 17891 17892 } 17893 return; 17894 } 17895 17896 // Prepare the mapping object to be iterated 17897 if ( !mapping ) { 17898 mapping = [ {} ]; 17899 } 17900 if ( !$.isArray( mapping ) ) { 17901 mapping = [ mapping ]; 17902 } 17903 mapping_len = mapping.length; 17904 17905 // Ensure the mapping is an array of Mapping Object 17906 for ( j = 0; j < mapping_len || j === 0; j += 1 ) { 17907 if ( typeof mapping[ j ] === "string" ) { 17908 mapping[ j ] = { 17909 value: mapping[ j ] 17910 }; 17911 } 17912 } 17913 17914 if ( queryAll ) { 17915 selElements = clone.querySelectorAll( queryAll ); 17916 17917 // Replicate this setting the in the mapping 17918 for ( j = 0; j < selElements.length || j === 0; j += 1 ) { 17919 if ( !mapping[ j ].selector && queryAll.indexOf( "nth-child" ) === -1 ) { 17920 mapping[ j ].selector = queryAll + ":nth-child(" + ( j + 1 ) + ")"; 17921 } else if ( !mapping[ j ].selector ) { 17922 mapping[ j ].selector = queryAll; 17923 } 17924 } 17925 } 17926 17927 17928 // 17929 // Process the mapping 17930 // 17931 for ( j = 0; j < mapping_len || j === 0; j += 1 ) { 17932 j_cache = mapping[ j ]; 17933 17934 // Reset the cache value special type flag 17935 cached_value_is_IRI = false;
17936 cached_value_is_HTML = false; 17937 cached_value_is_JSON = false; 17938 17939 // Get the element to be updated 17940 if ( j_cache.selector ) { 17941 cached_node = clone.querySelector( j_cache.selector ); 17942 } else { 17943 cached_node = clone; 17944 } 17945 17946 // Get the value to be set 17947 try { 17948 cached_value = getRawValue( content, j_cache ); 17949 } catch ( ex ) { 17950 17951 // The path don't exist, let continue to the next mapping item 17952 console.info( "JSON selector path for mapping don't exist in content" ); 17953 console.info( j_cache ); 17954 console.info( content ); 17955 continue; 17956 } 17957 17958 // Go to the next mapping if the value of JSON node don't exist to ensure we keep the default text set in the template, but move ahead if empty or null 17959 if ( typeof cached_value === "undefined" ) { 17960 continue; 17961 } 17962 17963 // Do the cache value contain special @type 17964 if ( cached_value && cached_value[ "@value" ] && cached_value[ "@type" ] ) { 17965 if ( !$.isArray( cached_value[ "@type" ] ) ) { 17966 cached_value[ "@type" ] = [ cached_value[ "@type" ] ]; 17967 } 17968 cached_value_is_IRI = cached_value[ "@type" ].indexOf( "@id" ) !== -1; 17969 cached_value_is_HTML = cached_value[ "@type" ].indexOf( "rdf:HTML" ) !== -1; 17970 cached_value_is_JSON = cached_value[ "@type" ].indexOf( "rdf:JSON" ) !== -1 || cached_value[ "@type" ].indexOf( "@json" ) !== -1; 17971 } 17972 17973 // Action the value 17974 if ( $.isArray( cached_value ) && ( j_cache.mapping || j_cache.queryall ) ) { 17975 17976 // Deep dive into the content if a mapping exist 17977 dataIterator( cached_node, cached_value, j_cache ); 17978 17979 } else if ( j_cache.mapping || j_cache.queryall || !j_cache.mapping && typeof j_cache.mapping === "object" ) { 17980 try { 17981 17982 // Map the inner mapping 17983 processMapping( template || elm, cached_node, cached_value, j_cache ); 17984 } catch ( ex ) { 17985 17986 if ( ex === "cached_node: null" && typeof cached_value === "object" ) { 17987 17988 // If it fail, let iterate the cached_value object 17989 dataIterator( cached_node, cached_value, j_cache ); 17990 } else { 17991 throw ex; 17992 } 17993 } 17994 } else if ( cached_value_is_IRI && cached_value_is_HTML ) { 17995 17996 // The import file type are expected to be HTML 17997 // Add the data-ajax instruction so the content would be added once the JSON mapping is completed and added on the page. 17998 cached_node.dataset.wbAjax = JSON.stringify( { 17999 url: cached_value[ "@value" ], 18000 type: "replace", 18001 dataType: cached_value_is_JSON ? "json" : null, 18002 encode: j_cache.encode 18003 } ); 18004 } else if ( cached_value_is_HTML && cached_value_is_JSON && !cached_value_is_IRI ) { 18005 18006 // Get content from the "@value" property which contain JSON value and use it as a string value 18007 cached_value = JSON.stringify( cached_value[ "@value" ] ); 18008 18009 // Map the value in the element 18010 mapValue( cached_node, cached_value, j_cache ); 18011 18012 } else if ( !cached_node && typeof cached_value === "object" ) { 18013 throw "cached_node: null"; 18014 } else if ( mappingConfig.mapping !== null ) { 18015 18016 cached_value = getValue( cached_value ); 18017 18018 // Serialize the value if it is an JS object and its not a null object 18019 if ( typeof cached_value === "object" && cached_value !== null ) { 18020 cached_value = JSON.stringify( cached_value ); 18021 } 18022 18023 // Map the value in the element 18024 mapValue( cached_node, cached_value, j_cache ); 18025 } 18026 18027 } 18028 18029 // Add the template, if applicable 18030 if ( template ) { 18031 if ( template.parentNode ) { 18032 18033 if ( !mappingConfig.append ) { 18034 template.parentNode.insertBefore( clone, template ); 18035 } else { 18036 template.parentNode.appendChild( clone ); 18037 } 18038 } else { 18039 upstreamClone.appendChild( clone ); 18040 } 18041 18042 return elm; 18043 } 18044 18045 }, 18046 18047 // Extract the value of an JS object 18048 getValue = function( source, pointer ) { 18049 18050 var value = getRawValue( source, pointer ); 18051 18052 // for JSON-LD @value support 18053 if ( typeof value === "object" && value !== null && Object.prototype.hasOwnProperty.call( value, "@value" ) ) { 18054 value = value[ "@value" ]; 18055 } 18056 18057 return value; 18058 }, 18059 18060 // Extract the value without considering it possible JSON-LD value 18061 getRawValue = function( source, pointer ) { 18062 18063 var value; 18064 pointer = pointer || false; // Ensure pointer is defined 18065 18066 // Get the value if source is string or pointer is pointing to root 18067 if ( typeof source === "string" || pointer === "/" || pointer === "/@value" || pointer.value === "/" || pointer.value === "/@value" ) { 18068 value = source; 18069 } else if ( typeof pointer === "string" ) { 18070 value = jsonpointer.get( source, pointer ); 18071 } else if ( pointer.value ) { 18072 value = jsonpointer.get( source, pointer.value ); 18073 } else { 18074 value = source; 18075 } 18076 18077 return value; 18078 }, 18079 18080 // Map a value into an HTML element or attribute 18081 mapValue = function( element, value, mappingConfig ) { 18082 18083 var attributeName, placeholderText; 18084 18085 attributeName = mappingConfig.attr; 18086 if ( attributeName ) { 18087 if ( !element.hasAttribute( attributeName ) ) { 18088 element.setAttribute( attributeName, "" ); 18089 } 18090 element = element.getAttributeNode( attributeName ); 18091 } 18092 18093 // Placeholder text replacement if any 18094 if ( mappingConfig.placeholder ) { 18095 placeholderText = element.textContent || ""; 18096 value = placeholderText.replace( mappingConfig.placeholder, value ); 18097 } 18098 18099 // Exclude null values and replace with default text 18100 if ( value !== null ) { 18101 if ( mappingConfig.isHTML ) { 18102 element.innerHTML = value; 18103 } else { 18104 element.textContent = value; 18105 } 18106 } 18107 }, 18108 18109 18110 // Filtering a JSON 18111 // Return true if trueness && falseness 18112 // Return false if !( trueness && falseness ) 18113 // trueness and falseness is an array of { "path": "", "value": "" } object 18114 filterPassJSON = function( obj, trueness, falseness ) { 18115 var i, i_cache, 18116 trueness_len = trueness ? trueness.length : 0, 18117 falseness_len = falseness ? falseness.length : 0, 18118 compareResult = false, 18119 isEqual; 18120 18121 if ( trueness_len || falseness_len ) { 18122 18123 for ( i = 0; i < trueness_len; i += 1 ) { 18124 i_cache = trueness[ i ]; 18125 isEqual = _equalsJSON( jsonpointer.get( obj, i_cache.path ), i_cache.value ); 18126 18127 if ( i_cache.optional ) { 18128 compareResult = compareResult || isEqual; 18129 } else if ( !isEqual ) { 18130 return false;
18131 } else { 18132 compareResult = true; 18133 } 18134 } 18135 if ( trueness_len && !compareResult ) { 18136 return false; 18137 } 18138 18139 for ( i = 0; i < falseness_len; i += 1 ) { 18140 i_cache = falseness[ i ]; 18141 isEqual = _equalsJSON( jsonpointer.get( obj, i_cache.path ), i_cache.value ); 18142 18143 if ( isEqual && !i_cache.optional || isEqual && i_cache.optional ) { 18144 return false; 18145 } 18146 } 18147 18148 } 18149 return true; 18150 }, 18151 18152 // 18153 _equalsJSON = function( a, b ) { 18154 switch ( typeof a ) { 18155 case "undefined": 18156 return false; 18157 case "boolean": 18158 case "string": 18159 case "number": 18160 return a === b; 18161 case "object": 18162 if ( a === null ) { 18163 return b === null; 18164 } 18165 var i, l; 18166 if ( $.isArray( a ) ) { 18167 if ( !$.isArray( b ) || a.length !== b.length ) { 18168 return false; 18169 } 18170 for ( i = 0, l = a.length; i < l; i++ ) { 18171 if ( !_equalsJSON( a[ i ], b[ i ] ) ) { 18172 return false; 18173 } 18174 } 18175 return true; 18176 } 18177 var bKeys = _objectKeys( b ), 18178 bLength = bKeys.length; 18179 if ( _objectKeys( a ).length !== bLength ) { 18180 return false; 18181 } 18182 for ( i in a ) { 18183 if ( !_equalsJSON( a[ i ], b[ i ] ) ) { 18184 return false; 18185 } 18186 } 18187 return true; 18188 default: 18189 return false; 18190 } 18191 }, 18192 _objectKeys = function( obj ) { 18193 var keys; 18194 if ( $.isArray( obj ) ) { 18195 keys = new Array( obj.length ); 18196 for ( var k = 0; k < keys.length; k++ ) { 18197 keys[ k ] = "" + k; 18198 } 18199 return keys; 18200 } 18201 if ( Object.keys ) { 18202 return Object.keys( obj ); 18203 } 18204 keys = []; 18205 for ( var i in obj ) { 18206 if ( Object.prototype.hasOwnProperty.call( obj, i ) ) { 18207 keys.push( i ); 18208 } 18209 } 18210 return keys; 18211 }, 18212 18213 // Manage JSON value After the json data has been fetched 18214 jsonUpdate = function( event ) { 18215 var elm = event.target, 18216 $elm = $( elm ), 18217 lstCall = $elm.data( dataQueue ), 18218 refId = lstCall.length, 18219 wbJsonConfig = event[ "wb-json" ]; 18220 18221 if ( !( wbJsonConfig.url && ( wbJsonConfig.type || wbJsonConfig.source ) ) ) { 18222 throw "Data JSON update not configured properly"; 18223 } 18224 18225 lstCall.push( wbJsonConfig ); 18226 $elm.data( dataQueue, lstCall ); 18227 18228 loadJSON( elm, wbJsonConfig.url, refId ); 18229 }; 18230 18231$document.on( "json-failed.wb", selector, function( event ) { 18232 18233 var elm = event.currentTarget, 18234 $elm = $( elm ), 18235 lstCall = $elm.data( dataQueue ), 18236 fetchObj = event.fetch, 18237 xhrResponse = fetchObj.xhr, 18238 itmSettings = lstCall[ fetchObj.refId ], 18239 failSettings = itmSettings.fail; 18240 18241 if ( failSettings ) { 18242 18243 // Mapping is always streamline because the data structure is a static object not an array 18244 failSettings.streamline = true; 18245 18246 // apply the templaty to display an error message 18247 applyTemplate( elm, failSettings, { 18248 error: fetchObj.error.message || xhrResponse.statusText, 18249 status: fetchObj.status, 18250 url: fetchObj.fetchOpts.url, 18251 response: { 18252 text: xhrResponse.responseText || "", 18253 status: xhrResponse.status, 18254 statusText: xhrResponse.statusText 18255 } 18256 } ); 18257 } 18258 18259 console.info( event.currentTarget ); 18260 console.error( "Error or bad JSON Fetched from url in " + componentName ); 18261} ); 18262 18263// Load template polyfill 18264Modernizr.load( { 18265 test: ( "content" in document.createElement( "template" ) ), 18266 nope: "site!deps/template" + wb.getMode() + ".js" 18267} ); 18268 18269$document.on( "timerpoke.wb " + initEvent + " " + updateEvent + " json-fetched.wb", selector, function( event ) { 18270 18271 if ( event.currentTarget === event.target ) { 18272 switch ( event.type ) { 18273 18274 case "timerpoke": 18275 case "wb-init": 18276 init( event ); 18277 break; 18278 case "wb-update": 18279 jsonUpdate( event ); 18280 break; 18281 default: 18282 jsonFetched( event ); 18283 break; 18284 } 18285 } 18286 18287 return true; 18288} ); 18289 18290// Add the timerpoke to initialize the plugin 18291for ( s = 0; s !== selectorsLength; s += 1 ) { 18292 wb.add( selectors[ s ] ); 18293} 18294 18295} )( jQuery, window, wb ); 18296 18297/** 18298 * @title WET-BOEW Disable Event 18299 * @overview Event creates the active offer for users that have disabled the event. 18300 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 18301 * @author @gc 18302 */ 18303( function( $, window, wb ) { 18304"use strict"; 18305 18306/* 18307 * Variable and function definitions. 18308 * These are global to the event - meaning that they will be initialized once per page, 18309 * not once per instance of event on the page. 18310 */
18311var componentName = "wb-disable", 18312 selector = "#wb-tphp", 18313 $document = wb.doc, 18314 allowOnDisableClass = "wb-disable-allow", 18315 allowedPlugins = [ 18316 { 18317 selectors: "[data-wb-jsonmanager]", 18318 initEvent: "wb-jsonmanager" 18319 }, 18320 { 18321 selectors: "[data-wb-postback]", 18322 initEvent: "wb-postback" 18323 }, 18324 { 18325 selectors: [ 18326 "[data-ajax-after]", 18327 "[data-ajax-append]", 18328 "[data-ajax-before]", 18329 "[data-ajax-prepend]", 18330 "[data-ajax-replace]", 18331 "[data-wb-ajax]" 18332 ], 18333 initEvent: "wb-data-ajax" 18334 }, 18335 { 18336 selectors: [ 18337 "[data-json-after]", 18338 "[data-json-append]", 18339 "[data-json-before]", 18340 "[data-json-prepend]", 18341 "[data-json-replace]", 18342 "[data-json-replacewith]", 18343 "[data-wb-json]" 18344 ], 18345 initEvent: "wb-data-json" 18346 } 18347 ], 18348 18349 /** 18350 * @method init 18351 * @param {jQuery Event} event Event that triggered the function call 18352 */ 18353 init = function( event ) { 18354 18355 // Start initialization 18356 // returns DOM object = proceed with init 18357 // returns undefined = do not proceed with init (e.g., already initialized) 18358 var elm = wb.init( event, componentName, selector, true ), 18359 nQuery = "?", 18360 $html = wb.html, 18361 i18n = wb.i18n, 18362 pageUrl = wb.pageUrlParts, 18363 param, 18364 noticeHeader = i18n( "disable-notice-h" ), 18365 noticeBody = i18n( "disable-notice" ), 18366 noticehtml = "<section", 18367 noticehtmlend = "</a>.</p></section>", 18368 canonicalUrl, 18369 canonicalLink; 18370 18371 if ( elm ) { 18372 18373 // Rebuild the query string 18374 for ( param in pageUrl.params ) { 18375 if ( param && Object.prototype.hasOwnProperty.call( pageUrl.params, param ) && param !== "wbdisable" ) { 18376 nQuery += param + "=" + pageUrl.params[ param ] + "&"; 18377 } 18378 } 18379 18380 try { 18381 if ( wb.isDisabled || wb.ie ) { 18382 $html.addClass( "wb-disable" ); 18383 18384 try { 18385 18386 // Store preference for WET plugins and polyfills to be disabled in localStorage 18387 localStorage.setItem( "wbdisable", "true" ); 18388 } catch ( e ) { 18389 18390 /* swallow error */ 18391 } 18392 18393 // Trigger initialization of plugins that are needed in basic mode 18394 runAllowedPlugins(); 18395 18396 // Add canonical link if not already present 18397 if ( !document.querySelector( "link[rel=canonical]" ) ) { 18398 18399 // Remove wbdisable from URL 18400 canonicalUrl = window.location.href.replace( /&?wbdisable=true/gi, "" ).replace( "?&", "?" ).replace( "?#", "#" ); 18401 18402 if ( canonicalUrl.indexOf( "?" ) === ( canonicalUrl.length - 1 ) ) { 18403 canonicalUrl = canonicalUrl.replace( "?", "" ); 18404 } 18405 18406 canonicalLink = document.createElement( "link" ); 18407 canonicalLink.rel = "canonical"; 18408 canonicalLink.href = canonicalUrl; 18409 18410 document.head.appendChild( canonicalLink ); 18411 } 18412 18413 // Add notice and link to re-enable WET plugins and polyfills 18414 let significantLinkId = wb.getId(); 18415 noticehtml = noticehtml + " class='container-fluid bg-warning text-center mrgn-tp-sm py-4'><h2 class='mrgn-tp-0'>" + noticeHeader + "</h2><p>" + noticeBody + "</p><p><a id='" + significantLinkId + "' rel='alternate' href='" + nQuery + "wbdisable=false'>" + i18n( "wb-enable" ) + noticehtmlend; 18416 $( elm ).after( noticehtml ); 18417 document.querySelector( "#" + significantLinkId ).setAttribute( "property", "significantLink" ); 18418 return true; 18419 } else { 18420 $html.addClass( "wb-enable" ); 18421 18422 if ( localStorage ) { 18423 18424 // Store preference for WET plugins and polyfills to be enabled in localStorage 18425 localStorage.setItem( "wbdisable", "false" ); 18426 } 18427 18428 // Remove variable from URL 18429 var lc = window.location.href.replace( /&?wbdisable=false/gi, "" ).replace( "?&", "?" ).replace( "?#", "#" ); 18430 if ( lc.indexOf( "?" ) === ( lc.length - 1 ) ) { 18431 lc = lc.replace( "?", "" ); 18432 } 18433 window.history.replaceState( "", "", lc ); 18434 } 18435 } catch ( error ) { 18436 18437 /* swallow error */ 18438 } 18439 18440 // Append the Basic HTML version link version 18441 // Add link to disable WET plugins and polyfills 18442 wb.addSkipLink( i18n( "wb-disable" ), { 18443 href: nQuery + "wbdisable=true", 18444 rel: "alternate" 18445 }, false, true ); 18446 18447 // Identify that initialization has completed 18448 wb.ready( $document, componentName ); 18449 } 18450 }, 18451 18452 // Trigger initialization of plugins that are needed in basic mode
18453 // TODO: Remove once basic initialization is implemented at the core level 18454 runAllowedPlugins = function() { 18455 allowedPlugins.forEach( allowedPlugin => { 18456 if ( typeof( allowedPlugin.selectors ) === "object" ) { 18457 allowedPlugin.selectors = allowedPlugin.selectors.join( "." + allowOnDisableClass + "," ); 18458 } 18459 18460 allowedPlugin.selectors = allowedPlugin.selectors + "." + allowOnDisableClass; 18461 18462 $( allowedPlugin.selectors ).trigger( "wb-init." + allowedPlugin.initEvent ); 18463 } ); 18464 }; 18465 18466// Bind the events 18467$document.on( "timerpoke.wb", selector, init ); 18468 18469// Go through allowed plugins once again when content has been added dynamically 18470$document.on( "wb-contentupdated", function() { 18471 runAllowedPlugins(); 18472} ); 18473 18474// Add the timer poke to initialize the plugin 18475wb.add( selector ); 18476 18477} )( jQuery, window, wb ); 18478 18479/** 18480 * @title WET-BOEW Focus 18481 * @overview User agent safe way of assigning focus to an element 18482 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 18483 * @author @pjackson28 18484 */ 18485( function( $, wb ) { 18486"use strict"; 18487 18488var $document = wb.doc, 18489 $window = wb.win, 18490 clickEvents = "click", 18491 setFocusEvent = "setfocus.wb", 18492 linkSelector = "a[href]", 18493 $linkTarget, 18494 18495 /** 18496 * @method processHash 18497 */ 18498 processHash = function() { 18499 var hash = wb.pageUrlParts.hash; 18500 18501 if ( hash && ( $linkTarget = $( "#" + wb.jqEscape( hash.substring( 1 ) ) ) ).length !== 0 ) { 18502 $linkTarget.trigger( setFocusEvent ); 18503 } 18504 }; 18505 18506// Bind the setfocus event 18507$document.on( setFocusEvent, function( event ) { 18508 if ( event.namespace === "wb" ) { 18509 var $elm = $( event.target ), 18510 $closedParents = $elm.not( "summary" ).parents( "details, [role='tabpanel']" ), 18511 $closedPanels, $closedPanel, len, i; 18512 18513 if ( $closedParents.length !== 0 ) { 18514 18515 // Open any closed ancestor details elements 18516 $closedParents.not( "[open]" ).children( "summary" ).trigger( "click" ); 18517 18518 // Open any closed tabpanels 18519 $closedPanels = $closedParents.filter( "[aria-hidden='true']" ); 18520 len = $closedPanels.length; 18521 for ( i = 0; i !== len; i += 1 ) { 18522 $closedPanel = $closedPanels.eq( i ); 18523 $closedPanel.closest( ".wb-tabs" ) 18524 .find( "#" + $closedPanel.attr( "aria-labelledby" ) ) 18525 .trigger( "click" ); 18526 } 18527 } 18528 18529 // Set the tabindex to -1 (as needed) to ensure the element is focusable 18530 $elm 18531 .filter( ":not([tabindex], a[href], button, input, textarea, select)" ) 18532 .attr( "tabindex", "-1" ); 18533 18534 // Assigns focus to an element (delay allows for revealing of hidden content) 18535 setTimeout( function() { 18536 $elm.trigger( "focus" ); 18537 18538 var $topBar = $( ".wb-bar-t[aria-hidden=false]" ); 18539 18540 // Ensure the top bar overlay does not conceal the focus target 18541 if ( $topBar.length !== 0 ) { 18542 document.documentElement.scrollTop -= $topBar.outerHeight(); 18543 } 18544 18545 return $elm; 18546 }, 100 ); 18547 } 18548} ); 18549 18550// Set focus to the target of a deep link from a different page 18551// (helps browsers that can't set the focus on their own) 18552$document.on( "wb-ready.wb", processHash ); 18553 18554// Handle any changes to the URL hash after the page has loaded 18555$window.on( "hashchange", function() { 18556 wb.pageUrlParts.hash = window.location.hash; 18557 if ( !wb.ignoreHashChange ) { 18558 processHash(); 18559 } 18560} ); 18561 18562// Helper for browsers that can't change keyboard and/or event focus on a same page link click 18563$document.on( clickEvents, linkSelector, function( event ) { 18564 var testHref = event.currentTarget.getAttribute( "href" ); 18565 18566 // Same page links only 18567 if ( testHref.length > 1 && testHref.charAt( 0 ) === "#" && !event.isDefaultPrevented() && 18568 ( $linkTarget = $( "#" + wb.jqEscape( testHref.substring( 1 ) ) ) ).length !== 0 ) { 18569 wb.ignoreHashChange = true; 18570 $linkTarget.trigger( setFocusEvent ); 18571 } 18572} ); 18573 18574} )( jQuery, wb ); 18575 18576/** 18577 * @title WET-BOEW JSON Manager 18578 * @overview Manage JSON dataset, execute JSON patch operation. 18579 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 18580 * @author @duboisp 18581 */ 18582/*global jsonpointer, jsonpatch */ 18583( function( $, window, wb ) { 18584"use strict"; 18585 18586/* 18587 * Variable and function definitions. 18588 * These are global to the plugin - meaning that they will be initialized once per page, 18589 * not once per instance of plugin on the page. So, this is a good place to define 18590 * variables that are common to all instances of the plugin on a page. 18591 */
18592var componentName = "wb-jsonmanager", 18593 selector = "[data-" + componentName + "]", 18594 initEvent = "wb-init." + componentName, 18595 postponeEvent = "postpone." + componentName, 18596 patchesEvent = "patches." + componentName, 18597 jsonFailedClass = "jsonfail", 18598 reloadFlag = "data-" + componentName + "-reload", 18599 dsNameRegistered = [], 18600 datasetCache = {}, 18601 datasetCacheSettings = {}, 18602 dsDelayed = {}, 18603 dsPostponePatches = {}, 18604 dsFetching = {}, 18605 dsFetchIsArray = {}, 18606 dsFetchMerged = {}, 18607 $document = wb.doc, 18608 defaults = { 18609 ops: [ 18610 { 18611 name: "patches", 18612 fn: function( obj, key, tree ) { 18613 var path = this.path, 18614 patches = this.patches, 18615 newTree = jsonpointer.get( tree, path ); 18616 18617 patches.forEach( ( patchConf ) => { 18618 patchConf.mainTree = tree; 18619 patchConf.pathParent = path; 18620 jsonpatch.apply( newTree, [ patchConf ] ); 18621 } ); 18622 } 18623 }, 18624 { 18625 name: "wb-count", 18626 fn: function( obj, key, tree ) { 18627 var countme = obj[ key ], 18628 len = 0, i_len, i, 18629 filter = this.filter || [ ], 18630 filternot = this.filternot || [ ]; 18631 18632 if ( !Array.isArray( filter ) ) { 18633 filter = [ filter ]; 18634 } 18635 if ( !Array.isArray( filternot ) ) { 18636 filternot = [ filternot ]; 18637 } 18638 18639 if ( ( filter.length || filternot.length ) && Array.isArray( countme ) ) { 18640 18641 // Iterate in obj[key] / item and check if is true for the given path is any. 18642 i_len = countme.length; 18643 18644 for ( i = 0; i !== i_len; i = i + 1 ) { 18645 if ( filterPassJSON( countme[ i ], filter, filternot ) ) { 18646 len = len + 1; 18647 } 18648 } 18649 } else if ( Array.isArray( countme ) ) { 18650 len = countme.length; 18651 } 18652 applyPatch( tree, "add", this.set, len ); 18653 } 18654 }, 18655 { 18656 name: "wb-first", 18657 fn: function( obj, key, tree ) { 18658 var currObj = obj[ key ]; 18659 if ( !Array.isArray( currObj ) || currObj.length === 0 ) { 18660 return; 18661 } 18662 18663 applyPatch( tree, "add", this.set, currObj[ 0 ] ); 18664 } 18665 }, 18666 { 18667 name: "wb-last", 18668 fn: function( obj, key, tree ) { 18669 var currObj = obj[ key ]; 18670 if ( !Array.isArray( currObj ) || currObj.length === 0 ) { 18671 return; 18672 } 18673 18674 applyPatch( tree, "add", this.set, currObj[ currObj.length - 1 ] ); 18675 } 18676 }, 18677 { 18678 name: "wb-nbtolocal", 18679 fn: function( obj, key, tree ) { 18680 var val = obj[ key ], 18681 loc = this.locale || window.wb.lang, 18682 suffix = this.suffix || "", 18683 prefix = this.prefix || ""; 18684 18685 if ( typeof val === "string" ) { 18686 val = parseFloat( val ); 18687 if ( isNaN( val ) ) { 18688 return; 18689 } 18690 } 18691 18692 applyPatch( tree, "replace", this.path, prefix + val.toLocaleString( loc ) + suffix ); 18693 } 18694 }, 18695 { 18696 name: "wb-decodeUTF8Base64", 18697 fn: function( obj, key, tree ) { 18698 var val = obj[ key ]; 18699 18700 if ( !this.set ) { 18701 applyPatch( tree, "replace", this.path, wb.decodeUTF8Base64( val ) ); 18702 } else { 18703 applyPatch( tree, "add", this.set, wb.decodeUTF8Base64( val ) ); 18704 } 18705 } 18706 }, 18707 { 18708 name: "wb-escapeHTML", 18709 fn: function( obj, key, tree ) { 18710 var val = obj[ key ]; 18711 18712 if ( !this.set ) { 18713 applyPatch( tree, "replace", this.path, wb.escapeHTML( val ) ); 18714 } else { 18715 applyPatch( tree, "add", this.set, wb.escapeHTML( val ) ); 18716 } 18717 } 18718 }, 18719 { 18720 name: "wb-toDateISO", 18721 fn: function( obj, key, tree ) { 18722 if ( !this.set ) { 18723 applyPatch( tree, "replace", this.path, wb.date.toDateISO( obj[ key ] ) ); 18724 } else { 18725 applyPatch( tree, "add", this.set, wb.date.toDateISO( obj[ key ] ) ); 18726 } 18727 } 18728 }, 18729 { 18730 name: "wb-toDateTimeISO", 18731 fn: function( obj, key, tree ) { 18732 if ( !this.set ) { 18733 applyPatch( tree, "replace", this.path, wb.date.toDateISO( obj[ key ], true ) ); 18734 } else { 18735 applyPatch( tree, "add", this.set, wb.date.toDateISO( obj[ key ], true ) ); 18736 } 18737 } 18738 }, 18739 { 18740 name: "wb-swap", 18741 fn: function( obj, key, tree ) { 18742 var val = obj[ key ], 18743 ref = this.ref, 18744 mainTree = this.mainTree || obj, 18745 path = this.path, 18746 newVal, 18747 refObject, refIsArray, valWasArray, 18748 i, i_len, i_item, 18749 j, j_len, j_item; 18750 18751 if ( val ) { 18752 18753 refObject = jsonpointer.get( mainTree, ref ); 18754 refIsArray = Array.isArray( refObject ); 18755 valWasArray = Array.isArray( val ); 18756 18757 if ( !valWasArray ) { 18758 val = [ val ]; 18759 } 18760 18761 i_len = val.length; 18762 18763 for ( i = 0; i !== i_len; i++ ) { 18764 i_item = val[ i ]; 18765 newVal = undefined; // Reinit 18766 if ( !refIsArray ) { 18767 i_item = i_item.replaceAll( "~", "~0" ).replaceAll( "/", "~1" ); // Escape slashed and tilde in val when the key is an IRI for JSON pointer compatibility 18768 newVal = mainTree ? jsonpointer.get( mainTree, ref + "/"
18768+ i_item ) : jsonpointer.get( tree, ref + "/" + i_item ); 18769 } else { 18770 18771 // Iterate until we found a corresponding value in the property "@id" 18772 j_len = refObject.length; 18773 for ( j = 0; j !== j_len; j++ ) { 18774 j_item = refObject[ j ]; 18775 if ( j_item[ "@id" ] && j_item[ "@id" ] === i_item ) { 18776 newVal = j_item; 18777 break; 18778 } 18779 } 18780 if ( !newVal ) { 18781 console.error( "wb-swap: Unable to find a corresponding value for: " + val + " in reference " + ref ); 18782 break; 18783 } 18784 } 18785 if ( newVal && !valWasArray ) { 18786 applyPatch( tree, "replace", path, newVal ); 18787 } else if ( newVal ) { 18788 applyPatch( tree, "replace", path + "/" + i, newVal ); 18789 } 18790 } 18791 } 18792 } 18793 } 18794 ], 18795 opsArray: [ 18796 { 18797 name: "wb-toDateISO", 18798 fn: function( arr ) { 18799 var setval = this.set, 18800 pathval = this.path, 18801 i, i_len = arr.length; 18802 for ( i = 0; i !== i_len; i += 1 ) { 18803 if ( setval ) { 18804 jsonpatch.apply( arr, [ 18805 { op: "wb-toDateISO", set: "/" + i + setval, path: "/" + i + pathval } 18806 ] ); 18807 } else { 18808 jsonpatch.apply( arr, [ 18809 { op: "wb-toDateISO", path: "/" + i + pathval } 18810 ] ); 18811 } 18812 } 18813 } 18814 }, 18815 { 18816 name: "wb-toDateTimeISO", 18817 fn: function( arr ) { 18818 var setval = this.set, 18819 pathval = this.path, 18820 i, i_len = arr.length; 18821 for ( i = 0; i !== i_len; i += 1 ) { 18822 if ( setval ) { 18823 jsonpatch.apply( arr, [ 18824 { op: "wb-toDateTimeISO", set: "/" + i + setval, path: "/" + i + pathval } 18825 ] ); 18826 } else { 18827 jsonpatch.apply( arr, [ 18828 { op: "wb-toDateTimeISO", path: "/" + i + pathval } 18829 ] ); 18830 } 18831 } 18832 } 18833 }, 18834 { 18835 name: "wb-swap", 18836 fn: function( arr ) { 18837 arr.forEach( ( item, i ) => { 18838 jsonpatch.apply( arr, [ 18839 { op: "wb-swap", path: "/" + i + this.path, ref: this.ref, mainTree: this.mainTree } 18840 ] ); 18841 } ); 18842 } 18843 }, 18844 { 18845 name: "patches", 18846 fn: function( arr ) { 18847 arr.forEach( ( item, i ) => { 18848 jsonpatch.apply( this.mainTree || arr, [ 18849 { op: "patches", path: ( this.pathParent || "" ) + "/" + i + this.path, patches: this.patches } 18850 ] ); 18851 } ); 18852 } 18853 } 18854 ], 18855 opsRoot: [], 18856 settings: { }, 18857 docMapKeys: { "referer": document.referrer, "locationHref": location.href } 18858 }, 18859 18860 // Add debug information after the JSON manager element 18861 debugPrintOut = function( $elm, name, json, patches ) { 18862 $elm.after( "<p lang=\"en\"><strong>JSON Manager Debug</strong> (" + name + ")</p><ul lang=\"en\"><li>JSON: <pre><code>" + JSON.stringify( json ) + "</code></pre></li><li>Patches: <pre><code>" + JSON.stringify( patches ) + "</code></pre>" ); 18863 console.log( json ); 18864 }, 18865 18866 /** 18867 * @method init 18868 * @param {jQuery Event} event Event that triggered the function call 18869 */ 18870 init = function( event ) { 18871 18872 // Start initialization 18873 // returns DOM object = proceed with init 18874 // returns undefined = do not proceed with init (e.g., already initialized) 18875 var elm = wb.init( event, componentName, selector ), 18876 $elm, 18877 jsSettings = window[ componentName ] || { }, 18878 ops, opsArray, opsRoot, 18879 i, i_len, i_cache, 18880 url, urlActual, dsName, 18881 fetchOpts = { }; 18882 18883 if ( elm ) { 18884 $elm = $( elm ); 18885 18886 // Load handlebars 18887 Modernizr.load( { 18888 18889 // For loading multiple dependencies 18890 load: [ 18891 "site!deps/json-patch" + wb.getMode() + ".js", 18892 "site!deps/jsonpointer" + wb.getMode() + ".js" 18893 ], 18894 testReady: function() { 18895 return window.jsonpatch && window.jsonpointer; 18896 }, 18897 complete: function() { 18898 var elmData = wb.getData( $elm, componentName ); 18899 18900 if ( !defaults.registered ) { 18901 ops = defaults.ops.concat( jsSettings.ops || [ ] ); 18902 opsArray = defaults.opsArray.concat( jsSettings.opsArray || [ ] ); 18903 opsRoot = defaults.opsRoot.concat( jsSettings.opsRoot || [ ] ); 18904 18905 if ( ops.length ) { 18906 for ( i = 0, i_len = ops.length; i !== i_len; i++ ) { 18907 i_cache = ops[ i ]; 18908 jsonpatch.registerOps( i_cache.name, i_cache.fn ); 18909 } 18910 } 18911 if ( opsArray.length ) { 18912 for ( i = 0, i_len = opsArray.length; i !== i_len; i++ ) { 18913 i_cache = opsArray[ i ]; 18914 jsonpatch.registerOpsArray( i_cache.name, i_cache.fn ); 18915 } 18916 } 18917 if ( opsRoot.length ) { 18918 for ( i = 0, i_len = opsRoot.length; i !== i_len; i++ ) { 18919 i_cache = opsRoot[ i ]; 18920 jsonpatch.registerOpsRoot( i_cache.name, i_cache.fn ); 18921 } 18922 } 18923 defaults.settings = $.extend( {}, defaults.settings, jsSettings.settings || {} ); 18924 defaults.registered = true; 18925 } 18926 18927 dsName = elmData.name; 18928 if ( !dsName || dsName in dsNameRegistered ) { 18929 throw "Dataset name must be unique"; 18930 } 18931 dsNameRegistered.push( dsName ); 18932 18933 url = elmData.url; 18934 18935 if ( url ) { 18936 18937 url = typeof url === "string" ? [ url ] : url; 18938 i_len = url.length; 18939 18940 dsFetching[ dsName ] = i_len; 18941 18942 for ( i = 0; i !== i_len; i++ ) { 18943 18944 urlActual = url[ i ]; 18945 18946 // Fetch default configuration 18947 fetchOpts = { 18948 nocache: elmData.nocache, 18949 nocachekey: elmData.nocachekey, 18950 data: elmData.data, 18951 contentType: elmData.contenttype, 18952 method: elmData.method 18953 }; 18954 18955 // When the "url" is an extended configuration 18956 if ( urlActual.url ) {
18957 fetchOpts.savingPath = urlActual.path || ""; 18958 fetchOpts.url = urlActual.url; 18959 } else { 18960 fetchOpts.url = urlActual; 18961 } 18962 18963 // Fetch the JSON 18964 $elm.trigger( { 18965 type: "json-fetch.wb", 18966 fetch: fetchOpts 18967 } ); 18968 18969 // If the URL is a dataset, make it ready 18970 if ( fetchOpts.url.charCodeAt( 0 ) === 35 && fetchOpts.url.charCodeAt( 1 ) === 91 ) { 18971 wb.ready( $elm, componentName ); 18972 } 18973 } 18974 } else if ( !url && elmData.extractor ) { 18975 $elm.trigger( { 18976 type: "json-fetched.wb", 18977 fetch: { 18978 response: {} 18979 } 18980 } ); 18981 wb.ready( $elm, componentName ); 18982 18983 } else { 18984 18985 $elm.trigger( { 18986 type: "json-fetch.wb" 18987 } ); 18988 wb.ready( $elm, componentName ); 18989 } 18990 18991 } 18992 } ); 18993 } 18994 }, 18995 extractData = function( elmObj ) { 18996 18997 var isGroup = false, 18998 selectedTag, 18999 targetTag, 19000 lastIndex = [], 19001 j_tag = "", 19002 group = {}, 19003 arrMap = [], 19004 node_children = [], 19005 j_node = 0, 19006 arrRepeatPath = [], 19007 combineToObj = function( cur_obj ) { 19008 if ( cur_obj.selector === j_tag ) { 19009 if ( !lastIndex.includes( j_tag ) ) { 19010 group[ cur_obj.path ] = cur_obj.attr && node_children[ j_node ].getAttributeNode( cur_obj.attr ) ? 19011 node_children[ j_node ].getAttributeNode( cur_obj.attr ).textContent : 19012 node_children[ j_node ].textContent; 19013 lastIndex.push( j_tag ); 19014 } 19015 } 19016 }, 19017 manageObjDir = function( selector, selectedValue, json_return ) { 19018 var arrPath = selector.path.split( "/" ).filter( Boolean ); 19019 19020 // Check if selectedValue is an empty value returned by querySelectorAll 19021 if ( selectedValue && selectedValue instanceof NodeList && selectedValue.length === 0 ) { 19022 selectedValue = null; 19023 } 19024 19025 if ( arrPath.length > 1 ) { 19026 var pointer = ""; 19027 pointer = arrPath.pop(); 19028 19029 if ( arrPath[ 0 ] && arrPath[ 0 ] !== "" ) { 19030 19031 if ( !json_return[ arrPath[ 0 ] ] && !arrRepeatPath.includes( arrPath[ 0 ] ) ) { 19032 arrRepeatPath.push( arrPath[ 0 ] ); 19033 json_return[ arrPath[ 0 ] ] = {}; 19034 } 19035 if ( selector.selectAll && !json_return[ arrPath[ 0 ] ] [ pointer ] ) { 19036 json_return[ arrPath[ 0 ] ] [ pointer ] = []; 19037 } 19038 if ( selector.selectAll ) { 19039 json_return[ arrPath[ 0 ] ] [ pointer ].push( selectedValue ); 19040 } else { 19041 json_return[ arrPath[ 0 ] ] [ pointer ] = selectedValue; 19042 } 19043 19044 } else { 19045 19046 if ( selector.selectAll ) { 19047 json_return[ arrPath[ 0 ] ].push( selectedValue ); 19048 } else { 19049 json_return[ pointer ] = selectedValue; 19050 } 19051 } 19052 } else { 19053 19054 if ( selector.selectAll ) { 19055 if ( !json_return[ selectedTag.path ] ) { 19056 json_return[ selectedTag.path ] = []; 19057 } 19058 json_return[ selectedTag.path ].push( selectedValue ); 19059 } else { 19060 json_return[ selectedTag.path ] = selectedValue; 19061 } 19062 } 19063 }, 19064 jsonSource = {}; 19065 19066 19067 for ( var tag = 0; tag <= elmObj.length - 1; tag++ ) { 19068 19069 selectedTag = elmObj[ tag ]; 19070 19071 if ( !selectedTag.interface ) { 19072 19073 targetTag = document.querySelectorAll( selectedTag.selector || "" ); 19074 isGroup = selectedTag.extractor && selectedTag.extractor.length >= 1 ? true : false; 19075 19076 if ( selectedTag.selectAll ) { 19077 19078 for ( var i_node = 0; i_node <= targetTag.length - 1; i_node++ ) { 19079 19080 var selectedTagValue = selectedTag.attr && targetTag [ i_node ].getAttributeNode( selectedTag.attr ) ? 19081 targetTag [ i_node ].getAttributeNode( selectedTag.attr ).textContent : 19082 targetTag [ i_node ].textContent; 19083 19084 manageObjDir( selectedTag, selectedTagValue, jsonSource ); 19085 } 19086 } 19087 19088 // extract from combined selectors and group the values e.g dt with dd 19089 if ( isGroup ) { 19090 19091 jsonSource[ selectedTag.path ] = []; 19092 19093 node_children = targetTag[ 0 ].children; 19094 19095 var extractorLength = Object.keys( selectedTag.extractor ).length; 19096 19097 for ( j_node = 0; j_node <= node_children.length - 1; j_node++ ) { 19098 19099 j_tag = node_children[ j_node ].tagName.toLowerCase(); 19100 19101 selectedTag.extractor.find( combineToObj ); 19102 if ( Object.keys( group ).length === extractorLength ) { 19103 arrMap.push( group ); 19104 group = {}; 19105 lastIndex = []; 19106 } 19107 } 19108 $.extend( jsonSource[ selectedTag.path ], arrMap ); 19109 } 19110 19111 if ( targetTag.length ) { 19112 targetTag = selectedTag.attr && targetTag [ 0 ].getAttributeNode( selectedTag.attr ) ? 19113 targetTag [ 0 ].getAttributeNode( selectedTag.attr ).textContent : 19114 targetTag [ 0 ].textContent; 19115 } 19116 19117 } else { 19118 19119 targetTag = defaults.docMapKeys[ selectedTag.interface ]; 19120 19121 manageObjDir( selectedTag, targetTag, jsonSource ); 19122 } 19123 19124 if ( !selectedTag.selectAll ) { 19125 if ( isGroup === false ) { 19126 manageObjDir( selectedTag, targetTag, jsonSource ); 19127 } 19128 } 19129 } 19130 19131 return jsonSource; 19132 }, 19133 19134 // Filtering a JSON 19135 // Return true if trueness && falseness 19136 // Return false if !( trueness && falseness ) 19137 // trueness and falseness is an array of { "path": "", "value": "" } object 19138 filterPassJSON = function( obj, trueness, falseness ) { 19139 var i, i_cache, 19140 trueness_len = trueness.length, 19141 falseness_len = falseness.length, 19142 compareResult = false, 19143 isEqual; 19144 19145 if ( trueness_len || falseness_len ) { 19146 19147 for ( i = 0; i < trueness_len; i += 1 ) { 19148 i_cache = trueness[ i ]; 19149 isEqual = _equalsJSON( jsonpointer.get( obj, i_cache.path ), i_cache.value ); 19150 19151 if ( i_cache.optional ) { 19152 compareResult = compareResult || isEqual; 19153 } else if ( !isEqual ) { 19154 return false;
19155 } else { 19156 compareResult = true; 19157 } 19158 } 19159 if ( trueness_len && !compareResult ) { 19160 return false; 19161 } 19162 19163 for ( i = 0; i < falseness_len; i += 1 ) { 19164 i_cache = falseness[ i ]; 19165 isEqual = _equalsJSON( jsonpointer.get( obj, i_cache.path ), i_cache.value ); 19166 19167 if ( isEqual && !i_cache.optional || isEqual && i_cache.optional ) { 19168 return false; 19169 } 19170 } 19171 19172 } 19173 return true; 19174 }, 19175 19176 // Utility function to compare two JSON value 19177 _equalsJSON = function( a, b ) { 19178 switch ( typeof a ) { 19179 case "undefined": 19180 return false; 19181 case "boolean": 19182 case "string": 19183 case "number": 19184 return a === b; 19185 case "object": 19186 if ( a === null ) { 19187 return b === null; 19188 } 19189 var i, l; 19190 if ( Array.isArray( a ) ) { 19191 if ( Array.isArray( b ) || a.length !== b.length ) { 19192 return false; 19193 } 19194 for ( i = 0, l = a.length; i < l; i++ ) { 19195 if ( !_equalsJSON( a[ i ], b[ i ] ) ) { 19196 return false; 19197 } 19198 } 19199 return true; 19200 } 19201 var bKeys = _objectKeys( b ), 19202 bLength = bKeys.length; 19203 if ( _objectKeys( a ).length !== bLength ) { 19204 return false; 19205 } 19206 for ( i = 0; i < bLength; i++ ) { 19207 if ( !_equalsJSON( a[ i ], b[ i ] ) ) { 19208 return false; 19209 } 19210 } 19211 return true; 19212 default: 19213 return false; 19214 } 19215 }, 19216 _objectKeys = function( obj ) { 19217 var keys; 19218 if ( Array.isArray( obj ) ) { 19219 keys = new Array( obj.length ); 19220 for ( var k = 0; k < keys.length; k++ ) { 19221 keys[ k ] = "" + k; 19222 } 19223 return keys; 19224 } 19225 if ( Object.keys ) { 19226 return Object.keys( obj ); 19227 } 19228 keys = []; 19229 for ( var i in obj ) { 19230 if ( Object.prototype.hasOwnProperty.call( obj, i ) ) { 19231 keys.push( i ); 19232 } 19233 } 19234 return keys; 19235 }, 19236 19237 // Utility function to apply a JSON patch 19238 applyPatch = function( tree, op, path, value ) { 19239 jsonpatch.apply( tree, [ 19240 { op: op, path: path, value: value } 19241 ] ); 19242 }, 19243 19244 // Create series of patches for filtering 19245 getPatchesToFilter = function( JSONsource, filterPath, filterTrueness, filterFaslseness ) { 19246 var filterObj, 19247 i, i_len; 19248 19249 if ( !Array.isArray( filterTrueness ) ) { 19250 filterTrueness = [ filterTrueness ]; 19251 } 19252 if ( !Array.isArray( filterFaslseness ) ) { 19253 filterFaslseness = [ filterFaslseness ]; 19254 } 19255 19256 filterObj = jsonpointer.get( JSONsource, filterPath ); 19257 if ( Array.isArray( filterObj ) ) { 19258 i_len = filterObj.length - 1; 19259 for ( i = i_len; i !== -1; i -= 1 ) { 19260 if ( !filterPassJSON( filterObj[ i ], filterTrueness, filterFaslseness ) ) { 19261 jsonpatch.apply( JSONsource, [ { op: "remove", path: filterPath + "/" + i } ] ); 19262 } 19263 } 19264 } 19265 return JSONsource; 19266 }; 19267 19268$document.on( "json-failed.wb", selector, function( event ) { 19269 var elm = event.target, 19270 $elm; 19271 19272 if ( elm === event.currentTarget ) { 19273 $elm = $( elm ); 19274 $elm.addClass( jsonFailedClass ); 19275 19276 // Identify that initialization has completed 19277 wb.ready( $elm, componentName ); 19278 } 19279} ); 19280 19281$document.on( "json-fetched.wb", selector, function( event ) { 19282 var elm = event.target, 19283 $elm = $( elm ), 19284 settings, 19285 fetchedOpts = event.fetch.fetchedOpts, 19286 isReloading = elm.hasAttribute( reloadFlag ), 19287 dsName, 19288 JSONresponse = event.fetch.response, 19289 isArrayResponse, 19290 resultSet, 19291 i, i_len, i_cache, backlog, selector, 19292 objIterator, savingPathSplit, 19293 patches, filterTrueness, filterFaslseness, filterPath, extractor; 19294 19295 if ( elm === event.currentTarget ) { 19296 settings = wb.getData( $elm, componentName ); 19297 19298 // Is the fetched JSON need to be wrap in another plain object 19299 if ( fetchedOpts && fetchedOpts.savingPath ) { 19300 savingPathSplit = fetchedOpts.savingPath.split( "/" ); 19301 19302 for ( i = savingPathSplit.length - 1; i > 0; i-- ) { 19303 if ( !savingPathSplit[ i ] ) { 19304 continue; 19305 } 19306 objIterator = {}; 19307 objIterator[ savingPathSplit[ i ] ] = JSONresponse; 19308 JSONresponse = objIterator; 19309 } 19310 } 19311 19312 // Determine if the response is an array 19313 isArrayResponse = Array.isArray( JSONresponse ); 19314 19315 // Ensure the response is an independant clone 19316 if ( isArrayResponse ) { 19317 JSONresponse = $.extend( true, [], JSONresponse ); 19318 } else { 19319 JSONresponse = $.extend( true, {}, JSONresponse ); 19320 } 19321 19322 dsName = settings.name; 19323 dsFetching[ dsName ]--; 19324 19325 // Ensure that we do have fetched and merged all urls everything before to move ahead 19326 dsFetchIsArray[ dsName ] = dsFetchIsArray[ dsName ] ? dsFetchIsArray[ dsName ] : isArrayResponse; 19327 19328 if ( dsFetchIsArray[ dsName ] !== isArrayResponse ) { 19329 throw "Can't merge, incompatible JSON type (array vs object)"; 19330 } 19331 19332 if ( !dsFetchMerged[ dsName ] ) { 19333 dsFetchMerged[ dsName ] = JSONresponse; 19334 } else if ( dsFetchMerged[ dsName ] && isArrayResponse ) { 19335 dsFetchMerged[ dsName ] = dsFetchMerged[ dsName ].concat( JSONresponse ); 19336 } else { 19337 dsFetchMerged[ dsName ] = $.extend( dsFetchMerged[ dsName ], JSONresponse ); 19338 } 19339 19340 // Quit and wait for the next fetch 19341 if ( !isReloading && dsFetching[ dsName ] ) { 19342 return; 19343 } 19344 19345 JSONresponse = dsFetchMerged[ dsName ]; 19346 19347 extractor = settings.extractor; 19348 if ( extractor ) { 19349 if ( !Array.isArray( extractor ) ) { 19350 extractor = [ extractor ]; 19351 } 19352 JSONresponse = $.extend( JSONresponse, extractData( extractor ) ); 19353 19354 } 19355 19356 dsName = "[" + dsName + "]"; 19357 patches = settings.patches || []; 19358 filterPath = settings.fpath; 19359 filterTrueness = settings.filter || []; 19360 filterFaslseness = settings.filternot || []; 19361 19362 if ( !Array.isArray( patches ) ) { 19363 patches = [ patches ]; 19364 } 19365 19366 // Apply a filtering 19367 if ( filterPath ) { 19368 JSONresponse = getPatchesToFilter( JSONresponse, filterPath, filterTrueness, filterFaslseness ); 19369 } 19370 19371 // Apply the wraproot 19372 if ( settings.wraproot ) { 19373 i_cache = { }; 19374 i_cache[ settings.wraproot ] = JSONresponse; 19375 JSONresponse = i_cache; 19376 } 19377 19378 // Apply the patches 19379 if ( patches.length ) { 19380 jsonpatch.apply( JSONresponse, patches ); 19381 } 19382 19383 if ( settings.debug ) { 19384 debugPrintOut( $elm, "initEvent", JSONresponse, patches ); 19385 } 19386 19387 try { 19388 datasetCache[ dsName ] = JSONresponse; 19389 } catch ( error ) { 19390 return; 19391 } 19392 datasetCacheSettings[ dsName ] = settings; 19393 19394 if ( isReloading ) { 19395 elm.removeAttribute( reloadFlag ); 19396 i_cache = dsPostponePatches[ dsName ]; 19397 if ( i_cache ) { 19398 $elm.trigger( i_cache ); 19399 } 19400 } 19401 19402 if ( !settings.wait && dsDelayed[ dsName ] ) { 19403 backlog = dsDelayed[ dsName ]; 19404 i_len = backlog.length; 19405 for ( i = 0; i !== i_len; i += 1 ) { 19406 i_cache = backlog[ i ]; 19407 selector = i_cache.selector; 19408 if ( selector.length ) { 19409 try { 19410 resultSet = jsonpointer.get( JSONresponse, selector ); 19411 } catch ( e ) { 19412 throw dsName + " - JSON selector not found: " + selector; 19413 } 19414 } else { 19415 resultSet = JSONresponse; 19416 } 19417 $( "#" + i_cache.callerId ).trigger( { 19418 type: "json-fetched.wb", 19419 fetch: { 19420 response: resultSet, 19421 status: "200", 19422 refId: i_cache.refId, 19423 xhr: null 19424 } 19425 }, this ); 19426 } 19427 } 19428 19429 // Identify that initialization has completed 19430 wb.ready( $elm, componentName ); 19431 } 19432} ); 19433 19434// Apply patches to a preloaded JSON data 19435$document.on( patchesEvent, selector, function( event ) { 19436 var elm = event.target, 19437 $elm = $( elm ), 19438 patches = event.patches, 19439 filterPath = event.fpath, 19440 filterTrueness = event.filter || [], 19441 filterFaslseness = event.filternot || [], 19442 isCumulative = !!event.cumulative, 19443 settings, 19444 dsName, 19445 dsJSON, resultSet, 19446 delayedLst, 19447 i, i_len, i_cache, pntrSelector; 19448 19449 if ( elm === event.currentTarget && Array.isArray( patches ) ) { 19450 settings = wb.getData( $elm, componentName ); 19451 19452 if ( !settings ) { 19453 return true; 19454 } 19455 dsName = "[" + settings.name + "]"; 19456 19457 // Check if the patches need to be hold until the next json-fetch event 19458 if ( elm.hasAttribute( reloadFlag ) ) { 19459 dsPostponePatches[ dsName ] = event; 19460 return true; 19461 } 19462 19463 if ( !dsDelayed[ dsName ] ) { 19464 throw "Applying patched on undefined dataset name: " + dsName; 19465 } 19466 19467 dsJSON = datasetCache[ dsName ]; 19468 if ( !isCumulative ) { 19469 dsJSON = $.extend( true, ( Array.isArray( dsJSON ) ? [] : {} ), dsJSON ); 19470 } 19471 19472 // Apply a filtering 19473 if ( filterPath ) { 19474 dsJSON = getPatchesToFilter( dsJSON, filterPath, filterTrueness, filterFaslseness ); 19475 } 19476 19477 jsonpatch.apply( dsJSON, patches ); 19478 19479 if ( settings.debug ) { 19480 debugPrintOut( $elm, "patchesEvent", dsJSON, patches ); 19481 } 19482 19483 delayedLst = dsDelayed[ dsName ]; 19484 i_len = delayedLst.length; 19485 for ( i = 0; i !== i_len; i += 1 ) { 19486 i_cache = delayedLst[ i ]; 19487 pntrSelector = i_cache.selector; 19488 if ( pntrSelector.length ) { 19489 try { 19490 resultSet = jsonpointer.get( dsJSON, pntrSelector ); 19491 } catch ( e ) { 19492 throw dsName + " - JSON selector not found: " + pntrSelector; 19493 } 19494 } else { 19495 resultSet = dsJSON; 19496 } 19497 $( "#" + i_cache.callerId ).trigger( { 19498 type: "json-fetched.wb", 19499 fetch: { 19500 response: resultSet, 19501 status: "200", 19502 refId: i_cache.refId, 19503 xhr: null 19504 } 19505 }, this ); 19506 } 19507 } 19508} ); 19509 19510 19511// Used by the JSON-fetch plugin for when trying fetching a resource that is mapped a dataset name 19512$document.on( postponeEvent, function( event ) { 19513 var jsonPostpone = event.postpone, 19514 dsName = jsonPostpone.dsname, 19515 callerId = jsonPostpone.callerId, 19516 refId = jsonPostpone.refId, 19517 selector = jsonPostpone.selector, 19518 resultSet; 19519 19520 if ( !dsDelayed[ dsName ] ) { 19521 dsDelayed[ dsName ] = [ ]; 19522 } 19523 19524 // Add to the delayed updates list 19525 dsDelayed[ dsName ].push( { 19526 "callerId": callerId, 19527 "refId": refId, 19528 "selector": selector 19529 } ); 19530 19531 // Send the data if the dataset is ready? 19532 if ( datasetCache[ dsName ] && !datasetCacheSettings[ dsName ].wait ) { 19533 resultSet = datasetCache[ dsName ]; 19534 if ( selector.length ) { 19535 try { 19536 resultSet = jsonpointer.get( resultSet, selector ); 19537 } catch ( e ) { 19538 throw dsName + " - JSON selector not found: " + selector; 19539 } 19540 } 19541 $( "#" + callerId ).trigger( { 19542 type: "json-fetched.wb", 19543 fetch: { 19544 response: resultSet, 19545 status: "200", 19546 refId: refId, 19547 xhr: null 19548 } 19549 }, this ); 19550 } 19551 19552} ); 19553 19554/* 19555 * Integration with wb-fieldflow 19556 * 19557 */ 19558function pushData( $elm, prop, data, reset ) { 19559 var dtCache = $elm.data( prop ); 19560 if ( !dtCache || reset ) { 19561 dtCache = []; 19562 } 19563 dtCache.push( data ); 19564 return $elm.data( prop, dtCache ); 19565} 19566 19567// Fieldflow "op" action 19568$document.on( "op.action.wb-fieldflow", ".wb-fieldflow", function( event, data ) { 19569 19570 if ( !data.op ) { 19571 return; 19572 } 19573 19574 // Postpone the event for form submission 19575 data.preventSubmit = true; 19576 pushData( $( data.provEvt ), "wb-fieldflow-submit", data ); 19577} ); 19578 19579// Fieldflow "op" submit 19580$document.on( "op.submit.wb-fieldflow", ".wb-fieldflow", function( event, data ) { 19581 19582 // Get the hbs Plugin 19583 var op = data.op, 19584 source = data.source, 19585 ops; 19586 19587 if ( !op ) { 19588 return true; 19589 } 19590 19591 if ( !Array.isArray( op ) ) { 19592 ops = []; 19593 ops.push( op ); 19594 } else { 19595 ops = op; 19596 } 19597 19598 $( source ).trigger( { 19599 type: "patches.wb-jsonmanager", 19600 patches: ops 19601 } ); 19602} ); 19603 19604// Bind the init event of the plugin 19605$document.on( "timerpoke.wb " + initEvent, selector, init ); 19606 19607 19608// Add the timer poke to initialize the plugin
19609wb.add( selector ); 19610 19611} )( jQuery, window, wb ); 19612 19613/** 19614 * @title WET-BOEW wb-postback 19615 * @overview This plugin implements AJAX request for form data to submit on same page without refresh 19616 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 19617 * @author @masterbee, @namjohn920, @GormFrank 19618 **/ 19619( function( $, wb ) { 19620"use strict"; 19621 19622var $document = wb.doc, 19623 componentName = "wb-postback", 19624 selector = "." + componentName, 19625 initEvent = "wb-init" + selector, 19626 failEvent = "fail" + selector, 19627 successEvent = "success" + selector, 19628 defaults = {}, 19629 19630 init = function( event ) { 19631 var elm = wb.init( event, componentName, selector ); 19632 19633 if ( elm ) { 19634 var $elm = $( elm ), 19635 settings = $.extend( 19636 true, 19637 {}, 19638 defaults, 19639 wb.getData( $elm, componentName ) 19640 ), 19641 multiple = typeof $elm.data( componentName + "-multiple" ) !== "undefined", 19642 classToggle = settings.toggle || "hide", 19643 selectorSuccess = settings.success, 19644 selectorFailure = settings.failure || selectorSuccess; 19645 const attrBlocked = "data-wb-blocked", 19646 attrSending = "data-wb-sending"; 19647 19648 elm.addEventListener( "submit", function( e ) { 19649 19650 // Prevent regular form submit 19651 e.preventDefault(); 19652 19653 //Check if the form use the validation plugin 19654 if ( elm.parentElement.classList.contains( "wb-frmvld" ) ) { 19655 19656 // Block invalid forms and allow valid ones 19657 if ( !$elm.valid() ) { 19658 $( this ).attr( attrBlocked, "true" ); 19659 } else { 19660 $( this ).removeAttr( attrBlocked ); 19661 } 19662 } 19663 19664 // Submit the form unless it's blocked or currently being sent 19665 if ( !$( this ).attr( attrBlocked ) && !$( this ).attr( attrSending ) ) { 19666 var data = $elm.serializeArray(), 19667 btn = e.submitter, 19668 $selectorSuccess = $( selectorSuccess ), 19669 $selectorFailure = $( selectorFailure ); 19670 19671 // Indicate that the form is currently being sent (to prevent multiple submissions in parallel) 19672 $( this ).attr( attrSending, true ); 19673 19674 // If the submit button contains a variable, add it to the form's paramaters 19675 // Note: Submitting a form via Enter will act as if the FIRST submit button was pressed. Therefore, that button's variable will be added (as opposed to nothing). This is in line with default form submission behaviour. 19676 if ( btn && btn.name ) { 19677 data.push( { name: btn.name, value: btn.value } ); 19678 } 19679 19680 // Hide feedback messages 19681 $selectorFailure.addClass( classToggle ); 19682 $selectorSuccess.addClass( classToggle ); 19683 19684 // Send the form through ajax and ignore the response body. 19685 $.ajax( { 19686 type: this.method, 19687 url: this.action, 19688 data: $.param( data ) 19689 } ) 19690 .done( function() { 19691 $elm.trigger( successEvent ); 19692 $selectorSuccess.removeClass( classToggle ); 19693 } ) 19694 .fail( function( response ) { 19695 $elm.trigger( failEvent, response ); 19696 $selectorFailure.removeClass( classToggle ); 19697 } ) 19698 .always( function() { 19699 19700 // Hide the form unless multiple submits are allowed 19701 if ( !multiple ) { 19702 $elm.addClass( classToggle ); 19703 } 19704 19705 // Remove the sending indicator now that submission is fully complete (i.e. HTTP response code has been received) 19706 $elm.removeAttr( attrSending ); 19707 } ); 19708 } 19709 } ); 19710 19711 wb.ready( $( elm ), componentName ); 19712 } 19713 }; 19714 19715// Bind the init event of the plugin 19716$document.on( "timerpoke.wb " + initEvent, selector, init ); 19717 19718// Add the timer poke to initialize the plugin 19719wb.add( selector ); 19720 19721} )( jQuery, wb ); 19722 19723/** 19724 * @title WET-BOEW Randomize 19725 * @overview This plugin randomly picks one of the child component to be shown on the browser. 19726 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 19727 * @author @masterbee @namjohn920 19728 */ 19729( function( $, window, wb ) { 19730"use strict"; 19731 19732var $document = wb.doc, 19733 componentName = "wb-randomize", 19734 selector = "[data-wb-randomize]", 19735 initEvent = "wb-init" + selector, 19736 defaults = {}, 19737 19738 init = function( event ) { 19739 var elm = wb.init( event, componentName, selector ), 19740 $elm, settings, $selectedElm, valuesList; 19741 19742 if ( elm ) { 19743 $elm = $( elm ); 19744 settings = $.extend( 19745 true, 19746 {}, 19747 defaults, 19748 window[ componentName ], 19749 wb.getData( $elm, componentName ) 19750 ); 19751 19752 if ( settings.attribute ) { 19753 if ( settings.values && Array.isArray( settings.values ) ) { 19754 valuesList = settings.values; 19755 shuffleArray( valuesList ); 19756 elm.setAttribute( settings.attribute, valuesList[ 0 ] ); 19757 } else { 19758 throw componentName + ": You must define the property \"values\" to an array of str
19758ings when \"attribute\" property is defined."; 19759 } 19760 } else { 19761 $selectedElm = settings.selector ? $( settings.selector, $elm ) : $elm.children(); 19762 19763 if ( !$selectedElm.length ) { 19764 throw componentName + " selector setting is invalid or no children"; 19765 } 19766 19767 if ( settings.shuffle ) { 19768 $selectedElm = wb.shuffleDOM( $selectedElm ); 19769 } 19770 19771 if ( settings.toggle ) { 19772 $selectedElm = wb.pickElements( $selectedElm, settings.number ); 19773 $selectedElm.toggleClass( settings.toggle ); 19774 } 19775 } 19776 19777 wb.ready( $elm, componentName ); 19778 } 19779 }, 19780 19781 shuffleArray = function( array ) { 19782 for ( let i = array.length - 1; i > 0; i-- ) { 19783 const j = Math.floor( Math.random() * ( i + 1 ) ); 19784 [ array[ i ], array[ j ] ] = [ array[ j ], array[ i ] ]; 19785 } 19786 }; 19787 19788// Bind the init event of the plugin 19789$document.on( "timerpoke.wb " + initEvent, selector, init ); 19790 19791// Add the timer poke to initialize the plugin 19792wb.add( selector ); 19793 19794} )( jQuery, window, wb ); 19795 19796/** 19797 * Web Experience Toolkit (WET) / Boîte à outils de l'expérience Web (BOEW) 19798 * @title Zebra 19799 * @overview Apply Zebra stripping on a complex data table and simulate column hovering effect 19800 * @license wet-boew.github.io/wet-boew/License-en.html / wet-boew.github.io/wet-boew/Licence-fr.html 19801 * @author @duboisp 19802 * 19803 */ 19804( function( $, window, document, wb ) { 19805"use strict"; 19806 19807/** 19808 * Variable and function definitions. 19809 * These are global to the plugin - meaning that they will be initialized once per page, 19810 * not once per instance of plugin on the page. So, this is a good place to define 19811 * variables that are common to all instances of the plugin on a page. 19812 */ 19813var componentName = "wb-zebra", 19814 selector = "." + componentName, 19815 hoverColClass = componentName + "-col-hover", 19816 selectorHoverCol = "." + hoverColClass + " td, " + hoverColClass + " th", 19817 initEvent = "wb-init" + selector, 19818 tableParsingEvent = "passiveparse.wb-tableparser", 19819 tableParsingCompleteEvent = "parsecomplete.wb-tableparser", 19820 $document = wb.doc, 19821 idCount = 0, 19822 i18n, i18nText, 19823 19824 /** 19825 * Main Entry function to apply the complex zebra stripping 19826 * @method zebraTable 19827 * @param {jQuery DOM element} $elm table element use to apply complex zebra stripping 19828 */ 19829 zebraTable = function( $elm ) { 19830 var i, iLength, tblGroup, 19831 19832 // Cache the table parsed results 19833 tblparser = $elm.data().tblparser; 19834 19835 function addCellClass( arr, className ) { 19836 var i, iLength; 19837 19838 for ( i = 0, iLength = arr.length; i !== iLength; i += 1 ) { 19839 $( arr[ i ].elem ).addClass( className ); 19840 } 19841 } 19842 19843 // Key Cell 19844 if ( tblparser.keycell ) { 19845 addCellClass( tblparser.keycell, "wb-cell-key" ); 19846 } 19847 19848 // Description Cell 19849 if ( tblparser.desccell ) { 19850 addCellClass( tblparser.desccell, "wb-cell-desc" ); 19851 } 19852 19853 // Layout Cell 19854 if ( tblparser.layoutCell ) { 19855 addCellClass( tblparser.layoutCell, "wb-cell-layout" ); 19856 } 19857 19858 // Summary Row Group 19859 if ( tblparser.lstrowgroup ) { 19860 for ( i = 0, iLength = tblparser.lstrowgroup.length; i !== iLength; i += 1 ) { 19861 tblGroup = tblparser.lstrowgroup[ i ]; 19862 19863 // Add a class to the row 19864 if ( tblGroup.type === 3 || tblGroup.row[ 0 ].type === 3 ) { 19865 $( tblGroup.elem ).addClass( "wb-group-summary" ); 19866 } 19867 } 19868 } 19869 19870 // Summary Column Group 19871 if ( tblparser.colgroup ) { 19872 for ( i = 0, iLength = tblparser.colgroup.length; i !== iLength; i += 1 ) { 19873 tblGroup = tblparser.colgroup[ i ]; 19874 19875 // Add a class to the row 19876 if ( tblGroup.type === 3 ) { 19877 $( tblGroup.elem ).addClass( "wb-group-summary" ); 19878 } 19879 } 19880 } 19881 19882 // Identify that initialization has completed 19883 wb.ready( $elm, componentName ); 19884 }, 19885 19886 /** 19887 * @method init 19888 * @param {jQuery Event} event Event that triggered the function call 19889 */ 19890 init = function( event ) { 19891 19892 // Start initialization 19893 // returns DOM object = proceed with init 19894 // returns undefined = do not proceed with init (e.g., already initialized) 19895 var elm = wb.init( event, componentName, selector ), 19896 deps = [ 19897 "site!deps/tableparser" + wb.getMode() + ".js" 19898 ], 19899 elmId; 19900 19901 if ( elm ) { 19902 elmId = elm.id; 19903 19904 // Ensure there is a unique id on the element 19905 if ( !elmId ) { 19906 elmId = componentName + "-id-" + idCount; 19907 idCount += 1; 19908 elm.id = elmId; 19909 } 19910 19911 // Only initialize the i18nText once 19912 if ( !i18nText ) { 19913 i18n = wb.i18n; 19914 i18nText = { 19915 tableMention: i18n( "hyphen" ) + i18n( "tbl-txt" ), 19916 tableFollowing: i18n( "hyphen" ) + i18n( "tbl-dtls" ) 19917 }; 19918 } 19919 19920 // Load the required dependencies 19921 Modernizr.load( { 19922 19923 // For loading multiple dependencies 19924 load: deps, 19925 complete: function() { 19926 19927 // Let's parse the table 19928 $( "#" + elmId ).trigger( tableParsingEvent ); 19929 } 19930 } ); 19931 } 19932 }; 19933 19934// Bind the init event of the plugin 19935$document.on( "timerpoke.wb " + initEvent + " " + tableParsingCompleteEvent, selector, function( event ) { 19936 var eventTarget = event.target; 19937 19938 switch ( event.type ) { 19939 19940 /* 19941 * Init 19942 */ 19943 case "timerpoke": 19944 case "wb-init": 19945 init( event ); 19946 break; 19947 19948 /* 19949 * Data table parsed 19950 */ 19951 case "parsecomplete": 19952 if ( event.currentTarget === eventTarget ) { 19953 zebraTable( $( eventTarget ) ); 19954 } 19955 break; 19956 } 19957 19958 /* 19959 * Since we are working with events we want to ensure that we are being passive about our control, 19960 * so returning true allows for events to always continue 19961 */ 19962 return true; 19963} ); 19964 19965// Applying the hover, Simulate Column Hovering Effect 19966$document.on( "mouseenter focusin", selectorHoverCol, function( event ) { 19967 var tblparserCell = $( event.currentTarget ).data().tblparser; 19968 19969 if ( tblparserCell.col && tblparserCell.col.elem ) { 19970 $( tblparserCell.col.elem ).addClass( "table-hover" ); 19971 } 19972} ); 19973 19974// Removing the hover, Simulate Column Hovering Effect 19975$document.on( "mouseleave focusout", selectorHoverCol, function( event ) { 19976 var tblparserCell = $( event.currentTarget ).data().tblparser; 19977 19978 if ( tblparserCell.col && tblparserCell.col.elem ) { 19979 $( tblparserCell.col.elem ).removeClass( "table-hover" ); 19980 } 19981} ); 19982 19983// Add the timer poke to initialize the plugin
19984wb.add( selector ); 19985 19986} )( jQuery, window, document, wb );
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.