Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

ENHANCEMENT Allowing batch checkbox selection of TableListField rows …

…with TableListField->Markable and TableListField->addSelectOptions() (from r105266)

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@112473 467b73ca-7a2a-4603-9d3b-597d59a354a9
  • Loading branch information...
commit bcbe9c254dace82d0914e8c14349231867c50825 1 parent bedc3ef
@chillu chillu authored
View
12 css/TableListField.css
@@ -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
79 forms/TableListField.php
@@ -89,6 +89,11 @@ class TableListField extends FormField {
public $MarkableTitle = null;
/**
+ * @var array See {@link SelectOptions()}
+ */
+ protected $selectOptions = array();
+
+ /**
* @var $readOnly boolean Deprecated, please use $permssions instead
*/
protected $readOnly;
@@ -1199,14 +1204,57 @@ function getCastedValue($value, $castingDefinition) {
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 @@ function MarkingCheckbox() {
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
55 javascript/TableListField.js
@@ -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,7 +127,51 @@ TableListField.prototype = {
this._summarise();
},
- refresh: function(e, params, oncomplete) {
+ /**
+ * 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);
if(el.nodeName != "a") el = Event.findElement(e,"a");
View
3  templates/ComplexTableField.ss
@@ -1,5 +1,8 @@
<div id="$id" class="$CSSClasses $extraClass field" href="$CurrentLink">
<div class="middleColumn">
+ <% if Markable %>
+ <% include TableListField_SelectOptions %>
+ <% end_if %>
<% include TableListField_PageControls %>
<table class="data">
<thead>
View
2  templates/Includes/TableListField_Item.ss
@@ -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
8 templates/Includes/TableListField_SelectOptions.ss
@@ -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
3  templates/RelationComplexTableField.ss
@@ -1,4 +1,7 @@
<div id="$id" class="$CSSClasses $extraClass field" href="$CurrentLink">
+ <% if Markable %>
+ <% include TableListField_SelectOptions %>
+ <% end_if %>
<% include TableListField_PageControls %>
<table class="data">
<thead>
View
7 templates/TableListField.ss
@@ -1,5 +1,10 @@
<div id="$id" class="$CSSClasses $extraClass 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
49 tests/forms/TableListFieldTest.php
@@ -22,7 +22,7 @@ function testCanReferenceCustomMethodsAndFieldsOnObject() {
), 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 @@ function testUnpaginatedSourceItemGeneration() {
$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 @@ function testUnpaginatedSourceItemGeneration() {
$form = new Form(new TableListFieldTest_TestController(), "TestForm", new FieldSet(
$table
), new FieldSet());
-
+
$items = $table->sourceItems();
$this->assertNotNull($items);
@@ -61,7 +61,7 @@ function testUnpaginatedSourceItemGeneration() {
$item5->ID => "a5"
), $itemMap);
}
-
+
function testFirstPageOfPaginatedSourceItemGeneration() {
$item1 = $this->objFromFixture('TableListFieldTest_Obj', 'one');
$item2 = $this->objFromFixture('TableListFieldTest_Obj', 'two');
@@ -69,7 +69,7 @@ function testFirstPageOfPaginatedSourceItemGeneration() {
$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 @@ function testFirstPageOfPaginatedSourceItemGeneration() {
$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 @@ function testSecondPageOfPaginatedSourceItemGeneration() {
$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 @@ function testSecondPageOfPaginatedSourceItemGeneration() {
$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
5 tests/forms/TableListFieldTest.yml
@@ -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:
Please sign in to comment.
Something went wrong with that request. Please try again.