mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge commit 'ec0c259' into 3
* commit 'ec0c259': BUG: Reinstate tab and form focus states (fixes CMS #732 and #817)
This commit is contained in:
commit
218ef0b599
@ -86,16 +86,12 @@
|
|||||||
|
|
||||||
// Show validation errors if necessary
|
// Show validation errors if necessary
|
||||||
if(this.hasClass('validationerror')) {
|
if(this.hasClass('validationerror')) {
|
||||||
// TODO validation shouldnt need a special case
|
|
||||||
statusMessage(ss.i18n._t('ModelAdmin.VALIDATIONERROR', 'Validation Error'), 'bad');
|
|
||||||
|
|
||||||
// Ensure the first validation error is visible
|
// Ensure the first validation error is visible
|
||||||
var firstTabWithErrors = this.find('.message.validation:first').closest('.tab');
|
var tabError = this.find('.message.validation, .message.required').first().closest('.tab');
|
||||||
$('.cms-container').clearCurrentTabState(); // clear state to avoid override later on
|
$('.cms-container').clearCurrentTabState(); // clear state to avoid override later on
|
||||||
this.redraw();
|
tabError.closest('.ss-tabset').tabs('option', 'active', tabError.index('.tab'));
|
||||||
firstTabWithErrors.closest('.ss-tabset').tabs('select', firstTabWithErrors.attr('id'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._super();
|
this._super();
|
||||||
},
|
},
|
||||||
onremove: function() {
|
onremove: function() {
|
||||||
@ -104,15 +100,6 @@
|
|||||||
},
|
},
|
||||||
onmatch: function() {
|
onmatch: function() {
|
||||||
this._super();
|
this._super();
|
||||||
|
|
||||||
// focus input on first form element. Exclude elements which
|
|
||||||
// specifically opt-out of this behaviour via "data-skip-autofocus".
|
|
||||||
// This opt-out is useful if the first visible field is shown far down a scrollable area,
|
|
||||||
// for example for the pagination input field after a long GridField listing.
|
|
||||||
// Skip if an element in the form is already focused.
|
|
||||||
if(!this.find(document.activeElement).length) {
|
|
||||||
this.find(':input:not(:submit)[data-skip-autofocus!="true"]').filter(':visible:first').focus();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onunmatch: function() {
|
onunmatch: function() {
|
||||||
this._super();
|
this._super();
|
||||||
@ -185,6 +172,162 @@
|
|||||||
this.trigger('validate', {isValid: isValid});
|
this.trigger('validate', {isValid: isValid});
|
||||||
|
|
||||||
return isValid;
|
return isValid;
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
* Track focus on htmleditor fields
|
||||||
|
*/
|
||||||
|
'from .htmleditor': {
|
||||||
|
oneditorinit: function(e){
|
||||||
|
var self = this,
|
||||||
|
field = $(e.target).closest('.field.htmleditor'),
|
||||||
|
editor = field.find('textarea.htmleditor').getEditor().getInstance();
|
||||||
|
|
||||||
|
// TinyMCE 4 will add a focus event, but for now, use click
|
||||||
|
editor.onClick.add(function(e){
|
||||||
|
self.saveFieldFocus(field.attr('id'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
* Track focus on inputs
|
||||||
|
*/
|
||||||
|
'from .cms-edit-form :input:not(:submit)': {
|
||||||
|
onclick: function(e){
|
||||||
|
this.saveFieldFocus($(e.target).attr('id'));
|
||||||
|
},
|
||||||
|
onfocus: function(e){
|
||||||
|
this.saveFieldFocus($(e.target).attr('id'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
* Track focus on treedropdownfields.
|
||||||
|
*/
|
||||||
|
'from .cms-edit-form .treedropdown *': {
|
||||||
|
onfocusin: function(e){
|
||||||
|
var field = $(e.target).closest('.field.treedropdown');
|
||||||
|
this.saveFieldFocus(field.attr('id'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
* Track focus on chosen selects
|
||||||
|
*/
|
||||||
|
'from .cms-edit-form .dropdown .chzn-container a': {
|
||||||
|
onfocusin: function(e){
|
||||||
|
var field = $(e.target).closest('.field.dropdown');
|
||||||
|
this.saveFieldFocus(field.attr('id'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
* Restore fields after tabs are restored
|
||||||
|
*/
|
||||||
|
'from .cms-container': {
|
||||||
|
ontabstaterestored: function(e){
|
||||||
|
this.restoreFieldFocus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
* Saves focus in Window session storage so it that can be restored on page load
|
||||||
|
*/
|
||||||
|
saveFieldFocus: function(selected){
|
||||||
|
if(typeof(window.sessionStorage)=="undefined" || window.sessionStorage === null) return;
|
||||||
|
|
||||||
|
var id = $(this).attr('id'),
|
||||||
|
focusElements = [];
|
||||||
|
|
||||||
|
focusElements.push({
|
||||||
|
id:id,
|
||||||
|
selected:selected
|
||||||
|
});
|
||||||
|
|
||||||
|
if(focusElements) {
|
||||||
|
try {
|
||||||
|
window.sessionStorage.setItem(id, JSON.stringify(focusElements));
|
||||||
|
} catch(err) {
|
||||||
|
if (err.code === DOMException.QUOTA_EXCEEDED_ERR && window.sessionStorage.length === 0) {
|
||||||
|
// If this fails we ignore the error as the only issue is that it
|
||||||
|
// does not remember the focus state.
|
||||||
|
// This is a Safari bug which happens when private browsing is enabled.
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Set focus or window to previously saved fields.
|
||||||
|
* Requires HTML5 sessionStorage support.
|
||||||
|
*
|
||||||
|
* Must follow tab restoration, as reliant on active tab
|
||||||
|
*/
|
||||||
|
restoreFieldFocus: function(){
|
||||||
|
if(typeof(window.sessionStorage)=="undefined" || window.sessionStorage === null) return;
|
||||||
|
|
||||||
|
var self = this,
|
||||||
|
hasSessionStorage = (typeof(window.sessionStorage)!=="undefined" && window.sessionStorage),
|
||||||
|
sessionData = hasSessionStorage ? window.sessionStorage.getItem(this.attr('id')) : null,
|
||||||
|
sessionStates = sessionData ? JSON.parse(sessionData) : false,
|
||||||
|
elementID,
|
||||||
|
tabbed = this.find('.ss-tabset'),
|
||||||
|
activeTab,
|
||||||
|
elementTab,
|
||||||
|
toggleComposite,
|
||||||
|
scrollY;
|
||||||
|
|
||||||
|
if(hasSessionStorage && sessionStates.length > 0){
|
||||||
|
|
||||||
|
$.each(sessionStates, function(i, sessionState) {
|
||||||
|
if(self.is('#' + sessionState.id)){
|
||||||
|
elementID = $('#' + sessionState.selected);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if($(elementID).length < 1){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
activeTab = $(elementID).closest('.ss-tabset').find('.ui-tabs-nav .ui-tabs-active .ui-tabs-anchor').attr('id');
|
||||||
|
elementTab = 'tab-' + $(elementID).closest('.ss-tabset .ui-tabs-panel').attr('id');
|
||||||
|
|
||||||
|
// Last focussed element differs to last selected tab, do nothing
|
||||||
|
if(tabbed && elementTab !== activeTab){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleComposite = $(elementID).closest('.togglecomposite');
|
||||||
|
|
||||||
|
//Reopen toggle fields
|
||||||
|
if(toggleComposite.length > 0){
|
||||||
|
toggleComposite.accordion('activate', toggleComposite.find('.ui-accordion-header'));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Calculate position for scroll
|
||||||
|
scrollY = $(elementID).position().top;
|
||||||
|
|
||||||
|
//Fall back to nearest visible element if hidden (for select type fields)
|
||||||
|
if(!$(elementID).is(':visible')){
|
||||||
|
elementID = '#' + $(elementID).closest('.field').attr('id');
|
||||||
|
scrollY = $(elementID).position().top;
|
||||||
|
}
|
||||||
|
|
||||||
|
//set focus to focus variable if element focusable
|
||||||
|
$(elementID).focus();
|
||||||
|
|
||||||
|
// Scroll fallback when element is not focusable
|
||||||
|
// Only scroll if element at least half way down window
|
||||||
|
if(scrollY > $(window).height() / 2){
|
||||||
|
self.find('.cms-content-fields').scrollTop(scrollY);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// If there is no focus data attribute set, focus input on first form element. Exclude elements which
|
||||||
|
// specifically opt-out of this behaviour via "data-skip-autofocus".
|
||||||
|
// This opt-out is useful if the first visible field is shown far down a scrollable area,
|
||||||
|
// for example for the pagination input field after a long GridField listing.
|
||||||
|
// Skip if an element in the form is already focused.
|
||||||
|
|
||||||
|
this.find(':input:not(:submit)[data-skip-autofocus!="true"]').filter(':visible:first').focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -805,7 +805,9 @@ jQuery.noConflict();
|
|||||||
var index, tabset = $(this), tabsetId = tabset.attr('id'), tab,
|
var index, tabset = $(this), tabsetId = tabset.attr('id'), tab,
|
||||||
forcedTab = tabset.find('.ss-tabs-force-active');
|
forcedTab = tabset.find('.ss-tabs-force-active');
|
||||||
|
|
||||||
if(!tabset.data('tabs')) return; // don't act on uninit'ed controls
|
if(!tabset.data('tabs')){
|
||||||
|
return; // don't act on uninit'ed controls
|
||||||
|
}
|
||||||
|
|
||||||
// The tabs may have changed, notify the widget that it should update its internal state.
|
// The tabs may have changed, notify the widget that it should update its internal state.
|
||||||
tabset.tabs('refresh');
|
tabset.tabs('refresh');
|
||||||
@ -815,13 +817,20 @@ jQuery.noConflict();
|
|||||||
index = forcedTab.index();
|
index = forcedTab.index();
|
||||||
} else if(overrideStates && overrideStates[tabsetId]) {
|
} else if(overrideStates && overrideStates[tabsetId]) {
|
||||||
tab = tabset.find(overrideStates[tabsetId].tabSelector);
|
tab = tabset.find(overrideStates[tabsetId].tabSelector);
|
||||||
if(tab.length) index = tab.index();
|
if(tab.length){
|
||||||
|
index = tab.index();
|
||||||
|
}
|
||||||
} else if(sessionStates) {
|
} else if(sessionStates) {
|
||||||
$.each(sessionStates, function(i, sessionState) {
|
$.each(sessionStates, function(i, sessionState) {
|
||||||
if(tabset.is('#' + sessionState.id)) index = sessionState.selected;
|
if(tabset.is('#' + sessionState.id)){
|
||||||
});
|
index = sessionState.selected;
|
||||||
}
|
}
|
||||||
if(index !== null) tabset.tabs('select', index);
|
});
|
||||||
|
}
|
||||||
|
if(index !== null){
|
||||||
|
tabset.tabs('option', 'active', index);
|
||||||
|
self.trigger('tabstaterestored');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
data-url-tree="$Link(tree)"
|
data-url-tree="$Link(tree)"
|
||||||
data-title="$TitleURLEncoded"
|
data-title="$TitleURLEncoded"
|
||||||
<% if $Description %>title="$Description"<% end_if %>
|
<% if $Description %>title="$Description"<% end_if %>
|
||||||
<% if $Metadata %>data-metadata="$Metadata"<% end_if %>>
|
<% if $Metadata %>data-metadata="$Metadata"<% end_if %> tabindex="0">
|
||||||
<input id="$ID" type="hidden" name="$Name" value="$Value" />
|
<input id="$ID" type="hidden" name="$Name" value="$Value" />
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user