API CHANGE Adjusted ModelAdmin CSS, JS and PHP to fit with underlying LeftAndMain changes: Using jquery.concrete, removing some redundant form saving logic in ModelAdmin.js, returning <form> tags within ajax responses in ModelAdmin controllers

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/cms/trunk@92766 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2009-11-21 03:17:34 +00:00
parent 1a5ed26cd5
commit 001151a66b
5 changed files with 145 additions and 410 deletions

View File

@ -145,10 +145,8 @@ abstract class ModelAdmin extends LeftAndMain {
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery/jquery.js'); Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery/jquery.js');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-livequery/jquery.livequery.js'); Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-livequery/jquery.livequery.js');
//Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-ui/ui.core.js'); Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-ui/ui.core.js');
//Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-ui/ui.tabs.js'); Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-ui/ui.tabs.js');
Requirements::javascript(THIRDPARTY_DIR . '/jquery/plugins/form/jquery.form.js');
Requirements::javascript(THIRDPARTY_DIR . '/jquery/plugins/effen/jquery.fn.js');
Requirements::javascript(SAPPHIRE_DIR . '/javascript/jquery/jquery_improvements.js'); Requirements::javascript(SAPPHIRE_DIR . '/javascript/jquery/jquery_improvements.js');
Requirements::javascript(CMS_DIR . '/javascript/ModelAdmin.js'); Requirements::javascript(CMS_DIR . '/javascript/ModelAdmin.js');
} }
@ -606,25 +604,22 @@ class ModelAdmin_CollectionController extends Controller {
// Get the results form to be rendered // Get the results form to be rendered
$resultsForm = $this->ResultsForm(array_merge($form->getData(), $request)); $resultsForm = $this->ResultsForm(array_merge($form->getData(), $request));
// Before rendering, let's get the total number of results returned // Before rendering, let's get the total number of results returned
$tableField = $resultsForm->Fields()->fieldByName($this->modelClass); $tableField = $resultsForm->Fields()->dataFieldByName($this->modelClass);
$numResults = $tableField->TotalCount(); $numResults = $tableField->TotalCount();
if($numResults) { if($numResults) {
return new SS_HTTPResponse( $msg = sprintf(
$resultsForm->forTemplate(), _t('ModelAdmin.FOUNDRESULTS',"Your search found %s matching items"),
200, $numResults
sprintf(
_t('ModelAdmin.FOUNDRESULTS',"Your search found %s matching items"),
$numResults
)
); );
} else { } else {
return new SS_HTTPResponse( $msg = _t('ModelAdmin.NORESULTS',"Your search didn't return any matching items");
$resultsForm->forTemplate(),
200,
_t('ModelAdmin.NORESULTS',"Your search didn't return any matching items")
);
} }
return new SS_HTTPResponse(
$resultsForm->formHtmlContent(),
200,
$msg
);
} }
/** /**
@ -720,13 +715,14 @@ class ModelAdmin_CollectionController extends Controller {
$this, $this,
'ResultsForm', 'ResultsForm',
new FieldSet( new FieldSet(
new HeaderField('SearchResultsHeader',_t('ModelAdmin.SEARCHRESULTS','Search Results'), 2), new TabSet('Root',
$tf new Tab('SearchResults',
_t('ModelAdmin.SEARCHRESULTS','Search Results'),
$tf
)
)
), ),
new FieldSet( new FieldSet()
new FormAction("goBack", _t('ModelAdmin.GOBACK', "Back")),
new FormAction("goForward", _t('ModelAdmin.GOFORWARD', "Forward"))
)
); );
// Include the search criteria on the results form URL, but not dodgy variables like those below // Include the search criteria on the results form URL, but not dodgy variables like those below
@ -750,7 +746,7 @@ class ModelAdmin_CollectionController extends Controller {
*/ */
function add($request) { function add($request) {
return new SS_HTTPResponse( return new SS_HTTPResponse(
$this->AddForm()->forAjaxTemplate(), $this->AddForm()->formHtmlContent(),
200, 200,
sprintf( sprintf(
_t('ModelAdmin.ADDFORM', "Fill out this form to add a %s to the database."), _t('ModelAdmin.ADDFORM', "Fill out this form to add a %s to the database."),
@ -817,7 +813,7 @@ class ModelAdmin_CollectionController extends Controller {
if(Director::is_ajax()) { if(Director::is_ajax()) {
$recordController = new ModelAdmin_RecordController($this, $request, $model->ID); $recordController = new ModelAdmin_RecordController($this, $request, $model->ID);
return new SS_HTTPResponse( return new SS_HTTPResponse(
$recordController->EditForm()->forAjaxTemplate(), $recordController->EditForm()->formHtmlContent(),
200, 200,
sprintf( sprintf(
_t('ModelAdmin.LOADEDFOREDITING', "Loaded '%s' for editing."), _t('ModelAdmin.LOADEDFOREDITING', "Loaded '%s' for editing."),
@ -868,7 +864,7 @@ class ModelAdmin_RecordController extends Controller {
if ($this->currentRecord) { if ($this->currentRecord) {
if(Director::is_ajax()) { if(Director::is_ajax()) {
return new SS_HTTPResponse( return new SS_HTTPResponse(
$this->EditForm()->forAjaxTemplate(), $this->EditForm()->formHtmlContent(),
200, 200,
sprintf( sprintf(
_t('ModelAdmin.LOADEDFOREDITING', "Loaded '%s' for editing."), _t('ModelAdmin.LOADEDFOREDITING', "Loaded '%s' for editing."),
@ -911,8 +907,6 @@ class ModelAdmin_RecordController extends Controller {
$deleteAction->addExtraClass('delete'); $deleteAction->addExtraClass('delete');
} }
$actions->insertFirst(new FormAction("goBack", _t('ModelAdmin.GOBACK', "Back")));
$form = new Form($this, "EditForm", $fields, $actions, $validator); $form = new Form($this, "EditForm", $fields, $actions, $validator);
$form->loadDataFrom($this->currentRecord); $form->loadDataFrom($this->currentRecord);
@ -937,7 +931,7 @@ class ModelAdmin_RecordController extends Controller {
} }
// Behaviour switched on ajax. // Behaviour switched on .
if(Director::is_ajax()) { if(Director::is_ajax()) {
return $this->edit($request); return $this->edit($request);
} else { } else {
@ -968,7 +962,7 @@ class ModelAdmin_RecordController extends Controller {
function view($request) { function view($request) {
if($this->currentRecord) { if($this->currentRecord) {
$form = $this->ViewForm(); $form = $this->ViewForm();
return $form->forAjaxTemplate(); return $form->formHtmlContent();
} else { } else {
return _t('ModelAdmin.ITEMNOTFOUND'); return _t('ModelAdmin.ITEMNOTFOUND');
} }

View File

@ -3,9 +3,8 @@
clear: both; clear: both;
} }
body.ModelAdmin #left { body.ModelAdmin .ui-layout-west {
width: 270px; width: 280px;
overflow: auto;
} }
body.ModelAdmin #Form_AddForm fieldset { body.ModelAdmin #Form_AddForm fieldset {
@ -70,10 +69,6 @@ body.ModelAdmin #right{
overflow:auto !important; overflow:auto !important;
} }
body.ModelAdmin #right #ModelAdminPanel {
padding-bottom: 30px;
}
body.ModelAdmin #right table.results { body.ModelAdmin #right table.results {
margin:6px; margin:6px;
padding:0; padding:0;
@ -117,16 +112,16 @@ body.ModelAdmin #statusMessage {
/* Form */ /* Form */
#LeftPane form { body.ModelAdmin .ui-layout-west form {
border-bottom: 2px solid #ddd; border-bottom: 2px solid #ddd;
padding-bottom: 10px; padding-bottom: 10px;
} }
#LeftPane #Form_ImportForm { body.ModelAdmin .ui-layout-west #Form_ImportForm {
border-bottom: none; border-bottom: none;
} }
#LeftPane form .Actions { body.ModelAdmin .ui-layout-west form .Actions {
margin: 5px 0; margin: 5px 0;
} }
@ -137,11 +132,11 @@ body.ModelAdmin #right .ajaxActions {
bottom: 35px !important; bottom: 35px !important;
} }
#LeftPane .tab { body.ModelAdmin .ui-layout-west .tab {
padding: 7px; padding: 7px;
} }
.tab h3 { body.ModelAdmin .tab h3 {
margin: 10px 0 5px 0; margin: 10px 0 5px 0;
font-size: 12px; font-size: 12px;
} }

View File

@ -9,49 +9,7 @@
* @todo alias the $ function instead of literal jQuery * @todo alias the $ function instead of literal jQuery
*/ */
(function($) { (function($) {
$(document).ready(function() {
/**
* Generic ajax error handler
*/
$('form').livequery('ajaxError', function (XMLHttpRequest, textStatus, errorThrown) {
$('input', this).removeClass('loading');
statusMessage(ss.i18n._t('ModelAdmin.ERROR', 'Error'), 'bad');
});
/**
* Add class ajaxActions class to the parent of Add button of AddForm
* so it float to the right
*/
$('#Form_AddForm_action_doCreate').livequery(function(){
$(this).parent().addClass('ajaxActions');
});
/*
* Highlight buttons on click
*/
$('input[type=submit]').livequery('click', function() {
$(this).addClass('loading');
});
$("#right").scroll( function () {
positionActionArea();
});
$(window).resize( function() {
positionActionArea();
});
/*
* Make the status message and ajax action button fixed
*/
function positionActionArea() {
if ( $.browser.msie && $.browser.version.indexOf("6.", 0)==0 ) {
newTopValue = $("#right").scrollTop()+$(window).height()-139;
$('.ajaxActions').css('top', newTopValue);
$('#statusMessage').css('top', newTopValue);
}
}
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
// Search form // Search form
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
@ -59,329 +17,136 @@ $(document).ready(function() {
/** /**
* If a dropdown is used to choose between the classes, it is handled by this code * If a dropdown is used to choose between the classes, it is handled by this code
*/ */
$('#ModelClassSelector select') $('#ModelClassSelector select').concrete({
// Set up an onchange function to show the applicable form and hide all others onmatch: function() {
.change(function() { // Set up an onchange function to show the applicable form and hide all others
var $selector = $(this); this.bind('change', function(e) {
$('option', this).each(function() { var $selector = $(this);
var $form = $('#'+$(this).val()); this.find('option').each(function() {
if($selector.val() == $(this).val()) $form.show(); var $form = $('#'+$(this).val());
else $form.hide(); if($selector.val() == $(this).val()) $form.show();
else $form.hide();
});
}); });
})
// Initialise the form by calling this onchange event straight away // Initialise the form by calling this onchange event straight away
.change(); this.change();
}
/** });
* Stores a jQuery reference to the last submitted search form.
*/
__lastSearch = null;
/** /**
* Submits a search filter query and attaches event handlers * Submits a search filter query and attaches event handlers
* to the response table, excluding the import form because * to the response table, excluding the import form because
* file ($_FILES) submission doesn't work using AJAX * file ($_FILES) submission doesn't work using AJAX
* *
* Note: This is used for Form_CreateForm too * Note: This is used for Form_CreateForm and all Form_SearchForm_* variations
*
* @todo use livequery to manage ResultTable click handlers
*/ */
$('#SearchForm_holder .tab form:not(#Form_ImportForm)').submit(function () { $('#SearchForm_holder form').concrete({
var $form = $(this); onmatch: function() {
var self = this;
// @todo TinyMCE coupling this.bind('submit', function(e) {
jQuery('#Form_EditForm').concrete('ss').cleanup(); // Import forms are processed without ajax
if(self.is('#Form_ImportForm')) return true;
$('#ModelAdminPanel').fn('startHistory', $(this).attr('action'), $(this).formToArray()); $('#Form_EditForm').concrete('ss').loadForm(
$('#ModelAdminPanel').load( self.attr('action'),
$(this).attr('action'), null,
$(this).formToArray(), {data: self.serialize()}
standardStatusHandler(function(result) { );
if(!this.future || !this.future.length) {
$('#Form_EditForm_action_goForward, #Form_ResultsForm_action_goForward').hide();
}
if(!this.history || this.history.length <= 1) {
$('#Form_EditForm_action_goBack, #Form_ResultsForm_action_goBack').hide();
}
$('#form_actions_right').remove(); return false;
Behaviour.apply(); });
}
if(window.onresize) window.onresize();
// Remove the loading indicators from the buttons
$('input[type=submit]', $form).removeClass('loading');
},
// Failure handler - we should still remove loading indicator
function () {
$('input[type=submit]', $form).removeClass('loading');
})
);
return false;
});
/**
* Clear search button
*/
$('#SearchForm_holder button[name=action_clearsearch]').click(function(e) {
$(this.form).resetForm();
return false;
}); });
/** /**
* Column selection in search form * Column selection in search form
*/ */
$('a.form_frontend_function.toggle_result_assembly').click(function(){ $('a.form_frontend_function.toggle_result_assembly').concrete({
var toggleElement = $(this).next(); onclick: function(e) {
toggleElement.toggle(); var toggleElement = $(this).next();
return false; toggleElement.toggle();
return false;
}
}); });
$('a.form_frontend_function.tick_all_result_assembly').click(function(){ $('a.form_frontend_function.tick_all_result_assembly').concrete({
var resultAssembly = $(this).prevAll('div#ResultAssembly').find('ul li input'); onclick: function(e) {
resultAssembly.attr('checked', 'checked'); var resultAssembly = $(this).prevAll('div#ResultAssembly').find('ul li input');
return false; resultAssembly.attr('checked', 'checked');
return false;
}
}); });
$('a.form_frontend_function.untick_all_result_assembly').click(function(){ $('a.form_frontend_function.untick_all_result_assembly').concrete({
var resultAssembly = $(this).prevAll('div#ResultAssembly').find('ul li input'); onclick: function(e) {
resultAssembly.removeAttr('checked'); var resultAssembly = $(this).prevAll('div#ResultAssembly').find('ul li input');
return false; resultAssembly.removeAttr('checked');
return false;
}
}); });
//////////////////////////////////////////////////////////////////
// Results list
//////////////////////////////////////////////////////////////////
/** /**
* Table record handler for search result record * Table record handler for search result record
* @todo: Shouldn't this be part of TableListField?
*/ */
$('#right #Form_ResultsForm tbody td a:not(.deletelink,.downloadlink)') $('form[name=Form_ResultsForm] tbody td a').concrete({
.livequery('click', function(){ onmatch: function() {
$(this).parent().parent().addClass('loading'); // TODO Replace with concrete event handler
var el = $(this); this.bind('click', function(e) {
$('#ModelAdminPanel').fn('addHistory', el.attr('href')); if($(this).hasClass('deletelink downloadlink')) return true;
$('#ModelAdminPanel').fn('loadForm', el.attr('href'));
return false;
});
////////////////////////////////////////////////////////////////// $('#Form_EditForm').concrete('ss').loadForm($(this).attr('href'));
// RHS detail form return false;
////////////////////////////////////////////////////////////////// });
}
/**
* RHS panel Back button
*/
$('#Form_EditForm_action_goBack, #Form_ResultsForm_action_goBack').livequery('click', function() {
$('#ModelAdminPanel').fn('goBack');
return false;
}); });
/** /**
* RHS panel Back button * Add object button
*/ */
$('#Form_ResultsForm_action_goForward').livequery('click', function() { $('#Form_ManagedModelsSelect').concrete({
$('#ModelAdminPanel').fn('goForward'); onmatch: function() {
return false; this.bind('submit', function(){
}); className = $('select option:selected', this).val();
requestPath = $(this).attr('action').replace('ManagedModelsSelect', className + '/add');
/** var $button = $(':submit', this);
* RHS panel Save button $('#Form_EditForm').concrete('ss').loadForm(
*/ requestPath,
$('#right input[name=action_doSave],#right input[name=action_doCreate]').livequery('click', function(){ function() {
var form = $('#right form'); $button.removeClass('loading');
var formAction = form.attr('action') + '?' + $(this).fieldSerialize(); $button = null;
}
// @todo TinyMCE coupling );
if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave(); return false;
});
// Post the data to save }
$.post(formAction, form.formToArray(), function(result){
// @todo TinyMCE coupling
jQuery('#Form_EditForm').concrete('ss').cleanup();
$('#right #ModelAdminPanel').html(result);
if($('#right #ModelAdminPanel form').hasClass('validationerror')) {
statusMessage(ss.i18n._t('ModelAdmin.VALIDATIONERROR', 'Validation Error'), 'bad');
} else {
statusMessage(ss.i18n._t('ModelAdmin.SAVED', 'Saved'), 'good');
}
// TODO/SAM: It seems a bit of a hack to have to list all the little updaters here.
// Is livequery a solution?
Behaviour.apply(); // refreshes ComplexTableField
if(window.onresize) window.onresize();
}, 'html');
return false;
}); });
/** /**
* RHS panel Delete button * RHS panel Delete button
*/ */
$('#right input[name=action_doDelete]').livequery('click', function(){ $('#Form_EditForm input[name=action_doDelete]').concrete({
var confirmed = confirm(ss.i18n._t('ModelAdmin.REALLYDELETE', 'Really delete?')); onmatch: function() {
if(!confirmed) { this.bind('click', function() {
$(this).removeClass('loading') var confirmed = confirm(ss.i18n._t('ModelAdmin.REALLYDELETE', 'Really delete?'));
return false; if(!confirmed) {
$(this).removeClass('loading')
return false;
}
});
} }
var form = $('#right form');
var formAction = form.attr('action') + '?' + $(this).fieldSerialize();
// The POST actually handles the delete
$.post(formAction, form.formToArray(), function(result){
// On success, the panel is refreshed and a status message shown.
$('#right #ModelAdminPanel').html(result);
statusMessage(ss.i18n._t('ModelAdmin.DELETED', 'Successfully deleted'));
$('#form_actions_right').remove();
// To do - convert everything to jQuery so that this isn't needed
Behaviour.apply(); // refreshes ComplexTableField
});
return false;
});
//////////////////////////////////////////////////////////////////
// Import/Add form
//////////////////////////////////////////////////////////////////
/**
* Add object button
*/
$('#Form_ManagedModelsSelect').submit(function(){
className = $('select option:selected', this).val();
requestPath = $(this).attr('action').replace('ManagedModelsSelect', className + '/add');
var $button = $(':submit', this);
$('#ModelAdminPanel').fn(
'loadForm',
requestPath,
function() {
$button.removeClass('loading');
$button = null;
}
);
$('#form_actions_right').remove();
return false;
}); });
/** /**
* Toggle import specifications * Toggle import specifications
*/ */
$('.importSpec .details').hide(); $('.importSpec').concrete({
$('.importSpec a.detailsLink').click(function() { onmatch: function() {
$('#' + $(this).attr('href').replace(/.*#/,'')).toggle(); this.hide();
return false; this.find('a.detailsLink').click(function() {
}); $('#' + $(this).attr('href').replace(/.*#/,'')).toggle();
return false;
////////////////////////////////////////////////////////////////// });
// Helper functions
//////////////////////////////////////////////////////////////////
$('#ModelAdminPanel').fn({
/**
* Load a detail editing form into the main edit panel
* @todo Convert everything to jQuery so that the built-in load method can be used with this instead
*/
loadForm: function(url, successCallback) {
// @todo TinyMCE coupling
jQuery('#Form_EditForm').concrete('ss').cleanup();
$('#right #ModelAdminPanel').load(url, standardStatusHandler(function(result) {
if(typeof(successCallback) == 'function') successCallback.apply();
if(!this.future || !this.future.length) {
$('#Form_EditForm_action_goForward, #Form_ResultsForm_action_goForward').hide();
}
if(!this.history || this.history.length <= 1) {
$('#Form_EditForm_action_goBack, #Form_ResultsForm_action_goBack').hide();
}
Behaviour.apply(); // refreshes ComplexTableField
if(window.onresize) window.onresize();
}));
},
startHistory: function(url, data) {
this.history = [];
$(this).fn('addHistory', url, data);
},
/**
* Add an item to the history, to be accessed by goBack and goForward
*/
addHistory: function(url, data) {
// Combine data into URL
if(data) {
if(url.indexOf('?') == -1) url += '?' + $.param(data);
else url += '&' + $.param(data);
}
// Add to history
if(this.history == null) this.history = [];
this.history.push(url);
// Reset future
this.future = [];
},
goBack: function() {
if(this.history && this.history.length) {
if(this.future == null) this.future = [];
var currentPage = this.history.pop();
var previousPage = this.history[this.history.length-1];
this.future.push(currentPage);
$(this).fn('loadForm', previousPage);
}
},
goForward: function() {
if(this.future && this.future.length) {
if(this.future == null) this.future = [];
var nextPage = this.future.pop();
this.history.push(nextPage);
$(this).fn('loadForm', nextPage);
}
} }
}); });
/**
* Standard SilverStripe status handler for ajax responses
* It will generate a status message out of the response, and only call the callback for successful responses
*
* To use:
* Instead of passing your callback function as:
* function(response) { ... }
*
* Pass it as this:
* standardStatusHandler(function(response) { ... })
*/
function standardStatusHandler(callback, failureCallback) {
return function(response, status, xhr) {
// If the response is takne from $.ajax's complete handler, then swap the variables around
if(response.status) {
xhr = response;
response = xhr.responseText;
}
if(status == 'success') {
statusMessage(xhr.statusText, "good");
$(this).each(callback, [response, status, xhr]);
} else {
errorMessage(xhr.statusText);
if(failureCallback) $(this).each(failureCallback, [response, status, xhr]);
}
}
}
})
})(jQuery); })(jQuery);

View File

@ -1,28 +1,26 @@
<div id="LeftPane"> <div id="SearchForm_holder" class="leftbottom ss-tabset ui-layout-content">
<div id="SearchForm_holder" class="leftbottom"> <% if SearchClassSelector = tabs %>
<% if SearchClassSelector = tabs %> <ul>
<ul class="tabstrip"> <% control ModelForms %>
<li class="$FirstLast"><a href="#{$Form.Name}_$ClassName">$Title</a></li>
<% end_control %>
</ul>
<% end_if %>
<% if SearchClassSelector = dropdown %>
<p id="ModelClassSelector">
Search for:
<select>
<% control ModelForms %> <% control ModelForms %>
<li class="$FirstLast"><a href="#{$Form.Name}_$ClassName">$Title</a></li> <option value="{$Form.Name}_$ClassName">$Title</option>
<% end_control %> <% end_control %>
</ul> </select>
<% end_if %> </p>
<% end_if %>
<% if SearchClassSelector = dropdown %>
<p id="ModelClassSelector"> <% control ModelForms %>
<% _t('ModelAdmin.SEARCHFOR','Search for:') %> <div class="tab" id="{$Form.Name}_$ClassName">
<select> $Content
<% control ModelForms %> </div>
<option value="{$Form.Name}_$ClassName">$Title</option> <% end_control %>
<% end_control %>
</select>
</p>
<% end_if %>
<% control ModelForms %>
<div class="tab" id="{$Form.Name}_$ClassName">
$Content
</div>
<% end_control %>
</div>
</div> </div>

View File

@ -1,17 +0,0 @@
<div id="ModelAdminPanel">
<% if EditForm %>
$EditForm
<% else %>
<form id="Form_EditForm" action="admin?executeForm=EditForm" method="post" enctype="multipart/form-data">
<h1>$ApplicationName</h1>
<p><% sprintf(_t('WELCOME1','Welcome to %s. Please choose on one of the entries in the left pane.'),$ApplicationName) %></p>
</form>
<% end_if %>
</div>
<p id="statusMessage" style="visibility:hidden"></p>