2016-06-07 2 views
0

Я искал многоуровневое вертикальное меню с возможностью использовать родительские ссылки в течение очень долгого времени. Наконец, я нашел navgoco jQuery, который я использую в своем проекте. Я абсолютно люблю этот плагин. Единственная проблема, с которой я сталкиваюсь, заключается в том, что она недоступна с клавиатуры. Есть ли способ исправить это?Как сделать клавиатуру навигации доступной?

https://jsfiddle.net/webIra7/xs4mr7at/7/

(function($) { 
 

 
\t "use strict"; 
 

 
\t /** 
 
\t * Plugin Constructor. Every menu must have a unique id which will either 
 
\t * be the actual id attribute or its index in the page. 
 
\t * 
 
\t * @param {Element} el 
 
\t * @param {Object} options 
 
\t * @param {Integer} idx 
 
\t * @returns {Object} Plugin Instance 
 
\t */ 
 
\t var Plugin = function(el, options, idx) { 
 
\t \t this.el = el; 
 
\t \t this.$el = $(el); 
 
\t \t this.options = options; 
 
\t \t this.uuid = this.$el.attr('id') ? this.$el.attr('id') : idx; 
 
\t \t this.state = {}; 
 
\t \t this.init(); 
 
\t \t return this; 
 
\t }; 
 

 
\t /** 
 
\t * Plugin methods 
 
\t */ 
 
\t Plugin.prototype = { 
 
\t \t /** 
 
\t \t * Load cookie, assign a unique data-index attribute to 
 
\t \t * all sub-menus and show|hide them according to cookie 
 
\t \t * or based on the parent open class. Find all parent li > a 
 
\t \t * links add the carent if it's on and attach the event click 
 
\t \t * to them. 
 
\t \t */ 
 
\t \t init: function() { 
 
\t \t \t var self = this; 
 
\t \t \t self._load(); 
 
\t \t \t self.$el.find('ul').each(function(idx) { 
 
\t \t \t \t var sub = $(this); 
 
\t \t \t \t sub.attr('data-index', idx); 
 
\t \t \t \t if (self.options.save && self.state.hasOwnProperty(idx)) { 
 
\t \t \t \t \t sub.parent().addClass(self.options.openClass); 
 
\t \t \t \t \t sub.show(); 
 
\t \t \t \t } else if (sub.parent().hasClass(self.options.openClass)) { 
 
\t \t \t \t \t sub.show(); 
 
\t \t \t \t \t self.state[idx] = 1; 
 
\t \t \t \t } else { 
 
\t \t \t \t \t sub.hide(); 
 
\t \t \t \t } 
 
\t \t \t }); 
 

 
\t \t \t var caret = $('<span></span>').prepend(self.options.caretHtml); 
 
\t \t \t var links = self.$el.find("li > a"); 
 
\t \t \t self._trigger(caret, false); 
 
\t \t \t self._trigger(links, true); 
 
\t \t \t self.$el.find("li:has(ul) > a").prepend(caret); 
 
\t \t }, 
 
\t \t /** 
 
\t \t * Add the main event trigger to toggle menu items to the given sources 
 
\t \t * @param {Element} sources 
 
\t \t * @param {Boolean} isLink 
 
\t \t */ 
 
\t \t _trigger: function(sources, isLink) { 
 
\t \t \t var self = this; 
 
\t \t \t sources.on('click', function(event) { 
 
\t \t \t \t event.stopPropagation(); 
 
\t \t \t \t var sub = isLink ? $(this).next() : $(this).parent().next(); 
 
\t \t \t \t var isAnchor = false; 
 
\t \t \t \t if (isLink) { 
 
\t \t \t \t \t var href = $(this).attr('href'); 
 
\t \t \t \t \t isAnchor = href === undefined || href === '' || href === '#'; 
 
\t \t \t \t } 
 
\t \t \t \t sub = sub.length > 0 ? sub : false; 
 
\t \t \t \t self.options.onClickBefore.call(this, event, sub); 
 

 
\t \t \t \t if (!isLink || sub && isAnchor) { 
 
\t \t \t \t \t event.preventDefault(); 
 
\t \t \t \t \t self._toggle(sub, sub.is(":hidden")); 
 
\t \t \t \t \t self._save(); 
 
\t \t \t \t } else if (self.options.accordion) { 
 
\t \t \t \t \t var allowed = self.state = self._parents($(this)); 
 
\t \t \t \t \t self.$el.find('ul').filter(':visible').each(function() { 
 
\t \t \t \t \t \t var sub = $(this), 
 
\t \t \t \t \t \t \t idx = sub.attr('data-index'); 
 

 
\t \t \t \t \t \t if (!allowed.hasOwnProperty(idx)) { 
 
\t \t \t \t \t \t \t self._toggle(sub, false); 
 
\t \t \t \t \t \t } 
 
\t \t \t \t \t }); 
 
\t \t \t \t \t self._save(); 
 
\t \t \t \t } 
 
\t \t \t \t self.options.onClickAfter.call(this, event, sub); 
 
\t \t \t }); 
 
\t \t }, 
 
\t \t /** 
 
\t \t * Accepts a JQuery Element and a boolean flag. If flag is false it removes the `open` css 
 
\t \t * class from the parent li and slides up the sub-menu. If flag is open it adds the `open` 
 
\t \t * css class to the parent li and slides down the menu. If accordion mode is on all 
 
\t \t * sub-menus except the direct parent tree will close. Internally an object with the menus 
 
\t \t * states is maintained for later save duty. 
 
\t \t * 
 
\t \t * @param {Element} sub 
 
\t \t * @param {Boolean} open 
 
\t \t */ 
 
\t \t _toggle: function(sub, open) { 
 
\t \t \t var self = this, 
 
\t \t \t \t idx = sub.attr('data-index'), 
 
\t \t \t \t parent = sub.parent(); 
 

 
\t \t \t self.options.onToggleBefore.call(this, sub, open); 
 
\t \t \t if (open) { 
 
\t \t \t \t parent.addClass(self.options.openClass); 
 
\t \t \t \t sub.slideDown(self.options.slide); 
 
\t \t \t \t self.state[idx] = 1; 
 

 
\t \t \t \t if (self.options.accordion) { 
 
\t \t \t \t \t var allowed = self.state = self._parents(sub); 
 
\t \t \t \t \t allowed[idx] = self.state[idx] = 1; 
 

 
\t \t \t \t \t self.$el.find('ul').filter(':visible').each(function() { 
 
\t \t \t \t \t \t var sub = $(this), 
 
\t \t \t \t \t \t \t idx = sub.attr('data-index'); 
 

 
\t \t \t \t \t \t if (!allowed.hasOwnProperty(idx)) { 
 
\t \t \t \t \t \t \t self._toggle(sub, false); 
 
\t \t \t \t \t \t } 
 
\t \t \t \t \t }); 
 
\t \t \t \t } 
 
\t \t \t } else { 
 
\t \t \t \t parent.removeClass(self.options.openClass); 
 
\t \t \t \t sub.slideUp(self.options.slide); 
 
\t \t \t \t self.state[idx] = 0; 
 
\t \t \t } 
 
\t \t \t self.options.onToggleAfter.call(this, sub, open); 
 
\t \t }, 
 
\t \t /** 
 
\t \t * Returns all parents of a sub-menu. When obj is true It returns an object with indexes for 
 
\t \t * keys and the elements as values, if obj is false the object is filled with the value `1`. 
 
\t \t * 
 
\t \t * @since v0.1.2 
 
\t \t * @param {Element} sub 
 
\t \t * @param {Boolean} obj 
 
\t \t * @returns {Object} 
 
\t \t */ 
 
\t \t _parents: function(sub, obj) { 
 
\t \t \t var result = {}, 
 
\t \t \t \t parent = sub.parent(), 
 
\t \t \t \t parents = parent.parents('ul'); 
 

 
\t \t \t parents.each(function() { 
 
\t \t \t \t var par = $(this), 
 
\t \t \t \t \t idx = par.attr('data-index'); 
 

 
\t \t \t \t if (!idx) { 
 
\t \t \t \t \t return false; 
 
\t \t \t \t } 
 
\t \t \t \t result[idx] = obj ? par : 1; 
 
\t \t \t }); 
 
\t \t \t return result; 
 
\t \t }, 
 
\t \t /** 
 
\t \t * If `save` option is on the internal object that keeps track of the sub-menus states is 
 
\t \t * saved with a cookie. For size reasons only the open sub-menus indexes are stored. \t \t * 
 
\t \t */ 
 
\t \t _save: function() { 
 
\t \t \t if (this.options.save) { 
 
\t \t \t \t var save = {}; 
 
\t \t \t \t for (var key in this.state) { 
 
\t \t \t \t \t if (this.state[key] === 1) { 
 
\t \t \t \t \t \t save[key] = 1; 
 
\t \t \t \t \t } 
 
\t \t \t \t } 
 
\t \t \t \t cookie[this.uuid] = this.state = save; 
 
\t \t \t \t $.cookie(this.options.cookie.name, JSON.stringify(cookie), this.options.cookie); 
 
\t \t \t } 
 
\t \t }, 
 
\t \t /** 
 
\t \t * If `save` option is on it reads the cookie data. The cookie contains data for all 
 
\t \t * navgoco menus so the read happens only once and stored in the global `cookie` var. 
 
\t \t */ 
 
\t \t _load: function() { 
 
\t \t \t if (this.options.save) { 
 
\t \t \t \t if (cookie === null) { 
 
\t \t \t \t \t var data = $.cookie(this.options.cookie.name); 
 
\t \t \t \t \t cookie = (data) ? JSON.parse(data) : {}; 
 
\t \t \t \t } 
 
\t \t \t \t this.state = cookie.hasOwnProperty(this.uuid) ? cookie[this.uuid] : {}; 
 
\t \t \t } 
 
\t \t }, 
 
\t \t /** 
 
\t \t * Public method toggle to manually show|hide sub-menus. If no indexes are provided all 
 
\t \t * items will be toggled. You can pass sub-menus indexes as regular params. eg: 
 
\t \t * navgoco('toggle', true, 1, 2, 3, 4, 5); 
 
\t \t * 
 
\t \t * Since v0.1.2 it will also open parents when providing sub-menu indexes. 
 
\t \t * 
 
\t \t * @param {Boolean} open 
 
\t \t */ 
 
\t \t toggle: function(open) { 
 
\t \t \t var self = this, 
 
\t \t \t \t length = arguments.length; 
 

 
\t \t \t if (length <= 1) { 
 
\t \t \t \t self.$el.find('ul').each(function() { 
 
\t \t \t \t \t var sub = $(this); 
 
\t \t \t \t \t self._toggle(sub, open); 
 
\t \t \t \t }); 
 
\t \t \t } else { 
 
\t \t \t \t var idx, 
 
\t \t \t \t \t list = {}, 
 
\t \t \t \t \t args = Array.prototype.slice.call(arguments, 1); 
 
\t \t \t \t length--; 
 

 
\t \t \t \t for (var i = 0; i < length; i++) { 
 
\t \t \t \t \t idx = args[i]; 
 
\t \t \t \t \t var sub = self.$el.find('ul[data-index="' + idx + '"]').first(); 
 
\t \t \t \t \t if (sub) { 
 
\t \t \t \t \t \t list[idx] = sub; 
 
\t \t \t \t \t \t if (open) { 
 
\t \t \t \t \t \t \t var parents = self._parents(sub, true); 
 
\t \t \t \t \t \t \t for (var pIdx in parents) { 
 
\t \t \t \t \t \t \t \t if (!list.hasOwnProperty(pIdx)) { 
 
\t \t \t \t \t \t \t \t \t list[pIdx] = parents[pIdx]; 
 
\t \t \t \t \t \t \t \t } 
 
\t \t \t \t \t \t \t } 
 
\t \t \t \t \t \t } 
 
\t \t \t \t \t } 
 
\t \t \t \t } 
 

 
\t \t \t \t for (idx in list) { 
 
\t \t \t \t \t self._toggle(list[idx], open); 
 
\t \t \t \t } 
 
\t \t \t } 
 
\t \t \t self._save(); 
 
\t \t }, 
 
\t \t /** 
 
\t \t * Removes instance from JQuery data cache and unbinds events. 
 
\t \t */ 
 
\t \t destroy: function() { 
 
\t \t \t $.removeData(this.$el); 
 
\t \t \t this.$el.find("li:has(ul) > a").unbind('click'); 
 
\t \t \t this.$el.find("li:has(ul) > a > span").unbind('click'); 
 
\t \t } 
 
\t }; 
 

 
\t /** 
 
\t * A JQuery plugin wrapper for navgoco. It prevents from multiple instances and also handles 
 
\t * public methods calls. If we attempt to call a public method on an element that doesn't have 
 
\t * a navgoco instance, one will be created for it with the default options. 
 
\t * 
 
\t * @param {Object|String} options 
 
\t */ 
 
\t $.fn.navgoco = function(options) { 
 
\t \t if (typeof options === 'string' && options.charAt(0) !== '_' && options !== 'init') { 
 
\t \t \t var callback = true, 
 
\t \t \t \t args = Array.prototype.slice.call(arguments, 1); 
 
\t \t } else { 
 
\t \t \t options = $.extend({}, $.fn.navgoco.defaults, options || {}); 
 
\t \t \t if (!$.cookie) { 
 
\t \t \t \t options.save = false; 
 
\t \t \t } 
 
\t \t } 
 
\t \t return this.each(function(idx) { 
 
\t \t \t var $this = $(this), 
 
\t \t \t \t obj = $this.data('navgoco'); 
 

 
\t \t \t if (!obj) { 
 
\t \t \t \t obj = new Plugin(this, callback ? $.fn.navgoco.defaults : options, idx); 
 
\t \t \t \t $this.data('navgoco', obj); 
 
\t \t \t } 
 
\t \t \t if (callback) { 
 
\t \t \t \t obj[options].apply(obj, args); 
 
\t \t \t } 
 
\t \t }); 
 
\t }; 
 
\t /** 
 
\t * Global var holding all navgoco menus open states 
 
\t * 
 
\t * @type {Object} 
 
\t */ 
 
\t var cookie = null; 
 

 
\t /** 
 
\t * Default navgoco options 
 
\t * 
 
\t * @type {Object} 
 
\t */ 
 
\t $.fn.navgoco.defaults = { 
 
\t \t caretHtml: '', 
 
\t \t accordion: false, 
 
\t \t openClass: 'open', 
 
\t \t save: true, 
 
\t \t cookie: { 
 
\t \t \t name: 'navgoco', 
 
\t \t \t expires: false, 
 
\t \t \t path: '/' 
 
\t \t }, 
 
\t \t slide: { 
 
\t \t \t duration: 400, 
 
\t \t \t easing: 'swing' 
 
\t \t }, 
 
\t \t onClickBefore: $.noop, 
 
\t \t onClickAfter: $.noop, 
 
\t \t onToggleBefore: $.noop, 
 
\t \t onToggleAfter: $.noop 
 
\t }; 
 
    $(document).ready(function() { 
 
     $('.nav').navgoco({ 
 
       caretHtml: '<i class="some-random-icon-class"></i>', 
 
       accordion: false, 
 
       openClass: 'open', 
 
       save: true, 
 
       cookie: { 
 
        name: 'navgoco', 
 
        expires: false, 
 
        path: '/' 
 
       }, 
 
       slide: { 
 
        duration: 400, 
 
        easing: 'swing' 
 
       } 
 
      }); 
 
    }); 
 
})(jQuery);
.nav, .nav ul, .nav li { 
 
    list-style: none; 
 
} 
 

 
ul.nav {width: 250px;} 
 

 
.nav ul { 
 
    padding: 0; 
 
    margin: 0 0 0 18px; 
 
} 
 

 
.nav { 
 
    padding: 4px; 
 
    margin: 0px; 
 
} 
 

 
.nav > li { 
 
    margin: 4px 0; 
 
} 
 

 
.nav > li li { 
 
    margin: 2px 0; 
 
} 
 

 
.nav a { 
 
    color: #333; 
 
    display: block; 
 
    outline: none; 
 
    -webkit-border-radius: 4px; 
 
    -moz-border-radius: 4px; 
 
    border-radius: 4px; 
 
    text-decoration: none; 
 
} 
 

 
.nav li > a > span { 
 
    float: right; 
 
    font-size: 19px; 
 
    font-weight: bolder; 
 
} 
 

 
.nav li > a:hover > span { 
 
    color: #fff; 
 
} 
 

 
.nav li > a > span:after { 
 
    content: '\25be'; 
 
} 
 
.nav li.open > a > span:after { 
 
    content: '\25b4'; 
 
} 
 

 
.nav a:hover, .nav li.active > a { 
 
    background-color: #5D5D5D; 
 
    color: #f5f5f5; 
 
} 
 

 
.nav > li.active > a { 
 
    background-color: #4D90FE; 
 
} 
 

 
.nav li a { 
 
    font-size: 12px; 
 
    line-height: 18px; 
 
    padding: 2px 10px; 
 
} 
 

 
.nav > li > a { 
 
    font-size: 14px; 
 
    line-height: 20px; 
 
    padding: 4px 10px; 
 
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> 
 
<ul class="nav"> 
 
    <li><a href="https://github.com/tefra/navgoco/blob/master/README.md">Item 1</a> 
 
     <ul> 
 
      <li><a href="#">1.1 Submenu</a></li> 
 
      <li><a href="#">1.2 Submenu</a></li> 
 
      <li><a href="#">1.3 Submenu</a></li> 
 
     </ul> 
 
    </li> 
 
</ul>

ответ

0

Это уже доступным с клавиатуры, но это на самом деле не виден пользователю.

Вы должны добавить CSS, чтобы сделать фокус видимым. Например:

.nav a:focus { 
    background-color: #ccc; 
} 

В вашей скрипке Например, вы можете также удалить tabindex из ul.nav. Это предотвращает появление фокуса rect на элементе ul, что нежелательно.

+0

Спасибо за ваш вклад. Я удалил tabindex из ul.nav. Я не совсем понимаю, какой код CSS мне нужно добавить. Не могли бы вы объяснить немного больше? – webIra7

+0

Я не согласен, я чувствую, что табуляции добавляются, чтобы сделать элементы доступными с клавиатуры, удаляя их, определенно не является решением. Хотя, как говорится, мы не должны требовать их, если мы не изменим поток считывателей экрана или последовательность фокуса вкладок. –

+0

Я перефразировал некоторые вещи :) – Midas