954 lines
116 KiB
JavaScript
954 lines
116 KiB
JavaScript
|
/*!
|
||
|
* domready (c) Dustin Diaz 2014 - License MIT
|
||
|
*/
|
||
|
!function(e,t){typeof module!="undefined"?module.exports=t():typeof define=="function"&&typeof define.amd=="object"?define(t):this[e]=t()}("domready",function(){var e=[],t,n=document,r=n.documentElement.doScroll,i="DOMContentLoaded",s=(r?/^loaded|^c/:/^loaded|^i|^c/).test(n.readyState);return s||n.addEventListener(i,t=function(){n.removeEventListener(i,t),s=1;while(t=e.shift())t()}),function(t){s?setTimeout(t,0):e.push(t)}});
|
||
|
/*! jQuery v2.2.4 | (c) jQuery Foundation | jquery.org/license */
|
||
|
!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="2.2.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isPlainObject:function(a){var b;if("object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype||{},"isPrototypeOf"))return!1;for(b in a);return void 0===b||k.call(a,b)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=d.createElement("script"),b.text=a,d.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:h.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(d=e.call(arguments,2),f=function(){return a.apply(b||this,d.concat(e.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"
|
||
|
}catch(e){}O.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return O.hasData(a)||N.hasData(a)},data:function(a,b,c){return O.access(a,b,c)},removeData:function(a,b){O.remove(a,b)},_data:function(a,b,c){return N.access(a,b,c)},_removeData:function(a,b){N.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=O.get(f),1===f.nodeType&&!N.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),R(f,d,e[d])));N.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){O.set(this,a)}):K(this,function(b){var c,d;if(f&&void 0===b){if(c=O.get(f,a)||O.get(f,a.replace(Q,"-$&").toLowerCase()),void 0!==c)return c;if(d=n.camelCase(a),c=O.get(f,d),void 0!==c)return c;if(c=R(f,d,void 0),void 0!==c)return c}else d=n.camelCase(a),this.each(function(){var c=O.get(this,d);O.set(this,d,b),a.indexOf("-")>-1&&void 0!==c&&O.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){O.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=N.get(a,b),c&&(!d||n.isArray(c)?d=N.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return N.get(a,c)||N.access(a,c,{empty:n.Callbacks("once memory").add(function(){N.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=N.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),U=["Top","Right","Bottom","Left"],V=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)};function W(a,b,c,d){var e,f=1,g=20,h=d?function(){return d.cur()}:function(){return n.css(a,b,"")},i=h(),j=c&&c[3]||(n.cssNumber[b]?"":"px"),k=(n.cssNumber[b]||"px"!==j&&+i)&&T.exec(n.css(a,b));if(k&&k[3]!==j){j=j||k[3],c=c||[],k=+i||1;do f=f||".5",k/=f,n.style(a,b,k+j);while(f!==(f=h()/i)&&1!==f&&--g)}return c&&(k=+k||+i||0,e=c[1]?k+(c[1]+1)*c[2]:+c[2],d&&(d.unit=j,d.start=k,d.end=e)),e}var X=/^(?:checkbox|radio)$/i,Y=/<([\w:-]+)/,Z=/^$|\/(?:java|ecma)script/i,$={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};$.optgroup=$.option,$.tbody=$.tfoot=$.colgroup=$.caption=$.thead,$.th=$.td;function _(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function aa(a,b){for(var c=0,d=a.length;d>c;c++)N.set(a[c],"globalEval",!b||N.get(b[c],"globalEval"))}var ba=/<|&#?\w+;/;function ca(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],o=0,p=a.length;p>o;o++)if(f=a[o],f||0===f)if("object"===n.type(f))n.merge(m,f.nodeType?[f]:f);else if(ba.test(f)){g=g||l.appendChild(b.createElement("div")),h=(Y.exec(f)||["",""])[1].toLowerCase(),i=$[h]||$._default,g.innerHTML=i[1]+n.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;n.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push
|
||
|
void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=n.find.attr(a,"tabindex");return b?parseInt(b,10):cb.test(a.nodeName)||db.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),l.optSelected||(n.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),n.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){n.propFix[this.toLowerCase()]=this});var eb=/[\t\r\n\f]/g;function fb(a){return a.getAttribute&&a.getAttribute("class")||""}n.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(n.isFunction(a))return this.each(function(b){n(this).addClass(a.call(this,b,fb(this)))});if("string"==typeof a&&a){b=a.match(G)||[];while(c=this[i++])if(e=fb(c),d=1===c.nodeType&&(" "+e+" ").replace(eb," ")){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=n.trim(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(n.isFunction(a))return this.each(function(b){n(this).removeClass(a.call(this,b,fb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(G)||[];while(c=this[i++])if(e=fb(c),d=1===c.nodeType&&(" "+e+" ").replace(eb," ")){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=n.trim(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):n.isFunction(a)?this.each(function(c){n(this).toggleClass(a.call(this,c,fb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=n(this),f=a.match(G)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=fb(this),b&&N.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":N.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+fb(c)+" ").replace(eb," ").indexOf(b)>-1)return!0;return!1}});var gb=/\r/g,hb=/[\x20\t\r\n\f]+/g;n.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=n.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,n(this).val()):a,null==e?e="":"number"==typeof e?e+="":n.isArray(e)&&(e=n.map(e,function(a){return null==a?"":a+""})),b=n.valHooks[this.type]||n.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=n.valHooks[e.type]||n.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(gb,""):null==c?"":c)}}}),n.extend({valHooks:{option:{get:function(a){var b=n.find.attr(a,"value");return null!=b?b:n.trim(n.text(a)).replace(hb," ")}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],(c.selected||i===e)&&(l.optDisabled?!c.disabled:null===c.getAttribute("disabled"))&&(!c.parentNode.disabled||!n.nodeName(c.parentNode,"optgroup"))){if(b=n(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=n.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=n.inArray(n.valHooks.option.get(d),f)>-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),n.each(["radio","checkbox"],function(){n.valHooks[this]={set:function(a,b){return n.isArray(b)?a.checked=n.inArray(n(a).val(),b)>-1:void 0}},l.checkOn||(n.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var ib=/^(?:focusinfocus|focusoutblur)$/;n.extend(n.event,{trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!ib.test(q+n.event.triggered)&&(q.inde
|
||
|
;
|
||
|
/*!
|
||
|
* jQuery Once v2.1.1 - http://github.com/robloach/jquery-once
|
||
|
* @license MIT, GPL-2.0
|
||
|
* http://opensource.org/licenses/MIT
|
||
|
* http://opensource.org/licenses/GPL-2.0
|
||
|
*/
|
||
|
(function(e){"use strict";if(typeof exports==="object"){e(require("jquery"))}else if(typeof define==="function"&&define.amd){define(["jquery"],e)}else{e(jQuery)}})(function(e){"use strict";var n=function(e){e=e||"once";if(typeof e!=="string"){throw new Error("The jQuery Once id parameter must be a string")}return e};e.fn.once=function(t){var r="jquery-once-"+n(t);return this.filter(function(){return e(this).data(r)!==true}).data(r,true)};e.fn.removeOnce=function(e){return this.findOnce(e).removeData("jquery-once-"+n(e))};e.fn.findOnce=function(t){var r="jquery-once-"+n(t);return this.filter(function(){return e(this).data(r)===true})}});
|
||
|
|
||
|
/**
|
||
|
* @file
|
||
|
* Parse inline JSON and initialize the drupalSettings global object.
|
||
|
*/
|
||
|
|
||
|
(function () {
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
// Use direct child elements to harden against XSS exploits when CSP is on.
|
||
|
var settingsElement = document.querySelector('head > script[type="application/json"][data-drupal-selector="drupal-settings-json"], body > script[type="application/json"][data-drupal-selector="drupal-settings-json"]');
|
||
|
|
||
|
/**
|
||
|
* Variable generated by Drupal with all the configuration created from PHP.
|
||
|
*
|
||
|
* @global
|
||
|
*
|
||
|
* @type {object}
|
||
|
*/
|
||
|
window.drupalSettings = {};
|
||
|
|
||
|
if (settingsElement !== null) {
|
||
|
window.drupalSettings = JSON.parse(settingsElement.textContent);
|
||
|
}
|
||
|
})();
|
||
|
;
|
||
|
/**
|
||
|
* @file
|
||
|
* Defines the Drupal JavaScript API.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* A jQuery object, typically the return value from a `$(selector)` call.
|
||
|
*
|
||
|
* Holds an HTMLElement or a collection of HTMLElements.
|
||
|
*
|
||
|
* @typedef {object} jQuery
|
||
|
*
|
||
|
* @prop {number} length=0
|
||
|
* Number of elements contained in the jQuery object.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Variable generated by Drupal that holds all translated strings from PHP.
|
||
|
*
|
||
|
* Content of this variable is automatically created by Drupal when using the
|
||
|
* Interface Translation module. It holds the translation of strings used on
|
||
|
* the page.
|
||
|
*
|
||
|
* This variable is used to pass data from the backend to the frontend. Data
|
||
|
* contained in `drupalSettings` is used during behavior initialization.
|
||
|
*
|
||
|
* @global
|
||
|
*
|
||
|
* @var {object} drupalTranslations
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Global Drupal object.
|
||
|
*
|
||
|
* All Drupal JavaScript APIs are contained in this namespace.
|
||
|
*
|
||
|
* @global
|
||
|
*
|
||
|
* @namespace
|
||
|
*/
|
||
|
window.Drupal = {behaviors: {}, locale: {}};
|
||
|
|
||
|
// JavaScript should be made compatible with libraries other than jQuery by
|
||
|
// wrapping it in an anonymous closure.
|
||
|
(function (Drupal, drupalSettings, drupalTranslations) {
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
/**
|
||
|
* Helper to rethrow errors asynchronously.
|
||
|
*
|
||
|
* This way Errors bubbles up outside of the original callstack, making it
|
||
|
* easier to debug errors in the browser.
|
||
|
*
|
||
|
* @param {Error|string} error
|
||
|
* The error to be thrown.
|
||
|
*/
|
||
|
Drupal.throwError = function (error) {
|
||
|
setTimeout(function () { throw error; }, 0);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Custom error thrown after attach/detach if one or more behaviors failed.
|
||
|
* Initializes the JavaScript behaviors for page loads and Ajax requests.
|
||
|
*
|
||
|
* @callback Drupal~behaviorAttach
|
||
|
*
|
||
|
* @param {HTMLDocument|HTMLElement} context
|
||
|
* An element to detach behaviors from.
|
||
|
* @param {?object} settings
|
||
|
* An object containing settings for the current context. It is rarely used.
|
||
|
*
|
||
|
* @see Drupal.attachBehaviors
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Reverts and cleans up JavaScript behavior initialization.
|
||
|
*
|
||
|
* @callback Drupal~behaviorDetach
|
||
|
*
|
||
|
* @param {HTMLDocument|HTMLElement} context
|
||
|
* An element to attach behaviors to.
|
||
|
* @param {object} settings
|
||
|
* An object containing settings for the current context.
|
||
|
* @param {string} trigger
|
||
|
* One of `'unload'`, `'move'`, or `'serialize'`.
|
||
|
*
|
||
|
* @see Drupal.detachBehaviors
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @typedef {object} Drupal~behavior
|
||
|
*
|
||
|
* @prop {Drupal~behaviorAttach} attach
|
||
|
* Function run on page load and after an Ajax call.
|
||
|
* @prop {Drupal~behaviorDetach} detach
|
||
|
* Function run when content is serialized or removed from the page.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Holds all initialization methods.
|
||
|
*
|
||
|
* @namespace Drupal.behaviors
|
||
|
*
|
||
|
* @type {Object.<string, Drupal~behavior>}
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Defines a behavior to be run during attach and detach phases.
|
||
|
*
|
||
|
* Attaches all registered behaviors to a page element.
|
||
|
*
|
||
|
* Behaviors are event-triggered actions that attach to page elements,
|
||
|
* enhancing default non-JavaScript UIs. Behaviors are registered in the
|
||
|
* {@link Drupal.behaviors} object using the method 'attach' and optionally
|
||
|
* also 'detach'.
|
||
|
*
|
||
|
* {@link Drupal.attachBehaviors} is added below to the `jQuery.ready` event
|
||
|
* and therefore runs on initial page load. Developers implementing Ajax in
|
||
|
* their solutions should also call this function after new page content has
|
||
|
* been loaded, feeding in an element to be processed, in order to attach all
|
||
|
* behaviors to the new content.
|
||
|
*
|
||
|
* Behaviors should use `var elements =
|
||
|
* $(context).find(selector).once('behavior-name');` to ensure the behavior is
|
||
|
* attached only once to a given element. (Doing so enables the reprocessing
|
||
|
* of given elements, which may be needed on occasion despite the ability to
|
||
|
* limit behavior attachment to a particular element.)
|
||
|
*
|
||
|
* @example
|
||
|
* Drupal.behaviors.behaviorName = {
|
||
|
* attach: function (context, settings) {
|
||
|
* // ...
|
||
|
* },
|
||
|
* detach: function (context, settings, trigger) {
|
||
|
* // ...
|
||
|
* }
|
||
|
* };
|
||
|
*
|
||
|
* @param {HTMLDocument|HTMLElement} [context=document]
|
||
|
* An element to attach behaviors to.
|
||
|
* @param {object} [settings=drupalSettings]
|
||
|
* An object containing settings for the current context. If none is given,
|
||
|
* the global {@link drupalSettings} object is used.
|
||
|
*
|
||
|
* @see Drupal~behaviorAttach
|
||
|
* @see Drupal.detachBehaviors
|
||
|
*
|
||
|
* @throws {Drupal~DrupalBehaviorError}
|
||
|
*/
|
||
|
Drupal.attachBehaviors = function (context, settings) {
|
||
|
context = context || document;
|
||
|
settings = settings || drupalSettings;
|
||
|
var behaviors = Drupal.behaviors;
|
||
|
// Execute all of them.
|
||
|
for (var i in behaviors) {
|
||
|
if (behaviors.hasOwnProperty(i) && typeof behaviors[i].attach === 'function') {
|
||
|
// Don't stop the execution of behaviors in case of an error.
|
||
|
try {
|
||
|
behaviors[i].attach(context, settings);
|
||
|
}
|
||
|
catch (e) {
|
||
|
Drupal.throwError(e);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Detaches registered behaviors from a page element.
|
||
|
*
|
||
|
* Developers implementing Ajax in their solutions should call this function
|
||
|
* before page content is about to be removed, feeding in an element to be
|
||
|
* processed, in order to allow special behaviors to detach from the content.
|
||
|
*
|
||
|
* Such implementations should use `.findOnce()` and `.removeOnce()` to find
|
||
|
* elements with their corresponding `Drupal.behaviors.behaviorName.attach`
|
||
|
* implementation, i.e. `.removeOnce('behaviorName')`, to ensure the behavior
|
||
|
* is detached only from previously processed elements.
|
||
|
*
|
||
|
* @param {HTMLDocument|HTMLElement} [context=document]
|
||
|
* An element to detach behaviors from.
|
||
|
* @param {object} [settings=drupalSettings]
|
||
|
* An object containing settings for the current context. If none given,
|
||
|
* the global {@link drupalSettings} object is used.
|
||
|
* @param {string} [trigger='unload']
|
||
|
* A string containing what's causing the behaviors to be detached. The
|
||
|
* possible triggers are:
|
||
|
* - `'unload'`: The context element is being removed from the DOM.
|
||
|
* - `'move'`: The element is about to be moved within the DOM (for example,
|
||
|
* during a tabledrag row swap). After the move is completed,
|
||
|
* {@link Drupal.attachBehaviors} is called, so that the behavior can undo
|
||
|
* whatever it did in response to the move. Many behaviors won't need to
|
||
|
* do anything simply in response to the element being moved, but because
|
||
|
* IFRAME elements reload their "src" when being moved within the DOM,
|
||
|
* behaviors bound to IFRAME elements (like WYSIWYG editors) may need to
|
||
|
* take some action.
|
||
|
* - `'serialize'`: When an Ajax form is submitted, this is called with the
|
||
|
* form as the context. This provides every behavior within the form an
|
||
|
* opportunity to ensure that the field elements have correct content
|
||
|
* in them before the form is serialized. The canonical use-case is so
|
||
|
* that WYSIWYG editors can update the hidden textarea to which they are
|
||
|
* bound.
|
||
|
*
|
||
|
* @throws {Drupal~DrupalBehaviorError}
|
||
|
*
|
||
|
* @see Drupal~behaviorDetach
|
||
|
* @see Drupal.attachBehaviors
|
||
|
*/
|
||
|
Drupal.detachBehaviors = function (context, settings, trigger) {
|
||
|
context = context || document;
|
||
|
settings = settings || drupalSettings;
|
||
|
trigger = trigger || 'unload';
|
||
|
var behaviors = Drupal.behaviors;
|
||
|
// Execute all of them.
|
||
|
for (var i in behaviors) {
|
||
|
if (behaviors.hasOwnProperty(i) && typeof behaviors[i].detach === 'function') {
|
||
|
// Don't stop the execution of behaviors in case of an error.
|
||
|
try {
|
||
|
behaviors[i].detach(context, settings, trigger);
|
||
|
}
|
||
|
catch (e) {
|
||
|
Drupal.throwError(e);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Encodes special characters in a plain-text string for display as HTML.
|
||
|
*
|
||
|
* @param {string} str
|
||
|
* The string to be encoded.
|
||
|
*
|
||
|
* @return {string}
|
||
|
* The encoded string.
|
||
|
*
|
||
|
* @ingroup sanitization
|
||
|
*/
|
||
|
Drupal.checkPlain = function (str) {
|
||
|
str = str.toString()
|
||
|
.replace(/&/g, '&')
|
||
|
.replace(/"/g, '"')
|
||
|
.replace(/</g, '<')
|
||
|
.replace(/>/g, '>');
|
||
|
return str;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Replaces placeholders with sanitized values in a string.
|
||
|
*
|
||
|
* @param {string} str
|
||
|
* A string with placeholders.
|
||
|
* @param {object} args
|
||
|
* An object of replacements pairs to make. Incidences of any key in this
|
||
|
* array are replaced with the corresponding value. Based on the first
|
||
|
* character of the key, the value is escaped and/or themed:
|
||
|
* - `'!variable'`: inserted as is.
|
||
|
* - `'@variable'`: escape plain text to HTML ({@link Drupal.checkPlain}).
|
||
|
* - `'%variable'`: escape text and theme as a placeholder for user-
|
||
|
* submitted content ({@link Drupal.checkPlain} +
|
||
|
* `{@link Drupal.theme}('placeholder')`).
|
||
|
*
|
||
|
* @return {string}
|
||
|
* The formatted string.
|
||
|
*
|
||
|
* @see Drupal.t
|
||
|
*/
|
||
|
Drupal.formatString = function (str, args) {
|
||
|
// Keep args intact.
|
||
|
var processedArgs = {};
|
||
|
// Transform arguments before inserting them.
|
||
|
for (var key in args) {
|
||
|
if (args.hasOwnProperty(key)) {
|
||
|
switch (key.charAt(0)) {
|
||
|
// Escaped only.
|
||
|
case '@':
|
||
|
processedArgs[key] = Drupal.checkPlain(args[key]);
|
||
|
break;
|
||
|
|
||
|
// Pass-through.
|
||
|
case '!':
|
||
|
processedArgs[key] = args[key];
|
||
|
break;
|
||
|
|
||
|
// Escaped and placeholder.
|
||
|
default:
|
||
|
processedArgs[key] = Drupal.theme('placeholder', args[key]);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return Drupal.stringReplace(str, processedArgs, null);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Replaces substring.
|
||
|
*
|
||
|
* The longest keys will be tried first. Once a substring has been replaced,
|
||
|
* its new value will not be searched again.
|
||
|
*
|
||
|
* @param {string} str
|
||
|
* A string with placeholders.
|
||
|
* @param {object} args
|
||
|
* Key-value pairs.
|
||
|
* @param {Array|null} keys
|
||
|
* Array of keys from `args`. Internal use only.
|
||
|
*
|
||
|
* @return {string}
|
||
|
* The replaced string.
|
||
|
*/
|
||
|
Drupal.stringReplace = function (str, args, keys) {
|
||
|
if (str.length === 0) {
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
// If the array of keys is not passed then collect the keys from the args.
|
||
|
if (!Array.isArray(keys)) {
|
||
|
keys = [];
|
||
|
for (var k in args) {
|
||
|
if (args.hasOwnProperty(k)) {
|
||
|
keys.push(k);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Order the keys by the character length. The shortest one is the first.
|
||
|
keys.sort(function (a, b) { return a.length - b.length; });
|
||
|
}
|
||
|
|
||
|
if (keys.length === 0) {
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
// Take next longest one from the end.
|
||
|
var key = keys.pop();
|
||
|
var fragments = str.split(key);
|
||
|
|
||
|
if (keys.length) {
|
||
|
for (var i = 0; i < fragments.length; i++) {
|
||
|
// Process each fragment with a copy of remaining keys.
|
||
|
fragments[i] = Drupal.stringReplace(fragments[i], args, keys.slice(0));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fragments.join(args[key]);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Translates strings to the page language, or a given language.
|
||
|
*
|
||
|
* See the documentation of the server-side t() function for further details.
|
||
|
*
|
||
|
* @param {string} str
|
||
|
* A string containing the English text to translate.
|
||
|
* @param {Object.<string, string>} [args]
|
||
|
* An object of replacements pairs to make after translation. Incidences
|
||
|
* of any key in this array are replaced with the corresponding value.
|
||
|
* See {@link Drupal.formatString}.
|
||
|
* @param {object} [options]
|
||
|
* Additional options for translation.
|
||
|
* @param {string} [options.context='']
|
||
|
* The context the source string belongs to.
|
||
|
*
|
||
|
* @return {string}
|
||
|
* The formatted string.
|
||
|
* The translated string.
|
||
|
*/
|
||
|
Drupal.t = function (str, args, options) {
|
||
|
options = options || {};
|
||
|
options.context = options.context || '';
|
||
|
|
||
|
// Fetch the localized version of the string.
|
||
|
if (typeof drupalTranslations !== 'undefined' && drupalTranslations.strings && drupalTranslations.strings[options.context] && drupalTranslations.strings[options.context][str]) {
|
||
|
str = drupalTranslations.strings[options.context][str];
|
||
|
}
|
||
|
|
||
|
if (args) {
|
||
|
str = Drupal.formatString(str, args);
|
||
|
}
|
||
|
return str;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Returns the URL to a Drupal page.
|
||
|
*
|
||
|
* @param {string} path
|
||
|
* Drupal path to transform to URL.
|
||
|
*
|
||
|
* @return {string}
|
||
|
* The full URL.
|
||
|
*/
|
||
|
Drupal.url = function (path) {
|
||
|
return drupalSettings.path.baseUrl + drupalSettings.path.pathPrefix + path;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Returns the passed in URL as an absolute URL.
|
||
|
*
|
||
|
* @param {string} url
|
||
|
* The URL string to be normalized to an absolute URL.
|
||
|
*
|
||
|
* @return {string}
|
||
|
* The normalized, absolute URL.
|
||
|
*
|
||
|
* @see https://github.com/angular/angular.js/blob/v1.4.4/src/ng/urlUtils.js
|
||
|
* @see https://grack.com/blog/2009/11/17/absolutizing-url-in-javascript
|
||
|
* @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L53
|
||
|
*/
|
||
|
Drupal.url.toAbsolute = function (url) {
|
||
|
var urlParsingNode = document.createElement('a');
|
||
|
|
||
|
// Decode the URL first; this is required by IE <= 6. Decoding non-UTF-8
|
||
|
// strings may throw an exception.
|
||
|
try {
|
||
|
url = decodeURIComponent(url);
|
||
|
}
|
||
|
catch (e) {
|
||
|
// Empty.
|
||
|
}
|
||
|
|
||
|
urlParsingNode.setAttribute('href', url);
|
||
|
|
||
|
// IE <= 7 normalizes the URL when assigned to the anchor node similar to
|
||
|
// the other browsers.
|
||
|
return urlParsingNode.cloneNode(false).href;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Returns true if the URL is within Drupal's base path.
|
||
|
*
|
||
|
* @param {string} url
|
||
|
* The URL string to be tested.
|
||
|
*
|
||
|
* @return {bool}
|
||
|
* `true` if local.
|
||
|
*
|
||
|
* @see https://github.com/jquery/jquery-ui/blob/1.11.4/ui/tabs.js#L58
|
||
|
*/
|
||
|
Drupal.url.isLocal = function (url) {
|
||
|
// Always use browser-derived absolute URLs in the comparison, to avoid
|
||
|
// attempts to break out of the base path using directory traversal.
|
||
|
var absoluteUrl = Drupal.url.toAbsolute(url);
|
||
|
var protocol = location.protocol;
|
||
|
|
||
|
// Consider URLs that match this site's base URL but use HTTPS instead of HTTP
|
||
|
// as local as well.
|
||
|
if (protocol === 'http:' && absoluteUrl.indexOf('https:') === 0) {
|
||
|
protocol = 'https:';
|
||
|
}
|
||
|
var baseUrl = protocol + '//' + location.host + drupalSettings.path.baseUrl.slice(0, -1);
|
||
|
|
||
|
// Decoding non-UTF-8 strings may throw an exception.
|
||
|
try {
|
||
|
absoluteUrl = decodeURIComponent(absoluteUrl);
|
||
|
}
|
||
|
catch (e) {
|
||
|
// Empty.
|
||
|
}
|
||
|
try {
|
||
|
baseUrl = decodeURIComponent(baseUrl);
|
||
|
}
|
||
|
catch (e) {
|
||
|
// Empty.
|
||
|
}
|
||
|
|
||
|
// The given URL matches the site's base URL, or has a path under the site's
|
||
|
// base URL.
|
||
|
return absoluteUrl === baseUrl || absoluteUrl.indexOf(baseUrl + '/') === 0;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Formats a string containing a count of items.
|
||
|
*
|
||
|
* This function ensures that the string is pluralized correctly. Since
|
||
|
* {@link Drupal.t} is called by this function, make sure not to pass
|
||
|
* already-localized strings to it.
|
||
|
*
|
||
|
* See the documentation of the server-side
|
||
|
* \Drupal\Core\StringTranslation\TranslationInterface::formatPlural()
|
||
|
* function for more details.
|
||
|
*
|
||
|
* @param {number} count
|
||
|
* The item count to display.
|
||
|
* @param {string} singular
|
||
|
* The string for the singular case. Please make sure it is clear this is
|
||
|
* singular, to ease translation (e.g. use "1 new comment" instead of "1
|
||
|
* new"). Do not use @count in the singular string.
|
||
|
* @param {string} plural
|
||
|
* The string for the plural case. Please make sure it is clear this is
|
||
|
* plural, to ease translation. Use @count in place of the item count, as in
|
||
|
* "@count new comments".
|
||
|
* @param {object} [args]
|
||
|
* An object of replacements pairs to make after translation. Incidences
|
||
|
* of any key in this array are replaced with the corresponding value.
|
||
|
* See {@link Drupal.formatString}.
|
||
|
* Note that you do not need to include @count in this array.
|
||
|
* This replacement is done automatically for the plural case.
|
||
|
* @param {object} [options]
|
||
|
* The options to pass to the {@link Drupal.t} function.
|
||
|
*
|
||
|
* @return {string}
|
||
|
* A translated string.
|
||
|
*/
|
||
|
Drupal.formatPlural = function (count, singular, plural, args, options) {
|
||
|
args = args || {};
|
||
|
args['@count'] = count;
|
||
|
|
||
|
var pluralDelimiter = drupalSettings.pluralDelimiter;
|
||
|
var translations = Drupal.t(singular + pluralDelimiter + plural, args, options).split(pluralDelimiter);
|
||
|
var index = 0;
|
||
|
|
||
|
// Determine the index of the plural form.
|
||
|
if (typeof drupalTranslations !== 'undefined' && drupalTranslations.pluralFormula) {
|
||
|
index = count in drupalTranslations.pluralFormula ? drupalTranslations.pluralFormula[count] : drupalTranslations.pluralFormula['default'];
|
||
|
}
|
||
|
else if (args['@count'] !== 1) {
|
||
|
index = 1;
|
||
|
}
|
||
|
|
||
|
return translations[index];
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Encodes a Drupal path for use in a URL.
|
||
|
*
|
||
|
* For aesthetic reasons slashes are not escaped.
|
||
|
*
|
||
|
* @param {string} item
|
||
|
* Unencoded path.
|
||
|
*
|
||
|
* @return {string}
|
||
|
* The encoded path.
|
||
|
*/
|
||
|
Drupal.encodePath = function (item) {
|
||
|
return window.encodeURIComponent(item).replace(/%2F/g, '/');
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Generates the themed representation of a Drupal object.
|
||
|
*
|
||
|
* All requests for themed output must go through this function. It examines
|
||
|
* the request and routes it to the appropriate theme function. If the current
|
||
|
* theme does not provide an override function, the generic theme function is
|
||
|
* called.
|
||
|
*
|
||
|
* @example
|
||
|
* <caption>To retrieve the HTML for text that should be emphasized and
|
||
|
* displayed as a placeholder inside a sentence.</caption>
|
||
|
* Drupal.theme('placeholder', text);
|
||
|
*
|
||
|
* @namespace
|
||
|
*
|
||
|
* @param {function} func
|
||
|
* The name of the theme function to call.
|
||
|
* @param {...args}
|
||
|
* Additional arguments to pass along to the theme function.
|
||
|
*
|
||
|
* @return {string|object|HTMLElement|jQuery}
|
||
|
* Any data the theme function returns. This could be a plain HTML string,
|
||
|
* but also a complex object.
|
||
|
*/
|
||
|
Drupal.theme = function (func) {
|
||
|
var args = Array.prototype.slice.apply(arguments, [1]);
|
||
|
if (func in Drupal.theme) {
|
||
|
return Drupal.theme[func].apply(this, args);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Formats text for emphasized display in a placeholder inside a sentence.
|
||
|
*
|
||
|
* @param {string} str
|
||
|
* The text to format (plain-text).
|
||
|
*
|
||
|
* @return {string}
|
||
|
* The formatted text (html).
|
||
|
*/
|
||
|
Drupal.theme.placeholder = function (str) {
|
||
|
return '<em class="placeholder">' + Drupal.checkPlain(str) + '</em>';
|
||
|
};
|
||
|
|
||
|
})(Drupal, window.drupalSettings, window.drupalTranslations);
|
||
|
;
|
||
|
// Allow other JavaScript libraries to use $.
|
||
|
if (window.jQuery) {
|
||
|
jQuery.noConflict();
|
||
|
}
|
||
|
|
||
|
// Class indicating that JS is enabled; used for styling purpose.
|
||
|
document.documentElement.className += ' js';
|
||
|
|
||
|
// JavaScript should be made compatible with libraries other than jQuery by
|
||
|
// wrapping it in an anonymous closure.
|
||
|
|
||
|
(function (domready, Drupal, drupalSettings) {
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
// Attach all behaviors.
|
||
|
domready(function () { Drupal.attachBehaviors(document, drupalSettings); });
|
||
|
|
||
|
})(domready, Drupal, window.drupalSettings);
|
||
|
;
|
||
|
/**
|
||
|
* @file
|
||
|
* Adapted from underscore.js with the addition Drupal namespace.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Limits the invocations of a function in a given time frame.
|
||
|
*
|
||
|
* The debounce function wrapper should be used sparingly. One clear use case
|
||
|
* is limiting the invocation of a callback attached to the window resize event.
|
||
|
*
|
||
|
* Before using the debounce function wrapper, consider first whether the
|
||
|
* callback could be attached to an event that fires less frequently or if the
|
||
|
* function can be written in such a way that it is only invoked under specific
|
||
|
* conditions.
|
||
|
*
|
||
|
* @param {function} func
|
||
|
* The function to be invoked.
|
||
|
* @param {number} wait
|
||
|
* The time period within which the callback function should only be
|
||
|
* invoked once. For example if the wait period is 250ms, then the callback
|
||
|
* will only be called at most 4 times per second.
|
||
|
* @param {bool} immediate
|
||
|
* Whether we wait at the beginning or end to execute the function.
|
||
|
*
|
||
|
* @return {function}
|
||
|
* The debounced function.
|
||
|
*/
|
||
|
Drupal.debounce = function (func, wait, immediate) {
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
var timeout;
|
||
|
var result;
|
||
|
return function () {
|
||
|
var context = this;
|
||
|
var args = arguments;
|
||
|
var later = function () {
|
||
|
timeout = null;
|
||
|
if (!immediate) {
|
||
|
result = func.apply(context, args);
|
||
|
}
|
||
|
};
|
||
|
var callNow = immediate && !timeout;
|
||
|
clearTimeout(timeout);
|
||
|
timeout = setTimeout(later, wait);
|
||
|
if (callNow) {
|
||
|
result = func.apply(context, args);
|
||
|
}
|
||
|
return result;
|
||
|
};
|
||
|
};
|
||
|
;
|
||
|
/*! jquery.cookie v1.4.1 | MIT */
|
||
|
!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?a(require("jquery")):a(jQuery)}(function(a){function b(a){return h.raw?a:encodeURIComponent(a)}function c(a){return h.raw?a:decodeURIComponent(a)}function d(a){return b(h.json?JSON.stringify(a):String(a))}function e(a){0===a.indexOf('"')&&(a=a.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\"));try{return a=decodeURIComponent(a.replace(g," ")),h.json?JSON.parse(a):a}catch(b){}}function f(b,c){var d=h.raw?b:e(b);return a.isFunction(c)?c(d):d}var g=/\+/g,h=a.cookie=function(e,g,i){if(void 0!==g&&!a.isFunction(g)){if(i=a.extend({},h.defaults,i),"number"==typeof i.expires){var j=i.expires,k=i.expires=new Date;k.setTime(+k+864e5*j)}return document.cookie=[b(e),"=",d(g),i.expires?"; expires="+i.expires.toUTCString():"",i.path?"; path="+i.path:"",i.domain?"; domain="+i.domain:"",i.secure?"; secure":""].join("")}for(var l=e?void 0:{},m=document.cookie?document.cookie.split("; "):[],n=0,o=m.length;o>n;n++){var p=m[n].split("="),q=c(p.shift()),r=p.join("=");if(e&&e===q){l=f(r,g);break}e||void 0===(r=f(r))||(l[q]=r)}return l};h.defaults={},a.removeCookie=function(b,c){return void 0===a.cookie(b)?!1:(a.cookie(b,"",a.extend({},c,{expires:-1})),!a.cookie(b))}});;
|
||
|
/**
|
||
|
* @file
|
||
|
* Form features.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Triggers when a value in the form changed.
|
||
|
*
|
||
|
* The event triggers when content is typed or pasted in a text field, before
|
||
|
* the change event triggers.
|
||
|
*
|
||
|
* @event formUpdated
|
||
|
*/
|
||
|
|
||
|
(function ($, Drupal, debounce) {
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
/**
|
||
|
* Retrieves the summary for the first element.
|
||
|
*
|
||
|
* @return {string}
|
||
|
* The text of the summary.
|
||
|
*/
|
||
|
$.fn.drupalGetSummary = function () {
|
||
|
var callback = this.data('summaryCallback');
|
||
|
return (this[0] && callback) ? $.trim(callback(this[0])) : '';
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Sets the summary for all matched elements.
|
||
|
*
|
||
|
* @param {function} callback
|
||
|
* Either a function that will be called each time the summary is
|
||
|
* retrieved or a string (which is returned each time).
|
||
|
*
|
||
|
* @return {jQuery}
|
||
|
* jQuery collection of the current element.
|
||
|
*
|
||
|
* @fires event:summaryUpdated
|
||
|
*
|
||
|
* @listens event:formUpdated
|
||
|
*/
|
||
|
$.fn.drupalSetSummary = function (callback) {
|
||
|
var self = this;
|
||
|
|
||
|
// To facilitate things, the callback should always be a function. If it's
|
||
|
// not, we wrap it into an anonymous function which just returns the value.
|
||
|
if (typeof callback !== 'function') {
|
||
|
var val = callback;
|
||
|
callback = function () { return val; };
|
||
|
}
|
||
|
|
||
|
return this
|
||
|
.data('summaryCallback', callback)
|
||
|
// To prevent duplicate events, the handlers are first removed and then
|
||
|
// (re-)added.
|
||
|
.off('formUpdated.summary')
|
||
|
.on('formUpdated.summary', function () {
|
||
|
self.trigger('summaryUpdated');
|
||
|
})
|
||
|
// The actual summaryUpdated handler doesn't fire when the callback is
|
||
|
// changed, so we have to do this manually.
|
||
|
.trigger('summaryUpdated');
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Prevents consecutive form submissions of identical form values.
|
||
|
*
|
||
|
* Repetitive form submissions that would submit the identical form values
|
||
|
* are prevented, unless the form values are different to the previously
|
||
|
* submitted values.
|
||
|
*
|
||
|
* This is a simplified re-implementation of a user-agent behavior that
|
||
|
* should be natively supported by major web browsers, but at this time, only
|
||
|
* Firefox has a built-in protection.
|
||
|
*
|
||
|
* A form value-based approach ensures that the constraint is triggered for
|
||
|
* consecutive, identical form submissions only. Compared to that, a form
|
||
|
* button-based approach would (1) rely on [visible] buttons to exist where
|
||
|
* technically not required and (2) require more complex state management if
|
||
|
* there are multiple buttons in a form.
|
||
|
*
|
||
|
* This implementation is based on form-level submit events only and relies
|
||
|
* on jQuery's serialize() method to determine submitted form values. As such,
|
||
|
* the following limitations exist:
|
||
|
*
|
||
|
* - Event handlers on form buttons that preventDefault() do not receive a
|
||
|
* double-submit protection. That is deemed to be fine, since such button
|
||
|
* events typically trigger reversible client-side or server-side
|
||
|
* operations that are local to the context of a form only.
|
||
|
* - Changed values in advanced form controls, such as file inputs, are not
|
||
|
* part of the form values being compared between consecutive form submits
|
||
|
* (due to limitations of jQuery.serialize()). That is deemed to be
|
||
|
* acceptable, because if the user forgot to attach a file, then the size of
|
||
|
* HTTP payload will most likely be small enough to be fully passed to the
|
||
|
* server endpoint within (milli)seconds. If a user mistakenly attached a
|
||
|
* wrong file and is technically versed enough to cancel the form submission
|
||
|
* (and HTTP payload) in order to attach a different file, then that
|
||
|
* edge-case is not supported here.
|
||
|
*
|
||
|
* Lastly, all forms submitted via HTTP GET are idempotent by definition of
|
||
|
* HTTP standards, so excluded in this implementation.
|
||
|
*
|
||
|
* @type {Drupal~behavior}
|
||
|
*/
|
||
|
Drupal.behaviors.formSingleSubmit = {
|
||
|
attach: function () {
|
||
|
function onFormSubmit(e) {
|
||
|
var $form = $(e.currentTarget);
|
||
|
var formValues = $form.serialize();
|
||
|
var previousValues = $form.attr('data-drupal-form-submit-last');
|
||
|
if (previousValues === formValues) {
|
||
|
e.preventDefault();
|
||
|
}
|
||
|
else {
|
||
|
$form.attr('data-drupal-form-submit-last', formValues);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$('body').once('form-single-submit')
|
||
|
.on('submit.singleSubmit', 'form:not([method~="GET"])', onFormSubmit);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Sends a 'formUpdated' event each time a form element is modified.
|
||
|
*
|
||
|
* @param {HTMLElement} element
|
||
|
* The element to trigger a form updated event on.
|
||
|
*
|
||
|
* @fires event:formUpdated
|
||
|
*/
|
||
|
function triggerFormUpdated(element) {
|
||
|
$(element).trigger('formUpdated');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Collects the IDs of all form fields in the given form.
|
||
|
*
|
||
|
* @param {HTMLFormElement} form
|
||
|
* The form element to search.
|
||
|
*
|
||
|
* @return {Array}
|
||
|
* Array of IDs for form fields.
|
||
|
*/
|
||
|
function fieldsList(form) {
|
||
|
var $fieldList = $(form).find('[name]').map(function (index, element) {
|
||
|
// We use id to avoid name duplicates on radio fields and filter out
|
||
|
// elements with a name but no id.
|
||
|
return element.getAttribute('id');
|
||
|
});
|
||
|
// Return a true array.
|
||
|
return $.makeArray($fieldList);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Triggers the 'formUpdated' event on form elements when they are modified.
|
||
|
*
|
||
|
* @type {Drupal~behavior}
|
||
|
*
|
||
|
* @prop {Drupal~behaviorAttach} attach
|
||
|
* Attaches formUpdated behaviors.
|
||
|
* @prop {Drupal~behaviorDetach} detach
|
||
|
* Detaches formUpdated behaviors.
|
||
|
*
|
||
|
* @fires event:formUpdated
|
||
|
*/
|
||
|
Drupal.behaviors.formUpdated = {
|
||
|
attach: function (context) {
|
||
|
var $context = $(context);
|
||
|
var contextIsForm = $context.is('form');
|
||
|
var $forms = (contextIsForm ? $context : $context.find('form')).once('form-updated');
|
||
|
var formFields;
|
||
|
|
||
|
if ($forms.length) {
|
||
|
// Initialize form behaviors, use $.makeArray to be able to use native
|
||
|
// forEach array method and have the callback parameters in the right
|
||
|
// order.
|
||
|
$.makeArray($forms).forEach(function (form) {
|
||
|
var events = 'change.formUpdated input.formUpdated ';
|
||
|
var eventHandler = debounce(function (event) { triggerFormUpdated(event.target); }, 300);
|
||
|
formFields = fieldsList(form).join(',');
|
||
|
|
||
|
form.setAttribute('data-drupal-form-fields', formFields);
|
||
|
$(form).on(events, eventHandler);
|
||
|
});
|
||
|
}
|
||
|
// On ajax requests context is the form element.
|
||
|
if (contextIsForm) {
|
||
|
formFields = fieldsList(context).join(',');
|
||
|
// @todo replace with form.getAttribute() when #1979468 is in.
|
||
|
var currentFields = $(context).attr('data-drupal-form-fields');
|
||
|
// If there has been a change in the fields or their order, trigger
|
||
|
// formUpdated.
|
||
|
if (formFields !== currentFields) {
|
||
|
triggerFormUpdated(context);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
},
|
||
|
detach: function (context, settings, trigger) {
|
||
|
var $context = $(context);
|
||
|
var contextIsForm = $context.is('form');
|
||
|
if (trigger === 'unload') {
|
||
|
var $forms = (contextIsForm ? $context : $context.find('form')).removeOnce('form-updated');
|
||
|
if ($forms.length) {
|
||
|
$.makeArray($forms).forEach(function (form) {
|
||
|
form.removeAttribute('data-drupal-form-fields');
|
||
|
$(form).off('.formUpdated');
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Prepopulate form fields with information from the visitor browser.
|
||
|
*
|
||
|
* @type {Drupal~behavior}
|
||
|
*
|
||
|
* @prop {Drupal~behaviorAttach} attach
|
||
|
* Attaches the behavior for filling user info from browser.
|
||
|
*/
|
||
|
Drupal.behaviors.fillUserInfoFromBrowser = {
|
||
|
attach: function (context, settings) {
|
||
|
var userInfo = ['name', 'mail', 'homepage'];
|
||
|
var $forms = $('[data-user-info-from-browser]').once('user-info-from-browser');
|
||
|
if ($forms.length) {
|
||
|
userInfo.map(function (info) {
|
||
|
var $element = $forms.find('[name=' + info + ']');
|
||
|
var browserData = localStorage.getItem('Drupal.visitor.' + info);
|
||
|
var emptyOrDefault = ($element.val() === '' || ($element.attr('data-drupal-default-value') === $element.val()));
|
||
|
if ($element.length && emptyOrDefault && browserData) {
|
||
|
$element.val(browserData);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
$forms.on('submit', function () {
|
||
|
userInfo.map(function (info) {
|
||
|
var $element = $forms.find('[name=' + info + ']');
|
||
|
if ($element.length) {
|
||
|
localStorage.setItem('Drupal.visitor.' + info, $element.val());
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
})(jQuery, Drupal, Drupal.debounce);
|
||
|
;
|