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);
}
/**
* 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.
*
@ -427,6 +439,7 @@ class ModelAdmin_CollectionController extends Controller {
$model = singleton($this->modelClass);
$source = $model->summaryFields();
$value = array();
if($source) foreach ($source as $fieldName => $label){
$value[] = $fieldName;
}
@ -474,8 +487,17 @@ class ModelAdmin_CollectionController extends Controller {
* @return string
*/
function search($request, $form) {
// Get the results form to be rendered
$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
*/
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) {
if ($this->currentRecord) {
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 {
// This is really quite ugly; to fix will require a change in the way that customise() works. :-(
return $this->parentController->parentController->customise(array(

View File

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

View File

@ -75,6 +75,11 @@ li.action button {
li.action button:hover {
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,
button.delete:hover,

View File

@ -8,15 +8,39 @@
* @todo add live query to manage application of events to DOM refreshes
* @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
*/
jQuery('ul.tabstrip').tabs();
$('ul.tabstrip').tabs();
/*
* Highlight buttons on click
*/
$('input[type=submit]').click(function() {
$(this).addClass('loading');
});
//////////////////////////////////////////////////////////////////
// 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.
@ -29,45 +53,47 @@ jQuery(document).ready(function() {
*
* @todo use livequery to manage ResultTable click handlers
*/
jQuery('#SearchForm_holder .tab form').submit(function(){
__lastSearch = jQuery(this);
form = jQuery(this);
data = formData(form);
jQuery.get(form.attr('action'), data, function(result){
jQuery('#right #ModelAdminPanel').html(result);
jQuery('#form_actions_right').remove();
Behaviour.apply();
});
return false;
$('#SearchForm_holder .tab form').submit(function () {
var $form = $(this);
$('#ModelAdminPanel').load($(this).attr('action'), $(this).formToArray(), standardStatusHandler(function(result) {
__lastSearch = $form;
$('#form_actions_right').remove();
Behaviour.apply();
// 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
*/
jQuery('#SearchForm_holder button[name=action_clearsearch]').click(function(e) {
jQuery(this.form).clearForm();
$('#SearchForm_holder button[name=action_clearsearch]').click(function(e) {
$(this.form).clearForm();
return false;
});
/**
* Column selection in search form
*/
jQuery('a.form_frontend_function.toggle_result_assembly').click(function(){
var toggleElement = jQuery(this).next();
$('a.form_frontend_function.toggle_result_assembly').click(function(){
var toggleElement = $(this).next();
toggleElement.toggle();
return false;
});
jQuery('a.form_frontend_function.tick_all_result_assembly').click(function(){
var resultAssembly = jQuery('div#ResultAssembly ul li input');
$('a.form_frontend_function.tick_all_result_assembly').click(function(){
var resultAssembly = $('div#ResultAssembly ul li input');
resultAssembly.attr('checked', 'checked');
return false;
});
jQuery('a.form_frontend_function.untick_all_result_assembly').click(function(){
var resultAssembly = jQuery('div#ResultAssembly ul li input');
$('a.form_frontend_function.untick_all_result_assembly').click(function(){
var resultAssembly = $('div#ResultAssembly ul li input');
resultAssembly.removeAttr('checked');
return false;
});
@ -79,18 +105,19 @@ jQuery(document).ready(function() {
/**
* Table record handler for search result record
*/
jQuery('#right #Form_ResultsForm tbody td a')
$('#right #Form_ResultsForm tbody td a')
.livequery('click', function(){
var el = jQuery(this);
$(this).parent().parent().addClass('loading');
var el = $(this);
showRecord(el.attr('href'));
return false;
})
.hover(
function(){
jQuery(this).addClass('over').siblings().addClass('over')
$(this).addClass('over').siblings().addClass('over')
},
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
*/
jQuery('#Form_EditForm_action_goBack').livequery('click', function() {
$('#Form_EditForm_action_goBack').livequery('click', function() {
$(this).addClass('loading');
if(__lastSearch) __lastSearch.trigger('submit');
return false;
});
@ -109,20 +138,22 @@ jQuery(document).ready(function() {
/**
* RHS panel Save button
*/
jQuery('#right #form_actions_right input[name=action_doSave]').livequery('click', function(){
var form = jQuery('#right form');
var formAction = form.attr('action') + '?' + jQuery(this).fieldSerialize();
$('#right #form_actions_right input[name=action_doSave]').livequery('click', function(){
$(this).addClass('loading');
var form = $('#right form');
var formAction = form.attr('action') + '?' + $(this).fieldSerialize();
// Post the data to save
jQuery.post(formAction, formData(form), function(result){
jQuery('#right #ModelAdminPanel').html(result);
$.post(formAction, formData(form), function(result){
$('#right #ModelAdminPanel').html(result);
statusMessage("Saved");
// 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
jQuery('#right ul.tabstrip').tabs();
$('#right ul.tabstrip').tabs();
});
return false;
@ -131,24 +162,26 @@ jQuery(document).ready(function() {
/**
* 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?");
if(!confirmed) return false;
var form = jQuery('#right form');
var formAction = form.attr('action') + '?' + jQuery(this).fieldSerialize();
$(this).addClass('loading');
var form = $('#right form');
var formAction = form.attr('action') + '?' + $(this).fieldSerialize();
// 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.
jQuery('#right #ModelAdminPanel').html(result);
$('#right #ModelAdminPanel').html(result);
statusMessage("Deleted");
$('#form_actions_right').remove();
// 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
jQuery('#right ul.tabstrip').tabs();
$('#right ul.tabstrip').tabs();
});
return false;
@ -162,9 +195,9 @@ jQuery(document).ready(function() {
/**
* Add object button
*/
jQuery('#Form_ManagedModelsSelect').submit(function(){
className = jQuery('select option:selected', this).val();
requestPath = jQuery(this).attr('action').replace('ManagedModelsSelect', className + '/add');
$('#Form_ManagedModelsSelect').submit(function(){
className = $('select option:selected', this).val();
requestPath = $(this).attr('action').replace('ManagedModelsSelect', className + '/add');
showRecord(requestPath);
return false;
});
@ -172,9 +205,9 @@ jQuery(document).ready(function() {
/**
* Toggle import specifications
*/
jQuery('#Form_ImportForm_holder .spec .details').hide();
jQuery('#Form_ImportForm_holder .spec a.detailsLink').click(function() {
jQuery('#' + jQuery(this).attr('href').replace(/.*#/,'')).toggle();
$('#Form_ImportForm_holder .spec .details').hide();
$('#Form_ImportForm_holder .spec a.detailsLink').click(function() {
$('#' + $(this).attr('href').replace(/.*#/,'')).toggle();
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?
*/
function showRecord(uri) {
jQuery('#right #ModelAdminPanel').load(uri, standardStatusHandler(function(result) {
jQuery('#SearchForm_holder').tabs();
$('#right #ModelAdminPanel').load(uri, standardStatusHandler(function(result) {
$('#SearchForm_holder').tabs();
Behaviour.apply(); // refreshes ComplexTableField
jQuery('#right ul.tabstrip').tabs();
$('#right ul.tabstrip').tabs();
}));
}
@ -200,8 +233,8 @@ jQuery(document).ready(function() {
*/
function formData(scope) {
var data = {};
jQuery('*[name]', scope).each(function(){
var t = jQuery(this);
$('*[name]', scope).each(function(){
var t = $(this);
if(t.attr('type') != 'checkbox' || t.attr('checked') == true) {
data[t.attr('name')] = t.val();
}
@ -220,18 +253,26 @@ jQuery(document).ready(function() {
* Pass it as this:
* standardStatusHandler(function(response) { ... })
*/
function standardStatusHandler(callback) {
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");
callback(response, status, xhr);
$(this).each(callback, [response, status, xhr]);
} else {
statusMessage(xhr.statusText, "bad");
if(failureCallback) $(this).each(failureCallback, [response, status, xhr]);
}
}
}
});
})
})(jQuery);
/**
* @todo Terrible HACK, but thats the cms UI...

View File

@ -1,15 +1,29 @@
<div id="LeftPane">
<h2><% _t('SEARCHLISTINGS','Search Listings') %></h2>
<div id="SearchForm_holder" class="leftbottom">
<% if SearchClassSelector = tabs %>
<ul class="tabstrip">
<% 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 %>
</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 %>
<div class="tab" id="{$Form.Name}_$ClassName">
$Form
</div>
<div class="tab" id="{$Form.Name}_$ClassName">
$Form
</div>
<% end_control %>
</div>
<h2><% _t('ADDLISTING','Add Listing') %></h2>