summaryrefslogtreecommitdiff
path: root/www/crm/wp-admin/js/theme-plugin-editor.js
diff options
context:
space:
mode:
Diffstat (limited to 'www/crm/wp-admin/js/theme-plugin-editor.js')
-rw-r--r--www/crm/wp-admin/js/theme-plugin-editor.js1006
1 files changed, 1006 insertions, 0 deletions
diff --git a/www/crm/wp-admin/js/theme-plugin-editor.js b/www/crm/wp-admin/js/theme-plugin-editor.js
new file mode 100644
index 00000000..6127b2cf
--- /dev/null
+++ b/www/crm/wp-admin/js/theme-plugin-editor.js
@@ -0,0 +1,1006 @@
+/**
+ * @output wp-admin/js/theme-plugin-editor.js
+ */
+
+/* eslint no-magic-numbers: ["error", { "ignore": [-1, 0, 1] }] */
+
+if ( ! window.wp ) {
+ window.wp = {};
+}
+
+wp.themePluginEditor = (function( $ ) {
+ 'use strict';
+ var component, TreeLinks;
+
+ component = {
+ l10n: {
+ lintError: {
+ singular: '',
+ plural: ''
+ },
+ saveAlert: '',
+ saveError: ''
+ },
+ codeEditor: {},
+ instance: null,
+ noticeElements: {},
+ dirty: false,
+ lintErrors: []
+ };
+
+ /**
+ * Initialize component.
+ *
+ * @since 4.9.0
+ *
+ * @param {jQuery} form - Form element.
+ * @param {object} settings - Settings.
+ * @param {object|boolean} settings.codeEditor - Code editor settings (or `false` if syntax highlighting is disabled).
+ * @returns {void}
+ */
+ component.init = function init( form, settings ) {
+
+ component.form = form;
+ if ( settings ) {
+ $.extend( component, settings );
+ }
+
+ component.noticeTemplate = wp.template( 'wp-file-editor-notice' );
+ component.noticesContainer = component.form.find( '.editor-notices' );
+ component.submitButton = component.form.find( ':input[name=submit]' );
+ component.spinner = component.form.find( '.submit .spinner' );
+ component.form.on( 'submit', component.submit );
+ component.textarea = component.form.find( '#newcontent' );
+ component.textarea.on( 'change', component.onChange );
+ component.warning = $( '.file-editor-warning' );
+ component.docsLookUpButton = component.form.find( '#docs-lookup' );
+ component.docsLookUpList = component.form.find( '#docs-list' );
+
+ if ( component.warning.length > 0 ) {
+ component.showWarning();
+ }
+
+ if ( false !== component.codeEditor ) {
+ /*
+ * Defer adding notices until after DOM ready as workaround for WP Admin injecting
+ * its own managed dismiss buttons and also to prevent the editor from showing a notice
+ * when the file had linting errors to begin with.
+ */
+ _.defer( function() {
+ component.initCodeEditor();
+ } );
+ }
+
+ $( component.initFileBrowser );
+
+ $( window ).on( 'beforeunload', function() {
+ if ( component.dirty ) {
+ return component.l10n.saveAlert;
+ }
+ return undefined;
+ } );
+
+ component.docsLookUpList.on( 'change', function() {
+ var option = $( this ).val();
+ if ( '' === option ) {
+ component.docsLookUpButton.prop( 'disabled', true );
+ } else {
+ component.docsLookUpButton.prop( 'disabled', false );
+ }
+ } );
+ };
+
+ /**
+ * Set up and display the warning modal.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ component.showWarning = function() {
+ // Get the text within the modal.
+ var rawMessage = component.warning.find( '.file-editor-warning-message' ).text();
+ // Hide all the #wpwrap content from assistive technologies.
+ $( '#wpwrap' ).attr( 'aria-hidden', 'true' );
+ // Detach the warning modal from its position and append it to the body.
+ $( document.body )
+ .addClass( 'modal-open' )
+ .append( component.warning.detach() );
+ // Reveal the modal and set focus on the go back button.
+ component.warning
+ .removeClass( 'hidden' )
+ .find( '.file-editor-warning-go-back' ).focus();
+ // Get the links and buttons within the modal.
+ component.warningTabbables = component.warning.find( 'a, button' );
+ // Attach event handlers.
+ component.warningTabbables.on( 'keydown', component.constrainTabbing );
+ component.warning.on( 'click', '.file-editor-warning-dismiss', component.dismissWarning );
+ // Make screen readers announce the warning message after a short delay (necessary for some screen readers).
+ setTimeout( function() {
+ wp.a11y.speak( wp.sanitize.stripTags( rawMessage.replace( /\s+/g, ' ' ) ), 'assertive' );
+ }, 1000 );
+ };
+
+ /**
+ * Constrain tabbing within the warning modal.
+ *
+ * @since 4.9.0
+ * @param {object} event jQuery event object.
+ * @returns {void}
+ */
+ component.constrainTabbing = function( event ) {
+ var firstTabbable, lastTabbable;
+
+ if ( 9 !== event.which ) {
+ return;
+ }
+
+ firstTabbable = component.warningTabbables.first()[0];
+ lastTabbable = component.warningTabbables.last()[0];
+
+ if ( lastTabbable === event.target && ! event.shiftKey ) {
+ firstTabbable.focus();
+ event.preventDefault();
+ } else if ( firstTabbable === event.target && event.shiftKey ) {
+ lastTabbable.focus();
+ event.preventDefault();
+ }
+ };
+
+ /**
+ * Dismiss the warning modal.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ component.dismissWarning = function() {
+
+ wp.ajax.post( 'dismiss-wp-pointer', {
+ pointer: component.themeOrPlugin + '_editor_notice'
+ });
+
+ // Hide modal.
+ component.warning.remove();
+ $( '#wpwrap' ).removeAttr( 'aria-hidden' );
+ $( 'body' ).removeClass( 'modal-open' );
+ };
+
+ /**
+ * Callback for when a change happens.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ component.onChange = function() {
+ component.dirty = true;
+ component.removeNotice( 'file_saved' );
+ };
+
+ /**
+ * Submit file via Ajax.
+ *
+ * @since 4.9.0
+ * @param {jQuery.Event} event - Event.
+ * @returns {void}
+ */
+ component.submit = function( event ) {
+ var data = {}, request;
+ event.preventDefault(); // Prevent form submission in favor of Ajax below.
+ $.each( component.form.serializeArray(), function() {
+ data[ this.name ] = this.value;
+ } );
+
+ // Use value from codemirror if present.
+ if ( component.instance ) {
+ data.newcontent = component.instance.codemirror.getValue();
+ }
+
+ if ( component.isSaving ) {
+ return;
+ }
+
+ // Scroll ot the line that has the error.
+ if ( component.lintErrors.length ) {
+ component.instance.codemirror.setCursor( component.lintErrors[0].from.line );
+ return;
+ }
+
+ component.isSaving = true;
+ component.textarea.prop( 'readonly', true );
+ if ( component.instance ) {
+ component.instance.codemirror.setOption( 'readOnly', true );
+ }
+
+ component.spinner.addClass( 'is-active' );
+ request = wp.ajax.post( 'edit-theme-plugin-file', data );
+
+ // Remove previous save notice before saving.
+ if ( component.lastSaveNoticeCode ) {
+ component.removeNotice( component.lastSaveNoticeCode );
+ }
+
+ request.done( function( response ) {
+ component.lastSaveNoticeCode = 'file_saved';
+ component.addNotice({
+ code: component.lastSaveNoticeCode,
+ type: 'success',
+ message: response.message,
+ dismissible: true
+ });
+ component.dirty = false;
+ } );
+
+ request.fail( function( response ) {
+ var notice = $.extend(
+ {
+ code: 'save_error',
+ message: component.l10n.saveError
+ },
+ response,
+ {
+ type: 'error',
+ dismissible: true
+ }
+ );
+ component.lastSaveNoticeCode = notice.code;
+ component.addNotice( notice );
+ } );
+
+ request.always( function() {
+ component.spinner.removeClass( 'is-active' );
+ component.isSaving = false;
+
+ component.textarea.prop( 'readonly', false );
+ if ( component.instance ) {
+ component.instance.codemirror.setOption( 'readOnly', false );
+ }
+ } );
+ };
+
+ /**
+ * Add notice.
+ *
+ * @since 4.9.0
+ *
+ * @param {object} notice - Notice.
+ * @param {string} notice.code - Code.
+ * @param {string} notice.type - Type.
+ * @param {string} notice.message - Message.
+ * @param {boolean} [notice.dismissible=false] - Dismissible.
+ * @param {Function} [notice.onDismiss] - Callback for when a user dismisses the notice.
+ * @returns {jQuery} Notice element.
+ */
+ component.addNotice = function( notice ) {
+ var noticeElement;
+
+ if ( ! notice.code ) {
+ throw new Error( 'Missing code.' );
+ }
+
+ // Only let one notice of a given type be displayed at a time.
+ component.removeNotice( notice.code );
+
+ noticeElement = $( component.noticeTemplate( notice ) );
+ noticeElement.hide();
+
+ noticeElement.find( '.notice-dismiss' ).on( 'click', function() {
+ component.removeNotice( notice.code );
+ if ( notice.onDismiss ) {
+ notice.onDismiss( notice );
+ }
+ } );
+
+ wp.a11y.speak( notice.message );
+
+ component.noticesContainer.append( noticeElement );
+ noticeElement.slideDown( 'fast' );
+ component.noticeElements[ notice.code ] = noticeElement;
+ return noticeElement;
+ };
+
+ /**
+ * Remove notice.
+ *
+ * @since 4.9.0
+ *
+ * @param {string} code - Notice code.
+ * @returns {boolean} Whether a notice was removed.
+ */
+ component.removeNotice = function( code ) {
+ if ( component.noticeElements[ code ] ) {
+ component.noticeElements[ code ].slideUp( 'fast', function() {
+ $( this ).remove();
+ } );
+ delete component.noticeElements[ code ];
+ return true;
+ }
+ return false;
+ };
+
+ /**
+ * Initialize code editor.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ component.initCodeEditor = function initCodeEditor() {
+ var codeEditorSettings, editor;
+
+ codeEditorSettings = $.extend( {}, component.codeEditor );
+
+ /**
+ * Handle tabbing to the field before the editor.
+ *
+ * @since 4.9.0
+ *
+ * @returns {void}
+ */
+ codeEditorSettings.onTabPrevious = function() {
+ $( '#templateside' ).find( ':tabbable' ).last().focus();
+ };
+
+ /**
+ * Handle tabbing to the field after the editor.
+ *
+ * @since 4.9.0
+ *
+ * @returns {void}
+ */
+ codeEditorSettings.onTabNext = function() {
+ $( '#template' ).find( ':tabbable:not(.CodeMirror-code)' ).first().focus();
+ };
+
+ /**
+ * Handle change to the linting errors.
+ *
+ * @since 4.9.0
+ *
+ * @param {Array} errors - List of linting errors.
+ * @returns {void}
+ */
+ codeEditorSettings.onChangeLintingErrors = function( errors ) {
+ component.lintErrors = errors;
+
+ // Only disable the button in onUpdateErrorNotice when there are errors so users can still feel they can click the button.
+ if ( 0 === errors.length ) {
+ component.submitButton.toggleClass( 'disabled', false );
+ }
+ };
+
+ /**
+ * Update error notice.
+ *
+ * @since 4.9.0
+ *
+ * @param {Array} errorAnnotations - Error annotations.
+ * @returns {void}
+ */
+ codeEditorSettings.onUpdateErrorNotice = function onUpdateErrorNotice( errorAnnotations ) {
+ var message, noticeElement;
+
+ component.submitButton.toggleClass( 'disabled', errorAnnotations.length > 0 );
+
+ if ( 0 !== errorAnnotations.length ) {
+ if ( 1 === errorAnnotations.length ) {
+ message = component.l10n.lintError.singular.replace( '%d', '1' );
+ } else {
+ message = component.l10n.lintError.plural.replace( '%d', String( errorAnnotations.length ) );
+ }
+ noticeElement = component.addNotice({
+ code: 'lint_errors',
+ type: 'error',
+ message: message,
+ dismissible: false
+ });
+ noticeElement.find( 'input[type=checkbox]' ).on( 'click', function() {
+ codeEditorSettings.onChangeLintingErrors( [] );
+ component.removeNotice( 'lint_errors' );
+ } );
+ } else {
+ component.removeNotice( 'lint_errors' );
+ }
+ };
+
+ editor = wp.codeEditor.initialize( $( '#newcontent' ), codeEditorSettings );
+ editor.codemirror.on( 'change', component.onChange );
+
+ // Improve the editor accessibility.
+ $( editor.codemirror.display.lineDiv )
+ .attr({
+ role: 'textbox',
+ 'aria-multiline': 'true',
+ 'aria-labelledby': 'theme-plugin-editor-label',
+ 'aria-describedby': 'editor-keyboard-trap-help-1 editor-keyboard-trap-help-2 editor-keyboard-trap-help-3 editor-keyboard-trap-help-4'
+ });
+
+ // Focus the editor when clicking on its label.
+ $( '#theme-plugin-editor-label' ).on( 'click', function() {
+ editor.codemirror.focus();
+ });
+
+ component.instance = editor;
+ };
+
+ /**
+ * Initialization of the file browser's folder states.
+ *
+ * @since 4.9.0
+ * @returns {void}
+ */
+ component.initFileBrowser = function initFileBrowser() {
+
+ var $templateside = $( '#templateside' );
+
+ // Collapse all folders.
+ $templateside.find( '[role="group"]' ).parent().attr( 'aria-expanded', false );
+
+ // Expand ancestors to the current file.
+ $templateside.find( '.notice' ).parents( '[aria-expanded]' ).attr( 'aria-expanded', true );
+
+ // Find Tree elements and enhance them.
+ $templateside.find( '[role="tree"]' ).each( function() {
+ var treeLinks = new TreeLinks( this );
+ treeLinks.init();
+ } );
+
+ // Scroll the current file into view.
+ $templateside.find( '.current-file:first' ).each( function() {
+ if ( this.scrollIntoViewIfNeeded ) {
+ this.scrollIntoViewIfNeeded();
+ } else {
+ this.scrollIntoView( false );
+ }
+ } );
+ };
+
+ /* jshint ignore:start */
+ /* jscs:disable */
+ /* eslint-disable */
+
+ /**
+ * Creates a new TreeitemLink.
+ *
+ * @since 4.9.0
+ * @class
+ * @private
+ * @see {@link https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/treeview-2b.html|W3C Treeview Example}
+ * @license W3C-20150513
+ */
+ var TreeitemLink = (function () {
+ /**
+ * This content is licensed according to the W3C Software License at
+ * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
+ *
+ * File: TreeitemLink.js
+ *
+ * Desc: Treeitem widget that implements ARIA Authoring Practices
+ * for a tree being used as a file viewer
+ *
+ * Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt
+ */
+
+ /**
+ * @constructor
+ *
+ * @desc
+ * Treeitem object for representing the state and user interactions for a
+ * treeItem widget
+ *
+ * @param node
+ * An element with the role=tree attribute
+ */
+
+ var TreeitemLink = function (node, treeObj, group) {
+
+ // Check whether node is a DOM element
+ if (typeof node !== 'object') {
+ return;
+ }
+
+ node.tabIndex = -1;
+ this.tree = treeObj;
+ this.groupTreeitem = group;
+ this.domNode = node;
+ this.label = node.textContent.trim();
+ this.stopDefaultClick = false;
+
+ if (node.getAttribute('aria-label')) {
+ this.label = node.getAttribute('aria-label').trim();
+ }
+
+ this.isExpandable = false;
+ this.isVisible = false;
+ this.inGroup = false;
+
+ if (group) {
+ this.inGroup = true;
+ }
+
+ var elem = node.firstElementChild;
+
+ while (elem) {
+
+ if (elem.tagName.toLowerCase() == 'ul') {
+ elem.setAttribute('role', 'group');
+ this.isExpandable = true;
+ break;
+ }
+
+ elem = elem.nextElementSibling;
+ }
+
+ this.keyCode = Object.freeze({
+ RETURN: 13,
+ SPACE: 32,
+ PAGEUP: 33,
+ PAGEDOWN: 34,
+ END: 35,
+ HOME: 36,
+ LEFT: 37,
+ UP: 38,
+ RIGHT: 39,
+ DOWN: 40
+ });
+ };
+
+ TreeitemLink.prototype.init = function () {
+ this.domNode.tabIndex = -1;
+
+ if (!this.domNode.getAttribute('role')) {
+ this.domNode.setAttribute('role', 'treeitem');
+ }
+
+ this.domNode.addEventListener('keydown', this.handleKeydown.bind(this));
+ this.domNode.addEventListener('click', this.handleClick.bind(this));
+ this.domNode.addEventListener('focus', this.handleFocus.bind(this));
+ this.domNode.addEventListener('blur', this.handleBlur.bind(this));
+
+ if (this.isExpandable) {
+ this.domNode.firstElementChild.addEventListener('mouseover', this.handleMouseOver.bind(this));
+ this.domNode.firstElementChild.addEventListener('mouseout', this.handleMouseOut.bind(this));
+ }
+ else {
+ this.domNode.addEventListener('mouseover', this.handleMouseOver.bind(this));
+ this.domNode.addEventListener('mouseout', this.handleMouseOut.bind(this));
+ }
+ };
+
+ TreeitemLink.prototype.isExpanded = function () {
+
+ if (this.isExpandable) {
+ return this.domNode.getAttribute('aria-expanded') === 'true';
+ }
+
+ return false;
+
+ };
+
+ /* EVENT HANDLERS */
+
+ TreeitemLink.prototype.handleKeydown = function (event) {
+ var tgt = event.currentTarget,
+ flag = false,
+ _char = event.key,
+ clickEvent;
+
+ function isPrintableCharacter(str) {
+ return str.length === 1 && str.match(/\S/);
+ }
+
+ function printableCharacter(item) {
+ if (_char == '*') {
+ item.tree.expandAllSiblingItems(item);
+ flag = true;
+ }
+ else {
+ if (isPrintableCharacter(_char)) {
+ item.tree.setFocusByFirstCharacter(item, _char);
+ flag = true;
+ }
+ }
+ }
+
+ this.stopDefaultClick = false;
+
+ if (event.altKey || event.ctrlKey || event.metaKey) {
+ return;
+ }
+
+ if (event.shift) {
+ if (event.keyCode == this.keyCode.SPACE || event.keyCode == this.keyCode.RETURN) {
+ event.stopPropagation();
+ this.stopDefaultClick = true;
+ }
+ else {
+ if (isPrintableCharacter(_char)) {
+ printableCharacter(this);
+ }
+ }
+ }
+ else {
+ switch (event.keyCode) {
+ case this.keyCode.SPACE:
+ case this.keyCode.RETURN:
+ if (this.isExpandable) {
+ if (this.isExpanded()) {
+ this.tree.collapseTreeitem(this);
+ }
+ else {
+ this.tree.expandTreeitem(this);
+ }
+ flag = true;
+ }
+ else {
+ event.stopPropagation();
+ this.stopDefaultClick = true;
+ }
+ break;
+
+ case this.keyCode.UP:
+ this.tree.setFocusToPreviousItem(this);
+ flag = true;
+ break;
+
+ case this.keyCode.DOWN:
+ this.tree.setFocusToNextItem(this);
+ flag = true;
+ break;
+
+ case this.keyCode.RIGHT:
+ if (this.isExpandable) {
+ if (this.isExpanded()) {
+ this.tree.setFocusToNextItem(this);
+ }
+ else {
+ this.tree.expandTreeitem(this);
+ }
+ }
+ flag = true;
+ break;
+
+ case this.keyCode.LEFT:
+ if (this.isExpandable && this.isExpanded()) {
+ this.tree.collapseTreeitem(this);
+ flag = true;
+ }
+ else {
+ if (this.inGroup) {
+ this.tree.setFocusToParentItem(this);
+ flag = true;
+ }
+ }
+ break;
+
+ case this.keyCode.HOME:
+ this.tree.setFocusToFirstItem();
+ flag = true;
+ break;
+
+ case this.keyCode.END:
+ this.tree.setFocusToLastItem();
+ flag = true;
+ break;
+
+ default:
+ if (isPrintableCharacter(_char)) {
+ printableCharacter(this);
+ }
+ break;
+ }
+ }
+
+ if (flag) {
+ event.stopPropagation();
+ event.preventDefault();
+ }
+ };
+
+ TreeitemLink.prototype.handleClick = function (event) {
+
+ // only process click events that directly happened on this treeitem
+ if (event.target !== this.domNode && event.target !== this.domNode.firstElementChild) {
+ return;
+ }
+
+ if (this.isExpandable) {
+ if (this.isExpanded()) {
+ this.tree.collapseTreeitem(this);
+ }
+ else {
+ this.tree.expandTreeitem(this);
+ }
+ event.stopPropagation();
+ }
+ };
+
+ TreeitemLink.prototype.handleFocus = function (event) {
+ var node = this.domNode;
+ if (this.isExpandable) {
+ node = node.firstElementChild;
+ }
+ node.classList.add('focus');
+ };
+
+ TreeitemLink.prototype.handleBlur = function (event) {
+ var node = this.domNode;
+ if (this.isExpandable) {
+ node = node.firstElementChild;
+ }
+ node.classList.remove('focus');
+ };
+
+ TreeitemLink.prototype.handleMouseOver = function (event) {
+ event.currentTarget.classList.add('hover');
+ };
+
+ TreeitemLink.prototype.handleMouseOut = function (event) {
+ event.currentTarget.classList.remove('hover');
+ };
+
+ return TreeitemLink;
+ })();
+
+ /**
+ * Creates a new TreeLinks.
+ *
+ * @since 4.9.0
+ * @class
+ * @private
+ * @see {@link https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/treeview-2b.html|W3C Treeview Example}
+ * @license W3C-20150513
+ */
+ TreeLinks = (function () {
+ /*
+ * This content is licensed according to the W3C Software License at
+ * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
+ *
+ * File: TreeLinks.js
+ *
+ * Desc: Tree widget that implements ARIA Authoring Practices
+ * for a tree being used as a file viewer
+ *
+ * Author: Jon Gunderson, Ku Ja Eun and Nicholas Hoyt
+ */
+
+ /*
+ * @constructor
+ *
+ * @desc
+ * Tree item object for representing the state and user interactions for a
+ * tree widget
+ *
+ * @param node
+ * An element with the role=tree attribute
+ */
+
+ var TreeLinks = function (node) {
+ // Check whether node is a DOM element
+ if (typeof node !== 'object') {
+ return;
+ }
+
+ this.domNode = node;
+
+ this.treeitems = [];
+ this.firstChars = [];
+
+ this.firstTreeitem = null;
+ this.lastTreeitem = null;
+
+ };
+
+ TreeLinks.prototype.init = function () {
+
+ function findTreeitems(node, tree, group) {
+
+ var elem = node.firstElementChild;
+ var ti = group;
+
+ while (elem) {
+
+ if ((elem.tagName.toLowerCase() === 'li' && elem.firstElementChild.tagName.toLowerCase() === 'span') || elem.tagName.toLowerCase() === 'a') {
+ ti = new TreeitemLink(elem, tree, group);
+ ti.init();
+ tree.treeitems.push(ti);
+ tree.firstChars.push(ti.label.substring(0, 1).toLowerCase());
+ }
+
+ if (elem.firstElementChild) {
+ findTreeitems(elem, tree, ti);
+ }
+
+ elem = elem.nextElementSibling;
+ }
+ }
+
+ // initialize pop up menus
+ if (!this.domNode.getAttribute('role')) {
+ this.domNode.setAttribute('role', 'tree');
+ }
+
+ findTreeitems(this.domNode, this, false);
+
+ this.updateVisibleTreeitems();
+
+ this.firstTreeitem.domNode.tabIndex = 0;
+
+ };
+
+ TreeLinks.prototype.setFocusToItem = function (treeitem) {
+
+ for (var i = 0; i < this.treeitems.length; i++) {
+ var ti = this.treeitems[i];
+
+ if (ti === treeitem) {
+ ti.domNode.tabIndex = 0;
+ ti.domNode.focus();
+ }
+ else {
+ ti.domNode.tabIndex = -1;
+ }
+ }
+
+ };
+
+ TreeLinks.prototype.setFocusToNextItem = function (currentItem) {
+
+ var nextItem = false;
+
+ for (var i = (this.treeitems.length - 1); i >= 0; i--) {
+ var ti = this.treeitems[i];
+ if (ti === currentItem) {
+ break;
+ }
+ if (ti.isVisible) {
+ nextItem = ti;
+ }
+ }
+
+ if (nextItem) {
+ this.setFocusToItem(nextItem);
+ }
+
+ };
+
+ TreeLinks.prototype.setFocusToPreviousItem = function (currentItem) {
+
+ var prevItem = false;
+
+ for (var i = 0; i < this.treeitems.length; i++) {
+ var ti = this.treeitems[i];
+ if (ti === currentItem) {
+ break;
+ }
+ if (ti.isVisible) {
+ prevItem = ti;
+ }
+ }
+
+ if (prevItem) {
+ this.setFocusToItem(prevItem);
+ }
+ };
+
+ TreeLinks.prototype.setFocusToParentItem = function (currentItem) {
+
+ if (currentItem.groupTreeitem) {
+ this.setFocusToItem(currentItem.groupTreeitem);
+ }
+ };
+
+ TreeLinks.prototype.setFocusToFirstItem = function () {
+ this.setFocusToItem(this.firstTreeitem);
+ };
+
+ TreeLinks.prototype.setFocusToLastItem = function () {
+ this.setFocusToItem(this.lastTreeitem);
+ };
+
+ TreeLinks.prototype.expandTreeitem = function (currentItem) {
+
+ if (currentItem.isExpandable) {
+ currentItem.domNode.setAttribute('aria-expanded', true);
+ this.updateVisibleTreeitems();
+ }
+
+ };
+
+ TreeLinks.prototype.expandAllSiblingItems = function (currentItem) {
+ for (var i = 0; i < this.treeitems.length; i++) {
+ var ti = this.treeitems[i];
+
+ if ((ti.groupTreeitem === currentItem.groupTreeitem) && ti.isExpandable) {
+ this.expandTreeitem(ti);
+ }
+ }
+
+ };
+
+ TreeLinks.prototype.collapseTreeitem = function (currentItem) {
+
+ var groupTreeitem = false;
+
+ if (currentItem.isExpanded()) {
+ groupTreeitem = currentItem;
+ }
+ else {
+ groupTreeitem = currentItem.groupTreeitem;
+ }
+
+ if (groupTreeitem) {
+ groupTreeitem.domNode.setAttribute('aria-expanded', false);
+ this.updateVisibleTreeitems();
+ this.setFocusToItem(groupTreeitem);
+ }
+
+ };
+
+ TreeLinks.prototype.updateVisibleTreeitems = function () {
+
+ this.firstTreeitem = this.treeitems[0];
+
+ for (var i = 0; i < this.treeitems.length; i++) {
+ var ti = this.treeitems[i];
+
+ var parent = ti.domNode.parentNode;
+
+ ti.isVisible = true;
+
+ while (parent && (parent !== this.domNode)) {
+
+ if (parent.getAttribute('aria-expanded') == 'false') {
+ ti.isVisible = false;
+ }
+ parent = parent.parentNode;
+ }
+
+ if (ti.isVisible) {
+ this.lastTreeitem = ti;
+ }
+ }
+
+ };
+
+ TreeLinks.prototype.setFocusByFirstCharacter = function (currentItem, _char) {
+ var start, index;
+ _char = _char.toLowerCase();
+
+ // Get start index for search based on position of currentItem
+ start = this.treeitems.indexOf(currentItem) + 1;
+ if (start === this.treeitems.length) {
+ start = 0;
+ }
+
+ // Check remaining slots in the menu
+ index = this.getIndexFirstChars(start, _char);
+
+ // If not found in remaining slots, check from beginning
+ if (index === -1) {
+ index = this.getIndexFirstChars(0, _char);
+ }
+
+ // If match was found...
+ if (index > -1) {
+ this.setFocusToItem(this.treeitems[index]);
+ }
+ };
+
+ TreeLinks.prototype.getIndexFirstChars = function (startIndex, _char) {
+ for (var i = startIndex; i < this.firstChars.length; i++) {
+ if (this.treeitems[i].isVisible) {
+ if (_char === this.firstChars[i]) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ };
+
+ return TreeLinks;
+ })();
+
+ /* jshint ignore:end */
+ /* jscs:enable */
+ /* eslint-enable */
+
+ return component;
+})( jQuery );