Implemented keyboard navigation. Minor fix in the scrollpad class.

This commit is contained in:
alekseybobkov 2015-04-15 21:48:16 -07:00
parent 87a2ea1350
commit 7fb36588cc
10 changed files with 177 additions and 41 deletions

View File

@ -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(scrollbarSize<contentSize){if(this.options.direction=='vertical')
Scrollpad.prototype.updateScrollbarSize=function(){this.scrollbarElement.removeAttribute('data-hidden')
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(scrollbarSize<contentSize){if(this.options.direction=='vertical')
this.dragHandleElement.setAttribute('style','top: '+handleOffset+'px; height: '+handleSize+'px')
else
this.dragHandleElement.setAttribute('style','left: '+handleOffset+'px; width: '+handleSize+'px')

View File

@ -87,6 +87,10 @@
this.scrollContentElement[scrollAttr] = 0
}
Scrollpad.prototype.update = function() {
this.updateScrollbarSize()
}
// SCROLLPAD INTERNAL METHODS
// ============================
@ -156,6 +160,8 @@
}
Scrollpad.prototype.updateScrollbarSize = function() {
this.scrollbarElement.removeAttribute('data-hidden')
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,

View File

@ -47,6 +47,9 @@ div[data-control="media-manager"] .media-list li {
-moz-border-radius: 3px;
border-radius: 3px;
}
div[data-control="media-manager"] .media-list li:focus {
outline: none;
}
div[data-control="media-manager"] .media-list li .icon-container {
display: table;
}
@ -345,6 +348,9 @@ div[data-control="media-manager"] table.table tr:hover div.item-title a {
div[data-control="media-manager"] table.table tr[data-item-type=folder] i.icon-folder {
color: #4da7e8;
}
div[data-control="media-manager"] table.table tr:focus {
outline: none;
}
div[data-control="media-manager"] div[data-control="selection-marker"] {
position: absolute;
z-index: 250;

View File

@ -42,7 +42,12 @@ this.selectionMarker=null
this.thumbnailQueue=[]
this.navigationAjax=null
BaseProto.dispose.call(this)}
MediaManager.prototype.getSelectedItems=function(returnNotProcessed){var items=this.$el.get(0).querySelectorAll('[data-type="media-item"].selected'),result=[]
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
for(var i=0,len=items.length;i<len;i++){var item=items[i],itemDetails={itemType:item.getAttribute('data-item-type'),path:item.getAttribute('data-path'),title:item.getAttribute('data-title'),documentType:item.getAttribute('data-document-type'),folder:item.getAttribute('data-folder'),publicUrl:item.getAttribute('data-public-url')}
@ -98,6 +103,7 @@ MediaManager.prototype.setFilter=function(filter){var data={filter:filter,path:t
this.execNavigationRequest('onSetFilter',data)}
MediaManager.prototype.isSearchMode=function(){return this.$el.find('[data-type="search-mode"]').val()=='true'}
MediaManager.prototype.initScroll=function(){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')}
MediaManager.prototype.scrollToTop=function(){this.$el.find('.control-scrollpad').scrollpad('scrollToStart')}
MediaManager.prototype.removeAttachedControls=function(){this.$el.find('[data-control=toolbar]').toolbar('dispose')
@ -109,12 +115,16 @@ this.selectTimer=null}
MediaManager.prototype.selectItem=function(node,expandSelection){if(!expandSelection){var items=this.$el.get(0).querySelectorAll('[data-type="media-item"].selected')
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)
node.setAttribute('class','selected')
else{if(node.getAttribute('class')=='selected')
node.setAttribute('class','')
else
node.setAttribute('class','selected')}
node.focus()
this.clearSelectTimer()
if(this.isPreviewSidebarVisible()){this.selectTimer=setTimeout(this.proxy(this.updateSidebarPreview),100)}}
MediaManager.prototype.clearDblTouchTimer=function(){if(this.dblTouchTimer===null)
@ -122,11 +132,28 @@ return
clearTimeout(this.dblTouchTimer)
this.dblTouchTimer=null}
MediaManager.prototype.clearDblTouchFlag=function(){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)}
MediaManager.prototype.gotoFolder=function(path,resetSearch){var data={path:path,resetSearch:resetSearch!==undefined?1:0}
this.execNavigationRequest('onGoToFolder',data)}
MediaManager.prototype.afterNavigate=function(){this.scrollToTop()
this.generateThumbnails()
this.updateSidebarPreview(true)}
this.updateSidebarPreview(true)
this.selectFirstItem()
this.updateScroll()}
MediaManager.prototype.refresh=function(){var data={path:this.$el.find('[data-type="current-folder"]').val(),clearCache:true}
this.execNavigationRequest('onGoToFolder',data)}
MediaManager.prototype.execNavigationRequest=function(handler,data,element)
@ -138,6 +165,13 @@ this.releaseNavigationAjax()}
$.oc.stripeLoadIndicator.show()
this.navigationAjax=element.request(this.options.alias+'::'+handler,{data:data}).always(function(){$.oc.stripeLoadIndicator.hide()}).done(this.proxy(this.afterNavigate)).always(this.proxy(this.releaseNavigationAjax))}
MediaManager.prototype.releaseNavigationAjax=function(){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'){this.$el.trigger('popupcommand',['insert'])}}
MediaManager.prototype.isPreviewSidebarVisible=function(){return!this.$el.find('[data-control="preview-sidebar"]').hasClass('hide')}
MediaManager.prototype.toggleSidebar=function(ev){var isVisible=this.isPreviewSidebarVisible(),$sidebar=this.$el.find('[data-control="preview-sidebar"]'),$button=$(ev.target)
if(!isVisible){$sidebar.removeClass('hide')
@ -344,13 +378,8 @@ MediaManager.prototype.onMovePopupHidden=function(ev,button,popup){$(popup).off(
MediaManager.prototype.itemsMoved=function(){this.$el.find('button[data-command="move"]').popup('hide')
this.afterNavigate()}
MediaManager.prototype.onNavigate=function(ev){var $item=$(ev.target).closest('[data-type="media-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'){this.$el.trigger('popupcommand',['insert'])}
this.navigateToItem($item)
if($(ev.target).data('label')!='public-url')
return false}
MediaManager.prototype.onCommandClick=function(ev){var command=$(ev.currentTarget).data('command')
switch(command){case'refresh':this.refresh()
@ -418,8 +447,15 @@ this.selectionMarker.style.height=Math.abs(deltaY)+'px'}}}
MediaManager.prototype.onSortingChanged=function(ev){var data={sortBy:$(ev.target).val(),path:this.$el.find('[data-type="current-folder"]').val()}
this.execNavigationRequest('onSetSorting',data)}
MediaManager.prototype.onKeyDown=function(ev){var eventHandled=false
if(ev.which==13){this.$el.trigger('popupcommand',['insert'])
eventHandled=true}
switch(ev.which){case 13:var items=this.getSelectedItems(true,true)
if(items.length>0)
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}

View File

@ -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;}

View File

@ -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) {

View File

@ -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) {

View File

@ -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"] {

View File

@ -5,7 +5,7 @@
<ul class="media-list <?= $listClass ?>">
<?php if (count($items) > 0 || !$isRootFolder): ?>
<?php if (!$isRootFolder && !$searchMode): ?>
<li data-type="media-item" data-item-type="folder" data-root data-path="<?= e(dirname($currentFolder)) ?>">
<li tabindex="0" data-type="media-item" data-item-type="folder" data-root data-path="<?= e(dirname($currentFolder)) ?>">
<div class="icon-container folder">
<div class="icon-wrapper"><i class="icon-folder"></i></div>
</div>
@ -28,6 +28,7 @@
data-public-url="<?= e($item->publicUrl) ?>"
data-document-type="<?= e($itemType) ?>"
data-folder="<?= e(dirname($item->path)) ?>"
tabindex="0"
>
<?= $this->makePartial('item-icon', ['itemType'=>$itemType, 'item'=>$item]) ?>

View File

@ -4,13 +4,13 @@
<table class="table data">
<col />
<col width="100px" />
<col width="100px" />
<col width="130px" />
<col width="130px" />
<tbody class="icons clickable">
<?php if (count($items) > 0 || !$isRootFolder): ?>
<?php if (!$isRootFolder && !$searchMode): ?>
<tr data-type="media-item" data-item-type="folder" data-root data-path="<?= e(dirname($currentFolder)) ?>">
<tr data-type="media-item" data-item-type="folder" data-root data-path="<?= e(dirname($currentFolder)) ?>" tabindex="0">
<td><i class="icon-folder"></i>..</td>
<td></td>
<td></td>
@ -30,6 +30,7 @@
data-public-url="<?= e($item->publicUrl) ?>"
data-document-type="<?= e($itemType) ?>"
data-folder="<?= e(dirname($item->path)) ?>"
tabindex="0"
>
<td>
<div class="item-title no-wrap-text">