From df7713f894361a8e70b127fd1446af04944ba466 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Sat, 21 Nov 2009 02:36:00 +0000 Subject: [PATCH] ENHANCEMENT Initial 'concrete' implementation of CMS forms incl. ajax saving, validation placeholders and session-pinging MINOR Removed 'innerLayout' from CMSMain.js javascript git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/cms/trunk@92599 467b73ca-7a2a-4603-9d3b-597d59a354a9 --- javascript/CMSMain.js | 120 ++++++++++---------- javascript/LeftAndMain.js | 226 +++++++++++++++++++++++++++++++++++++- 2 files changed, 284 insertions(+), 62 deletions(-) diff --git a/javascript/CMSMain.js b/javascript/CMSMain.js index 0d98ad59..df71e8f7 100644 --- a/javascript/CMSMain.js +++ b/javascript/CMSMain.js @@ -1,66 +1,68 @@ var outerLayout; -var innerLayout; -jQuery(document).ready(function () { - - // layout containing the tree, CMS menu, the main form etc. - outerLayout = jQuery('body').layout({ - defaults: { - // TODO Reactivate once we have localized values - togglerTip_open: '', - togglerTip_closed: '', - resizerTip: '', - sliderTip: '' +(function($) { + $('body.CMSMain').concrete({ss:{cmsMain:{ + mainLayout: null, + + onmatch: function() { + this.mainLayout = this.ss().cmsMain()._setupLayout(); }, - // contains CMSMenu - north: { - slidable: false, - resizable: false, - size: 35, - togglerLength_open: 0 - }, - // "Page view", "profile" and "logout" links - south: { - slidable: false, - resizable: false, - size: 20, - togglerLength_open: 0 - }, - // "Insert link" etc. - east: { - initClosed: true, - fxName: "none" - }, - // Tree, page version history - west: { - size: 250, - onresize: function () { jQuery("#treepanes").accordion("resize"); }, - onopen: function () { jQuery("#treepanes").accordion("resize"); }, - fxName: "none" - }, - // Page forms - center: { - onresize: "innerLayout.resizeAll" + + /** + * Initialize jQuery layout manager with the following panes: + * - east: Tree, Page Version History, Site Reports + * - center: Form + * - west: "Insert Image", "Insert Link", "Insert Flash" panes + * - north: CMS area menu bar + * - south: "Page view", "profile" and "logout" links + */ + _setupLayout: function() { + // layout containing the tree, CMS menu, the main form etc. + var layout = $('body').layout({ + defaults: { + // TODO Reactivate once we have localized values + togglerTip_open: '', + togglerTip_closed: '', + resizerTip: '', + sliderTip: '' + }, + north: { + slidable: false, + resizable: false, + size: 35, + togglerLength_open: 0 + }, + south: { + slidable: false, + resizable: false, + size: 20, + togglerLength_open: 0 + }, + east: { + initClosed: true, + fxName: "none" + }, + west: { + size: 250, + onresize: function () { $("#treepanes").accordion("resize"); }, + onopen: function () { $("#treepanes").accordion("resize"); }, + fxName: "none" + }, + center: {} + }); + + // Adjust tree accordion etc. in left panel to work correctly + // with jQuery.layout (see http://layout.jquery-dev.net/tips.html#Widget_Accordion) + this.find("#treepanes").accordion({ + fillSpace: true, + animated: false + }); + + return layout; } - }); + }}}); - // Layout for the form and its buttons - innerLayout = jQuery('#right').layout({ - center: {}, - south: { - slidable: false, - resizable: false, - size: 30, - togglerLength_open: 0 - } + $('#Form_EditForm').concrete({ }) - - // Adjust tree accordion etc. in left panel to work correctly - // with jQuery.layout (see http://layout.jquery-dev.net/tips.html#Widget_Accordion) - jQuery("#treepanes").accordion({ - fillSpace: true, - animated: false - }); - -}); \ No newline at end of file +})(jQuery); \ No newline at end of file diff --git a/javascript/LeftAndMain.js b/javascript/LeftAndMain.js index fda9d5ba..24a769bd 100644 --- a/javascript/LeftAndMain.js +++ b/javascript/LeftAndMain.js @@ -1,3 +1,225 @@ +(function($) { + + /** + * Main LeftAndMain interface with some control + * panel and an edit form. + * + * Events: + * - beforeSave + * - afterSave + * - beforeValidate + * - afterValidate + */ + $('.LeftAndMain').concrete({ss:{leftAndMain:{ + onmatch: function() { + this.ss().leftAndMain()._setupPinging(); + this.ss().leftAndMain()._setupButtons(); + }, + + _setupPinging: function() { + var pingIntervalSeconds = 5*60; + + // setup pinging for login expiry + setInterval(function() { + $.get("Security/ping"); + }, 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'); + }); + }); + } + }}}); + + /** + * 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:{ + onmatch: function() { + var $this = this; + // artificially delay the resize event 200ms + // to avoid overlapping height changes in different onresize() methods + $(window).resize(function () { + var timerID = "timerLayout_"+this.id; + if (window[timerID]) clearTimeout(window[timerID]); + window[timerID] = setTimeout(function() {$this.ss()._resizeChildren();}, 200); + }).trigger('resize'); + + // trigger resize whenever new tabs are shown + // @todo This is called multiple times when tabs are loaded + this.find('.ss-tabset').bind('tabsshow', function() {$this.ss()._resizeChildren();}); + }, + + /** + * Suppress submission unless it is handled through save() + */ + 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.ss().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.ss().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(/(]+href *= *")#/g, '$1' + window.location.href.replace(/#.*$/,'') + '#'); + + // Rewrite iframe links (for IE) + result = result.replace(/(]*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(); + } + }, + + /** + * Resize elements in center panel + * to fit the boundary box provided by the layout manager + */ + _resizeChildren: function() { + this.fitHeightToParent(); + $('fieldset', this).fitHeightToParent(); + // Order of resizing is important: Outer to inner + // TODO Only supports two levels of tabs at the moment + $('fieldset > .ss-tabset', this).fitHeightToParent(); + $('fieldset > .ss-tabset > .tab', this).fitHeightToParent(); + $('fieldset > .ss-tabset > .tab > .ss-tabset', this).fitHeightToParent(); + $('fieldset > .ss-tabset > .tab > .ss-tabset > .tab', this).fitHeightToParent(); + } + }}); + + $('#Form_EditForm .Actions :submit').concrete({ss:{ + onclick: function(e) { + $(this[0].form).ss().ajaxSubmit(this); + return false; + } + }}); +})(jQuery); + +jQuery(document).ready(function() { + // @todo remove + jQuery.concrete.triggerMatching(); +}); + + + + + + + + + + + + + + var _AJAX_LOADING = false; Behaviour.register({ @@ -442,9 +664,7 @@ function hideIndicator(id) { Effect.Fade(id, {duration: 0.3}); } -setInterval(function() { - new Ajax.Request("Security/ping"); -}, 180*1000); + /** * Find and enable TinyMCE on all htmleditor fields