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-livequery/jquery.livequery.js');
//Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-ui/ui.core.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 . '/thirdparty/jquery-ui/ui.core.js');
Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/jquery-ui/ui.tabs.js');
Requirements::javascript(SAPPHIRE_DIR . '/javascript/jquery/jquery_improvements.js');
Requirements::javascript(CMS_DIR . '/javascript/ModelAdmin.js');
}
@ -606,25 +604,22 @@ class ModelAdmin_CollectionController extends Controller {
// Get the results form to be rendered
$resultsForm = $this->ResultsForm(array_merge($form->getData(), $request));
// 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();
if($numResults) {
return new SS_HTTPResponse(
$resultsForm->forTemplate(),
200,
sprintf(
_t('ModelAdmin.FOUNDRESULTS',"Your search found %s matching items"),
$numResults
)
$msg = sprintf(
_t('ModelAdmin.FOUNDRESULTS',"Your search found %s matching items"),
$numResults
);
} else {
return new SS_HTTPResponse(
$resultsForm->forTemplate(),
200,
_t('ModelAdmin.NORESULTS',"Your search didn't return any matching items")
);
$msg = _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,
'ResultsForm',
new FieldSet(
new HeaderField('SearchResultsHeader',_t('ModelAdmin.SEARCHRESULTS','Search Results'), 2),
$tf
new TabSet('Root',
new Tab('SearchResults',
_t('ModelAdmin.SEARCHRESULTS','Search Results'),
$tf
)
)
),
new FieldSet(
new FormAction("goBack", _t('ModelAdmin.GOBACK', "Back")),
new FormAction("goForward", _t('ModelAdmin.GOFORWARD', "Forward"))
)
new FieldSet()
);
// 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) {
return new SS_HTTPResponse(
$this->AddForm()->forAjaxTemplate(),
$this->AddForm()->formHtmlContent(),
200,
sprintf(
_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()) {
$recordController = new ModelAdmin_RecordController($this, $request, $model->ID);
return new SS_HTTPResponse(
$recordController->EditForm()->forAjaxTemplate(),
$recordController->EditForm()->formHtmlContent(),
200,
sprintf(
_t('ModelAdmin.LOADEDFOREDITING', "Loaded '%s' for editing."),
@ -868,7 +864,7 @@ class ModelAdmin_RecordController extends Controller {
if ($this->currentRecord) {
if(Director::is_ajax()) {
return new SS_HTTPResponse(
$this->EditForm()->forAjaxTemplate(),
$this->EditForm()->formHtmlContent(),
200,
sprintf(
_t('ModelAdmin.LOADEDFOREDITING', "Loaded '%s' for editing."),
@ -911,8 +907,6 @@ class ModelAdmin_RecordController extends Controller {
$deleteAction->addExtraClass('delete');
}
$actions->insertFirst(new FormAction("goBack", _t('ModelAdmin.GOBACK', "Back")));
$form = new Form($this, "EditForm", $fields, $actions, $validator);
$form->loadDataFrom($this->currentRecord);
@ -937,7 +931,7 @@ class ModelAdmin_RecordController extends Controller {
}
// Behaviour switched on ajax.
// Behaviour switched on .
if(Director::is_ajax()) {
return $this->edit($request);
} else {
@ -968,7 +962,7 @@ class ModelAdmin_RecordController extends Controller {
function view($request) {
if($this->currentRecord) {
$form = $this->ViewForm();
return $form->forAjaxTemplate();
return $form->formHtmlContent();
} else {
return _t('ModelAdmin.ITEMNOTFOUND');
}

View File

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

View File

@ -9,49 +9,7 @@
* @todo alias the $ function instead of literal jQuery
*/
(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
//////////////////////////////////////////////////////////////////
@ -59,329 +17,136 @@ $(document).ready(function() {
/**
* 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();
$('#ModelClassSelector select').concrete({
onmatch: function() {
// Set up an onchange function to show the applicable form and hide all others
this.bind('change', function(e) {
var $selector = $(this);
this.find('option').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.
*/
__lastSearch = null;
// Initialise the form by calling this onchange event straight away
this.change();
}
});
/**
* Submits a search filter query and attaches event handlers
* to the response table, excluding the import form because
* file ($_FILES) submission doesn't work using AJAX
*
* Note: This is used for Form_CreateForm too
*
* @todo use livequery to manage ResultTable click handlers
* Note: This is used for Form_CreateForm and all Form_SearchForm_* variations
*/
$('#SearchForm_holder .tab form:not(#Form_ImportForm)').submit(function () {
var $form = $(this);
// @todo TinyMCE coupling
jQuery('#Form_EditForm').concrete('ss').cleanup();
$('#SearchForm_holder form').concrete({
onmatch: function() {
var self = this;
this.bind('submit', function(e) {
// Import forms are processed without ajax
if(self.is('#Form_ImportForm')) return true;
$('#ModelAdminPanel').fn('startHistory', $(this).attr('action'), $(this).formToArray());
$('#ModelAdminPanel').load(
$(this).attr('action'),
$(this).formToArray(),
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_EditForm').concrete('ss').loadForm(
self.attr('action'),
null,
{data: self.serialize()}
);
$('#form_actions_right').remove();
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;
return false;
});
}
});
/**
* Column selection in search form
*/
$('a.form_frontend_function.toggle_result_assembly').click(function(){
var toggleElement = $(this).next();
toggleElement.toggle();
return false;
$('a.form_frontend_function.toggle_result_assembly').concrete({
onclick: function(e) {
var toggleElement = $(this).next();
toggleElement.toggle();
return false;
}
});
$('a.form_frontend_function.tick_all_result_assembly').click(function(){
var resultAssembly = $(this).prevAll('div#ResultAssembly').find('ul li input');
resultAssembly.attr('checked', 'checked');
return false;
$('a.form_frontend_function.tick_all_result_assembly').concrete({
onclick: function(e) {
var resultAssembly = $(this).prevAll('div#ResultAssembly').find('ul li input');
resultAssembly.attr('checked', 'checked');
return false;
}
});
$('a.form_frontend_function.untick_all_result_assembly').click(function(){
var resultAssembly = $(this).prevAll('div#ResultAssembly').find('ul li input');
resultAssembly.removeAttr('checked');
return false;
$('a.form_frontend_function.untick_all_result_assembly').concrete({
onclick: function(e) {
var resultAssembly = $(this).prevAll('div#ResultAssembly').find('ul li input');
resultAssembly.removeAttr('checked');
return false;
}
});
//////////////////////////////////////////////////////////////////
// Results list
//////////////////////////////////////////////////////////////////
/**
* Table record handler for search result record
* @todo: Shouldn't this be part of TableListField?
*/
$('#right #Form_ResultsForm tbody td a:not(.deletelink,.downloadlink)')
.livequery('click', function(){
$(this).parent().parent().addClass('loading');
var el = $(this);
$('#ModelAdminPanel').fn('addHistory', el.attr('href'));
$('#ModelAdminPanel').fn('loadForm', el.attr('href'));
return false;
});
$('form[name=Form_ResultsForm] tbody td a').concrete({
onmatch: function() {
// TODO Replace with concrete event handler
this.bind('click', function(e) {
if($(this).hasClass('deletelink downloadlink')) return true;
//////////////////////////////////////////////////////////////////
// RHS detail form
//////////////////////////////////////////////////////////////////
/**
* RHS panel Back button
*/
$('#Form_EditForm_action_goBack, #Form_ResultsForm_action_goBack').livequery('click', function() {
$('#ModelAdminPanel').fn('goBack');
return false;
$('#Form_EditForm').concrete('ss').loadForm($(this).attr('href'));
return false;
});
}
});
/**
* RHS panel Back button
* Add object button
*/
$('#Form_ResultsForm_action_goForward').livequery('click', function() {
$('#ModelAdminPanel').fn('goForward');
return false;
});
/**
* RHS panel Save button
*/
$('#right input[name=action_doSave],#right input[name=action_doCreate]').livequery('click', function(){
var form = $('#right form');
var formAction = form.attr('action') + '?' + $(this).fieldSerialize();
// @todo TinyMCE coupling
if(typeof tinyMCE != 'undefined') tinyMCE.triggerSave();
// 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;
$('#Form_ManagedModelsSelect').concrete({
onmatch: function() {
this.bind('submit', function(){
className = $('select option:selected', this).val();
requestPath = $(this).attr('action').replace('ManagedModelsSelect', className + '/add');
var $button = $(':submit', this);
$('#Form_EditForm').concrete('ss').loadForm(
requestPath,
function() {
$button.removeClass('loading');
$button = null;
}
);
return false;
});
}
});
/**
* RHS panel Delete button
*/
$('#right input[name=action_doDelete]').livequery('click', function(){
var confirmed = confirm(ss.i18n._t('ModelAdmin.REALLYDELETE', 'Really delete?'));
if(!confirmed) {
$(this).removeClass('loading')
return false;
$('#Form_EditForm input[name=action_doDelete]').concrete({
onmatch: function() {
this.bind('click', function() {
var confirmed = confirm(ss.i18n._t('ModelAdmin.REALLYDELETE', 'Really delete?'));
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
*/
$('.importSpec .details').hide();
$('.importSpec 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);
}
$('.importSpec').concrete({
onmatch: function() {
this.hide();
this.find('a.detailsLink').click(function() {
$('#' + $(this).attr('href').replace(/.*#/,'')).toggle();
return false;
});
}
});
/**
* 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);

View File

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