diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php index 7bd961fbc..d3548e44c 100755 --- a/admin/code/LeftAndMain.php +++ b/admin/code/LeftAndMain.php @@ -377,17 +377,20 @@ class LeftAndMain extends Controller { if(!$title) $title = preg_replace('/Admin$/', '', $class); return $title; } - + public function show($request) { // TODO Necessary for TableListField URLs to work properly if($request->param('ID')) $this->setCurrentPageID($request->param('ID')); if($this->isAjax()) { - SSViewer::setOption('rewriteHashlinks', false); - $form = $this->getEditForm($request->param('ID')); - $content = $form->formHtmlContent(); + if($request->getVar('cms-view-form')) { + $form = $this->getEditForm(); + $content = $form->forTemplate(); + } else { + // Rendering is handled by template, which will call EditForm() eventually + $content = $this->renderWith($this->getTemplatesWithSuffix('_Content')); + } } else { - // Rendering is handled by template, which will call EditForm() eventually $content = $this->renderWith($this->getViewer('show')); } @@ -636,7 +639,7 @@ class LeftAndMain extends Controller { // write process might've changed the record, so we reload before returning $form = $this->getEditForm($record->ID); - return $form->formHtmlContent(); + return $form->forTemplate(); } public function delete($data, $form) { @@ -648,7 +651,7 @@ class LeftAndMain extends Controller { $record->delete(); if($this->isAjax()) { - return $this->EmptyForm()->formHtmlContent(); + return $this->EmptyForm()->forTemplate(); } else { $this->redirectBack(); } @@ -954,7 +957,7 @@ class LeftAndMain extends Controller { return $record->ID; } else if($this->isAjax()) { $form = $this->getEditForm($record->ID); - return $form->formHtmlContent(); + return $form->forTemplate(); } else { return $this->redirect(Controller::join_links($this->Link('show'), $record->ID)); } diff --git a/admin/code/ModelAdmin.php b/admin/code/ModelAdmin.php index dae73578b..2ac480841 100755 --- a/admin/code/ModelAdmin.php +++ b/admin/code/ModelAdmin.php @@ -664,7 +664,7 @@ class ModelAdmin_CollectionController extends Controller { $msg = _t('ModelAdmin.NORESULTS',"Your search didn't return any matching items"); } return new SS_HTTPResponse( - $resultsForm->formHtmlContent(), + $resultsForm->forTemplate(), 200, $msg ); @@ -794,7 +794,7 @@ class ModelAdmin_CollectionController extends Controller { */ function add($request) { return new SS_HTTPResponse( - $this->AddForm()->formHtmlContent(), + $this->AddForm()->forTemplate(), 200, sprintf( _t('ModelAdmin.ADDFORM', "Fill out this form to add a %s to the database."), @@ -845,6 +845,7 @@ class ModelAdmin_CollectionController extends Controller { $form = new Form($this, "AddForm", $fields, $actions, $validator); $form->loadDataFrom($newRecord); + $form->addExtraClass('cms-edit-form'); return $form; } @@ -862,7 +863,7 @@ class ModelAdmin_CollectionController extends Controller { $class = $this->parentController->getRecordControllerClass($this->getModelClass()); $recordController = new $class($this, $request, $model->ID); return new SS_HTTPResponse( - $recordController->EditForm()->formHtmlContent(), + $recordController->EditForm()->forTemplate(), 200, sprintf( _t('ModelAdmin.LOADEDFOREDITING', "Loaded '%s' for editing."), @@ -912,7 +913,7 @@ class ModelAdmin_RecordController extends Controller { function edit($request) { if ($this->currentRecord) { if($this->isAjax()) { - $this->response->setBody($this->EditForm()->formHtmlContent()); + $this->response->setBody($this->EditForm()->forTemplate()); $this->response->setStatusCode( 200, sprintf( @@ -924,10 +925,10 @@ class ModelAdmin_RecordController extends Controller { } else { // This is really quite ugly; to fix will require a change in the way that customise() works. :-( return $this->parentController->parentController->customise(array( - 'Right' => $this->parentController->parentController->customise(array( + 'Content' => $this->parentController->parentController->customise(array( 'EditForm' => $this->EditForm() - ))->renderWith(array("{$this->class}_right",'LeftAndMain_right')) - ))->renderWith(array('ModelAdmin','LeftAndMain')); + ))->renderWith(array("{$this->class}_Content",'ModelAdmin_Content', 'LeftAndMain_Content')) + ))->renderWith(array('ModelAdmin', 'LeftAndMain')); } } else { return _t('ModelAdmin.ITEMNOTFOUND', "I can't find that item"); @@ -963,6 +964,7 @@ class ModelAdmin_RecordController extends Controller { $form = new Form($this, "EditForm", $fields, $actions, $validator); $form->loadDataFrom($this->currentRecord); + $form->addExtraClass('cms-edit-form'); return $form; } @@ -1017,7 +1019,7 @@ class ModelAdmin_RecordController extends Controller { function view($request) { if($this->currentRecord) { $form = $this->ViewForm(); - return $form->formHtmlContent(); + return $form->forTemplate(); } else { return _t('ModelAdmin.ITEMNOTFOUND'); } diff --git a/admin/css/layout.css b/admin/css/layout.css index de4a5a1c4..773c6cc08 100755 --- a/admin/css/layout.css +++ b/admin/css/layout.css @@ -27,8 +27,8 @@ html, body { width: 100%; height: 100%; padding: 0; margin: 0; overflow: hidden; .cms-menu .cms-panel-content { width: 250px; } .cms-menu.collapsed { width: 40px; } -.cms-preview, .cms-menu, .cms-content, .cms-content-header, .cms-content-tools, .cms-content-form { display: -moz-inline-box; -moz-box-orient: vertical; display: inline-block; vertical-align: middle; *vertical-align: auto; } -.cms-preview, .cms-menu, .cms-content, .cms-content-header, .cms-content-tools, .cms-content-form { *display: inline; } +.cms-preview, .cms-menu, .cms-content, .cms-content-header, .cms-content-tools, .cms-content-form, .cms-edit-form { display: -moz-inline-box; -moz-box-orient: vertical; display: inline-block; vertical-align: middle; *vertical-align: auto; } +.cms-preview, .cms-menu, .cms-content, .cms-content-header, .cms-content-tools, .cms-content-form, .cms-edit-form { *display: inline; } .cms-content-tools { width: 230px; overflow: auto; } .cms-content-tools .cms-panel-header, .cms-content-tools .cms-panel-content { padding: 10px; } diff --git a/admin/css/screen.css b/admin/css/screen.css index 63707e33c..184bd9165 100755 --- a/admin/css/screen.css +++ b/admin/css/screen.css @@ -144,7 +144,7 @@ li.jstree-closed > ul { display: none; } .cms-menu-list li { background-color: #b0bfc6; background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #b0bfc6), color-stop(100%, #758f9b)); background-image: -moz-linear-gradient(top, #b0bfc6 0%, #758f9b 100%); background-image: linear-gradient(top, #b0bfc6 0%, #758f9b 100%); border-bottom: 1px solid #aaaaaa; } .cms-menu-list li a { display: block; height: 32px; vertical-align: middle; font-size: 14px; text-shadow: #aaaaaa 1px 1px 1px; color: #333333; padding: 5px; } -.cms-menu-list li a .icon { display: block; float: left; margin-right: 5px; background: url('../images/icons-32.png?1305837920') no-repeat; width: 32px; height: 32px; overflow: hidden; background-position: 0px 0px; } +.cms-menu-list li a .icon { display: block; float: left; margin-right: 5px; background: url('../images/icons-32.png?1306441269') no-repeat; width: 32px; height: 32px; overflow: hidden; background-position: 0px 0px; } .cms-menu-list li a:hover .icon { background-position: -32px 0px; } .cms-menu-list li a .text { display: block; padding-top: 10px; } .cms-menu-list li.current { background-color: #338dc1; background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(0%, #338dc1), color-stop(100%, #1e5270)); background-image: -moz-linear-gradient(top, #338dc1 0%, #1e5270 100%); background-image: linear-gradient(top, #338dc1 0%, #1e5270 100%); } diff --git a/admin/javascript/LeftAndMain.AddForm.js b/admin/javascript/LeftAndMain.AddForm.js index 896f0e47b..8f9176cf8 100755 --- a/admin/javascript/LeftAndMain.AddForm.js +++ b/admin/javascript/LeftAndMain.AddForm.js @@ -91,8 +91,9 @@ data.push({name:button.attr('name'),value:button.val()}); // TODO Should be set by hiddenfield already - jQuery('.cms-edit-form').entwine('ss').loadForm( + jQuery('.cms-content').entwine('ss').loadForm( this.attr('action'), + null, function() { // Tree updates are triggered by Form_EditForm load events button.removeClass('loading'); @@ -129,7 +130,7 @@ dropdown.find('option').remove(); //Use tree hints to find allowed children for this node - if (className && typeof siteTreeHints !== 'undefined') { + if (className && siteTreeHints) { disallowed = siteTreeHints[className].disallowedChildren; } diff --git a/admin/javascript/LeftAndMain.Content.js b/admin/javascript/LeftAndMain.Content.js index 529dedd87..896eda0af 100644 --- a/admin/javascript/LeftAndMain.Content.js +++ b/admin/javascript/LeftAndMain.Content.js @@ -4,36 +4,228 @@ $('.LeftAndMain .cms-content').entwine({ - redraw: function() { + onmatch: function() { + var self = this; + + // Listen to tree selection events + $('.cms-tree').bind('select_node.jstree', function(e, data) { + var node = data.rslt.obj, loadedNodeID = self.find(':input[name=ID]').val() + + // Don't allow checking disabled nodes + if($(node).hasClass('disabled')) return false; + + // Don't allow reloading of currently selected node, + // mainly to avoid doing an ajax request on initial page load + if($(node).data('id') == loadedNodeID) return; + + var url = $(node).find('a:first').attr('href'); + if(url && url != '#') { + var xmlhttp = self.loadForm( + url, + null, + function(response) {} + ); + } else { + self.removeForm(); + } + }); + + this._super(); + }, + + beforeLoad: function(url) { + this.addClass('loading'); + this.cleanup(); }, cleanup: function() { this.empty(); }, - - loadPanel: function(url, callback, ajaxOptions) { + + /** + * Function: loadForm + * + * Parameters: + * (String) url - .. + * (Function) callback - (Optional) Called after the form content as been loaded + * (Object) ajaxOptions - Object literal merged into the jQuery.ajax() call (Optional) + * + * Returns: + * (XMLHTTPRequest) + */ + loadForm: function(url, form, callback, ajaxOptions) { var self = this; + if(!form || !form.length) var form = $('.cms-content-form form:first'); - this.trigger('load', {url: url, args: arguments}); + // Alert when unsaved changes are present + if(form._checkChangeTracker(true) == false) return false; + + // hide existing form - shown again through _loadResponse() + form.addClass('loading'); - this.cleanup(); + this.trigger('loadform', {form: form, url: url}); + + form.cleanup(); - // TODO Add browser history support - return $.ajax($.extend({ + return jQuery.ajax(jQuery.extend({ url: url, + // Ensure that form view is loaded (rather than whole "Content" template) + data: {'cms-view-form': 1}, complete: function(xmlhttp, status) { - self.loadPanel_onSuccess(xmlhttp.responseText, status, xmlhttp); - self.removeClass('loading'); - + self.loadForm_responseHandler(form, xmlhttp.responseText, status, xmlhttp); if(callback) callback.apply(self, arguments); }, dataType: 'html' }, ajaxOptions)); }, + + loadForm_responseHandler: function(oldForm, html, status, xmlhttp) { + oldForm.replaceWith(html); // triggers onmatch() on form + + // set status message based on response + var _statusMessage = (xmlhttp.getResponseHeader('X-Status')) ? xmlhttp.getResponseHeader('X-Status') : xmlhttp.statusText; + }, + + /** + * Function: ajaxSubmit + * + * Parameters: + * {DOMElement} button - The pressed button (optional) + * {Function} callback - Called in complete() handler of jQuery.ajax() + * {Object} ajaxOptions - Object literal to merge into $.ajax() call + * {boolean} loadResponse - Render response through _loadResponse() (Default: true) + * + * Returns: + * (boolean) + */ + submitForm: function(form, button, callback, ajaxOptions, loadResponse) { + var self = this; + + // look for save button + if(!button) button = this.find('.Actions :submit[name=action_save]'); + // default to first button if none given - simulates browser behaviour + if(!button) button = this.find('.Actions :submit:first'); + + this.trigger('submitform', {form: form, button: button}); + + // set button to "submitting" state + $(button).addClass('loading'); + + // @todo TinyMCE coupling + if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave(); + + // validate if required + if(!form.validate()) { + // TODO Automatically switch to the tab/position of the first error + statusMessage("Validation failed.", "bad"); - loadPanel_onSuccess: function(html, status, xmlhttp) { - this.html(html); - this.redraw(); + $(button).removeClass('loading'); + + return false; + } + + // save tab selections in order to reconstruct them later + var selectedTabs = []; + form.find('.ss-tabset').each(function(i, el) { + if($(el).attr('id')) selectedTabs.push({id:$(el).attr('id'), selected:$(el).tabs('option', 'selected')}); + }); + + // get all data from the form + var formData = form.serializeArray(); + // add button action + formData.push({name: $(button).attr('name'), value:'1'}); + jQuery.ajax(jQuery.extend({ + url: form.attr('action'), + data: formData, + type: 'POST', + complete: function(xmlhttp, status) { + $(button).removeClass('loading'); + + // TODO This should be using the plugin API + form.removeClass('changed'); + + if(callback) callback(xmlhttp, status); + + // pass along original form data to enable old/new comparisons + if(loadResponse !== false) { + self.submitForm_responseHandler(form, xmlhttp.responseText, status, xmlhttp, formData); + } + + // Re-init tabs (in case the form tag itself is a tabset) + if(self.hasClass('ss-tabset')) self.removeClass('ss-tabset').addClass('ss-tabset'); + + // re-select previously saved tabs + $.each(selectedTabs, function(i, selectedTab) { + form.find('#' + selectedTab.id).tabs('select', selectedTab.selected); + }); + }, + dataType: 'html' + }, ajaxOptions)); + + return false; + }, + + /** + * Function: _loadResponse + * + * Parameters: + * {String} data - Either HTML for straight insertion, or eval'ed JavaScript. + * If passed as HTML, it is assumed that everying inside the