ENHANCEMENT Allowing batch checkbox selection of TableListField rows with TableListField->Markable and TableListField->addSelectOptions()

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/branches/2.4@105266 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2010-05-20 07:28:05 +00:00 committed by Sam Minnee
parent ca873121fa
commit ba2aabd702
10 changed files with 202 additions and 19 deletions

View File

@ -181,6 +181,18 @@ form .TableField .message {
width: auto;
}
.TableListField .selectOptions {
overflow: auto;
font: 1.3em;
margin: 0;
padding: 0;
}
.TableListField .selectOptions li {
float: left;
margin: 0px 5px;
}
.TableListField .PageControls {
margin: 5px 0;
text-align:center;

View File

@ -88,6 +88,11 @@ class TableListField extends FormField {
public $MarkableTitle = null;
/**
* @var array See {@link SelectOptions()}
*/
protected $selectOptions = array();
/**
* @var $readOnly boolean Deprecated, please use $permssions instead
*/
@ -1199,14 +1204,57 @@ JS
return $value;
}
/**
* #########################
* Highlighting
* #########################
*/
function setHighlightConditions($conditions) {
$this->highlightConditions = $conditions;
}
/**
* See {@link SelectOptions()} for introduction.
*
* @param $options array Options to add, key being a unique identifier of the action,
* and value a title for the rendered link element (can contain HTML).
* The keys for 'all' and 'none' have special behaviour associated
* through TableListField.js JavaScript.
* For any other key, the JavaScript automatically checks all checkboxes contained in
* <td> elements with a matching classname.
*/
function addSelectOptions($options){
foreach($options as $k => $title)
$this->selectOptions[$k] = $title;
}
/**
* Remove one all more table's {@link $selectOptions}
*
* @param $optionsNames array
*/
function removeSelectOptions($names){
foreach($names as $name){
unset($this->selectOptions[trim($name)]);
}
}
/**
* Return the table's {@link $selectOptions}.
* Used to toggle checkboxes for each table row through button elements.
*
* Requires {@link Markable()} to return TRUE.
* This is only functional with JavaScript enabled.
*
* @return DataObjectSet of ArrayData objects
*/
function SelectOptions(){
if(!$this->selectOptions) return;
$selectOptionsSet = new DataObjectSet();
foreach($this->selectOptions as $k => $v) {
$selectOptionsSet->push(new ArrayData(array(
'Key' => $k,
'Value' => $v
)));
}
return $selectOptionsSet;
}
}
/**
@ -1381,6 +1429,27 @@ class TableListField_Item extends ViewableData {
return "<input class=\"checkbox\" type=\"checkbox\" name=\"$name\" value=\"{$this->item->ID}\" />";
}
/**
* According to {@link TableListField->selectOptions}, each record will check if the options' key on the object is true,
* if it is true, add the key as a class to the record
*
* @return string Value for a 'class' HTML attribute.
*/
function SelectOptionClasses(){
$tagArray = array('markingcheckbox');
$options = $this->parent->selectOptions;
if(!empty($options)){
foreach($options as $optionKey => $optionTitle){
if($optionKey !== 'all' && $optionKey !== 'none'){
if($this->$optionKey) {
$tagArray[] = $optionKey;
}
}
}
}
return implode(" ",$tagArray);
}
function HighlightClasses() {
$classes = array();
foreach($this->parent->highlightConditions as $condition) {

View File

@ -5,7 +5,7 @@ TableListField.prototype = {
initialize: function() {
var rules = {};
rules['#'+this.id+' table.data a.deletelink'] = {
onclick: this.deleteRecord.bind(this)
};
@ -36,7 +36,12 @@ TableListField.prototype = {
// do nothing for clicks in marking box cells (e.g. if checkbox is missed)
}
};
// rules for selection options on click event
rules['#'+this.id+' .selectOptions a'] = {
onclick: this.markRecords.bind(this)
};
// initialize summary (if needed)
// TODO Breaks with nested divs
var summaryCols = $$('tfoot tr.summary td', this);
@ -122,6 +127,50 @@ TableListField.prototype = {
this._summarise();
},
/**
* according to the clicked element in "Select bar", mark records that have same class as the element.
*/
markRecords: function(e){
var el = Event.element(e);
if(el.nodeName != "a") el = Event.findElement(e,"a");
if(el.rel == "all"){
this.markAll();
}else if(el.rel == 'none') {
this.unmarkAll();
}else{
this.unmarkAll();
var records = $$('#' + this.id + ' td.' + el.rel + ' input.checkbox');
var i=0;
for(i; i<records.length; i++){
records[i].checked = 'checked';
}
}
return false;
},
/**
* mark all record in current view of the table
*/
markAll: function(e){
var records = $$('#'+this.id+' td.markingcheckbox input.checkbox');
var i=0;
for(i; i<records.length; i++){
records[i].checked = 'checked';
}
},
/**
* unmark all records in current view of the table
*/
unmarkAll: function(e){
var records = $$('#'+this.id+' td.markingcheckbox input.checkbox');
var i=0;
for(i; i<records.length; i++){
records[i].checked = '';
}
},
refresh: function(e) {
if(e) {
var el = Event.element(e);

View File

@ -1,5 +1,8 @@
<div id="$id" class="$CSSClasses field" href="$CurrentLink">
<div class="middleColumn">
<% if Markable %>
<% include TableListField_SelectOptions %>
<% end_if %>
<% include TableListField_PageControls %>
<table class="data">
<thead>

View File

@ -1,5 +1,5 @@
<tr id="record-$Parent.id-$ID"<% if HighlightClasses %> class="$HighlightClasses"<% end_if %>>
<% if Markable %><td width="16" class="markingcheckbox">$MarkingCheckbox</td><% end_if %>
<% if Markable %><td width="16" class="$SelectOptionClasses">$MarkingCheckbox</td><% end_if %>
<% control Fields %>
<td class="field-$Title.HTMLATT $FirstLast $Name">$Value</td>
<% end_control %>

View File

@ -0,0 +1,8 @@
<% if SelectOptions %>
<ul class="selectOptions">
<li><% _t('TableListField.SELECT', 'Select:') %></li>
<% control SelectOptions %>
<li><a rel="$Key" href="#" title="$Key">$Value</a></li>
<% end_control %>
</ul>
<% end_if %>

View File

@ -1,4 +1,7 @@
<div id="$id" class="$CSSClasses" href="$CurrentLink">
<% if Markable %>
<% include TableListField_SelectOptions %>
<% end_if %>
<% include TableListField_PageControls %>
<table class="data">
<thead>

View File

@ -1,5 +1,10 @@
<div id="$id" class="$CSSClasses field">
<% if Print %><% else %><% include TableListField_PageControls %><% end_if %>
<% if Print %><% else %>
<% if Markable %>
<% include TableListField_SelectOptions %>
<% end_if %>
<% include TableListField_PageControls %>
<% end_if %>
<table class="data">
<thead>
<tr>

View File

@ -22,7 +22,7 @@ class TableListFieldTest extends SapphireTest {
), new FieldSet());
$result = $table->FieldHolder();
// Do a quick check to ensure that some of the D() and getE() values got through
$this->assertRegExp('/>\s*a2\s*</', $result);
$this->assertRegExp('/>\s*a2\/b2\/c2\s*</', $result);
@ -36,7 +36,7 @@ class TableListFieldTest extends SapphireTest {
$item4 = $this->objFromFixture('TableListFieldTest_Obj', 'four');
$item5 = $this->objFromFixture('TableListFieldTest_Obj', 'five');
/* In this simple case, the source items should just list all the data objects specified */
// In this simple case, the source items should just list all the data objects specified
$table = new TableListField("Tester", "TableListFieldTest_Obj", array(
"A" => "Col A",
"B" => "Col B",
@ -48,7 +48,7 @@ class TableListFieldTest extends SapphireTest {
$form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldSet(
$table
), new FieldSet());
$items = $table->sourceItems();
$this->assertNotNull($items);
@ -61,7 +61,7 @@ class TableListFieldTest extends SapphireTest {
$item5->ID => "a5"
), $itemMap);
}
function testFirstPageOfPaginatedSourceItemGeneration() {
$item1 = $this->objFromFixture('TableListFieldTest_Obj', 'one');
$item2 = $this->objFromFixture('TableListFieldTest_Obj', 'two');
@ -69,7 +69,7 @@ class TableListFieldTest extends SapphireTest {
$item4 = $this->objFromFixture('TableListFieldTest_Obj', 'four');
$item5 = $this->objFromFixture('TableListFieldTest_Obj', 'five');
/* With pagination enabled, only the first page of items should be shown */
// With pagination enabled, only the first page of items should be shown
$table = new TableListField("Tester", "TableListFieldTest_Obj", array(
"A" => "Col A",
"B" => "Col B",
@ -81,13 +81,13 @@ class TableListFieldTest extends SapphireTest {
$form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldSet(
$table
), new FieldSet());
$table->ShowPagination = true;
$table->PageSize = 2;
$items = $table->sourceItems();
$this->assertNotNull($items);
$itemMap = $items->toDropdownMap("ID", "A") ;
$this->assertEquals(array(
$item1->ID => "a1",
@ -102,7 +102,7 @@ class TableListFieldTest extends SapphireTest {
$item4 = $this->objFromFixture('TableListFieldTest_Obj', 'four');
$item5 = $this->objFromFixture('TableListFieldTest_Obj', 'five');
/* With pagination enabled, only the first page of items should be shown */
// With pagination enabled, only the first page of items should be shown
$table = new TableListField("Tester", "TableListFieldTest_Obj", array(
"A" => "Col A",
"B" => "Col B",
@ -114,18 +114,46 @@ class TableListFieldTest extends SapphireTest {
$form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldSet(
$table
), new FieldSet());
$table->ShowPagination = true;
$table->PageSize = 2;
$_REQUEST['ctf']['Tester']['start'] = 2;
$items = $table->sourceItems();
$this->assertNotNull($items);
$itemMap = $items->toDropdownMap("ID", "A") ;
$this->assertEquals(array($item3->ID => "a3", $item4->ID => "a4"), $itemMap);
}
function testSelectOptionsAddRemove() {
$table = new TableListField("Tester", "TableListFieldTest_Obj", array(
"A" => "Col A",
));
$this->assertNull($table->SelectOptions(), 'Empty by default');
$table->addSelectOptions(array("F"=>"FieldF", 'G'=>'FieldG'));
$this->assertEquals($table->SelectOptions()->map('Key', 'Value'), array("F"=>"FieldF",'G'=>'FieldG'));
$table->removeSelectOptions(array("F"));
$this->assertEquals($table->SelectOptions()->map('Key', 'Value'), array("G"=>"FieldG"));
}
function testSelectOptionsRendering() {
$table = new TableListField("Tester", "TableListFieldTest_Obj", array(
"A" => "Col A",
));
$table->Markable = true;
$table->addSelectOptions(array("F"=>"FieldF"));
$tableHTML = $table->FieldHolder();
$this->assertContains('rel="F"', $tableHTML);
$this->assertRegExp('/<tr[^>]*id="record-Tester-1"[^>]*>[^<]*<td[^>]*class="markingcheckbox F"[^>]*>/si', $tableHTML);
$this->assertRegExp('/<tr[^>]*id="record-Tester-2"[^>]*>[^<]*<td[^>]*class="markingcheckbox"[^>]*>/si', $tableHTML);
$this->assertRegExp('/<tr[^>]*id="record-Tester-3"[^>]*>[^<]*<td[^>]*class="markingcheckbox F"[^>]*>/si', $tableHTML);
}
/**
* Get that visiting the field's URL returns the content of the field.
* This capability is used by ajax
@ -191,6 +219,7 @@ class TableListFieldTest_Obj extends DataObject implements TestOnly {
"A" => "Varchar",
"B" => "Varchar",
"C" => "Varchar",
"F" => "Boolean",
);
static $default_sort = "A";

View File

@ -3,22 +3,27 @@ TableListFieldTest_Obj:
A: a1
B: b1
C: c1
F: true
two:
A: a2
B: b2
C: c2
F: false
three:
A: a3
B: b3
C: c3
F: true
four:
A: a4
B: b4
C: c4
D: false
five:
A: a5
B: b5
C: c5
F: true
TableListFieldTest_CsvExport:
exportone: