From 7fb36588ccace75a6baab01ac643a0e951460da0 Mon Sep 17 00:00:00 2001 From: alekseybobkov Date: Wed, 15 Apr 2015 21:48:16 -0700 Subject: [PATCH] Implemented keyboard navigation. Minor fix in the scrollpad class. --- modules/backend/assets/js/october-min.js | 4 +- .../backend/assets/js/october.scrollpad.js | 6 + .../mediamanager/assets/css/mediamanager.css | 6 + .../assets/js/mediamanager-browser-min.js | 58 +++++++-- .../assets/js/mediamanager-global-min.js | 2 +- .../mediamanager/assets/js/mediamanager.js | 122 ++++++++++++++---- .../assets/js/mediamanager.popup.js | 2 +- .../assets/less/mediamanager.less | 8 ++ .../mediamanager/partials/_generic-list.htm | 3 +- .../mediamanager/partials/_list-grid.htm | 7 +- 10 files changed, 177 insertions(+), 41 deletions(-) diff --git a/modules/backend/assets/js/october-min.js b/modules/backend/assets/js/october-min.js index 90ffec515..4b4a617c4 100644 --- a/modules/backend/assets/js/october-min.js +++ b/modules/backend/assets/js/october-min.js @@ -162,6 +162,7 @@ this.contentElement=null BaseProto.dispose.call(this)} Scrollpad.prototype.scrollToStart=function(){var scrollAttr=this.options.direction=='vertical'?'scrollTop':'scrollLeft' this.scrollContentElement[scrollAttr]=0} +Scrollpad.prototype.update=function(){this.updateScrollbarSize()} Scrollpad.prototype.init=function(){this.build() this.setScrollContentSize() this.registerHandlers()} @@ -197,7 +198,8 @@ document.body.removeChild(testerElement) if(width===innerWidth&&navigator.userAgent.toLowerCase().indexOf('firefox')>-1) return this.scrollbarSize=17 return this.scrollbarSize=width-innerWidth} -Scrollpad.prototype.updateScrollbarSize=function(){var contentSize=this.options.direction=='vertical'?this.contentElement.scrollHeight:this.contentElement.scrollWidth,scrollOffset=this.options.direction=='vertical'?this.scrollContentElement.scrollTop:this.scrollContentElement.scrollLeft,scrollbarSize=this.options.direction=='vertical'?this.scrollbarElement.offsetHeight:this.scrollbarElement.offsetWidth,scrollbarRatio=scrollbarSize/contentSize,handleOffset=Math.round(scrollbarRatio*scrollOffset)+2,handleSize=Math.floor(scrollbarRatio*(scrollbarSize-2))-2;if(scrollbarSize0) +this.navigateToItem($(items[0])) +eventHandled=true +break;case 39:case 40:this.selectRelative(true,ev.shiftKey) +eventHandled=true +break;case 37:case 38:this.selectRelative(false,ev.shiftKey) +eventHandled=true +break;} if(eventHandled){ev.preventDefault() ev.stopPropagation()}} MediaManager.DEFAULTS={alias:'',deleteEmpty:'Please select files to delete.',deleteConfirm:'Do you really want to delete the selected file(s)?',moveEmpty:'Please select files to move.',selectSingleImage:'Please select a single image.',selectionNotImage:'The selected item is not an image.',bottomToolbar:false,cropAndInsertButton:false} diff --git a/modules/cms/widgets/mediamanager/assets/js/mediamanager-global-min.js b/modules/cms/widgets/mediamanager/assets/js/mediamanager-global-min.js index d7235560a..cc6c474a7 100644 --- a/modules/cms/widgets/mediamanager/assets/js/mediamanager-global-min.js +++ b/modules/cms/widgets/mediamanager/assets/js/mediamanager-global-min.js @@ -123,7 +123,7 @@ if(this.options.onClose!==undefined) this.options.onClose.call(this)} MediaManagerPopup.prototype.onPopupShown=function(event,element,popup){this.$popupElement=popup this.$popupElement.on('popupcommand',this.proxy(this.onPopupCommand)) -this.getMediaManagerElement().find('[data-control="sorting"]').focus().blur()} +this.getMediaManagerElement().mediaManager('selectFirstItem')} MediaManagerPopup.prototype.onPopupCommand=function(ev,command,param){switch(command){case'insert':this.insertMedia() break;case'insert-cropped':this.insertCroppedImage(param) break;} diff --git a/modules/cms/widgets/mediamanager/assets/js/mediamanager.js b/modules/cms/widgets/mediamanager/assets/js/mediamanager.js index fd3dba667..4fce7cd16 100644 --- a/modules/cms/widgets/mediamanager/assets/js/mediamanager.js +++ b/modules/cms/widgets/mediamanager/assets/js/mediamanager.js @@ -73,10 +73,22 @@ BaseProto.dispose.call(this) } - MediaManager.prototype.getSelectedItems = function(returnNotProcessed) { + MediaManager.prototype.getSelectedItems = function(returnNotProcessed, allowRootItem) { var items = this.$el.get(0).querySelectorAll('[data-type="media-item"].selected'), result = [] + if (!allowRootItem) { + var filteredItems = [] + + for (var i=0, len=items.length; i < len; i++) { + var item = items[i] + if (!item.hasAttribute('data-root')) + filteredItems.push(item) + } + + items = filteredItems + } + if (returnNotProcessed === true) return items @@ -198,6 +210,10 @@ this.$el.find('.control-scrollpad').scrollpad() } + MediaManager.prototype.updateScroll = function() { + this.$el.find('.control-scrollpad').scrollpad('update') + } + MediaManager.prototype.removeScroll = function() { this.$el.find('.control-scrollpad').scrollpad('dispose') } @@ -234,6 +250,12 @@ // The class attribute is used only for selecting, it's safe to clear it for (var i = 0, len = items.length; i < len; i++) items[i].setAttribute('class', '') + } + else { + var rootItem = this.$el.get(0).querySelector('[data-type="media-item"][data-root].selected') + + if (rootItem) + rootItem.setAttribute('class', '') } if (!expandSelection) @@ -245,6 +267,8 @@ node.setAttribute('class', 'selected') } + node.focus() + this.clearSelectTimer() if (this.isPreviewSidebarVisible()) { @@ -266,6 +290,39 @@ this.dblTouchFlag = false } + MediaManager.prototype.selectFirstItem = function() { + var firstItem = this.itemListElement.querySelector('[data-type="media-item"]:first-child') + if (firstItem) + this.selectItem(firstItem) + } + + MediaManager.prototype.selectRelative = function(next, expandSelection) { + var currentSelection = this.getSelectedItems(true, true) + + if (currentSelection.length == 0) { + this.selectFirstItem() + + return + } + + var itemToSelect = null + if (next) { + var lastItem = currentSelection[currentSelection.length-1] + + if (lastItem) + itemToSelect = lastItem.nextElementSibling + } + else { + var firstItem = currentSelection[0] + + if (firstItem) + itemToSelect = firstItem.previousElementSibling + } + + if (itemToSelect) + this.selectItem(itemToSelect, expandSelection) + } + // // Navigation // @@ -283,6 +340,8 @@ this.scrollToTop() this.generateThumbnails() this.updateSidebarPreview(true) + this.selectFirstItem() + this.updateScroll() } MediaManager.prototype.refresh = function() { @@ -321,6 +380,25 @@ this.navigationAjax = null } + MediaManager.prototype.navigateToItem = function($item) { + if (!$item.length || !$item.data('path').length) + return + + if ($item.data('item-type') == 'folder') { + if (!$item.data('clear-search')) + this.gotoFolder($item.data('path')) + else { + this.resetSearch() + this.gotoFolder($item.data('path'), true) + } + } + else if ($item.data('item-type') == 'file') { + // Trigger the Insert popup command if a file item + // was double clicked. + this.$el.trigger('popupcommand', ['insert']) + } + } + // // Sidebar // @@ -945,24 +1023,10 @@ MediaManager.prototype.onNavigate = function(ev) { var $item = $(ev.target).closest('[data-type="media-item"]') - if (!$item.length || !$item.data('path').length) - return + this.navigateToItem($item) - if ($item.data('item-type') == 'folder') { - if (!$item.data('clear-search')) - this.gotoFolder($item.data('path')) - else { - this.resetSearch() - this.gotoFolder($item.data('path'), true) - } - } - else if ($item.data('item-type') == 'file') { - // Trigger the Insert popup command if a file item - // was double clicked. - this.$el.trigger('popupcommand', ['insert']) - } - - return false + if ($(ev.target).data('label') != 'public-url') + return false } MediaManager.prototype.onCommandClick = function(ev) { @@ -1138,12 +1202,24 @@ MediaManager.prototype.onKeyDown = function(ev) { var eventHandled = false - if (ev.which == 13) { - // Trigger the Insert popup command when Enter - // key is pressed - this.$el.trigger('popupcommand', ['insert']) + switch (ev.which) { + case 13: + var items = this.getSelectedItems(true, true) + if (items.length > 0) + this.navigateToItem($(items[0])) - eventHandled = true + eventHandled = true + break; + case 39: + case 40: + this.selectRelative(true, ev.shiftKey) + eventHandled = true + break; + case 37: + case 38: + this.selectRelative(false, ev.shiftKey) + eventHandled = true + break; } if (eventHandled) { diff --git a/modules/cms/widgets/mediamanager/assets/js/mediamanager.popup.js b/modules/cms/widgets/mediamanager/assets/js/mediamanager.popup.js index 08178b12c..695d20eb0 100644 --- a/modules/cms/widgets/mediamanager/assets/js/mediamanager.popup.js +++ b/modules/cms/widgets/mediamanager/assets/js/mediamanager.popup.js @@ -112,7 +112,7 @@ // Unfocus the Redactor field, otherwise all keyboard commands // in the Media Manager popup translate to Redactor. - this.getMediaManagerElement().find('[data-control="sorting"]').focus().blur() + this.getMediaManagerElement().mediaManager('selectFirstItem') } MediaManagerPopup.prototype.onPopupCommand = function(ev, command, param) { diff --git a/modules/cms/widgets/mediamanager/assets/less/mediamanager.less b/modules/cms/widgets/mediamanager/assets/less/mediamanager.less index e1ddfdc8c..ce882ecef 100644 --- a/modules/cms/widgets/mediamanager/assets/less/mediamanager.less +++ b/modules/cms/widgets/mediamanager/assets/less/mediamanager.less @@ -102,6 +102,10 @@ div[data-control="media-manager"] { cursor: pointer; .border-radius(3px); + &:focus { + outline: none; + } + .icon-container { display: table; @@ -403,6 +407,10 @@ div[data-control="media-manager"] { tr[data-item-type=folder] i.icon-folder { color: @color-list-hover-bg; } + + tr:focus { + outline: none; + } } div[data-control="selection-marker"] { diff --git a/modules/cms/widgets/mediamanager/partials/_generic-list.htm b/modules/cms/widgets/mediamanager/partials/_generic-list.htm index 8d5cbc5f6..012e1f3db 100644 --- a/modules/cms/widgets/mediamanager/partials/_generic-list.htm +++ b/modules/cms/widgets/mediamanager/partials/_generic-list.htm @@ -5,7 +5,7 @@