Updated ModelAdmin.js to use $ instead of jQuery for executing jQuery items.

Added support for specifying a SearchClassSelector() method on ModelAdmin, which returns 'tabs' or 'dropdown', to enable a better UI for ModelAdmins that manage a large number of different classes.
Updated ModelAdmin's javascript to provide more status indicator (loading icons, success/failure messages), using HTTP status codes and custom status text.

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/cms/trunk@62334 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Sam Minnee 2008-09-13 04:45:30 +00:00
parent ad6ed7f1a7
commit dcb9e53ed0
5 changed files with 148 additions and 60 deletions

View File

@ -159,6 +159,18 @@ abstract class ModelAdmin extends LeftAndMain {
return new $class($this, $model); return new $class($this, $model);
} }
/**
* This method can be overloaded to specify the UI by which the search class is chosen.
*
* It can create a tab strip or a dropdown. The dropdown is useful when there are a large number of classes.
* By default, it will show a tabs for 1-3 classes, and a dropdown for 4 or more classes.
*
* @return String: 'tabs' or 'dropdown'
*/
public function SearchClassSelector() {
return sizeof($this->getManagedModels()) > 3 ? 'dropdown' : 'tabs';
}
/** /**
* Allows to choose which record needs to be created. * Allows to choose which record needs to be created.
* *
@ -427,6 +439,7 @@ class ModelAdmin_CollectionController extends Controller {
$model = singleton($this->modelClass); $model = singleton($this->modelClass);
$source = $model->summaryFields(); $source = $model->summaryFields();
$value = array();
if($source) foreach ($source as $fieldName => $label){ if($source) foreach ($source as $fieldName => $label){
$value[] = $fieldName; $value[] = $fieldName;
} }
@ -474,8 +487,17 @@ class ModelAdmin_CollectionController extends Controller {
* @return string * @return string
*/ */
function search($request, $form) { function search($request, $form) {
// Get the results form to be rendered
$resultsForm = $this->ResultsForm($form->getData()); $resultsForm = $this->ResultsForm($form->getData());
return $resultsForm->forTemplate(); // Before rendering, let's get the total number of results returned
$tableField = $resultsForm->Fields()->fieldByName($this->modelClass);
$numResults = $tableField->TotalCount();
if($numResults) {
return new HTTPResponse($resultsForm->forTemplate(), 200, "Your search found $numResults matching items");
} else {
return new HTTPResponse($resultsForm->forTemplate(), 404, "Your search didn't return any matching items");
}
} }
/** /**
@ -557,7 +579,7 @@ class ModelAdmin_CollectionController extends Controller {
* @return unknown * @return unknown
*/ */
function add($request) { function add($request) {
return $this->AddForm()->forAjaxTemplate(); return new HTTPResponse($this->AddForm()->forAjaxTemplate(), 200, "Fill out this form to add a $this->modelClass to the database.");
} }
/** /**
@ -626,7 +648,7 @@ class ModelAdmin_RecordController extends Controller {
function edit($request) { function edit($request) {
if ($this->currentRecord) { if ($this->currentRecord) {
if(Director::is_ajax()) { if(Director::is_ajax()) {
return new HTTPResponse($this->EditForm()->forAjaxTemplate(), 200, "Page loaded"); return new HTTPResponse($this->EditForm()->forAjaxTemplate(), 200, "Loaded '" . $this->currentRecord->Title . "' for editing.");
} else { } else {
// This is really quite ugly; to fix will require a change in the way that customise() works. :-( // This is really quite ugly; to fix will require a change in the way that customise() works. :-(
return $this->parentController->parentController->customise(array( return $this->parentController->parentController->customise(array(

View File

@ -31,6 +31,12 @@ body.ModelAdmin #SearchForm_holder {
margin-bottom:1em; margin-bottom:1em;
} }
body.ModelAdmin #ModelClassSelector {
border-bottom: 1px #AAA solid;
padding-bottom: 0.5em;
}
body.ModelAdmin #SearchForm_holder div.ResultAssemblyBlock{ body.ModelAdmin #SearchForm_holder div.ResultAssemblyBlock{
margin-bottom: 1em; margin-bottom: 1em;
} }

View File

@ -75,6 +75,11 @@ li.action button {
li.action button:hover { li.action button:hover {
background: #fff; background: #fff;
} }
input.action.loading, input.action.loading:hover {
padding-left: 22px;
background: url(../images/network-save.gif) 3px 2px no-repeat;
}
input.delete:hover, input.delete:hover,
button.delete:hover, button.delete:hover,

View File

@ -8,15 +8,39 @@
* @todo add live query to manage application of events to DOM refreshes * @todo add live query to manage application of events to DOM refreshes
* @todo alias the $ function instead of literal jQuery * @todo alias the $ function instead of literal jQuery
*/ */
jQuery(document).ready(function() { (function($) {
$(document).ready(function() {
/** /**
* Attach tabs plugin to the set of search filter and edit forms * Attach tabs plugin to the set of search filter and edit forms
*/ */
jQuery('ul.tabstrip').tabs(); $('ul.tabstrip').tabs();
/*
* Highlight buttons on click
*/
$('input[type=submit]').click(function() {
$(this).addClass('loading');
});
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
// Search form // Search form
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
/**
* If a dropdown is used to choose between the classes, it is handled by this code
*/
$('#ModelClassSelector select')
// Set up an onchange function to show the applicable form and hide all others
.change(function() {
var $selector = $(this);
$('option', this).each(function() {
var $form = $('#'+$(this).val());
if($selector.val() == $(this).val()) $form.show();
else $form.hide();
});
})
// Initialise the form by calling this onchange event straight away
.change();
/** /**
* Stores a jQuery reference to the last submitted search form. * Stores a jQuery reference to the last submitted search form.
@ -29,45 +53,47 @@ jQuery(document).ready(function() {
* *
* @todo use livequery to manage ResultTable click handlers * @todo use livequery to manage ResultTable click handlers
*/ */
jQuery('#SearchForm_holder .tab form').submit(function(){ $('#SearchForm_holder .tab form').submit(function () {
__lastSearch = jQuery(this); var $form = $(this);
$('#ModelAdminPanel').load($(this).attr('action'), $(this).formToArray(), standardStatusHandler(function(result) {
form = jQuery(this); __lastSearch = $form;
data = formData(form); $('#form_actions_right').remove();
jQuery.get(form.attr('action'), data, function(result){ Behaviour.apply();
jQuery('#right #ModelAdminPanel').html(result); // Remove the loading indicators from the buttons
jQuery('#form_actions_right').remove(); $('input[type=submit]', $form).removeClass('loading');
Behaviour.apply(); },
}); // Failure handler - we should still remove loading indicator
function () {
return false; $('input[type=submit]', $form).removeClass('loading');
}));
return false;
}); });
/** /**
* Clear search button * Clear search button
*/ */
jQuery('#SearchForm_holder button[name=action_clearsearch]').click(function(e) { $('#SearchForm_holder button[name=action_clearsearch]').click(function(e) {
jQuery(this.form).clearForm(); $(this.form).clearForm();
return false; return false;
}); });
/** /**
* Column selection in search form * Column selection in search form
*/ */
jQuery('a.form_frontend_function.toggle_result_assembly').click(function(){ $('a.form_frontend_function.toggle_result_assembly').click(function(){
var toggleElement = jQuery(this).next(); var toggleElement = $(this).next();
toggleElement.toggle(); toggleElement.toggle();
return false; return false;
}); });
jQuery('a.form_frontend_function.tick_all_result_assembly').click(function(){ $('a.form_frontend_function.tick_all_result_assembly').click(function(){
var resultAssembly = jQuery('div#ResultAssembly ul li input'); var resultAssembly = $('div#ResultAssembly ul li input');
resultAssembly.attr('checked', 'checked'); resultAssembly.attr('checked', 'checked');
return false; return false;
}); });
jQuery('a.form_frontend_function.untick_all_result_assembly').click(function(){ $('a.form_frontend_function.untick_all_result_assembly').click(function(){
var resultAssembly = jQuery('div#ResultAssembly ul li input'); var resultAssembly = $('div#ResultAssembly ul li input');
resultAssembly.removeAttr('checked'); resultAssembly.removeAttr('checked');
return false; return false;
}); });
@ -79,18 +105,19 @@ jQuery(document).ready(function() {
/** /**
* Table record handler for search result record * Table record handler for search result record
*/ */
jQuery('#right #Form_ResultsForm tbody td a') $('#right #Form_ResultsForm tbody td a')
.livequery('click', function(){ .livequery('click', function(){
var el = jQuery(this); $(this).parent().parent().addClass('loading');
var el = $(this);
showRecord(el.attr('href')); showRecord(el.attr('href'));
return false; return false;
}) })
.hover( .hover(
function(){ function(){
jQuery(this).addClass('over').siblings().addClass('over') $(this).addClass('over').siblings().addClass('over')
}, },
function(){ function(){
jQuery(this).removeClass('over').siblings().removeClass('over') $(this).removeClass('over').siblings().removeClass('over')
} }
); );
@ -101,7 +128,9 @@ jQuery(document).ready(function() {
/** /**
* RHS panel Back button * RHS panel Back button
*/ */
jQuery('#Form_EditForm_action_goBack').livequery('click', function() { $('#Form_EditForm_action_goBack').livequery('click', function() {
$(this).addClass('loading');
if(__lastSearch) __lastSearch.trigger('submit'); if(__lastSearch) __lastSearch.trigger('submit');
return false; return false;
}); });
@ -109,20 +138,22 @@ jQuery(document).ready(function() {
/** /**
* RHS panel Save button * RHS panel Save button
*/ */
jQuery('#right #form_actions_right input[name=action_doSave]').livequery('click', function(){ $('#right #form_actions_right input[name=action_doSave]').livequery('click', function(){
var form = jQuery('#right form'); $(this).addClass('loading');
var formAction = form.attr('action') + '?' + jQuery(this).fieldSerialize();
var form = $('#right form');
var formAction = form.attr('action') + '?' + $(this).fieldSerialize();
// Post the data to save // Post the data to save
jQuery.post(formAction, formData(form), function(result){ $.post(formAction, formData(form), function(result){
jQuery('#right #ModelAdminPanel').html(result); $('#right #ModelAdminPanel').html(result);
statusMessage("Saved"); statusMessage("Saved");
// TODO/SAM: It seems a bit of a hack to have to list all the little updaters here. // TODO/SAM: It seems a bit of a hack to have to list all the little updaters here.
// Is livequery a solution? // Is livequery a solution?
Behaviour.apply(); // refreshes ComplexTableField Behaviour.apply(); // refreshes ComplexTableField
jQuery('#right ul.tabstrip').tabs(); $('#right ul.tabstrip').tabs();
}); });
return false; return false;
@ -131,24 +162,26 @@ jQuery(document).ready(function() {
/** /**
* RHS panel Delete button * RHS panel Delete button
*/ */
jQuery('#right #form_actions_right input[name=action_doDelete]').livequery('click', function(){ $('#right #form_actions_right input[name=action_doDelete]').livequery('click', function(){
var confirmed = confirm("Do you really want to delete?"); var confirmed = confirm("Do you really want to delete?");
if(!confirmed) return false; if(!confirmed) return false;
var form = jQuery('#right form'); $(this).addClass('loading');
var formAction = form.attr('action') + '?' + jQuery(this).fieldSerialize(); var form = $('#right form');
var formAction = form.attr('action') + '?' + $(this).fieldSerialize();
// The POST actually handles the delete // The POST actually handles the delete
jQuery.post(formAction, formData(form), function(result){ $.post(formAction, formData(form), function(result){
// On success, the panel is refreshed and a status message shown. // On success, the panel is refreshed and a status message shown.
jQuery('#right #ModelAdminPanel').html(result); $('#right #ModelAdminPanel').html(result);
statusMessage("Deleted"); statusMessage("Deleted");
$('#form_actions_right').remove();
// TODO/SAM: It seems a bit of a hack to have to list all the little updaters here. // TODO/SAM: It seems a bit of a hack to have to list all the little updaters here.
// Is livequery a solution? // Is livequery a solution?
Behaviour.apply(); // refreshes ComplexTableField Behaviour.apply(); // refreshes ComplexTableField
jQuery('#right ul.tabstrip').tabs(); $('#right ul.tabstrip').tabs();
}); });
return false; return false;
@ -162,9 +195,9 @@ jQuery(document).ready(function() {
/** /**
* Add object button * Add object button
*/ */
jQuery('#Form_ManagedModelsSelect').submit(function(){ $('#Form_ManagedModelsSelect').submit(function(){
className = jQuery('select option:selected', this).val(); className = $('select option:selected', this).val();
requestPath = jQuery(this).attr('action').replace('ManagedModelsSelect', className + '/add'); requestPath = $(this).attr('action').replace('ManagedModelsSelect', className + '/add');
showRecord(requestPath); showRecord(requestPath);
return false; return false;
}); });
@ -172,9 +205,9 @@ jQuery(document).ready(function() {
/** /**
* Toggle import specifications * Toggle import specifications
*/ */
jQuery('#Form_ImportForm_holder .spec .details').hide(); $('#Form_ImportForm_holder .spec .details').hide();
jQuery('#Form_ImportForm_holder .spec a.detailsLink').click(function() { $('#Form_ImportForm_holder .spec a.detailsLink').click(function() {
jQuery('#' + jQuery(this).attr('href').replace(/.*#/,'')).toggle(); $('#' + $(this).attr('href').replace(/.*#/,'')).toggle();
return false; return false;
}); });
@ -187,10 +220,10 @@ jQuery(document).ready(function() {
* @todo Should this be turned into a method on the #Form_EditForm using effen or something? * @todo Should this be turned into a method on the #Form_EditForm using effen or something?
*/ */
function showRecord(uri) { function showRecord(uri) {
jQuery('#right #ModelAdminPanel').load(uri, standardStatusHandler(function(result) { $('#right #ModelAdminPanel').load(uri, standardStatusHandler(function(result) {
jQuery('#SearchForm_holder').tabs(); $('#SearchForm_holder').tabs();
Behaviour.apply(); // refreshes ComplexTableField Behaviour.apply(); // refreshes ComplexTableField
jQuery('#right ul.tabstrip').tabs(); $('#right ul.tabstrip').tabs();
})); }));
} }
@ -200,8 +233,8 @@ jQuery(document).ready(function() {
*/ */
function formData(scope) { function formData(scope) {
var data = {}; var data = {};
jQuery('*[name]', scope).each(function(){ $('*[name]', scope).each(function(){
var t = jQuery(this); var t = $(this);
if(t.attr('type') != 'checkbox' || t.attr('checked') == true) { if(t.attr('type') != 'checkbox' || t.attr('checked') == true) {
data[t.attr('name')] = t.val(); data[t.attr('name')] = t.val();
} }
@ -220,18 +253,26 @@ jQuery(document).ready(function() {
* Pass it as this: * Pass it as this:
* standardStatusHandler(function(response) { ... }) * standardStatusHandler(function(response) { ... })
*/ */
function standardStatusHandler(callback) { function standardStatusHandler(callback, failureCallback) {
return function(response, status, xhr) { 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') { if(status == 'success') {
statusMessage(xhr.statusText, "good"); statusMessage(xhr.statusText, "good");
callback(response, status, xhr); $(this).each(callback, [response, status, xhr]);
} else { } else {
statusMessage(xhr.statusText, "bad"); statusMessage(xhr.statusText, "bad");
if(failureCallback) $(this).each(failureCallback, [response, status, xhr]);
} }
} }
} }
}); })
})(jQuery);
/** /**
* @todo Terrible HACK, but thats the cms UI... * @todo Terrible HACK, but thats the cms UI...

View File

@ -1,15 +1,29 @@
<div id="LeftPane"> <div id="LeftPane">
<h2><% _t('SEARCHLISTINGS','Search Listings') %></h2> <h2><% _t('SEARCHLISTINGS','Search Listings') %></h2>
<div id="SearchForm_holder" class="leftbottom"> <div id="SearchForm_holder" class="leftbottom">
<% if SearchClassSelector = tabs %>
<ul class="tabstrip"> <ul class="tabstrip">
<% control SearchForms %> <% control SearchForms %>
<li class="first"><a href="#{$Form.Name}_$ClassName">$Title</a></li> <li class="$FirstLast"><a href="#{$Form.Name}_$ClassName">$Title</a></li>
<% end_control %> <% end_control %>
</ul> </ul>
<% end_if %>
<% if SearchClassSelector = dropdown %>
<p id="ModelClassSelector">
Search for:
<select>
<% control SearchForms %>
<option value="{$Form.Name}_$ClassName">$Title</option>
<% end_control %>
</select>
</p>
<% end_if %>
<% control SearchForms %> <% control SearchForms %>
<div class="tab" id="{$Form.Name}_$ClassName"> <div class="tab" id="{$Form.Name}_$ClassName">
$Form $Form
</div> </div>
<% end_control %> <% end_control %>
</div> </div>
<h2><% _t('ADDLISTING','Add Listing') %></h2> <h2><% _t('ADDLISTING','Add Listing') %></h2>