Drupal investigation

js_MLFkr8ldHqlCPyMGlYjTiqb_phWUJtwAqg4Wxq9hq4Q.js 94KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611
  1. /**
  2. * @file
  3. * Adapted from underscore.js with the addition Drupal namespace.
  4. */
  5. /**
  6. * Limits the invocations of a function in a given time frame.
  7. *
  8. * The debounce function wrapper should be used sparingly. One clear use case
  9. * is limiting the invocation of a callback attached to the window resize event.
  10. *
  11. * Before using the debounce function wrapper, consider first whether the
  12. * callback could be attached to an event that fires less frequently or if the
  13. * function can be written in such a way that it is only invoked under specific
  14. * conditions.
  15. *
  16. * @param {function} func
  17. * The function to be invoked.
  18. * @param {number} wait
  19. * The time period within which the callback function should only be
  20. * invoked once. For example if the wait period is 250ms, then the callback
  21. * will only be called at most 4 times per second.
  22. * @param {bool} immediate
  23. * Whether we wait at the beginning or end to execute the function.
  24. *
  25. * @return {function}
  26. * The debounced function.
  27. */
  28. Drupal.debounce = function (func, wait, immediate) {
  29. 'use strict';
  30. var timeout;
  31. var result;
  32. return function () {
  33. var context = this;
  34. var args = arguments;
  35. var later = function () {
  36. timeout = null;
  37. if (!immediate) {
  38. result = func.apply(context, args);
  39. }
  40. };
  41. var callNow = immediate && !timeout;
  42. clearTimeout(timeout);
  43. timeout = setTimeout(later, wait);
  44. if (callNow) {
  45. result = func.apply(context, args);
  46. }
  47. return result;
  48. };
  49. };
  50. ;
  51. /**
  52. * @file
  53. * Adds an HTML element and method to trigger audio UAs to read system messages.
  54. *
  55. * Use {@link Drupal.announce} to indicate to screen reader users that an
  56. * element on the page has changed state. For instance, if clicking a link
  57. * loads 10 more items into a list, one might announce the change like this.
  58. *
  59. * @example
  60. * $('#search-list')
  61. * .on('itemInsert', function (event, data) {
  62. * // Insert the new items.
  63. * $(data.container.el).append(data.items.el);
  64. * // Announce the change to the page contents.
  65. * Drupal.announce(Drupal.t('@count items added to @container',
  66. * {'@count': data.items.length, '@container': data.container.title}
  67. * ));
  68. * });
  69. */
  70. (function (Drupal, debounce) {
  71. 'use strict';
  72. var liveElement;
  73. var announcements = [];
  74. /**
  75. * Builds a div element with the aria-live attribute and add it to the DOM.
  76. *
  77. * @type {Drupal~behavior}
  78. *
  79. * @prop {Drupal~behaviorAttach} attach
  80. * Attaches the behavior for drupalAnnouce.
  81. */
  82. Drupal.behaviors.drupalAnnounce = {
  83. attach: function (context) {
  84. // Create only one aria-live element.
  85. if (!liveElement) {
  86. liveElement = document.createElement('div');
  87. liveElement.id = 'drupal-live-announce';
  88. liveElement.className = 'visually-hidden';
  89. liveElement.setAttribute('aria-live', 'polite');
  90. liveElement.setAttribute('aria-busy', 'false');
  91. document.body.appendChild(liveElement);
  92. }
  93. }
  94. };
  95. /**
  96. * Concatenates announcements to a single string; appends to the live region.
  97. */
  98. function announce() {
  99. var text = [];
  100. var priority = 'polite';
  101. var announcement;
  102. // Create an array of announcement strings to be joined and appended to the
  103. // aria live region.
  104. var il = announcements.length;
  105. for (var i = 0; i < il; i++) {
  106. announcement = announcements.pop();
  107. text.unshift(announcement.text);
  108. // If any of the announcements has a priority of assertive then the group
  109. // of joined announcements will have this priority.
  110. if (announcement.priority === 'assertive') {
  111. priority = 'assertive';
  112. }
  113. }
  114. if (text.length) {
  115. // Clear the liveElement so that repeated strings will be read.
  116. liveElement.innerHTML = '';
  117. // Set the busy state to true until the node changes are complete.
  118. liveElement.setAttribute('aria-busy', 'true');
  119. // Set the priority to assertive, or default to polite.
  120. liveElement.setAttribute('aria-live', priority);
  121. // Print the text to the live region. Text should be run through
  122. // Drupal.t() before being passed to Drupal.announce().
  123. liveElement.innerHTML = text.join('\n');
  124. // The live text area is updated. Allow the AT to announce the text.
  125. liveElement.setAttribute('aria-busy', 'false');
  126. }
  127. }
  128. /**
  129. * Triggers audio UAs to read the supplied text.
  130. *
  131. * The aria-live region will only read the text that currently populates its
  132. * text node. Replacing text quickly in rapid calls to announce results in
  133. * only the text from the most recent call to {@link Drupal.announce} being
  134. * read. By wrapping the call to announce in a debounce function, we allow for
  135. * time for multiple calls to {@link Drupal.announce} to queue up their
  136. * messages. These messages are then joined and append to the aria-live region
  137. * as one text node.
  138. *
  139. * @param {string} text
  140. * A string to be read by the UA.
  141. * @param {string} [priority='polite']
  142. * A string to indicate the priority of the message. Can be either
  143. * 'polite' or 'assertive'.
  144. *
  145. * @return {function}
  146. * The return of the call to debounce.
  147. *
  148. * @see http://www.w3.org/WAI/PF/aria-practices/#liveprops
  149. */
  150. Drupal.announce = function (text, priority) {
  151. // Save the text and priority into a closure variable. Multiple simultaneous
  152. // announcements will be concatenated and read in sequence.
  153. announcements.push({
  154. text: text,
  155. priority: priority
  156. });
  157. // Immediately invoke the function that debounce returns. 200 ms is right at
  158. // the cusp where humans notice a pause, so we will wait
  159. // at most this much time before the set of queued announcements is read.
  160. return (debounce(announce, 200)());
  161. };
  162. }(Drupal, Drupal.debounce));
  163. ;
  164. window.matchMedia||(window.matchMedia=function(){"use strict";var e=window.styleMedia||window.media;if(!e){var t=document.createElement("style"),i=document.getElementsByTagName("script")[0],n=null;t.type="text/css";t.id="matchmediajs-test";i.parentNode.insertBefore(t,i);n="getComputedStyle"in window&&window.getComputedStyle(t,null)||t.currentStyle;e={matchMedium:function(e){var i="@media "+e+"{ #matchmediajs-test { width: 1px; } }";if(t.styleSheet){t.styleSheet.cssText=i}else{t.textContent=i}return n.width==="1px"}}}return function(t){return{matches:e.matchMedium(t||"all"),media:t||"all"}}}());
  165. ;
  166. (function(){if(window.matchMedia&&window.matchMedia("all").addListener){return false}var e=window.matchMedia,i=e("only all").matches,n=false,t=0,a=[],r=function(i){clearTimeout(t);t=setTimeout(function(){for(var i=0,n=a.length;i<n;i++){var t=a[i].mql,r=a[i].listeners||[],o=e(t.media).matches;if(o!==t.matches){t.matches=o;for(var s=0,l=r.length;s<l;s++){r[s].call(window,t)}}}},30)};window.matchMedia=function(t){var o=e(t),s=[],l=0;o.addListener=function(e){if(!i){return}if(!n){n=true;window.addEventListener("resize",r,true)}if(l===0){l=a.push({mql:o,listeners:s})}s.push(e)};o.removeListener=function(e){for(var i=0,n=s.length;i<n;i++){if(s[i]===e){s.splice(i,1)}}};return o}})();
  167. ;
  168. /**
  169. * @file
  170. * Manages elements that can offset the size of the viewport.
  171. *
  172. * Measures and reports viewport offset dimensions from elements like the
  173. * toolbar that can potentially displace the positioning of other elements.
  174. */
  175. /**
  176. * @typedef {object} Drupal~displaceOffset
  177. *
  178. * @prop {number} top
  179. * @prop {number} left
  180. * @prop {number} right
  181. * @prop {number} bottom
  182. */
  183. /**
  184. * Triggers when layout of the page changes.
  185. *
  186. * This is used to position fixed element on the page during page resize and
  187. * Toolbar toggling.
  188. *
  189. * @event drupalViewportOffsetChange
  190. */
  191. (function ($, Drupal, debounce) {
  192. 'use strict';
  193. /**
  194. * @name Drupal.displace.offsets
  195. *
  196. * @type {Drupal~displaceOffset}
  197. */
  198. var offsets = {
  199. top: 0,
  200. right: 0,
  201. bottom: 0,
  202. left: 0
  203. };
  204. /**
  205. * Registers a resize handler on the window.
  206. *
  207. * @type {Drupal~behavior}
  208. */
  209. Drupal.behaviors.drupalDisplace = {
  210. attach: function () {
  211. // Mark this behavior as processed on the first pass.
  212. if (this.displaceProcessed) {
  213. return;
  214. }
  215. this.displaceProcessed = true;
  216. $(window).on('resize.drupalDisplace', debounce(displace, 200));
  217. }
  218. };
  219. /**
  220. * Informs listeners of the current offset dimensions.
  221. *
  222. * @function Drupal.displace
  223. *
  224. * @prop {Drupal~displaceOffset} offsets
  225. *
  226. * @param {bool} [broadcast]
  227. * When true or undefined, causes the recalculated offsets values to be
  228. * broadcast to listeners.
  229. *
  230. * @return {Drupal~displaceOffset}
  231. * An object whose keys are the for sides an element -- top, right, bottom
  232. * and left. The value of each key is the viewport displacement distance for
  233. * that edge.
  234. *
  235. * @fires event:drupalViewportOffsetChange
  236. */
  237. function displace(broadcast) {
  238. offsets = Drupal.displace.offsets = calculateOffsets();
  239. if (typeof broadcast === 'undefined' || broadcast) {
  240. $(document).trigger('drupalViewportOffsetChange', offsets);
  241. }
  242. return offsets;
  243. }
  244. /**
  245. * Determines the viewport offsets.
  246. *
  247. * @return {Drupal~displaceOffset}
  248. * An object whose keys are the for sides an element -- top, right, bottom
  249. * and left. The value of each key is the viewport displacement distance for
  250. * that edge.
  251. */
  252. function calculateOffsets() {
  253. return {
  254. top: calculateOffset('top'),
  255. right: calculateOffset('right'),
  256. bottom: calculateOffset('bottom'),
  257. left: calculateOffset('left')
  258. };
  259. }
  260. /**
  261. * Gets a specific edge's offset.
  262. *
  263. * Any element with the attribute data-offset-{edge} e.g. data-offset-top will
  264. * be considered in the viewport offset calculations. If the attribute has a
  265. * numeric value, that value will be used. If no value is provided, one will
  266. * be calculated using the element's dimensions and placement.
  267. *
  268. * @function Drupal.displace.calculateOffset
  269. *
  270. * @param {string} edge
  271. * The name of the edge to calculate. Can be 'top', 'right',
  272. * 'bottom' or 'left'.
  273. *
  274. * @return {number}
  275. * The viewport displacement distance for the requested edge.
  276. */
  277. function calculateOffset(edge) {
  278. var edgeOffset = 0;
  279. var displacingElements = document.querySelectorAll('[data-offset-' + edge + ']');
  280. var n = displacingElements.length;
  281. for (var i = 0; i < n; i++) {
  282. var el = displacingElements[i];
  283. // If the element is not visible, do consider its dimensions.
  284. if (el.style.display === 'none') {
  285. continue;
  286. }
  287. // If the offset data attribute contains a displacing value, use it.
  288. var displacement = parseInt(el.getAttribute('data-offset-' + edge), 10);
  289. // If the element's offset data attribute exits
  290. // but is not a valid number then get the displacement
  291. // dimensions directly from the element.
  292. if (isNaN(displacement)) {
  293. displacement = getRawOffset(el, edge);
  294. }
  295. // If the displacement value is larger than the current value for this
  296. // edge, use the displacement value.
  297. edgeOffset = Math.max(edgeOffset, displacement);
  298. }
  299. return edgeOffset;
  300. }
  301. /**
  302. * Calculates displacement for element based on its dimensions and placement.
  303. *
  304. * @param {HTMLElement} el
  305. * The jQuery element whose dimensions and placement will be measured.
  306. *
  307. * @param {string} edge
  308. * The name of the edge of the viewport that the element is associated
  309. * with.
  310. *
  311. * @return {number}
  312. * The viewport displacement distance for the requested edge.
  313. */
  314. function getRawOffset(el, edge) {
  315. var $el = $(el);
  316. var documentElement = document.documentElement;
  317. var displacement = 0;
  318. var horizontal = (edge === 'left' || edge === 'right');
  319. // Get the offset of the element itself.
  320. var placement = $el.offset()[horizontal ? 'left' : 'top'];
  321. // Subtract scroll distance from placement to get the distance
  322. // to the edge of the viewport.
  323. placement -= window['scroll' + (horizontal ? 'X' : 'Y')] || document.documentElement['scroll' + (horizontal ? 'Left' : 'Top')] || 0;
  324. // Find the displacement value according to the edge.
  325. switch (edge) {
  326. // Left and top elements displace as a sum of their own offset value
  327. // plus their size.
  328. case 'top':
  329. // Total displacement is the sum of the elements placement and size.
  330. displacement = placement + $el.outerHeight();
  331. break;
  332. case 'left':
  333. // Total displacement is the sum of the elements placement and size.
  334. displacement = placement + $el.outerWidth();
  335. break;
  336. // Right and bottom elements displace according to their left and
  337. // top offset. Their size isn't important.
  338. case 'bottom':
  339. displacement = documentElement.clientHeight - placement;
  340. break;
  341. case 'right':
  342. displacement = documentElement.clientWidth - placement;
  343. break;
  344. default:
  345. displacement = 0;
  346. }
  347. return displacement;
  348. }
  349. /**
  350. * Assign the displace function to a property of the Drupal global object.
  351. *
  352. * @ignore
  353. */
  354. Drupal.displace = displace;
  355. $.extend(Drupal.displace, {
  356. /**
  357. * Expose offsets to other scripts to avoid having to recalculate offsets.
  358. *
  359. * @ignore
  360. */
  361. offsets: offsets,
  362. /**
  363. * Expose method to compute a single edge offsets.
  364. *
  365. * @ignore
  366. */
  367. calculateOffset: calculateOffset
  368. });
  369. })(jQuery, Drupal, Drupal.debounce);
  370. ;
  371. /**
  372. * @file
  373. * Builds a nested accordion widget.
  374. *
  375. * Invoke on an HTML list element with the jQuery plugin pattern.
  376. *
  377. * @example
  378. * $('.toolbar-menu').drupalToolbarMenu();
  379. */
  380. (function ($, Drupal, drupalSettings) {
  381. 'use strict';
  382. /**
  383. * Store the open menu tray.
  384. */
  385. var activeItem = Drupal.url(drupalSettings.path.currentPath);
  386. $.fn.drupalToolbarMenu = function () {
  387. var ui = {
  388. handleOpen: Drupal.t('Extend'),
  389. handleClose: Drupal.t('Collapse')
  390. };
  391. /**
  392. * Handle clicks from the disclosure button on an item with sub-items.
  393. *
  394. * @param {Object} event
  395. * A jQuery Event object.
  396. */
  397. function toggleClickHandler(event) {
  398. var $toggle = $(event.target);
  399. var $item = $toggle.closest('li');
  400. // Toggle the list item.
  401. toggleList($item);
  402. // Close open sibling menus.
  403. var $openItems = $item.siblings().filter('.open');
  404. toggleList($openItems, false);
  405. }
  406. /**
  407. * Handle clicks from a menu item link.
  408. *
  409. * @param {Object} event
  410. * A jQuery Event object.
  411. */
  412. function linkClickHandler(event) {
  413. // If the toolbar is positioned fixed (and therefore hiding content
  414. // underneath), then users expect clicks in the administration menu tray
  415. // to take them to that destination but for the menu tray to be closed
  416. // after clicking: otherwise the toolbar itself is obstructing the view
  417. // of the destination they chose.
  418. if (!Drupal.toolbar.models.toolbarModel.get('isFixed')) {
  419. Drupal.toolbar.models.toolbarModel.set('activeTab', null);
  420. }
  421. // Stopping propagation to make sure that once a toolbar-box is clicked
  422. // (the whitespace part), the page is not redirected anymore.
  423. event.stopPropagation();
  424. }
  425. /**
  426. * Toggle the open/close state of a list is a menu.
  427. *
  428. * @param {jQuery} $item
  429. * The li item to be toggled.
  430. *
  431. * @param {Boolean} switcher
  432. * A flag that forces toggleClass to add or a remove a class, rather than
  433. * simply toggling its presence.
  434. */
  435. function toggleList($item, switcher) {
  436. var $toggle = $item.children('.toolbar-box').children('.toolbar-handle');
  437. switcher = (typeof switcher !== 'undefined') ? switcher : !$item.hasClass('open');
  438. // Toggle the item open state.
  439. $item.toggleClass('open', switcher);
  440. // Twist the toggle.
  441. $toggle.toggleClass('open', switcher);
  442. // Adjust the toggle text.
  443. $toggle
  444. .find('.action')
  445. // Expand Structure, Collapse Structure.
  446. .text((switcher) ? ui.handleClose : ui.handleOpen);
  447. }
  448. /**
  449. * Add markup to the menu elements.
  450. *
  451. * Items with sub-elements have a list toggle attached to them. Menu item
  452. * links and the corresponding list toggle are wrapped with in a div
  453. * classed with .toolbar-box. The .toolbar-box div provides a positioning
  454. * context for the item list toggle.
  455. *
  456. * @param {jQuery} $menu
  457. * The root of the menu to be initialized.
  458. */
  459. function initItems($menu) {
  460. var options = {
  461. class: 'toolbar-icon toolbar-handle',
  462. action: ui.handleOpen,
  463. text: ''
  464. };
  465. // Initialize items and their links.
  466. $menu.find('li > a').wrap('<div class="toolbar-box">');
  467. // Add a handle to each list item if it has a menu.
  468. $menu.find('li').each(function (index, element) {
  469. var $item = $(element);
  470. if ($item.children('ul.toolbar-menu').length) {
  471. var $box = $item.children('.toolbar-box');
  472. options.text = Drupal.t('@label', {'@label': $box.find('a').text()});
  473. $item.children('.toolbar-box')
  474. .append(Drupal.theme('toolbarMenuItemToggle', options));
  475. }
  476. });
  477. }
  478. /**
  479. * Adds a level class to each list based on its depth in the menu.
  480. *
  481. * This function is called recursively on each sub level of lists elements
  482. * until the depth of the menu is exhausted.
  483. *
  484. * @param {jQuery} $lists
  485. * A jQuery object of ul elements.
  486. *
  487. * @param {number} level
  488. * The current level number to be assigned to the list elements.
  489. */
  490. function markListLevels($lists, level) {
  491. level = (!level) ? 1 : level;
  492. var $lis = $lists.children('li').addClass('level-' + level);
  493. $lists = $lis.children('ul');
  494. if ($lists.length) {
  495. markListLevels($lists, level + 1);
  496. }
  497. }
  498. /**
  499. * On page load, open the active menu item.
  500. *
  501. * Marks the trail of the active link in the menu back to the root of the
  502. * menu with .menu-item--active-trail.
  503. *
  504. * @param {jQuery} $menu
  505. * The root of the menu.
  506. */
  507. function openActiveItem($menu) {
  508. var pathItem = $menu.find('a[href="' + location.pathname + '"]');
  509. if (pathItem.length && !activeItem) {
  510. activeItem = location.pathname;
  511. }
  512. if (activeItem) {
  513. var $activeItem = $menu.find('a[href="' + activeItem + '"]').addClass('menu-item--active');
  514. var $activeTrail = $activeItem.parentsUntil('.root', 'li').addClass('menu-item--active-trail');
  515. toggleList($activeTrail, true);
  516. }
  517. }
  518. // Return the jQuery object.
  519. return this.each(function (selector) {
  520. var $menu = $(this).once('toolbar-menu');
  521. if ($menu.length) {
  522. // Bind event handlers.
  523. $menu
  524. .on('click.toolbar', '.toolbar-box', toggleClickHandler)
  525. .on('click.toolbar', '.toolbar-box a', linkClickHandler);
  526. $menu.addClass('root');
  527. initItems($menu);
  528. markListLevels($menu);
  529. // Restore previous and active states.
  530. openActiveItem($menu);
  531. }
  532. });
  533. };
  534. /**
  535. * A toggle is an interactive element often bound to a click handler.
  536. *
  537. * @param {object} options
  538. * Options for the button.
  539. * @param {string} options.class
  540. * Class to set on the button.
  541. * @param {string} options.action
  542. * Action for the button.
  543. * @param {string} options.text
  544. * Used as label for the button.
  545. *
  546. * @return {string}
  547. * A string representing a DOM fragment.
  548. */
  549. Drupal.theme.toolbarMenuItemToggle = function (options) {
  550. return '<button class="' + options['class'] + '"><span class="action">' + options.action + '</span><span class="label">' + options.text + '</span></button>';
  551. };
  552. }(jQuery, Drupal, drupalSettings));
  553. ;
  554. /**
  555. * @file
  556. * Defines the behavior of the Drupal administration toolbar.
  557. */
  558. (function ($, Drupal, drupalSettings) {
  559. 'use strict';
  560. // Merge run-time settings with the defaults.
  561. var options = $.extend(
  562. {
  563. breakpoints: {
  564. 'toolbar.narrow': '',
  565. 'toolbar.standard': '',
  566. 'toolbar.wide': ''
  567. }
  568. },
  569. drupalSettings.toolbar,
  570. // Merge strings on top of drupalSettings so that they are not mutable.
  571. {
  572. strings: {
  573. horizontal: Drupal.t('Horizontal orientation'),
  574. vertical: Drupal.t('Vertical orientation')
  575. }
  576. }
  577. );
  578. /**
  579. * Registers tabs with the toolbar.
  580. *
  581. * The Drupal toolbar allows modules to register top-level tabs. These may
  582. * point directly to a resource or toggle the visibility of a tray.
  583. *
  584. * Modules register tabs with hook_toolbar().
  585. *
  586. * @type {Drupal~behavior}
  587. *
  588. * @prop {Drupal~behaviorAttach} attach
  589. * Attaches the toolbar rendering functionality to the toolbar element.
  590. */
  591. Drupal.behaviors.toolbar = {
  592. attach: function (context) {
  593. // Verify that the user agent understands media queries. Complex admin
  594. // toolbar layouts require media query support.
  595. if (!window.matchMedia('only screen').matches) {
  596. return;
  597. }
  598. // Process the administrative toolbar.
  599. $(context).find('#toolbar-administration').once('toolbar').each(function () {
  600. // Establish the toolbar models and views.
  601. var model = Drupal.toolbar.models.toolbarModel = new Drupal.toolbar.ToolbarModel({
  602. locked: JSON.parse(localStorage.getItem('Drupal.toolbar.trayVerticalLocked')) || false,
  603. activeTab: document.getElementById(JSON.parse(localStorage.getItem('Drupal.toolbar.activeTabID')))
  604. });
  605. Drupal.toolbar.views.toolbarVisualView = new Drupal.toolbar.ToolbarVisualView({
  606. el: this,
  607. model: model,
  608. strings: options.strings
  609. });
  610. Drupal.toolbar.views.toolbarAuralView = new Drupal.toolbar.ToolbarAuralView({
  611. el: this,
  612. model: model,
  613. strings: options.strings
  614. });
  615. Drupal.toolbar.views.bodyVisualView = new Drupal.toolbar.BodyVisualView({
  616. el: this,
  617. model: model
  618. });
  619. // Render collapsible menus.
  620. var menuModel = Drupal.toolbar.models.menuModel = new Drupal.toolbar.MenuModel();
  621. Drupal.toolbar.views.menuVisualView = new Drupal.toolbar.MenuVisualView({
  622. el: $(this).find('.toolbar-menu-administration').get(0),
  623. model: menuModel,
  624. strings: options.strings
  625. });
  626. // Handle the resolution of Drupal.toolbar.setSubtrees.
  627. // This is handled with a deferred so that the function may be invoked
  628. // asynchronously.
  629. Drupal.toolbar.setSubtrees.done(function (subtrees) {
  630. menuModel.set('subtrees', subtrees);
  631. var theme = drupalSettings.ajaxPageState.theme;
  632. localStorage.setItem('Drupal.toolbar.subtrees.' + theme, JSON.stringify(subtrees));
  633. // Indicate on the toolbarModel that subtrees are now loaded.
  634. model.set('areSubtreesLoaded', true);
  635. });
  636. // Attach a listener to the configured media query breakpoints.
  637. for (var label in options.breakpoints) {
  638. if (options.breakpoints.hasOwnProperty(label)) {
  639. var mq = options.breakpoints[label];
  640. var mql = Drupal.toolbar.mql[label] = window.matchMedia(mq);
  641. // Curry the model and the label of the media query breakpoint to
  642. // the mediaQueryChangeHandler function.
  643. mql.addListener(Drupal.toolbar.mediaQueryChangeHandler.bind(null, model, label));
  644. // Fire the mediaQueryChangeHandler for each configured breakpoint
  645. // so that they process once.
  646. Drupal.toolbar.mediaQueryChangeHandler.call(null, model, label, mql);
  647. }
  648. }
  649. // Trigger an initial attempt to load menu subitems. This first attempt
  650. // is made after the media query handlers have had an opportunity to
  651. // process. The toolbar starts in the vertical orientation by default,
  652. // unless the viewport is wide enough to accommodate a horizontal
  653. // orientation. Thus we give the Toolbar a chance to determine if it
  654. // should be set to horizontal orientation before attempting to load
  655. // menu subtrees.
  656. Drupal.toolbar.views.toolbarVisualView.loadSubtrees();
  657. $(document)
  658. // Update the model when the viewport offset changes.
  659. .on('drupalViewportOffsetChange.toolbar', function (event, offsets) {
  660. model.set('offsets', offsets);
  661. });
  662. // Broadcast model changes to other modules.
  663. model
  664. .on('change:orientation', function (model, orientation) {
  665. $(document).trigger('drupalToolbarOrientationChange', orientation);
  666. })
  667. .on('change:activeTab', function (model, tab) {
  668. $(document).trigger('drupalToolbarTabChange', tab);
  669. })
  670. .on('change:activeTray', function (model, tray) {
  671. $(document).trigger('drupalToolbarTrayChange', tray);
  672. });
  673. // If the toolbar's orientation is horizontal and no active tab is
  674. // defined then show the tray of the first toolbar tab by default (but
  675. // not the first 'Home' toolbar tab).
  676. if (Drupal.toolbar.models.toolbarModel.get('orientation') === 'horizontal' && Drupal.toolbar.models.toolbarModel.get('activeTab') === null) {
  677. Drupal.toolbar.models.toolbarModel.set({
  678. activeTab: $('.toolbar-bar .toolbar-tab:not(.home-toolbar-tab) a').get(0)
  679. });
  680. }
  681. });
  682. }
  683. };
  684. /**
  685. * Toolbar methods of Backbone objects.
  686. *
  687. * @namespace
  688. */
  689. Drupal.toolbar = {
  690. /**
  691. * A hash of View instances.
  692. *
  693. * @type {object.<string, Backbone.View>}
  694. */
  695. views: {},
  696. /**
  697. * A hash of Model instances.
  698. *
  699. * @type {object.<string, Backbone.Model>}
  700. */
  701. models: {},
  702. /**
  703. * A hash of MediaQueryList objects tracked by the toolbar.
  704. *
  705. * @type {object.<string, object>}
  706. */
  707. mql: {},
  708. /**
  709. * Accepts a list of subtree menu elements.
  710. *
  711. * A deferred object that is resolved by an inlined JavaScript callback.
  712. *
  713. * @type {jQuery.Deferred}
  714. *
  715. * @see toolbar_subtrees_jsonp().
  716. */
  717. setSubtrees: new $.Deferred(),
  718. /**
  719. * Respond to configured narrow media query changes.
  720. *
  721. * @param {Drupal.toolbar.ToolbarModel} model
  722. * A toolbar model
  723. * @param {string} label
  724. * Media query label.
  725. * @param {object} mql
  726. * A MediaQueryList object.
  727. */
  728. mediaQueryChangeHandler: function (model, label, mql) {
  729. switch (label) {
  730. case 'toolbar.narrow':
  731. model.set({
  732. isOriented: mql.matches,
  733. isTrayToggleVisible: false
  734. });
  735. // If the toolbar doesn't have an explicit orientation yet, or if the
  736. // narrow media query doesn't match then set the orientation to
  737. // vertical.
  738. if (!mql.matches || !model.get('orientation')) {
  739. model.set({orientation: 'vertical'}, {validate: true});
  740. }
  741. break;
  742. case 'toolbar.standard':
  743. model.set({
  744. isFixed: mql.matches
  745. });
  746. break;
  747. case 'toolbar.wide':
  748. model.set({
  749. orientation: ((mql.matches) ? 'horizontal' : 'vertical')
  750. }, {validate: true});
  751. // The tray orientation toggle visibility does not need to be
  752. // validated.
  753. model.set({
  754. isTrayToggleVisible: mql.matches
  755. });
  756. break;
  757. default:
  758. break;
  759. }
  760. }
  761. };
  762. /**
  763. * A toggle is an interactive element often bound to a click handler.
  764. *
  765. * @return {string}
  766. * A string representing a DOM fragment.
  767. */
  768. Drupal.theme.toolbarOrientationToggle = function () {
  769. return '<div class="toolbar-toggle-orientation"><div class="toolbar-lining">' +
  770. '<button class="toolbar-icon" type="button"></button>' +
  771. '</div></div>';
  772. };
  773. /**
  774. * Ajax command to set the toolbar subtrees.
  775. *
  776. * @param {Drupal.Ajax} ajax
  777. * {@link Drupal.Ajax} object created by {@link Drupal.ajax}.
  778. * @param {object} response
  779. * JSON response from the Ajax request.
  780. * @param {number} [status]
  781. * XMLHttpRequest status.
  782. */
  783. Drupal.AjaxCommands.prototype.setToolbarSubtrees = function (ajax, response, status) {
  784. Drupal.toolbar.setSubtrees.resolve(response.subtrees);
  785. };
  786. }(jQuery, Drupal, drupalSettings));
  787. ;
  788. /**
  789. * @file
  790. * A Backbone Model for collapsible menus.
  791. */
  792. (function (Backbone, Drupal) {
  793. 'use strict';
  794. /**
  795. * Backbone Model for collapsible menus.
  796. *
  797. * @constructor
  798. *
  799. * @augments Backbone.Model
  800. */
  801. Drupal.toolbar.MenuModel = Backbone.Model.extend(/** @lends Drupal.toolbar.MenuModel# */{
  802. /**
  803. * @type {object}
  804. *
  805. * @prop {object} subtrees
  806. */
  807. defaults: /** @lends Drupal.toolbar.MenuModel# */{
  808. /**
  809. * @type {object}
  810. */
  811. subtrees: {}
  812. }
  813. });
  814. }(Backbone, Drupal));
  815. ;
  816. /**
  817. * @file
  818. * A Backbone Model for the toolbar.
  819. */
  820. (function (Backbone, Drupal) {
  821. 'use strict';
  822. /**
  823. * Backbone model for the toolbar.
  824. *
  825. * @constructor
  826. *
  827. * @augments Backbone.Model
  828. */
  829. Drupal.toolbar.ToolbarModel = Backbone.Model.extend(/** @lends Drupal.toolbar.ToolbarModel# */{
  830. /**
  831. * @type {object}
  832. *
  833. * @prop activeTab
  834. * @prop activeTray
  835. * @prop isOriented
  836. * @prop isFixed
  837. * @prop areSubtreesLoaded
  838. * @prop isViewportOverflowConstrained
  839. * @prop orientation
  840. * @prop locked
  841. * @prop isTrayToggleVisible
  842. * @prop height
  843. * @prop offsets
  844. */
  845. defaults: /** @lends Drupal.toolbar.ToolbarModel# */{
  846. /**
  847. * The active toolbar tab. All other tabs should be inactive under
  848. * normal circumstances. It will remain active across page loads. The
  849. * active item is stored as an ID selector e.g. '#toolbar-item--1'.
  850. *
  851. * @type {string}
  852. */
  853. activeTab: null,
  854. /**
  855. * Represents whether a tray is open or not. Stored as an ID selector e.g.
  856. * '#toolbar-item--1-tray'.
  857. *
  858. * @type {string}
  859. */
  860. activeTray: null,
  861. /**
  862. * Indicates whether the toolbar is displayed in an oriented fashion,
  863. * either horizontal or vertical.
  864. *
  865. * @type {bool}
  866. */
  867. isOriented: false,
  868. /**
  869. * Indicates whether the toolbar is positioned absolute (false) or fixed
  870. * (true).
  871. *
  872. * @type {bool}
  873. */
  874. isFixed: false,
  875. /**
  876. * Menu subtrees are loaded through an AJAX request only when the Toolbar
  877. * is set to a vertical orientation.
  878. *
  879. * @type {bool}
  880. */
  881. areSubtreesLoaded: false,
  882. /**
  883. * If the viewport overflow becomes constrained, isFixed must be true so
  884. * that elements in the trays aren't lost off-screen and impossible to
  885. * get to.
  886. *
  887. * @type {bool}
  888. */
  889. isViewportOverflowConstrained: false,
  890. /**
  891. * The orientation of the active tray.
  892. *
  893. * @type {string}
  894. */
  895. orientation: 'vertical',
  896. /**
  897. * A tray is locked if a user toggled it to vertical. Otherwise a tray
  898. * will switch between vertical and horizontal orientation based on the
  899. * configured breakpoints. The locked state will be maintained across page
  900. * loads.
  901. *
  902. * @type {bool}
  903. */
  904. locked: false,
  905. /**
  906. * Indicates whether the tray orientation toggle is visible.
  907. *
  908. * @type {bool}
  909. */
  910. isTrayToggleVisible: false,
  911. /**
  912. * The height of the toolbar.
  913. *
  914. * @type {number}
  915. */
  916. height: null,
  917. /**
  918. * The current viewport offsets determined by {@link Drupal.displace}. The
  919. * offsets suggest how a module might position is components relative to
  920. * the viewport.
  921. *
  922. * @type {object}
  923. *
  924. * @prop {number} top
  925. * @prop {number} right
  926. * @prop {number} bottom
  927. * @prop {number} left
  928. */
  929. offsets: {
  930. top: 0,
  931. right: 0,
  932. bottom: 0,
  933. left: 0
  934. }
  935. },
  936. /**
  937. * @inheritdoc
  938. *
  939. * @param {object} attributes
  940. * Attributes for the toolbar.
  941. * @param {object} options
  942. * Options for the toolbar.
  943. *
  944. * @return {string|undefined}
  945. * Returns an error message if validation failed.
  946. */
  947. validate: function (attributes, options) {
  948. // Prevent the orientation being set to horizontal if it is locked, unless
  949. // override has not been passed as an option.
  950. if (attributes.orientation === 'horizontal' && this.get('locked') && !options.override) {
  951. return Drupal.t('The toolbar cannot be set to a horizontal orientation when it is locked.');
  952. }
  953. }
  954. });
  955. }(Backbone, Drupal));
  956. ;
  957. /**
  958. * @file
  959. * A Backbone view for the body element.
  960. */
  961. (function ($, Drupal, Backbone) {
  962. 'use strict';
  963. Drupal.toolbar.BodyVisualView = Backbone.View.extend(/** @lends Drupal.toolbar.BodyVisualView# */{
  964. /**
  965. * Adjusts the body element with the toolbar position and dimension changes.
  966. *
  967. * @constructs
  968. *
  969. * @augments Backbone.View
  970. */
  971. initialize: function () {
  972. this.listenTo(this.model, 'change:orientation change:offsets change:activeTray change:isOriented change:isFixed change:isViewportOverflowConstrained', this.render);
  973. },
  974. /**
  975. * @inheritdoc
  976. */
  977. render: function () {
  978. var $body = $('body');
  979. var orientation = this.model.get('orientation');
  980. var isOriented = this.model.get('isOriented');
  981. var isViewportOverflowConstrained = this.model.get('isViewportOverflowConstrained');
  982. $body
  983. // We are using JavaScript to control media-query handling for two
  984. // reasons: (1) Using JavaScript let's us leverage the breakpoint
  985. // configurations and (2) the CSS is really complex if we try to hide
  986. // some styling from browsers that don't understand CSS media queries.
  987. // If we drive the CSS from classes added through JavaScript,
  988. // then the CSS becomes simpler and more robust.
  989. .toggleClass('toolbar-vertical', (orientation === 'vertical'))
  990. .toggleClass('toolbar-horizontal', (isOriented && orientation === 'horizontal'))
  991. // When the toolbar is fixed, it will not scroll with page scrolling.
  992. .toggleClass('toolbar-fixed', (isViewportOverflowConstrained || this.model.get('isFixed')))
  993. // Toggle the toolbar-tray-open class on the body element. The class is
  994. // applied when a toolbar tray is active. Padding might be applied to
  995. // the body element to prevent the tray from overlapping content.
  996. .toggleClass('toolbar-tray-open', !!this.model.get('activeTray'))
  997. // Apply padding to the top of the body to offset the placement of the
  998. // toolbar bar element.
  999. .css('padding-top', this.model.get('offsets').top);
  1000. }
  1001. });
  1002. }(jQuery, Drupal, Backbone));
  1003. ;
  1004. /**
  1005. * @file
  1006. * A Backbone view for the collapsible menus.
  1007. */
  1008. (function ($, Backbone, Drupal) {
  1009. 'use strict';
  1010. Drupal.toolbar.MenuVisualView = Backbone.View.extend(/** @lends Drupal.toolbar.MenuVisualView# */{
  1011. /**
  1012. * Backbone View for collapsible menus.
  1013. *
  1014. * @constructs
  1015. *
  1016. * @augments Backbone.View
  1017. */
  1018. initialize: function () {
  1019. this.listenTo(this.model, 'change:subtrees', this.render);
  1020. },
  1021. /**
  1022. * @inheritdoc
  1023. */
  1024. render: function () {
  1025. var subtrees = this.model.get('subtrees');
  1026. // Add subtrees.
  1027. for (var id in subtrees) {
  1028. if (subtrees.hasOwnProperty(id)) {
  1029. this.$el
  1030. .find('#toolbar-link-' + id)
  1031. .once('toolbar-subtrees')
  1032. .after(subtrees[id]);
  1033. }
  1034. }
  1035. // Render the main menu as a nested, collapsible accordion.
  1036. if ('drupalToolbarMenu' in $.fn) {
  1037. this.$el
  1038. .children('.toolbar-menu')
  1039. .drupalToolbarMenu();
  1040. }
  1041. }
  1042. });
  1043. }(jQuery, Backbone, Drupal));
  1044. ;
  1045. /**
  1046. * @file
  1047. * A Backbone view for the aural feedback of the toolbar.
  1048. */
  1049. (function (Backbone, Drupal) {
  1050. 'use strict';
  1051. Drupal.toolbar.ToolbarAuralView = Backbone.View.extend(/** @lends Drupal.toolbar.ToolbarAuralView# */{
  1052. /**
  1053. * Backbone view for the aural feedback of the toolbar.
  1054. *
  1055. * @constructs
  1056. *
  1057. * @augments Backbone.View
  1058. *
  1059. * @param {object} options
  1060. * Options for the view.
  1061. * @param {object} options.strings
  1062. * Various strings to use in the view.
  1063. */
  1064. initialize: function (options) {
  1065. this.strings = options.strings;
  1066. this.listenTo(this.model, 'change:orientation', this.onOrientationChange);
  1067. this.listenTo(this.model, 'change:activeTray', this.onActiveTrayChange);
  1068. },
  1069. /**
  1070. * Announces an orientation change.
  1071. *
  1072. * @param {Drupal.toolbar.ToolbarModel} model
  1073. * The toolbar model in question.
  1074. * @param {string} orientation
  1075. * The new value of the orientation attribute in the model.
  1076. */
  1077. onOrientationChange: function (model, orientation) {
  1078. Drupal.announce(Drupal.t('Tray orientation changed to @orientation.', {
  1079. '@orientation': orientation
  1080. }));
  1081. },
  1082. /**
  1083. * Announces a changed active tray.
  1084. *
  1085. * @param {Drupal.toolbar.ToolbarModel} model
  1086. * The toolbar model in question.
  1087. * @param {HTMLElement} tray
  1088. * The new value of the tray attribute in the model.
  1089. */
  1090. onActiveTrayChange: function (model, tray) {
  1091. var relevantTray = (tray === null) ? model.previous('activeTray') : tray;
  1092. var action = (tray === null) ? Drupal.t('closed') : Drupal.t('opened');
  1093. var trayNameElement = relevantTray.querySelector('.toolbar-tray-name');
  1094. var text;
  1095. if (trayNameElement !== null) {
  1096. text = Drupal.t('Tray "@tray" @action.', {
  1097. '@tray': trayNameElement.textContent, '@action': action
  1098. });
  1099. }
  1100. else {
  1101. text = Drupal.t('Tray @action.', {'@action': action});
  1102. }
  1103. Drupal.announce(text);
  1104. }
  1105. });
  1106. }(Backbone, Drupal));
  1107. ;
  1108. /**
  1109. * @file
  1110. * A Backbone view for the toolbar element. Listens to mouse & touch.
  1111. */
  1112. (function ($, Drupal, drupalSettings, Backbone) {
  1113. 'use strict';
  1114. Drupal.toolbar.ToolbarVisualView = Backbone.View.extend(/** @lends Drupal.toolbar.ToolbarVisualView# */{
  1115. /**
  1116. * Event map for the `ToolbarVisualView`.
  1117. *
  1118. * @return {object}
  1119. * A map of events.
  1120. */
  1121. events: function () {
  1122. // Prevents delay and simulated mouse events.
  1123. var touchEndToClick = function (event) {
  1124. event.preventDefault();
  1125. event.target.click();
  1126. };
  1127. return {
  1128. 'click .toolbar-bar .toolbar-tab .trigger': 'onTabClick',
  1129. 'click .toolbar-toggle-orientation button': 'onOrientationToggleClick',
  1130. 'touchend .toolbar-bar .toolbar-tab .trigger': touchEndToClick,
  1131. 'touchend .toolbar-toggle-orientation button': touchEndToClick
  1132. };
  1133. },
  1134. /**
  1135. * Backbone view for the toolbar element. Listens to mouse & touch.
  1136. *
  1137. * @constructs
  1138. *
  1139. * @augments Backbone.View
  1140. *
  1141. * @param {object} options
  1142. * Options for the view object.
  1143. * @param {object} options.strings
  1144. * Various strings to use in the view.
  1145. */
  1146. initialize: function (options) {
  1147. this.strings = options.strings;
  1148. this.listenTo(this.model, 'change:activeTab change:orientation change:isOriented change:isTrayToggleVisible', this.render);
  1149. this.listenTo(this.model, 'change:mqMatches', this.onMediaQueryChange);
  1150. this.listenTo(this.model, 'change:offsets', this.adjustPlacement);
  1151. // Add the tray orientation toggles.
  1152. this.$el
  1153. .find('.toolbar-tray .toolbar-lining')
  1154. .append(Drupal.theme('toolbarOrientationToggle'));
  1155. // Trigger an activeTab change so that listening scripts can respond on
  1156. // page load. This will call render.
  1157. this.model.trigger('change:activeTab');
  1158. },
  1159. /**
  1160. * @inheritdoc
  1161. *
  1162. * @return {Drupal.toolbar.ToolbarVisualView}
  1163. * The `ToolbarVisualView` instance.
  1164. */
  1165. render: function () {
  1166. this.updateTabs();
  1167. this.updateTrayOrientation();
  1168. this.updateBarAttributes();
  1169. // Load the subtrees if the orientation of the toolbar is changed to
  1170. // vertical. This condition responds to the case that the toolbar switches
  1171. // from horizontal to vertical orientation. The toolbar starts in a
  1172. // vertical orientation by default and then switches to horizontal during
  1173. // initialization if the media query conditions are met. Simply checking
  1174. // that the orientation is vertical here would result in the subtrees
  1175. // always being loaded, even when the toolbar initialization ultimately
  1176. // results in a horizontal orientation.
  1177. //
  1178. // @see Drupal.behaviors.toolbar.attach() where admin menu subtrees
  1179. // loading is invoked during initialization after media query conditions
  1180. // have been processed.
  1181. if (this.model.changed.orientation === 'vertical' || this.model.changed.activeTab) {
  1182. this.loadSubtrees();
  1183. }
  1184. // Trigger a recalculation of viewport displacing elements. Use setTimeout
  1185. // to ensure this recalculation happens after changes to visual elements
  1186. // have processed.
  1187. window.setTimeout(function () {
  1188. Drupal.displace(true);
  1189. }, 0);
  1190. return this;
  1191. },
  1192. /**
  1193. * Responds to a toolbar tab click.
  1194. *
  1195. * @param {jQuery.Event} event
  1196. * The event triggered.
  1197. */
  1198. onTabClick: function (event) {
  1199. // If this tab has a tray associated with it, it is considered an
  1200. // activatable tab.
  1201. if (event.target.hasAttribute('data-toolbar-tray')) {
  1202. var activeTab = this.model.get('activeTab');
  1203. var clickedTab = event.target;
  1204. // Set the event target as the active item if it is not already.
  1205. this.model.set('activeTab', (!activeTab || clickedTab !== activeTab) ? clickedTab : null);
  1206. event.preventDefault();
  1207. event.stopPropagation();
  1208. }
  1209. },
  1210. /**
  1211. * Toggles the orientation of a toolbar tray.
  1212. *
  1213. * @param {jQuery.Event} event
  1214. * The event triggered.
  1215. */
  1216. onOrientationToggleClick: function (event) {
  1217. var orientation = this.model.get('orientation');
  1218. // Determine the toggle-to orientation.
  1219. var antiOrientation = (orientation === 'vertical') ? 'horizontal' : 'vertical';
  1220. var locked = antiOrientation === 'vertical';
  1221. // Remember the locked state.
  1222. if (locked) {
  1223. localStorage.setItem('Drupal.toolbar.trayVerticalLocked', 'true');
  1224. }
  1225. else {
  1226. localStorage.removeItem('Drupal.toolbar.trayVerticalLocked');
  1227. }
  1228. // Update the model.
  1229. this.model.set({
  1230. locked: locked,
  1231. orientation: antiOrientation
  1232. }, {
  1233. validate: true,
  1234. override: true
  1235. });
  1236. event.preventDefault();
  1237. event.stopPropagation();
  1238. },
  1239. /**
  1240. * Updates the display of the tabs: toggles a tab and the associated tray.
  1241. */
  1242. updateTabs: function () {
  1243. var $tab = $(this.model.get('activeTab'));
  1244. // Deactivate the previous tab.
  1245. $(this.model.previous('activeTab'))
  1246. .removeClass('is-active')
  1247. .prop('aria-pressed', false);
  1248. // Deactivate the previous tray.
  1249. $(this.model.previous('activeTray'))
  1250. .removeClass('is-active');
  1251. // Activate the selected tab.
  1252. if ($tab.length > 0) {
  1253. $tab
  1254. .addClass('is-active')
  1255. // Mark the tab as pressed.
  1256. .prop('aria-pressed', true);
  1257. var name = $tab.attr('data-toolbar-tray');
  1258. // Store the active tab name or remove the setting.
  1259. var id = $tab.get(0).id;
  1260. if (id) {
  1261. localStorage.setItem('Drupal.toolbar.activeTabID', JSON.stringify(id));
  1262. }
  1263. // Activate the associated tray.
  1264. var $tray = this.$el.find('[data-toolbar-tray="' + name + '"].toolbar-tray');
  1265. if ($tray.length) {
  1266. $tray.addClass('is-active');
  1267. this.model.set('activeTray', $tray.get(0));
  1268. }
  1269. else {
  1270. // There is no active tray.
  1271. this.model.set('activeTray', null);
  1272. }
  1273. }
  1274. else {
  1275. // There is no active tray.
  1276. this.model.set('activeTray', null);
  1277. localStorage.removeItem('Drupal.toolbar.activeTabID');
  1278. }
  1279. },
  1280. /**
  1281. * Update the attributes of the toolbar bar element.
  1282. */
  1283. updateBarAttributes: function () {
  1284. var isOriented = this.model.get('isOriented');
  1285. if (isOriented) {
  1286. this.$el.find('.toolbar-bar').attr('data-offset-top', '');
  1287. }
  1288. else {
  1289. this.$el.find('.toolbar-bar').removeAttr('data-offset-top');
  1290. }
  1291. // Toggle between a basic vertical view and a more sophisticated
  1292. // horizontal and vertical display of the toolbar bar and trays.
  1293. this.$el.toggleClass('toolbar-oriented', isOriented);
  1294. },
  1295. /**
  1296. * Updates the orientation of the active tray if necessary.
  1297. */
  1298. updateTrayOrientation: function () {
  1299. var orientation = this.model.get('orientation');
  1300. // The antiOrientation is used to render the view of action buttons like
  1301. // the tray orientation toggle.
  1302. var antiOrientation = (orientation === 'vertical') ? 'horizontal' : 'vertical';
  1303. // Update the orientation of the trays.
  1304. var $trays = this.$el.find('.toolbar-tray')
  1305. .removeClass('toolbar-tray-horizontal toolbar-tray-vertical')
  1306. .addClass('toolbar-tray-' + orientation);
  1307. // Update the tray orientation toggle button.
  1308. var iconClass = 'toolbar-icon-toggle-' + orientation;
  1309. var iconAntiClass = 'toolbar-icon-toggle-' + antiOrientation;
  1310. var $orientationToggle = this.$el.find('.toolbar-toggle-orientation')
  1311. .toggle(this.model.get('isTrayToggleVisible'));
  1312. $orientationToggle.find('button')
  1313. .val(antiOrientation)
  1314. .attr('title', this.strings[antiOrientation])
  1315. .text(this.strings[antiOrientation])
  1316. .removeClass(iconClass)
  1317. .addClass(iconAntiClass);
  1318. // Update data offset attributes for the trays.
  1319. var dir = document.documentElement.dir;
  1320. var edge = (dir === 'rtl') ? 'right' : 'left';
  1321. // Remove data-offset attributes from the trays so they can be refreshed.
  1322. $trays.removeAttr('data-offset-left data-offset-right data-offset-top');
  1323. // If an active vertical tray exists, mark it as an offset element.
  1324. $trays.filter('.toolbar-tray-vertical.is-active').attr('data-offset-' + edge, '');
  1325. // If an active horizontal tray exists, mark it as an offset element.
  1326. $trays.filter('.toolbar-tray-horizontal.is-active').attr('data-offset-top', '');
  1327. },
  1328. /**
  1329. * Sets the tops of the trays so that they align with the bottom of the bar.
  1330. */
  1331. adjustPlacement: function () {
  1332. var $trays = this.$el.find('.toolbar-tray');
  1333. if (!this.model.get('isOriented')) {
  1334. $trays.css('margin-top', 0);
  1335. $trays.removeClass('toolbar-tray-horizontal').addClass('toolbar-tray-vertical');
  1336. }
  1337. else {
  1338. // The toolbar container is invisible. Its placement is used to
  1339. // determine the container for the trays.
  1340. $trays.css('margin-top', this.$el.find('.toolbar-bar').outerHeight());
  1341. }
  1342. },
  1343. /**
  1344. * Calls the endpoint URI that builds an AJAX command with the rendered
  1345. * subtrees.
  1346. *
  1347. * The rendered admin menu subtrees HTML is cached on the client in
  1348. * localStorage until the cache of the admin menu subtrees on the server-
  1349. * side is invalidated. The subtreesHash is stored in localStorage as well
  1350. * and compared to the subtreesHash in drupalSettings to determine when the
  1351. * admin menu subtrees cache has been invalidated.
  1352. */
  1353. loadSubtrees: function () {
  1354. var $activeTab = $(this.model.get('activeTab'));
  1355. var orientation = this.model.get('orientation');
  1356. // Only load and render the admin menu subtrees if:
  1357. // (1) They have not been loaded yet.
  1358. // (2) The active tab is the administration menu tab, indicated by the
  1359. // presence of the data-drupal-subtrees attribute.
  1360. // (3) The orientation of the tray is vertical.
  1361. if (!this.model.get('areSubtreesLoaded') && typeof $activeTab.data('drupal-subtrees') !== 'undefined' && orientation === 'vertical') {
  1362. var subtreesHash = drupalSettings.toolbar.subtreesHash;
  1363. var theme = drupalSettings.ajaxPageState.theme;
  1364. var endpoint = Drupal.url('toolbar/subtrees/' + subtreesHash);
  1365. var cachedSubtreesHash = localStorage.getItem('Drupal.toolbar.subtreesHash.' + theme);
  1366. var cachedSubtrees = JSON.parse(localStorage.getItem('Drupal.toolbar.subtrees.' + theme));
  1367. var isVertical = this.model.get('orientation') === 'vertical';
  1368. // If we have the subtrees in localStorage and the subtree hash has not
  1369. // changed, then use the cached data.
  1370. if (isVertical && subtreesHash === cachedSubtreesHash && cachedSubtrees) {
  1371. Drupal.toolbar.setSubtrees.resolve(cachedSubtrees);
  1372. }
  1373. // Only make the call to get the subtrees if the orientation of the
  1374. // toolbar is vertical.
  1375. else if (isVertical) {
  1376. // Remove the cached menu information.
  1377. localStorage.removeItem('Drupal.toolbar.subtreesHash.' + theme);
  1378. localStorage.removeItem('Drupal.toolbar.subtrees.' + theme);
  1379. // The AJAX response's command will trigger the resolve method of the
  1380. // Drupal.toolbar.setSubtrees Promise.
  1381. Drupal.ajax({url: endpoint}).execute();
  1382. // Cache the hash for the subtrees locally.
  1383. localStorage.setItem('Drupal.toolbar.subtreesHash.' + theme, subtreesHash);
  1384. }
  1385. }
  1386. }
  1387. });
  1388. }(jQuery, Drupal, drupalSettings, Backbone));
  1389. ;
  1390. /*! jquery.cookie v1.4.1 | MIT */
  1391. !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))}});;
  1392. /* jQuery Foundation Joyride Plugin 2.1 | Copyright 2012, ZURB | www.opensource.org/licenses/mit-license.php */
  1393. (function(e,t,n){"use strict";var r={version:"2.0.3",tipLocation:"bottom",nubPosition:"auto",scroll:!0,scrollSpeed:300,timer:0,autoStart:!1,startTimerOnClick:!0,startOffset:0,nextButton:!0,tipAnimation:"fade",pauseAfter:[],tipAnimationFadeSpeed:300,cookieMonster:!1,cookieName:"joyride",cookieDomain:!1,cookiePath:!1,localStorage:!1,localStorageKey:"joyride",tipContainer:"body",modal:!1,expose:!1,postExposeCallback:e.noop,preRideCallback:e.noop,postRideCallback:e.noop,preStepCallback:e.noop,postStepCallback:e.noop,template:{link:'<a href="#close" class="joyride-close-tip">X</a>',timer:'<div class="joyride-timer-indicator-wrap"><span class="joyride-timer-indicator"></span></div>',tip:'<div class="joyride-tip-guide"><span class="joyride-nub"></span></div>',wrapper:'<div class="joyride-content-wrapper" role="dialog"></div>',button:'<a href="#" class="joyride-next-tip"></a>',modal:'<div class="joyride-modal-bg"></div>',expose:'<div class="joyride-expose-wrapper"></div>',exposeCover:'<div class="joyride-expose-cover"></div>'}},i=i||!1,s={},o={init:function(n){return this.each(function(){e.isEmptyObject(s)?(s=e.extend(!0,r,n),s.document=t.document,s.$document=e(s.document),s.$window=e(t),s.$content_el=e(this),s.$body=e(s.tipContainer),s.body_offset=e(s.tipContainer).position(),s.$tip_content=e("> li",s.$content_el),s.paused=!1,s.attempts=0,s.tipLocationPatterns={top:["bottom"],bottom:[],left:["right","top","bottom"],right:["left","top","bottom"]},o.jquery_check(),e.isFunction(e.cookie)||(s.cookieMonster=!1),(!s.cookieMonster||!e.cookie(s.cookieName))&&(!s.localStorage||!o.support_localstorage()||!localStorage.getItem(s.localStorageKey))&&(s.$tip_content.each(function(t){o.create({$li:e(this),index:t})}),s.autoStart&&(!s.startTimerOnClick&&s.timer>0?(o.show("init"),o.startTimer()):o.show("init"))),s.$document.on("click.joyride",".joyride-next-tip, .joyride-modal-bg",function(e){e.preventDefault(),s.$li.next().length<1?o.end():s.timer>0?(clearTimeout(s.automate),o.hide(),o.show(),o.startTimer()):(o.hide(),o.show())}),s.$document.on("click.joyride",".joyride-close-tip",function(e){e.preventDefault(),o.end()}),s.$window.bind("resize.joyride",function(t){if(s.$li){if(s.exposed&&s.exposed.length>0){var n=e(s.exposed);n.each(function(){var t=e(this);o.un_expose(t),o.expose(t)})}o.is_phone()?o.pos_phone():o.pos_default()}})):o.restart()})},resume:function(){o.set_li(),o.show()},nextTip:function(){s.$li.next().length<1?o.end():s.timer>0?(clearTimeout(s.automate),o.hide(),o.show(),o.startTimer()):(o.hide(),o.show())},tip_template:function(t){var n,r,i;return t.tip_class=t.tip_class||"",n=e(s.template.tip).addClass(t.tip_class),r=e.trim(e(t.li).html())+o.button_text(t.button_text)+s.template.link+o.timer_instance(t.index),i=e(s.template.wrapper),t.li.attr("data-aria-labelledby")&&i.attr("aria-labelledby",t.li.attr("data-aria-labelledby")),t.li.attr("data-aria-describedby")&&i.attr("aria-describedby",t.li.attr("data-aria-describedby")),n.append(i),n.first().attr("data-index",t.index),e(".joyride-content-wrapper",n).append(r),n[0]},timer_instance:function(t){var n;return t===0&&s.startTimerOnClick&&s.timer>0||s.timer===0?n="":n=o.outerHTML(e(s.template.timer)[0]),n},button_text:function(t){return s.nextButton?(t=e.trim(t)||"Next",t=o.outerHTML(e(s.template.button).append(t)[0])):t="",t},create:function(t){var n=t.$li.attr("data-button")||t.$li.attr("data-text"),r=t.$li.attr("class"),i=e(o.tip_template({tip_class:r,index:t.index,button_text:n,li:t.$li}));e(s.tipContainer).append(i)},show:function(t){var r={},i,u=[],a=0,f,l=null;if(s.$li===n||e.inArray(s.$li.index(),s.pauseAfter)===-1){s.paused?s.paused=!1:o.set_li(t),s.attempts=0;if(s.$li.length&&s.$target.length>0){t&&(s.preRideCallback(s.$li.index(),s.$next_tip),s.modal&&o.show_modal()),s.preStepCallback(s.$li.index(),s.$next_tip),u=(s.$li.data("options")||":").split(";"),a=u.length;for(i=a-1;i>=0;i--)f=u[i].split(":"),f.length===2&&(r[e.trim(f[0])]=e.trim(f[1]));s.tipSettings=e.extend({},s,r),s.tipSettings.tipLocationPattern=s.tipLocationPatterns[s.tipSettings.tipLocation],s.modal&&s.expose&&o.expose(),!/body/i.test(s.$target.selector)&&s.scroll&&o.scroll_to(),o.is_phone()?o.pos_phone(!0):o.pos_default(!0),l=e(".joyride-timer-indicator",s.$next_tip),/pop/i.test(s.tipAnimation)?(l.outerWidth(0),s.timer>0?(s.$next_tip.show(),l.animate({width:e(".joyride-timer-indicator-wrap",s.$next_tip).outerWidth()},s.timer)):s.$next_tip.show()):/fade/i.test(s.tipAnimation)&&(l.outerWidth(0),s.timer>0?(s.$next_tip.fadeIn(s.tipAnimationFadeSpeed),s.$next_tip.show(),l.animate({width:e(".joyride-timer-indicator-wrap",s.$next_tip).outerWidth()},s.timer)):s.$next_tip.fadeIn(s.tipAnimationFadeSpeed)),s.$current_tip=s.$next_tip,e(".joyride-next-tip",s.$current_tip).focus(),o.tabbable(s.$current_tip)}else s.$li&&s.$target.length<1?o.show():o.end()}else s.paused=!0},is_phone:function(){return i?i.mq("only screen and (max-width: 767px)"):s.$window.width()<767?!0:!1},support_localstorage:function(){return i?i.localstorage:!!t.localStorage},hide:function(){s.modal&&s.expose&&o.un_expose(),s.modal||e(".joyride-modal-bg").hide(),s.$current_tip.hide(),s.postStepCallback(s.$li.index(),s.$current_tip)},set_li:function(e){e?(s.$li=s.$tip_content.eq(s.startOffset),o.set_next_tip(),s.$current_tip=s.$next_tip):(s.$li=s.$li.next(),o.set_next_tip()),o.set_target()},set_next_tip:function(){s.$next_tip=e(".joyride-tip-guide[data-index="+s.$li.index()+"]")},set_target:function(){var t=s.$li.attr("data-class"),n=s.$li.attr("data-id"),r=function(){return n?e(s.document.getElementById(n)):t?e("."+t).filter(":visible").first():e("body")};s.$target=r()},scroll_to:function(){var t,n;t=s.$window.height()/2,n=Math.ceil(s.$target.offset().top-t+s.$next_tip.outerHeight()),e("html, body").stop().animate({scrollTop:n},s.scrollSpeed)},paused:function(){return e.inArray(s.$li.index()+1,s.pauseAfter)===-1?!0:!1},destroy:function(){e.isEmptyObject(s)||s.$document.off(".joyride"),e(t).off(".joyride"),e(".joyride-close-tip, .joyride-next-tip, .joyride-modal-bg").off(".joyride"),e(".joyride-tip-guide, .joyride-modal-bg").remove(),clearTimeout(s.automate),s={}},restart:function(){s.autoStart?(o.hide(),s.$li=n,o.show("init")):(!s.startTimerOnClick&&s.timer>0?(o.show("init"),o.startTimer()):o.show("init"),s.autoStart=!0)},pos_default:function(t){var n=Math.ceil(s.$window.height()/2),r=s.$next_tip.offset(),i=e(".joyride-nub",s.$next_tip),u=Math.ceil(i.outerWidth()/2),a=Math.ceil(i.outerHeight()/2),f=t||!1;f&&(s.$next_tip.css("visibility","hidden"),s.$next_tip.show());if(!/body/i.test(s.$target.selector)){var l=s.tipSettings.tipAdjustmentY?parseInt(s.tipSettings.tipAdjustmentY):0,c=s.tipSettings.tipAdjustmentX?parseInt(s.tipSettings.tipAdjustmentX):0;o.bottom()?(s.$next_tip.css({top:s.$target.offset().top+a+s.$target.outerHeight()+l,left:s.$target.offset().left+c}),/right/i.test(s.tipSettings.nubPosition)&&s.$next_tip.css("left",s.$target.offset().left-s.$next_tip.outerWidth()+s.$target.outerWidth()),o.nub_position(i,s.tipSettings.nubPosition,"top")):o.top()?(s.$next_tip.css({top:s.$target.offset().top-s.$next_tip.outerHeight()-a+l,left:s.$target.offset().left+c}),o.nub_position(i,s.tipSettings.nubPosition,"bottom")):o.right()?(s.$next_tip.css({top:s.$target.offset().top+l,left:s.$target.outerWidth()+s.$target.offset().left+u+c}),o.nub_position(i,s.tipSettings.nubPosition,"left")):o.left()&&(s.$next_tip.css({top:s.$target.offset().top+l,left:s.$target.offset().left-s.$next_tip.outerWidth()-u+c}),o.nub_position(i,s.tipSettings.nubPosition,"right")),!o.visible(o.corners(s.$next_tip))&&s.attempts<s.tipSettings.tipLocationPattern.length&&(i.removeClass("bottom").removeClass("top").removeClass("right").removeClass("left"),s.tipSettings.tipLocation=s.tipSettings.tipLocationPattern[s.attempts],s.attempts++,o.pos_default(!0))}else s.$li.length&&o.pos_modal(i);f&&(s.$next_tip.hide(),s.$next_tip.css("visibility","visible"))},pos_phone:function(t){var n=s.$next_tip.outerHeight(),r=s.$next_tip.offset(),i=s.$target.outerHeight(),u=e(".joyride-nub",s.$next_tip),a=Math.ceil(u.outerHeight()/2),f=t||!1;u.removeClass("bottom").removeClass("top").removeClass("right").removeClass("left"),f&&(s.$next_tip.css("visibility","hidden"),s.$next_tip.show()),/body/i.test(s.$target.selector)?s.$li.length&&o.pos_modal(u):o.top()?(s.$next_tip.offset({top:s.$target.offset().top-n-a}),u.addClass("bottom")):(s.$next_tip.offset({top:s.$target.offset().top+i+a}),u.addClass("top")),f&&(s.$next_tip.hide(),s.$next_tip.css("visibility","visible"))},pos_modal:function(e){o.center(),e.hide(),o.show_modal()},show_modal:function(){e(".joyride-modal-bg").length<1&&e("body").append(s.template.modal).show(),/pop/i.test(s.tipAnimation)?e(".joyride-modal-bg").show():e(".joyride-modal-bg").fadeIn(s.tipAnimationFadeSpeed)},expose:function(){var n,r,i,u,a="expose-"+Math.floor(Math.random()*1e4);if(arguments.length>0&&arguments[0]instanceof e)i=arguments[0];else{if(!s.$target||!!/body/i.test(s.$target.selector))return!1;i=s.$target}if(i.length<1)return t.console&&console.error("element not valid",i),!1;n=e(s.template.expose),s.$body.append(n),n.css({top:i.offset().top,left:i.offset().left,width:i.outerWidth(!0),height:i.outerHeight(!0)}),r=e(s.template.exposeCover),u={zIndex:i.css("z-index"),position:i.css("position")},i.css("z-index",n.css("z-index")*1+1),u.position=="static"&&i.css("position","relative"),i.data("expose-css",u),r.css({top:i.offset().top,left:i.offset().left,width:i.outerWidth(!0),height:i.outerHeight(!0)}),s.$body.append(r),n.addClass(a),r.addClass(a),s.tipSettings.exposeClass&&(n.addClass(s.tipSettings.exposeClass),r.addClass(s.tipSettings.exposeClass)),i.data("expose",a),s.postExposeCallback(s.$li.index(),s.$next_tip,i),o.add_exposed(i)},un_expose:function(){var n,r,i,u,a=!1;if(arguments.length>0&&arguments[0]instanceof e)r=arguments[0];else{if(!s.$target||!!/body/i.test(s.$target.selector))return!1;r=s.$target}if(r.length<1)return t.console&&console.error("element not valid",r),!1;n=r.data("expose"),i=e("."+n),arguments.length>1&&(a=arguments[1]),a===!0?e(".joyride-expose-wrapper,.joyride-expose-cover").remove():i.remove(),u=r.data("expose-css"),u.zIndex=="auto"?r.css("z-index",""):r.css("z-index",u.zIndex),u.position!=r.css("position")&&(u.position=="static"?r.css("position",""):r.css("position",u.position)),r.removeData("expose"),r.removeData("expose-z-index"),o.remove_exposed(r)},add_exposed:function(t){s.exposed=s.exposed||[],t instanceof e?s.exposed.push(t[0]):typeof t=="string"&&s.exposed.push(t)},remove_exposed:function(t){var n;t instanceof e?n=t[0]:typeof t=="string"&&(n=t),s.exposed=s.exposed||[];for(var r=0;r<s.exposed.length;r++)if(s.exposed[r]==n){s.exposed.splice(r,1);return}},center:function(){var e=s.$window;return s.$next_tip.css({top:(e.height()-s.$next_tip.outerHeight())/2+e.scrollTop(),left:(e.width()-s.$next_tip.outerWidth())/2+e.scrollLeft()}),!0},bottom:function(){return/bottom/i.test(s.tipSettings.tipLocation)},top:function(){return/top/i.test(s.tipSettings.tipLocation)},right:function(){return/right/i.test(s.tipSettings.tipLocation)},left:function(){return/left/i.test(s.tipSettings.tipLocation)},corners:function(e){var t=s.$window,n=t.height()/2,r=Math.ceil(s.$target.offset().top-n+s.$next_tip.outerHeight()),i=t.width()+t.scrollLeft(),o=t.height()+r,u=t.height()+t.scrollTop(),a=t.scrollTop();return r<a&&(r<0?a=0:a=r),o>u&&(u=o),[e.offset().top<a,i<e.offset().left+e.outerWidth(),u<e.offset().top+e.outerHeight(),t.scrollLeft()>e.offset().left]},visible:function(e){var t=e.length;while(t--)if(e[t])return!1;return!0},nub_position:function(e,t,n){t==="auto"?e.addClass(n):e.addClass(t)},startTimer:function(){s.$li.length?s.automate=setTimeout(function(){o.hide(),o.show(),o.startTimer()},s.timer):clearTimeout(s.automate)},end:function(){s.cookieMonster&&e.cookie(s.cookieName,"ridden",{expires:365,domain:s.cookieDomain,path:s.cookiePath}),s.localStorage&&localStorage.setItem(s.localStorageKey,!0),s.timer>0&&clearTimeout(s.automate),s.modal&&s.expose&&o.un_expose(),s.$current_tip&&s.$current_tip.hide(),s.$li&&(s.postStepCallback(s.$li.index(),s.$current_tip),s.postRideCallback(s.$li.index(),s.$current_tip)),e(".joyride-modal-bg").hide()},jquery_check:function(){return e.isFunction(e.fn.on)?!0:(e.fn.on=function(e,t,n){return this.delegate(t,e,n)},e.fn.off=function(e,t,n){return this.undelegate(t,e,n)},!1)},outerHTML:function(e){return e.outerHTML||(new XMLSerializer).serializeToString(e)},version:function(){return s.version},tabbable:function(t){e(t).on("keydown",function(n){if(!n.isDefaultPrevented()&&n.keyCode&&n.keyCode===27){n.preventDefault(),o.end();return}if(n.keyCode!==9)return;var r=e(t).find(":tabbable"),i=r.filter(":first"),s=r.filter(":last");n.target===s[0]&&!n.shiftKey?(i.focus(1),n.preventDefault()):n.target===i[0]&&n.shiftKey&&(s.focus(1),n.preventDefault())})}};e.fn.joyride=function(t){if(o[t])return o[t].apply(this,Array.prototype.slice.call(arguments,1));if(typeof t=="object"||!t)return o.init.apply(this,arguments);e.error("Method "+t+" does not exist on jQuery.joyride")}})(jQuery,this);
  1394. ;
  1395. /**
  1396. * @file
  1397. * Attaches behaviors for the Tour module's toolbar tab.
  1398. */
  1399. (function ($, Backbone, Drupal, document) {
  1400. 'use strict';
  1401. var queryString = decodeURI(window.location.search);
  1402. /**
  1403. * Attaches the tour's toolbar tab behavior.
  1404. *
  1405. * It uses the query string for:
  1406. * - tour: When ?tour=1 is present, the tour will start automatically after
  1407. * the page has loaded.
  1408. * - tips: Pass ?tips=class in the url to filter the available tips to the
  1409. * subset which match the given class.
  1410. *
  1411. * @example
  1412. * http://example.com/foo?tour=1&tips=bar
  1413. *
  1414. * @type {Drupal~behavior}
  1415. *
  1416. * @prop {Drupal~behaviorAttach} attach
  1417. * Attach tour functionality on `tour` events.
  1418. */
  1419. Drupal.behaviors.tour = {
  1420. attach: function (context) {
  1421. $('body').once('tour').each(function () {
  1422. var model = new Drupal.tour.models.StateModel();
  1423. new Drupal.tour.views.ToggleTourView({
  1424. el: $(context).find('#toolbar-tab-tour'),
  1425. model: model
  1426. });
  1427. model
  1428. // Allow other scripts to respond to tour events.
  1429. .on('change:isActive', function (model, isActive) {
  1430. $(document).trigger((isActive) ? 'drupalTourStarted' : 'drupalTourStopped');
  1431. })
  1432. // Initialization: check whether a tour is available on the current
  1433. // page.
  1434. .set('tour', $(context).find('ol#tour'));
  1435. // Start the tour immediately if toggled via query string.
  1436. if (/tour=?/i.test(queryString)) {
  1437. model.set('isActive', true);
  1438. }
  1439. });
  1440. }
  1441. };
  1442. /**
  1443. * @namespace
  1444. */
  1445. Drupal.tour = Drupal.tour || {
  1446. /**
  1447. * @namespace Drupal.tour.models
  1448. */
  1449. models: {},
  1450. /**
  1451. * @namespace Drupal.tour.views
  1452. */
  1453. views: {}
  1454. };
  1455. /**
  1456. * Backbone Model for tours.
  1457. *
  1458. * @constructor
  1459. *
  1460. * @augments Backbone.Model
  1461. */
  1462. Drupal.tour.models.StateModel = Backbone.Model.extend(/** @lends Drupal.tour.models.StateModel# */{
  1463. /**
  1464. * @type {object}
  1465. */
  1466. defaults: /** @lends Drupal.tour.models.StateModel# */{
  1467. /**
  1468. * Indicates whether the Drupal root window has a tour.
  1469. *
  1470. * @type {Array}
  1471. */
  1472. tour: [],
  1473. /**
  1474. * Indicates whether the tour is currently running.
  1475. *
  1476. * @type {bool}
  1477. */
  1478. isActive: false,
  1479. /**
  1480. * Indicates which tour is the active one (necessary to cleanly stop).
  1481. *
  1482. * @type {Array}
  1483. */
  1484. activeTour: []
  1485. }
  1486. });
  1487. Drupal.tour.views.ToggleTourView = Backbone.View.extend(/** @lends Drupal.tour.views.ToggleTourView# */{
  1488. /**
  1489. * @type {object}
  1490. */
  1491. events: {click: 'onClick'},
  1492. /**
  1493. * Handles edit mode toggle interactions.
  1494. *
  1495. * @constructs
  1496. *
  1497. * @augments Backbone.View
  1498. */
  1499. initialize: function () {
  1500. this.listenTo(this.model, 'change:tour change:isActive', this.render);
  1501. this.listenTo(this.model, 'change:isActive', this.toggleTour);
  1502. },
  1503. /**
  1504. * @inheritdoc
  1505. *
  1506. * @return {Drupal.tour.views.ToggleTourView}
  1507. * The `ToggleTourView` view.
  1508. */
  1509. render: function () {
  1510. // Render the visibility.
  1511. this.$el.toggleClass('hidden', this._getTour().length === 0);
  1512. // Render the state.
  1513. var isActive = this.model.get('isActive');
  1514. this.$el.find('button')
  1515. .toggleClass('is-active', isActive)
  1516. .prop('aria-pressed', isActive);
  1517. return this;
  1518. },
  1519. /**
  1520. * Model change handler; starts or stops the tour.
  1521. */
  1522. toggleTour: function () {
  1523. if (this.model.get('isActive')) {
  1524. var $tour = this._getTour();
  1525. this._removeIrrelevantTourItems($tour, this._getDocument());
  1526. var that = this;
  1527. if ($tour.find('li').length) {
  1528. $tour.joyride({
  1529. autoStart: true,
  1530. postRideCallback: function () { that.model.set('isActive', false); },
  1531. // HTML segments for tip layout.
  1532. template: {
  1533. link: '<a href=\"#close\" class=\"joyride-close-tip\">&times;</a>',
  1534. button: '<a href=\"#\" class=\"button button--primary joyride-next-tip\"></a>'
  1535. }
  1536. });
  1537. this.model.set({isActive: true, activeTour: $tour});
  1538. }
  1539. }
  1540. else {
  1541. this.model.get('activeTour').joyride('destroy');
  1542. this.model.set({isActive: false, activeTour: []});
  1543. }
  1544. },
  1545. /**
  1546. * Toolbar tab click event handler; toggles isActive.
  1547. *
  1548. * @param {jQuery.Event} event
  1549. * The click event.
  1550. */
  1551. onClick: function (event) {
  1552. this.model.set('isActive', !this.model.get('isActive'));
  1553. event.preventDefault();
  1554. event.stopPropagation();
  1555. },
  1556. /**
  1557. * Gets the tour.
  1558. *
  1559. * @return {jQuery}
  1560. * A jQuery element pointing to a `<ol>` containing tour items.
  1561. */
  1562. _getTour: function () {
  1563. return this.model.get('tour');
  1564. },
  1565. /**
  1566. * Gets the relevant document as a jQuery element.
  1567. *
  1568. * @return {jQuery}
  1569. * A jQuery element pointing to the document within which a tour would be
  1570. * started given the current state.
  1571. */
  1572. _getDocument: function () {
  1573. return $(document);
  1574. },
  1575. /**
  1576. * Removes tour items for elements that don't have matching page elements.
  1577. *
  1578. * Or that are explicitly filtered out via the 'tips' query string.
  1579. *
  1580. * @example
  1581. * <caption>This will filter out tips that do not have a matching
  1582. * page element or don't have the "bar" class.</caption>
  1583. * http://example.com/foo?tips=bar
  1584. *
  1585. * @param {jQuery} $tour
  1586. * A jQuery element pointing to a `<ol>` containing tour items.
  1587. * @param {jQuery} $document
  1588. * A jQuery element pointing to the document within which the elements
  1589. * should be sought.
  1590. *
  1591. * @see Drupal.tour.views.ToggleTourView#_getDocument
  1592. */
  1593. _removeIrrelevantTourItems: function ($tour, $document) {
  1594. var removals = false;
  1595. var tips = /tips=([^&]+)/.exec(queryString);
  1596. $tour
  1597. .find('li')
  1598. .each(function () {
  1599. var $this = $(this);
  1600. var itemId = $this.attr('data-id');
  1601. var itemClass = $this.attr('data-class');
  1602. // If the query parameter 'tips' is set, remove all tips that don't
  1603. // have the matching class.
  1604. if (tips && !$(this).hasClass(tips[1])) {
  1605. removals = true;
  1606. $this.remove();
  1607. return;
  1608. }
  1609. // Remove tip from the DOM if there is no corresponding page element.
  1610. if ((!itemId && !itemClass) ||
  1611. (itemId && $document.find('#' + itemId).length) ||
  1612. (itemClass && $document.find('.' + itemClass).length)) {
  1613. return;
  1614. }
  1615. removals = true;
  1616. $this.remove();
  1617. });
  1618. // If there were removals, we'll have to do some clean-up.
  1619. if (removals) {
  1620. var total = $tour.find('li').length;
  1621. if (!total) {
  1622. this.model.set({tour: []});
  1623. }
  1624. $tour
  1625. .find('li')
  1626. // Rebuild the progress data.
  1627. .each(function (index) {
  1628. var progress = Drupal.t('!tour_item of !total', {'!tour_item': index + 1, '!total': total});
  1629. $(this).find('.tour-progress').text(progress);
  1630. })
  1631. // Update the last item to have "End tour" as the button.
  1632. .eq(-1)
  1633. .attr('data-text', Drupal.t('End tour'));
  1634. }
  1635. }
  1636. });
  1637. })(jQuery, Backbone, Drupal, document);
  1638. ;
  1639. /**
  1640. * @file
  1641. * Manages page tabbing modifications made by modules.
  1642. */
  1643. /**
  1644. * Allow modules to respond to the constrain event.
  1645. *
  1646. * @event drupalTabbingConstrained
  1647. */
  1648. /**
  1649. * Allow modules to respond to the tabbingContext release event.
  1650. *
  1651. * @event drupalTabbingContextReleased
  1652. */
  1653. /**
  1654. * Allow modules to respond to the constrain event.
  1655. *
  1656. * @event drupalTabbingContextActivated
  1657. */
  1658. /**
  1659. * Allow modules to respond to the constrain event.
  1660. *
  1661. * @event drupalTabbingContextDeactivated
  1662. */
  1663. (function ($, Drupal) {
  1664. 'use strict';
  1665. /**
  1666. * Provides an API for managing page tabbing order modifications.
  1667. *
  1668. * @constructor Drupal~TabbingManager
  1669. */
  1670. function TabbingManager() {
  1671. /**
  1672. * Tabbing sets are stored as a stack. The active set is at the top of the
  1673. * stack. We use a JavaScript array as if it were a stack; we consider the
  1674. * first element to be the bottom and the last element to be the top. This
  1675. * allows us to use JavaScript's built-in Array.push() and Array.pop()
  1676. * methods.
  1677. *
  1678. * @type {Array.<Drupal~TabbingContext>}
  1679. */
  1680. this.stack = [];
  1681. }
  1682. /**
  1683. * Add public methods to the TabbingManager class.
  1684. */
  1685. $.extend(TabbingManager.prototype, /** @lends Drupal~TabbingManager# */{
  1686. /**
  1687. * Constrain tabbing to the specified set of elements only.
  1688. *
  1689. * Makes elements outside of the specified set of elements unreachable via
  1690. * the tab key.
  1691. *
  1692. * @param {jQuery} elements
  1693. * The set of elements to which tabbing should be constrained. Can also
  1694. * be a jQuery-compatible selector string.
  1695. *
  1696. * @return {Drupal~TabbingContext}
  1697. * The TabbingContext instance.
  1698. *
  1699. * @fires event:drupalTabbingConstrained
  1700. */
  1701. constrain: function (elements) {
  1702. // Deactivate all tabbingContexts to prepare for the new constraint. A
  1703. // tabbingContext instance will only be reactivated if the stack is
  1704. // unwound to it in the _unwindStack() method.
  1705. var il = this.stack.length;
  1706. for (var i = 0; i < il; i++) {
  1707. this.stack[i].deactivate();
  1708. }
  1709. // The "active tabbing set" are the elements tabbing should be constrained
  1710. // to.
  1711. var $elements = $(elements).find(':tabbable').addBack(':tabbable');
  1712. var tabbingContext = new TabbingContext({
  1713. // The level is the current height of the stack before this new
  1714. // tabbingContext is pushed on top of the stack.
  1715. level: this.stack.length,
  1716. $tabbableElements: $elements
  1717. });
  1718. this.stack.push(tabbingContext);
  1719. // Activates the tabbingContext; this will manipulate the DOM to constrain
  1720. // tabbing.
  1721. tabbingContext.activate();
  1722. // Allow modules to respond to the constrain event.
  1723. $(document).trigger('drupalTabbingConstrained', tabbingContext);
  1724. return tabbingContext;
  1725. },
  1726. /**
  1727. * Restores a former tabbingContext when an active one is released.
  1728. *
  1729. * The TabbingManager stack of tabbingContext instances will be unwound
  1730. * from the top-most released tabbingContext down to the first non-released
  1731. * tabbingContext instance. This non-released instance is then activated.
  1732. */
  1733. release: function () {
  1734. // Unwind as far as possible: find the topmost non-released
  1735. // tabbingContext.
  1736. var toActivate = this.stack.length - 1;
  1737. while (toActivate >= 0 && this.stack[toActivate].released) {
  1738. toActivate--;
  1739. }
  1740. // Delete all tabbingContexts after the to be activated one. They have
  1741. // already been deactivated, so their effect on the DOM has been reversed.
  1742. this.stack.splice(toActivate + 1);
  1743. // Get topmost tabbingContext, if one exists, and activate it.
  1744. if (toActivate >= 0) {
  1745. this.stack[toActivate].activate();
  1746. }
  1747. },
  1748. /**
  1749. * Makes all elements outside of the tabbingContext's set untabbable.
  1750. *
  1751. * Elements made untabbable have their original tabindex and autofocus
  1752. * values stored so that they might be restored later when this
  1753. * tabbingContext is deactivated.
  1754. *
  1755. * @param {Drupal~TabbingContext} tabbingContext
  1756. * The TabbingContext instance that has been activated.
  1757. */
  1758. activate: function (tabbingContext) {
  1759. var $set = tabbingContext.$tabbableElements;
  1760. var level = tabbingContext.level;
  1761. // Determine which elements are reachable via tabbing by default.
  1762. var $disabledSet = $(':tabbable')
  1763. // Exclude elements of the active tabbing set.
  1764. .not($set);
  1765. // Set the disabled set on the tabbingContext.
  1766. tabbingContext.$disabledElements = $disabledSet;
  1767. // Record the tabindex for each element, so we can restore it later.
  1768. var il = $disabledSet.length;
  1769. for (var i = 0; i < il; i++) {
  1770. this.recordTabindex($disabledSet.eq(i), level);
  1771. }
  1772. // Make all tabbable elements outside of the active tabbing set
  1773. // unreachable.
  1774. $disabledSet
  1775. .prop('tabindex', -1)
  1776. .prop('autofocus', false);
  1777. // Set focus on an element in the tabbingContext's set of tabbable
  1778. // elements. First, check if there is an element with an autofocus
  1779. // attribute. Select the last one from the DOM order.
  1780. var $hasFocus = $set.filter('[autofocus]').eq(-1);
  1781. // If no element in the tabbable set has an autofocus attribute, select
  1782. // the first element in the set.
  1783. if ($hasFocus.length === 0) {
  1784. $hasFocus = $set.eq(0);
  1785. }
  1786. $hasFocus.trigger('focus');
  1787. },
  1788. /**
  1789. * Restores that tabbable state of a tabbingContext's disabled elements.
  1790. *
  1791. * Elements that were made untabbable have their original tabindex and
  1792. * autofocus values restored.
  1793. *
  1794. * @param {Drupal~TabbingContext} tabbingContext
  1795. * The TabbingContext instance that has been deactivated.
  1796. */
  1797. deactivate: function (tabbingContext) {
  1798. var $set = tabbingContext.$disabledElements;
  1799. var level = tabbingContext.level;
  1800. var il = $set.length;
  1801. for (var i = 0; i < il; i++) {
  1802. this.restoreTabindex($set.eq(i), level);
  1803. }
  1804. },
  1805. /**
  1806. * Records the tabindex and autofocus values of an untabbable element.
  1807. *
  1808. * @param {jQuery} $el
  1809. * The set of elements that have been disabled.
  1810. * @param {number} level
  1811. * The stack level for which the tabindex attribute should be recorded.
  1812. */
  1813. recordTabindex: function ($el, level) {
  1814. var tabInfo = $el.data('drupalOriginalTabIndices') || {};
  1815. tabInfo[level] = {
  1816. tabindex: $el[0].getAttribute('tabindex'),
  1817. autofocus: $el[0].hasAttribute('autofocus')
  1818. };
  1819. $el.data('drupalOriginalTabIndices', tabInfo);
  1820. },
  1821. /**
  1822. * Restores the tabindex and autofocus values of a reactivated element.
  1823. *
  1824. * @param {jQuery} $el
  1825. * The element that is being reactivated.
  1826. * @param {number} level
  1827. * The stack level for which the tabindex attribute should be restored.
  1828. */
  1829. restoreTabindex: function ($el, level) {
  1830. var tabInfo = $el.data('drupalOriginalTabIndices');
  1831. if (tabInfo && tabInfo[level]) {
  1832. var data = tabInfo[level];
  1833. if (data.tabindex) {
  1834. $el[0].setAttribute('tabindex', data.tabindex);
  1835. }
  1836. // If the element did not have a tabindex at this stack level then
  1837. // remove it.
  1838. else {
  1839. $el[0].removeAttribute('tabindex');
  1840. }
  1841. if (data.autofocus) {
  1842. $el[0].setAttribute('autofocus', 'autofocus');
  1843. }
  1844. // Clean up $.data.
  1845. if (level === 0) {
  1846. // Remove all data.
  1847. $el.removeData('drupalOriginalTabIndices');
  1848. }
  1849. else {
  1850. // Remove the data for this stack level and higher.
  1851. var levelToDelete = level;
  1852. while (tabInfo.hasOwnProperty(levelToDelete)) {
  1853. delete tabInfo[levelToDelete];
  1854. levelToDelete++;
  1855. }
  1856. $el.data('drupalOriginalTabIndices', tabInfo);
  1857. }
  1858. }
  1859. }
  1860. });
  1861. /**
  1862. * Stores a set of tabbable elements.
  1863. *
  1864. * This constraint can be removed with the release() method.
  1865. *
  1866. * @constructor Drupal~TabbingContext
  1867. *
  1868. * @param {object} options
  1869. * A set of initiating values
  1870. * @param {number} options.level
  1871. * The level in the TabbingManager's stack of this tabbingContext.
  1872. * @param {jQuery} options.$tabbableElements
  1873. * The DOM elements that should be reachable via the tab key when this
  1874. * tabbingContext is active.
  1875. * @param {jQuery} options.$disabledElements
  1876. * The DOM elements that should not be reachable via the tab key when this
  1877. * tabbingContext is active.
  1878. * @param {bool} options.released
  1879. * A released tabbingContext can never be activated again. It will be
  1880. * cleaned up when the TabbingManager unwinds its stack.
  1881. * @param {bool} options.active
  1882. * When true, the tabbable elements of this tabbingContext will be reachable
  1883. * via the tab key and the disabled elements will not. Only one
  1884. * tabbingContext can be active at a time.
  1885. */
  1886. function TabbingContext(options) {
  1887. $.extend(this, /** @lends Drupal~TabbingContext# */{
  1888. /**
  1889. * @type {?number}
  1890. */
  1891. level: null,
  1892. /**
  1893. * @type {jQuery}
  1894. */
  1895. $tabbableElements: $(),
  1896. /**
  1897. * @type {jQuery}
  1898. */
  1899. $disabledElements: $(),
  1900. /**
  1901. * @type {bool}
  1902. */
  1903. released: false,
  1904. /**
  1905. * @type {bool}
  1906. */
  1907. active: false
  1908. }, options);
  1909. }
  1910. /**
  1911. * Add public methods to the TabbingContext class.
  1912. */
  1913. $.extend(TabbingContext.prototype, /** @lends Drupal~TabbingContext# */{
  1914. /**
  1915. * Releases this TabbingContext.
  1916. *
  1917. * Once a TabbingContext object is released, it can never be activated
  1918. * again.
  1919. *
  1920. * @fires event:drupalTabbingContextReleased
  1921. */
  1922. release: function () {
  1923. if (!this.released) {
  1924. this.deactivate();
  1925. this.released = true;
  1926. Drupal.tabbingManager.release(this);
  1927. // Allow modules to respond to the tabbingContext release event.
  1928. $(document).trigger('drupalTabbingContextReleased', this);
  1929. }
  1930. },
  1931. /**
  1932. * Activates this TabbingContext.
  1933. *
  1934. * @fires event:drupalTabbingContextActivated
  1935. */
  1936. activate: function () {
  1937. // A released TabbingContext object can never be activated again.
  1938. if (!this.active && !this.released) {
  1939. this.active = true;
  1940. Drupal.tabbingManager.activate(this);
  1941. // Allow modules to respond to the constrain event.
  1942. $(document).trigger('drupalTabbingContextActivated', this);
  1943. }
  1944. },
  1945. /**
  1946. * Deactivates this TabbingContext.
  1947. *
  1948. * @fires event:drupalTabbingContextDeactivated
  1949. */
  1950. deactivate: function () {
  1951. if (this.active) {
  1952. this.active = false;
  1953. Drupal.tabbingManager.deactivate(this);
  1954. // Allow modules to respond to the constrain event.
  1955. $(document).trigger('drupalTabbingContextDeactivated', this);
  1956. }
  1957. }
  1958. });
  1959. // Mark this behavior as processed on the first pass and return if it is
  1960. // already processed.
  1961. if (Drupal.tabbingManager) {
  1962. return;
  1963. }
  1964. /**
  1965. * @type {Drupal~TabbingManager}
  1966. */
  1967. Drupal.tabbingManager = new TabbingManager();
  1968. }(jQuery, Drupal));
  1969. ;
  1970. /**
  1971. * @file
  1972. * Attaches behaviors for the Contextual module's edit toolbar tab.
  1973. */
  1974. (function ($, Drupal, Backbone) {
  1975. 'use strict';
  1976. var strings = {
  1977. tabbingReleased: Drupal.t('Tabbing is no longer constrained by the Contextual module.'),
  1978. tabbingConstrained: Drupal.t('Tabbing is constrained to a set of @contextualsCount and the edit mode toggle.'),
  1979. pressEsc: Drupal.t('Press the esc key to exit.')
  1980. };
  1981. /**
  1982. * Initializes a contextual link: updates its DOM, sets up model and views.
  1983. *
  1984. * @param {HTMLElement} context
  1985. * A contextual links DOM element as rendered by the server.
  1986. */
  1987. function initContextualToolbar(context) {
  1988. if (!Drupal.contextual || !Drupal.contextual.collection) {
  1989. return;
  1990. }
  1991. var contextualToolbar = Drupal.contextualToolbar;
  1992. var model = contextualToolbar.model = new contextualToolbar.StateModel({
  1993. // Checks whether localStorage indicates we should start in edit mode
  1994. // rather than view mode.
  1995. // @see Drupal.contextualToolbar.VisualView.persist
  1996. isViewing: localStorage.getItem('Drupal.contextualToolbar.isViewing') !== 'false'
  1997. }, {
  1998. contextualCollection: Drupal.contextual.collection
  1999. });
  2000. var viewOptions = {
  2001. el: $('.toolbar .toolbar-bar .contextual-toolbar-tab'),
  2002. model: model,
  2003. strings: strings
  2004. };
  2005. new contextualToolbar.VisualView(viewOptions);
  2006. new contextualToolbar.AuralView(viewOptions);
  2007. }
  2008. /**
  2009. * Attaches contextual's edit toolbar tab behavior.
  2010. *
  2011. * @type {Drupal~behavior}
  2012. *
  2013. * @prop {Drupal~behaviorAttach} attach
  2014. * Attaches contextual toolbar behavior on a contextualToolbar-init event.
  2015. */
  2016. Drupal.behaviors.contextualToolbar = {
  2017. attach: function (context) {
  2018. if ($('body').once('contextualToolbar-init').length) {
  2019. initContextualToolbar(context);
  2020. }
  2021. }
  2022. };
  2023. /**
  2024. * Namespace for the contextual toolbar.
  2025. *
  2026. * @namespace
  2027. */
  2028. Drupal.contextualToolbar = {
  2029. /**
  2030. * The {@link Drupal.contextualToolbar.StateModel} instance.
  2031. *
  2032. * @type {?Drupal.contextualToolbar.StateModel}
  2033. */
  2034. model: null
  2035. };
  2036. })(jQuery, Drupal, Backbone);
  2037. ;
  2038. /**
  2039. * @file
  2040. * A Backbone Model for the state of Contextual module's edit toolbar tab.
  2041. */
  2042. (function (Drupal, Backbone) {
  2043. 'use strict';
  2044. Drupal.contextualToolbar.StateModel = Backbone.Model.extend(/** @lends Drupal.contextualToolbar.StateModel# */{
  2045. /**
  2046. * @type {object}
  2047. *
  2048. * @prop {bool} isViewing
  2049. * @prop {bool} isVisible
  2050. * @prop {number} contextualCount
  2051. * @prop {Drupal~TabbingContext} tabbingContext
  2052. */
  2053. defaults: /** @lends Drupal.contextualToolbar.StateModel# */{
  2054. /**
  2055. * Indicates whether the toggle is currently in "view" or "edit" mode.
  2056. *
  2057. * @type {bool}
  2058. */
  2059. isViewing: true,
  2060. /**
  2061. * Indicates whether the toggle should be visible or hidden. Automatically
  2062. * calculated, depends on contextualCount.
  2063. *
  2064. * @type {bool}
  2065. */
  2066. isVisible: false,
  2067. /**
  2068. * Tracks how many contextual links exist on the page.
  2069. *
  2070. * @type {number}
  2071. */
  2072. contextualCount: 0,
  2073. /**
  2074. * A TabbingContext object as returned by {@link Drupal~TabbingManager}:
  2075. * the set of tabbable elements when edit mode is enabled.
  2076. *
  2077. * @type {?Drupal~TabbingContext}
  2078. */
  2079. tabbingContext: null
  2080. },
  2081. /**
  2082. * Models the state of the edit mode toggle.
  2083. *
  2084. * @constructs
  2085. *
  2086. * @augments Backbone.Model
  2087. *
  2088. * @param {object} attrs
  2089. * Attributes for the backbone model.
  2090. * @param {object} options
  2091. * An object with the following option:
  2092. * @param {Backbone.collection} options.contextualCollection
  2093. * The collection of {@link Drupal.contextual.StateModel} models that
  2094. * represent the contextual links on the page.
  2095. */
  2096. initialize: function (attrs, options) {
  2097. // Respond to new/removed contextual links.
  2098. this.listenTo(options.contextualCollection, 'reset remove add', this.countContextualLinks);
  2099. this.listenTo(options.contextualCollection, 'add', this.lockNewContextualLinks);
  2100. // Automatically determine visibility.
  2101. this.listenTo(this, 'change:contextualCount', this.updateVisibility);
  2102. // Whenever edit mode is toggled, lock all contextual links.
  2103. this.listenTo(this, 'change:isViewing', function (model, isViewing) {
  2104. options.contextualCollection.each(function (contextualModel) {
  2105. contextualModel.set('isLocked', !isViewing);
  2106. });
  2107. });
  2108. },
  2109. /**
  2110. * Tracks the number of contextual link models in the collection.
  2111. *
  2112. * @param {Drupal.contextual.StateModel} contextualModel
  2113. * The contextual links model that was added or removed.
  2114. * @param {Backbone.Collection} contextualCollection
  2115. * The collection of contextual link models.
  2116. */
  2117. countContextualLinks: function (contextualModel, contextualCollection) {
  2118. this.set('contextualCount', contextualCollection.length);
  2119. },
  2120. /**
  2121. * Lock newly added contextual links if edit mode is enabled.
  2122. *
  2123. * @param {Drupal.contextual.StateModel} contextualModel
  2124. * The contextual links model that was added.
  2125. * @param {Backbone.Collection} [contextualCollection]
  2126. * The collection of contextual link models.
  2127. */
  2128. lockNewContextualLinks: function (contextualModel, contextualCollection) {
  2129. if (!this.get('isViewing')) {
  2130. contextualModel.set('isLocked', true);
  2131. }
  2132. },
  2133. /**
  2134. * Automatically updates visibility of the view/edit mode toggle.
  2135. */
  2136. updateVisibility: function () {
  2137. this.set('isVisible', this.get('contextualCount') > 0);
  2138. }
  2139. });
  2140. })(Drupal, Backbone);
  2141. ;
  2142. /**
  2143. * @file
  2144. * A Backbone View that provides the aural view of the edit mode toggle.
  2145. */
  2146. (function ($, Drupal, Backbone, _) {
  2147. 'use strict';
  2148. Drupal.contextualToolbar.AuralView = Backbone.View.extend(/** @lends Drupal.contextualToolbar.AuralView# */{
  2149. /**
  2150. * Tracks whether the tabbing constraint announcement has been read once.
  2151. *
  2152. * @type {bool}
  2153. */
  2154. announcedOnce: false,
  2155. /**
  2156. * Renders the aural view of the edit mode toggle (screen reader support).
  2157. *
  2158. * @constructs
  2159. *
  2160. * @augments Backbone.View
  2161. *
  2162. * @param {object} options
  2163. * Options for the view.
  2164. */
  2165. initialize: function (options) {
  2166. this.options = options;
  2167. this.listenTo(this.model, 'change', this.render);
  2168. this.listenTo(this.model, 'change:isViewing', this.manageTabbing);
  2169. $(document).on('keyup', _.bind(this.onKeypress, this));
  2170. },
  2171. /**
  2172. * @inheritdoc
  2173. *
  2174. * @return {Drupal.contextualToolbar.AuralView}
  2175. * The current contextual toolbar aural view.
  2176. */
  2177. render: function () {
  2178. // Render the state.
  2179. this.$el.find('button').attr('aria-pressed', !this.model.get('isViewing'));
  2180. return this;
  2181. },
  2182. /**
  2183. * Limits tabbing to the contextual links and edit mode toolbar tab.
  2184. */
  2185. manageTabbing: function () {
  2186. var tabbingContext = this.model.get('tabbingContext');
  2187. // Always release an existing tabbing context.
  2188. if (tabbingContext) {
  2189. tabbingContext.release();
  2190. Drupal.announce(this.options.strings.tabbingReleased);
  2191. }
  2192. // Create a new tabbing context when edit mode is enabled.
  2193. if (!this.model.get('isViewing')) {
  2194. tabbingContext = Drupal.tabbingManager.constrain($('.contextual-toolbar-tab, .contextual'));
  2195. this.model.set('tabbingContext', tabbingContext);
  2196. this.announceTabbingConstraint();
  2197. this.announcedOnce = true;
  2198. }
  2199. },
  2200. /**
  2201. * Announces the current tabbing constraint.
  2202. */
  2203. announceTabbingConstraint: function () {
  2204. var strings = this.options.strings;
  2205. Drupal.announce(Drupal.formatString(strings.tabbingConstrained, {
  2206. '@contextualsCount': Drupal.formatPlural(Drupal.contextual.collection.length, '@count contextual link', '@count contextual links')
  2207. }));
  2208. Drupal.announce(strings.pressEsc);
  2209. },
  2210. /**
  2211. * Responds to esc and tab key press events.
  2212. *
  2213. * @param {jQuery.Event} event
  2214. * The keypress event.
  2215. */
  2216. onKeypress: function (event) {
  2217. // The first tab key press is tracked so that an annoucement about tabbing
  2218. // constraints can be raised if edit mode is enabled when the page is
  2219. // loaded.
  2220. if (!this.announcedOnce && event.keyCode === 9 && !this.model.get('isViewing')) {
  2221. this.announceTabbingConstraint();
  2222. // Set announce to true so that this conditional block won't run again.
  2223. this.announcedOnce = true;
  2224. }
  2225. // Respond to the ESC key. Exit out of edit mode.
  2226. if (event.keyCode === 27) {
  2227. this.model.set('isViewing', true);
  2228. }
  2229. }
  2230. });
  2231. })(jQuery, Drupal, Backbone, _);
  2232. ;
  2233. /**
  2234. * @file
  2235. * A Backbone View that provides the visual view of the edit mode toggle.
  2236. */
  2237. (function (Drupal, Backbone) {
  2238. 'use strict';
  2239. Drupal.contextualToolbar.VisualView = Backbone.View.extend(/** @lends Drupal.contextualToolbar.VisualView# */{
  2240. /**
  2241. * Events for the Backbone view.
  2242. *
  2243. * @return {object}
  2244. * A mapping of events to be used in the view.
  2245. */
  2246. events: function () {
  2247. // Prevents delay and simulated mouse events.
  2248. var touchEndToClick = function (event) {
  2249. event.preventDefault();
  2250. event.target.click();
  2251. };
  2252. return {
  2253. click: function () {
  2254. this.model.set('isViewing', !this.model.get('isViewing'));
  2255. },
  2256. touchend: touchEndToClick
  2257. };
  2258. },
  2259. /**
  2260. * Renders the visual view of the edit mode toggle.
  2261. *
  2262. * Listens to mouse & touch and handles edit mode toggle interactions.
  2263. *
  2264. * @constructs
  2265. *
  2266. * @augments Backbone.View
  2267. */
  2268. initialize: function () {
  2269. this.listenTo(this.model, 'change', this.render);
  2270. this.listenTo(this.model, 'change:isViewing', this.persist);
  2271. },
  2272. /**
  2273. * @inheritdoc
  2274. *
  2275. * @return {Drupal.contextualToolbar.VisualView}
  2276. * The current contextual toolbar visual view.
  2277. */
  2278. render: function () {
  2279. // Render the visibility.
  2280. this.$el.toggleClass('hidden', !this.model.get('isVisible'));
  2281. // Render the state.
  2282. this.$el.find('button').toggleClass('is-active', !this.model.get('isViewing'));
  2283. return this;
  2284. },
  2285. /**
  2286. * Model change handler; persists the isViewing value to localStorage.
  2287. *
  2288. * `isViewing === true` is the default, so only stores in localStorage when
  2289. * it's not the default value (i.e. false).
  2290. *
  2291. * @param {Drupal.contextualToolbar.StateModel} model
  2292. * A {@link Drupal.contextualToolbar.StateModel} model.
  2293. * @param {bool} isViewing
  2294. * The value of the isViewing attribute in the model.
  2295. */
  2296. persist: function (model, isViewing) {
  2297. if (!isViewing) {
  2298. localStorage.setItem('Drupal.contextualToolbar.isViewing', 'false');
  2299. }
  2300. else {
  2301. localStorage.removeItem('Drupal.contextualToolbar.isViewing');
  2302. }
  2303. }
  2304. });
  2305. })(Drupal, Backbone);
  2306. ;
  2307. /**
  2308. * @file
  2309. * Replaces the home link in toolbar with a back to site link.
  2310. */
  2311. (function ($, Drupal, drupalSettings) {
  2312. 'use strict';
  2313. var pathInfo = drupalSettings.path;
  2314. var escapeAdminPath = sessionStorage.getItem('escapeAdminPath');
  2315. var windowLocation = window.location;
  2316. // Saves the last non-administrative page in the browser to be able to link
  2317. // back to it when browsing administrative pages. If there is a destination
  2318. // parameter there is not need to save the current path because the page is
  2319. // loaded within an existing "workflow".
  2320. if (!pathInfo.currentPathIsAdmin && !/destination=/.test(windowLocation.search)) {
  2321. sessionStorage.setItem('escapeAdminPath', windowLocation);
  2322. }
  2323. /**
  2324. * Replaces the "Home" link with "Back to site" link.
  2325. *
  2326. * Back to site link points to the last non-administrative page the user
  2327. * visited within the same browser tab.
  2328. *
  2329. * @type {Drupal~behavior}
  2330. *
  2331. * @prop {Drupal~behaviorAttach} attach
  2332. * Attaches the replacement functionality to the toolbar-escape-admin element.
  2333. */
  2334. Drupal.behaviors.escapeAdmin = {
  2335. attach: function () {
  2336. var $toolbarEscape = $('[data-toolbar-escape-admin]').once('escapeAdmin');
  2337. if ($toolbarEscape.length && pathInfo.currentPathIsAdmin) {
  2338. if (escapeAdminPath !== null) {
  2339. $toolbarEscape.attr('href', escapeAdminPath);
  2340. }
  2341. else {
  2342. $toolbarEscape.text(Drupal.t('Home'));
  2343. }
  2344. $toolbarEscape.closest('.toolbar-tab').removeClass('hidden');
  2345. }
  2346. }
  2347. };
  2348. })(jQuery, Drupal, drupalSettings);
  2349. ;