mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
3ee8f505b7
The main benefit of this is so that authors who make use of .editorconfig don't end up with whitespace changes in their PRs. Spaces vs. tabs has been left alone, although that could do with a tidy-up in SS4 after the switch to PSR-1/2. The command used was this: for match in '*.ss' '*.css' '*.scss' '*.html' '*.yml' '*.php' '*.js' '*.csv' '*.inc' '*.php5'; do find . -path ./thirdparty -not -prune -o -path ./admin/thirdparty -not -prune -o -type f -name "$match" -exec sed -E -i '' 's/[[:space:]]+$//' {} \+ find . -path ./thirdparty -not -prune -o -path ./admin/thirdparty -not -prune -o -type f -name "$match" | xargs perl -pi -e 's/ +$//' done
419 lines
13 KiB
JavaScript
419 lines
13 KiB
JavaScript
/**
|
|
* File: LeftAndMain.EditForm.js
|
|
*/
|
|
(function($) {
|
|
|
|
// Can't bind this through jQuery
|
|
window.onbeforeunload = function(e) {
|
|
var form = $('.cms-edit-form');
|
|
form.trigger('beforesubmitform');
|
|
if(form.is('.changed')) return ss.i18n._t('LeftAndMain.CONFIRMUNSAVEDSHORT');
|
|
};
|
|
|
|
$.entwine('ss', function($){
|
|
|
|
/**
|
|
* Class: .cms-edit-form
|
|
*
|
|
* Base edit form, provides ajaxified saving
|
|
* and reloading itself through the ajax return values.
|
|
* Takes care of resizing tabsets within the layout container.
|
|
*
|
|
* Change tracking is enabled on all fields within the form. If you want
|
|
* to disable change tracking for a specific field, add a "no-change-track"
|
|
* class to it.
|
|
*
|
|
* @name ss.Form_EditForm
|
|
* @require jquery.changetracker
|
|
*
|
|
* Events:
|
|
* ajaxsubmit - Form is about to be submitted through ajax
|
|
* validate - Contains validation result
|
|
* load - Form is about to be loaded through ajax
|
|
*/
|
|
$('.cms-edit-form').entwine(/** @lends ss.Form_EditForm */{
|
|
/**
|
|
* Variable: PlaceholderHtml
|
|
* (String_ HTML text to show when no form content is chosen.
|
|
* Will show inside the <form> tag.
|
|
*/
|
|
PlaceholderHtml: '',
|
|
|
|
/**
|
|
* Variable: ChangeTrackerOptions
|
|
* (Object)
|
|
*/
|
|
ChangeTrackerOptions: {
|
|
ignoreFieldSelector: '.no-change-track, .ss-upload :input, .cms-navigator :input'
|
|
},
|
|
|
|
/**
|
|
* Constructor: onmatch
|
|
*/
|
|
onadd: function() {
|
|
var self = this;
|
|
|
|
// Turn off autocomplete to fix the access tab randomly switching radio buttons in Firefox
|
|
// when refresh the page with an anchor tag in the URL. E.g: /admin#Root_Access.
|
|
// Autocomplete in the CMS also causes strangeness in other browsers,
|
|
// filling out sections of the form that the user does not want to be filled out,
|
|
// so this turns it off for all browsers.
|
|
// See the following page for demo and explanation of the Firefox bug:
|
|
// http://www.ryancramer.com/journal/entries/radio_buttons_firefox/
|
|
this.attr("autocomplete", "off");
|
|
|
|
this._setupChangeTracker();
|
|
|
|
// Catch navigation events before they reach handleStateChange(),
|
|
// in order to avoid changing the menu state if the action is cancelled by the user
|
|
// $('.cms-menu')
|
|
|
|
// Optionally get the form attributes from embedded fields, see Form->formHtmlContent()
|
|
for(var overrideAttr in {'action':true,'method':true,'enctype':true,'name':true}) {
|
|
var el = this.find(':input[name='+ '_form_' + overrideAttr + ']');
|
|
if(el) {
|
|
this.attr(overrideAttr, el.val());
|
|
el.remove();
|
|
}
|
|
}
|
|
|
|
// TODO
|
|
// // Rewrite # links
|
|
// html = html.replace(/(<a[^>]+href *= *")#/g, '$1' + window.location.href.replace(/#.*$/,'') + '#');
|
|
//
|
|
// // Rewrite iframe links (for IE)
|
|
// html = html.replace(/(<iframe[^>]*src=")([^"]+)("[^>]*>)/g, '$1' + $('base').attr('href') + '$2$3');
|
|
|
|
// Show validation errors if necessary
|
|
if(this.hasClass('validationerror')) {
|
|
// Ensure the first validation error is visible
|
|
var tabError = this.find('.message.validation, .message.required').first().closest('.tab');
|
|
$('.cms-container').clearCurrentTabState(); // clear state to avoid override later on
|
|
tabError.closest('.ss-tabset').tabs('option', 'active', tabError.index('.tab'));
|
|
}
|
|
|
|
this._super();
|
|
},
|
|
onremove: function() {
|
|
this.changetracker('destroy');
|
|
this._super();
|
|
},
|
|
onmatch: function() {
|
|
this._super();
|
|
},
|
|
onunmatch: function() {
|
|
this._super();
|
|
},
|
|
redraw: function() {
|
|
if(window.debug) console.log('redraw', this.attr('class'), this.get(0));
|
|
|
|
// Force initialization of tabsets to avoid layout glitches
|
|
this.add(this.find('.cms-tabset')).redrawTabs();
|
|
this.find('.cms-content-header').redraw();
|
|
},
|
|
|
|
/**
|
|
* Function: _setupChangeTracker
|
|
*/
|
|
_setupChangeTracker: function() {
|
|
// Don't bind any events here, as we dont replace the
|
|
// full <form> tag by any ajax updates they won't automatically reapply
|
|
this.changetracker(this.getChangeTrackerOptions());
|
|
},
|
|
|
|
/**
|
|
* Function: confirmUnsavedChanges
|
|
*
|
|
* Checks the jquery.changetracker plugin status for this form,
|
|
* and asks the user for confirmation via a browser dialog if changes are detected.
|
|
* Doesn't cancel any unload or form removal events, you'll need to implement this based on the return
|
|
* value of this message.
|
|
*
|
|
* If changes are confirmed for discard, the 'changed' flag is reset.
|
|
*
|
|
* Returns:
|
|
* (Boolean) FALSE if the user wants to abort with changes present, TRUE if no changes are detected
|
|
* or the user wants to discard them.
|
|
*/
|
|
confirmUnsavedChanges: function() {
|
|
this.trigger('beforesubmitform');
|
|
if(!this.is('.changed')) {
|
|
return true;
|
|
}
|
|
var confirmed = confirm(ss.i18n._t('LeftAndMain.CONFIRMUNSAVED'));
|
|
if(confirmed) {
|
|
// confirm discard changes
|
|
this.removeClass('changed');
|
|
}
|
|
return confirmed;
|
|
},
|
|
|
|
/**
|
|
* Function: onsubmit
|
|
*
|
|
* Suppress submission unless it is handled through ajaxSubmit().
|
|
*/
|
|
onsubmit: function(e, button) {
|
|
// Only submit if a button is present.
|
|
// This supressed submits from ENTER keys in input fields,
|
|
// which means the browser auto-selects the first available form button.
|
|
// This might be an unrelated button of the form field,
|
|
// or a destructive action (if "save" is not available, or not on first position).
|
|
if(this.prop("target") != "_blank") {
|
|
if(button) this.closest('.cms-container').submitForm(this, button);
|
|
return false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Function: validate
|
|
*
|
|
* Hook in (optional) validation routines.
|
|
* Currently clientside validation is not supported out of the box in the CMS.
|
|
*
|
|
* Todo:
|
|
* Placeholder implementation
|
|
*
|
|
* Returns:
|
|
* {boolean}
|
|
*/
|
|
validate: function() {
|
|
var isValid = true;
|
|
this.trigger('validate', {isValid: 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').length !== 0),
|
|
activeTab,
|
|
elementTab,
|
|
toggleComposite,
|
|
scrollY;
|
|
|
|
if(hasSessionStorage && sessionStates.length > 0){
|
|
$.each(sessionStates, function(i, sessionState) {
|
|
if(self.is('#' + sessionState.id)){
|
|
elementID = $('#' + sessionState.selected);
|
|
}
|
|
});
|
|
|
|
// If the element IDs saved in session states don't match up to anything in this particular form
|
|
// that probably means we haven't encountered this form yet, so focus on the first input
|
|
if($(elementID).length < 1){
|
|
this.focusFirstInput();
|
|
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 session storage is not supported or there is nothing stored yet, focus on the first input
|
|
this.focusFirstInput();
|
|
}
|
|
},
|
|
/**
|
|
* Skip if an element in the form is already focused. 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.
|
|
*/
|
|
focusFirstInput: function() {
|
|
this.find(':input:not(:submit)[data-skip-autofocus!="true"]').filter(':visible:first').focus();
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Class: .cms-edit-form .Actions :submit
|
|
*
|
|
* All buttons in the right CMS form go through here by default.
|
|
* We need this onclick overloading because we can't get to the
|
|
* clicked button from a form.onsubmit event.
|
|
*/
|
|
$('.cms-edit-form .Actions input.action[type=submit], .cms-edit-form .Actions button.action').entwine({
|
|
/**
|
|
* Function: onclick
|
|
*/
|
|
onclick: function(e) {
|
|
// Confirmation on delete.
|
|
if(
|
|
this.hasClass('gridfield-button-delete')
|
|
&& !confirm(ss.i18n._t('TABLEFIELD.DELETECONFIRMMESSAGE'))
|
|
) {
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
|
|
if(!this.is(':disabled')) {
|
|
this.parents('form').trigger('submit', [this]);
|
|
}
|
|
e.preventDefault();
|
|
return false;
|
|
}
|
|
});
|
|
|
|
/**
|
|
* If we've a history state to go back to, go back, otherwise fall back to
|
|
* submitting the form with the 'doCancel' action.
|
|
*/
|
|
$('.cms-edit-form .Actions input.action[type=submit].ss-ui-action-cancel, .cms-edit-form .Actions button.action.ss-ui-action-cancel').entwine({
|
|
onclick: function(e) {
|
|
if (History.getStateByIndex(1)) {
|
|
History.back();
|
|
} else {
|
|
this.parents('form').trigger('submit', [this]);
|
|
}
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Hide tabs when only one is available.
|
|
* Special case is actiontabs - tabs between buttons, where we want to have
|
|
* extra options hidden within a tab (even if only one) by default.
|
|
*/
|
|
$('.cms-edit-form .ss-tabset').entwine({
|
|
onmatch: function() {
|
|
if (!this.hasClass('ss-ui-action-tabset')) {
|
|
var tabs = this.find("> ul:first");
|
|
|
|
if(tabs.children("li").length == 1) {
|
|
tabs.hide().parent().addClass("ss-tabset-tabshidden");
|
|
}
|
|
}
|
|
|
|
this._super();
|
|
},
|
|
onunmatch: function() {
|
|
this._super();
|
|
}
|
|
});
|
|
|
|
});
|
|
|
|
}(jQuery));
|