silverstripe-cms/javascript/LeftAndMain.js
Ingo Schommer ba05455d32 MINOR Documentation
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/cms/trunk@92610 467b73ca-7a2a-4603-9d3b-597d59a354a9
2009-11-21 02:36:28 +00:00

699 lines
18 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(function($) {
/**
* Main LeftAndMain interface with some control
* panel and an edit form.
*
* Events:
* - beforeSave
* - afterSave
* - beforeValidate
* - afterValidate
*/
$('.LeftAndMain').concrete('ss', function($){return{
/**
*
*/
PingIntervalSeconds: 5*60,
onmatch: function() {
this._setupPinging();
this._setupButtons();
this._resizeChildren();
this._super();
},
_setupPinging: function() {
// setup pinging for login expiry
setInterval(function() {
jQuery.get("Security/ping");
}, this.PingIntervalSeconds() * 1000);
},
/**
* Make all buttons "hoverable" with jQuery theming.
*/
_setupButtons: function() {
// Initialize buttons
this.find(':submit, button').livequery(function() {
jQuery(this).addClass(
'ui-state-default ' +
'ui-corner-all'
)
.hover(
function() {
$(this).addClass('ui-state-hover');
},
function() {
$(this).removeClass('ui-state-hover');
}
)
.focus(function() {
$(this).addClass('ui-state-focus');
})
.blur(function() {
$(this).removeClass('ui-state-focus');
});
});
},
/**
* Resize elements in center panel
* to fit the boundary box provided by the layout manager
*/
_resizeChildren: function() {
$('#Form_EditForm').fitHeightToParent();
$('#Form_EditForm fieldset', this).fitHeightToParent();
// Order of resizing is important: Outer to inner
// TODO Only supports two levels of tabs at the moment
$('#Form_EditForm fieldset > .ss-tabset', this).fitHeightToParent();
$('#Form_EditForm fieldset > .ss-tabset > .tab', this).fitHeightToParent();
$('#Form_EditForm fieldset > .ss-tabset > .tab > .ss-tabset', this).fitHeightToParent();
$('#Form_EditForm fieldset > .ss-tabset > .tab > .ss-tabset > .tab', this).fitHeightToParent();
}
}});
/**
* Base edit form, provides ajaxified saving
* and reloading itself through the ajax return values.
* Takes care of resizing tabsets within the layout container.
*/
$('#Form_EditForm').concrete('ss',function($){return{
onmatch: function() {
var self = this;
// artificially delay the resize event 200ms
// to avoid overlapping height changes in different onresize() methods
$(window).resize(function () {
var timerID = "timerLeftAndMainResize";
if (window[timerID]) clearTimeout(window[timerID]);
window[timerID] = setTimeout(function() {self._resizeChildren();}, 200);
});
// trigger resize whenever new tabs are shown
// @todo This is called multiple times when tabs are loaded
this.find('.ss-tabset').bind('tabsshow', function() {self._resizeChildren();});
},
/**
* Suppress submission unless it is handled through ajaxSubmit()
*/
onsubmit: function(e) {
return false;
},
/**
* @param DOMElement button The pressed button (optiona)
*/
ajaxSubmit: function(button) {
// default to first button if none given - simulates browser behaviour
if(!button) button = this.find(':submit:first');
var $form = this;
this.trigger('beforeSubmit', [button]);
// set button to "submitting" state
$(button).addClass('loading');
// @todo TinyMCE coupling
if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave();
// validate if required
if(!this.validate()) {
this.trigger('validationError', [button]);
// TODO Automatically switch to the tab/position of the first error
statusMessage("Validation failed.", "bad");
if($('Form_EditForm_action_save') && $('Form_EditForm_action_save').stopLoading) $('Form_EditForm_action_save').stopLoading();
return false;
}
// get all data from the form
var data = this.serializeArray();
// add button action
data.push({name: $(button).attr('name'), value:'1'});
$.post(
this.attr('action'),
data,
function(result) {
$(button).removeClass('loading');
$form.trigger('afterSubmit', [result]);
$form.loadNewPage();
},
// @todo Currently all responses are assumed to be evaluated
'script'
);
return false;
},
/**
* Hook in (optional) validation routines.
* Currently clientside validation is not supported out of the box in the CMS.
*
* @return boolean
*/
validate: function() {
this.trigger('beforeValidate');
var isValid = true;
this.trigger('afterValidate', [isValid]);
return isValid;
},
loadNewPage: function(result) {
// TinyMCE coupling
if(typeof tinymce_removeAll != 'undefined') tinymce_removeAll();
// Rewrite # links
result = result.replace(/(<a[^>]+href *= *")#/g, '$1' + window.location.href.replace(/#.*$/,'') + '#');
// Rewrite iframe links (for IE)
result = result.replace(/(<iframe[^>]*src=")([^"]+)("[^>]*>)/g, '$1' + $('base').attr('href') + '$2$3');
// Prepare iframes for removal, otherwise we get loading bugs
this.find('iframe').each(function() {
this.contentWindow.location.href = 'about:blank';
this.remove();
})
this.html(result);
if(this.hasClass('validationerror')) {
statusMessage(ss.i18n._t('ModelAdmin.VALIDATIONERROR', 'Validation Error'), 'bad');
} else {
statusMessage(ss.i18n._t('ModelAdmin.SAVED', 'Saved'), 'good');
}
Behaviour.apply(); // refreshes ComplexTableField
// If there's a title field and it's got a "new XX" value, focus/select that first
// This is really a little too CMS-specific (as opposed to LeftAndMain), but the cleanup can happen after jQuery refactoring
if($('input#Form_EditForm_Title') && $('input#Form_EditForm_Title').value.match(/^new/i)) {
$('input#Form_EditForm_Title').select();
}
}
}});
/**
* 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.
*/
$('#Form_EditForm .Actions :submit').concrete('ss', function($){return{
onclick: function(e) {
$(this[0].form).ajaxSubmit(this);
return false;
}
}});
$('#TreeActions').concrete('ss', function($){return{
onmatch: function() {
// Setup "create", "search", "batch actions" layers above tree.
// All tab contents are closed by default.
this.tabs({
collapsible: true,
selected: null
});
}
}});
/**
* Link for editing the profile for a logged-in member
* through a popup. Required "greybox" javascript library.
*/
$('#EditMemberProfile').concrete('ss', function($){return{
onclick: function(e) {
GB_show('Edit Profile', this.attr('href'), 290, 500);
return false;
}
}});
})(jQuery);
var _AJAX_LOADING = false;
Behaviour.register({
'#MainMenu li' : {
onclick : function(event) {
return LeftAndMain_window_unload(); // Confirm if there are unsaved changes
window.location.href = this.getElementsByTagName('a')[0].href;
Event.stop(event);
}
}
});
LeftAndMain_window_unload = function() {
window.exiting = true; // this is used by prototype
if(typeof autoSave == 'function') {
return autoSave(true);
}
}
// Event.observe(window, 'beforeunload', LeftAndMain_window_unload);
/**
* Unlock the locked status message.
* Show a queued message, if one exists
*/
function unlockStatusMessage() {
statusMessage.locked = false;
if(statusMessage.queued) {
statusMessage(
statusMessage.queued.msg,
statusMessage.queued.type,
statusMessage.queued.showNetworkActivity);
statusMessage.queued = null;
}
}
/**
* Submit the given form and evaluate the Ajax response.
* Needs to be bound to an object with the following parameters to work:
* - form
* - action
* - verb
*
* The bound function can then be called, with the arguments passed
*/
function ajaxSubmitForm(automated, callAfter, form, action, verb) {
var alreadySaved = false;
if($(form).elements.length < 2) alreadySaved = true;
if(alreadySaved) {
if(callAfter) callAfter();
} else {
statusMessage(verb + '...', '', true);
var success = function(response) {
Ajax.Evaluator(response);
if(callAfter) callAfter();
}
if(callAfter) success = success.bind({callAfter : callAfter});
Ajax.SubmitForm(form, action, {
onSuccess : success,
onFailure : function(response) {
errorMessage('Error ' + verb, response);
}
});
}
return false;
};
/**
* Behaviour of the statuts message.
*/
Behaviour.register({
'#statusMessage' : {
showMessage : function(message, type, waitTime, clearManually) {
if(this.fadeTimer) {
clearTimeout(this.fadeTimer);
this.fadeTimer = null;
}
if(this.currentEffect) {
this.currentEffect.cancel();
this.currentEffect = null;
}
this.innerHTML = message;
this.className = type;
Element.setOpacity(this, 1);
//this.style.position = 'absolute';
this.style.display = '';
this.style.visibility = '';
if(!clearManually) {
this.fade(0.5,waitTime ? waitTime : 5);
}
},
clearMessage : function(waitTime) {
this.fade(0.5, waitTime);
},
fade: function(fadeTime, waitTime) {
if(!fadeTime) fadeTime = 0.5;
// Wait a bit before fading
if(waitTime) {
this.fadeTimer = setTimeout((function() {
this.fade(fadeTime);
}).bind(this), waitTime * 1000);
// Fade straight away
} else {
this.currentEffect = new Effect.Opacity(this,
{ duration: 0.5,
transition: Effect.Transitions.linear,
from: 1.0, to: 0.0,
afterFinish : this.afterFade.bind(this) });
}
},
afterFade : function() {
this.style.visibility = 'hidden';
this.style.display = 'none';
this.innerHTML = '';
}
}
});
/**
* Show a status message.
*
* @param msg String
* @param type String (optional) can be 'good' or 'bad'
* @param clearManually boolean Don't automatically fade message.
* @param container custom #statusMessage element to show message.
*/
function statusMessage(msg, type, clearManually, container) {
var statusMessageEl = $('statusMessage');
if(container != null) statusMessageEl = container;
if(statusMessageEl) {
if(msg) {
statusMessageEl.showMessage(msg, type, msg.length / 10, clearManually);
} else {
statusMessageEl.clearMessage();
}
}
}
function clearStatusMessage() {
$('statusMessage').clearMessage();
}
/**
* Called when something goes wrong
*/
function errorMessage(msg, fullMessage) {
// Show complex error for developers in the console
if(fullMessage) {
// Get the message from an Ajax response object
try {
if(typeof fullMessage == 'object') fullMessage = fullMessage.status + '//' + fullMessage.responseText;
} catch(er) {
fullMessage = "";
}
console.error(fullMessage);
}
msg = msg.replace(/\n/g,'<br>');
$('statusMessage').showMessage(msg,'bad');
}
function ajaxErrorHandler(response) {
errorMessage('Server Error', response);
}
/**
* Applying StatusTitle to an element will mean that the title attribute is shown as a statusmessage
* upon hover
*/
/* Commenting out because on IE6, IE7, and Safari 3, the statusmessage becomes
* 'null' on 2nd hover and because there is not room for long titles when
* action buttons are on the same line.
StatusTitle = Class.create();
StatusTitle.prototype = {
onmouseover : function() {
if(this.title) {
this.message = this.title;
this.title = null;
}
if(this.message) {
$('statusMessage').showMessage(this.message);
}
},
onmouseout : function() {
if(this.message) {
$('statusMessage').fade(0.3,1);
}
}
}
*/
/**
* ChangeTracker is a class that can be applied to forms to support change tracking on forms.
*/
ChangeTracker = Class.create();
ChangeTracker.prototype = {
initialize: function() {
this.resetElements();
},
/**
* Reset all the 'changed field' data.
*/
resetElements: function(debug) {
var elements = Form.getElements(this);
var i, element;
for(i=0;element=elements[i];i++) {
// Initialise each element
if(element.resetChanged) {
element.resetChanged();
} else {
element.originalSerialized = Form.Element.serialize(element);
}
}
},
field_changed: function() {
// Something a value will go from 'undefined' to ''. Ignore such changes
if((this.originalSerialized+'') == 'undefined') return Form.Element.serialize(this) ? true : false;
else return this.originalSerialized != Form.Element.serialize(this);
},
/**
* Returns true if something in the form has been changed
*/
isChanged: function() {
var elements = Form.getElements(this);
var i, element;
for(i=0;element=elements[i];i++) {
// NOTE: TinyMCE coupling
// Ignore mce-generated elements
if(element.className.substr(0,3) == 'mce') continue;
if(!element.isChanged) element.isChanged = this.field_changed;
if(!this.changeDetection_fieldsToIgnore[element.name] && element.isChanged()) {
//console.log('Changed:'+ element.id + '(' + this.originalSerialized +')->('+Form.Element.serialize(element)+')' );
//console.log(element)
return true;
}
}
return false;
},
changeDetection_fieldsToIgnore : {
'Sort' : true
},
/**
* Serialize only the fields to change.
* You can specify the names of fields that must be included as arguments
*/
serializeChangedFields: function() {
var elements = Form.getElements(this);
var queryComponent, queryComponents = new Array();
var i, element;
var forceFields = {};
if(arguments) {for(var i=0;i<arguments.length;i++) forceFields[arguments[i]] = true;}
for(i=0;element=elements[i];i++) {
if(!element.name.match(/^action_(.+)$/i)) // For dropdown those 'action_xxx' fields.
{ if(!element.isChanged) element.isChanged = this.field_changed;
if(forceFields[element.name] || (element.isChanged()) || element.name.match(/\[.*\]/g) ) {
queryComponent = Form.Element.serialize(element);
if (queryComponent)
queryComponents.push(queryComponent);
} else {
// Used by the Sapphire code to preserve the form field value
if( element.name.match( '/\]$/' ) )
queryComponents.push(element.name.substring( 0, element.name.length - 1 ) + '_unchanged' + ']=1' );
else
queryComponents.push(element.name + '_unchanged=1');
}
}
}
//alert(queryComponents.join('&'));
return queryComponents.join('&');
},
/**
* Serialize all the fields on the page
*/
serializeAllFields: function() {
return Form.serializeWithoutButtons(this);
}
}
function hideLoading() {
if($('Loading')) $('Loading').style.display = 'none';
Element.removeClassName(document.body, 'stillLoading');
}
function baseHref() {
var baseTags = document.getElementsByTagName('base');
if(baseTags) return baseTags[0].href;
else return "";
}
returnFalse = function() {
return false;
}
showResponseAsSuccessMessage = function(response) {
statusMessage(response.responseText, 'good');
}
/**
* This function is called by prototype when it receives notification that the user was logged out.
* It redirects back to the login form.
*/
function onSessionLost() {
w = window.open('Security/login');
if(w) {
alert("Please log in and then try again");
} else {
alert("Please enable pop-ups for this site");
}
}
var _CURRENT_CONTEXT_MENU = null;
/**
* Create a new context menu
* @param event The event object
* @param owner The DOM element that this context-menu was requested from
* @param menuItems A map of title -> method; context-menu operations to get called
*/
function createContextMenu(event, owner, menuItems) {
if(_CURRENT_CONTEXT_MENU) {
document.body.removeChild(_CURRENT_CONTEXT_MENU);
_CURRENT_CONTEXT_MENU = null;
}
var menu = document.createElement("ul");
menu.className = 'contextMenu';
menu.style.position = 'absolute';
menu.style.left = event.clientX + 'px';
menu.style.top = event.clientY + 'px';
var menuItemName, menuItemTag, menuATag;
for(menuItemName in menuItems) {
menuItemTag = document.createElement("li");
menuATag = document.createElement("a");
menuATag.href = "#";
menuATag.onclick = menuATag.oncontextmenu = contextmenu_onclick;
menuATag.innerHTML = menuItemName;
menuATag.handler = menuItems[menuItemName];
menuATag.owner = owner;
menuItemTag.appendChild(menuATag);
menu.appendChild(menuItemTag);
}
document.body.appendChild(menu);
document.body.onclick = contextmenu_close;
_CURRENT_CONTEXT_MENU = menu;
return menu;
}
function contextmenu_close() {
if(_CURRENT_CONTEXT_MENU) {
document.body.removeChild(_CURRENT_CONTEXT_MENU);
_CURRENT_CONTEXT_MENU = null;
}
}
function contextmenu_onclick() {
this.handler(this.owner);
contextmenu_close();
return false;
}
/**
* Shows an ajax loading indicator.
*
* @param id String Identifier for the newly created image
* @param container ID/DOM Element
* @param imgSrc String (optional)
* @param insertionType Object (optional) Prototype-style insertion-classes, defaults to Insertion.Bottom
* @param displayType String (optional) "inline" or "block"
*/
function showIndicator(id, container, imgSrc, insertionType, displayType) {
if(!id || !$(container)) return false;
if(!imgSrc) imgSrc = "cms/images/network-save.gif";
if(!displayType) displayType = "inline";
if(!insertionType) insertionType = Insertion.Bottom;
if(!$(id)) {
var html = '<img src="' + imgSrc + '" class="indicator ' + displayType + '" id="' + id + '" style="display: none" />';
new insertionType(container, html);
}
Effect.Appear(id);
}
function hideIndicator(id) {
Effect.Fade(id, {duration: 0.3});
}
/**
* Find and enable TinyMCE on all htmleditor fields
* Pulled in from old tinymce.template.js
*/
function nullConverter(url) {
return url;
}
Behaviour.register({
'textarea.htmleditor' : {
initialize : function() {
tinyMCE.execCommand("mceAddControl", true, this.id);
this.isChanged = function() {
return tinyMCE.getInstanceById(this.id).isDirty();
}
this.resetChanged = function() {
inst = tinyMCE.getInstanceById(this.id);
if (inst) inst.startContent = tinymce.trim(inst.getContent({format : 'raw', no_events : 1}));
}
}
}
});