1 <?php
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
28
29 class TableField extends TableListField {
30
31 protected $sourceClass;
32
33 protected $sourceFilter;
34
35 protected $fieldList;
36
37 38 39 40
41 protected $filterField = null;
42
43 44 45 46
47 protected $filterValue = null;
48
49 50 51 52 53
54 protected $fieldTypes;
55
56 protected $sourceSort;
57
58 protected $sourceJoin;
59
60 61 62
63 protected $template = "TableField";
64
65 66 67 68
69 protected ;
70
71 protected $tempForm;
72
73 74 75
76 protected $permissions = array(
77 "edit",
78 "delete",
79 "add",
80
81 );
82
83 public $transformationConditions = array();
84
85 86 87 88 89
90 protected $requiredFields = null;
91
92 93 94 95 96 97 98 99
100 public $showAddRow = true;
101
102 103 104 105 106 107
108 protected $relationAutoSetting = true;
109
110 function __construct($name, $sourceClass, $fieldList = null, $fieldTypes, $filterField = null,
111 $sourceFilter = null, $editExisting = true, $sourceSort = null, $sourceJoin = null) {
112
113 $this->fieldTypes = $fieldTypes;
114 $this->filterField = $filterField;
115
116 $this->editExisting = $editExisting;
117
118
119 if($filterField) {
120 $this->filterValue = $sourceFilter;
121 $sourceFilter = "\"$filterField\" = '" . Convert::raw2sql($sourceFilter) . "'";
122 }
123 parent::__construct($name, $sourceClass, $fieldList, $sourceFilter, $sourceSort, $sourceJoin);
124 }
125
126 127 128 129 130
131 function Headings() {
132 $i=0;
133 foreach($this->fieldList as $fieldName => $fieldTitle) {
134 $extraClass = "col".$i;
135 $class = $this->fieldTypes[$fieldName];
136 if(is_object($class)) $class = "";
137 $class = $class." ".$extraClass;
138 $headings[] = new ArrayData(array("Name" => $fieldName, "Title" => $fieldTitle, "Class" => $class));
139 $i++;
140 }
141 return new DataObjectSet($headings);
142 }
143
144 145 146 147 148 149
150 function ItemCount() {
151 return count($this->fieldList);
152 }
153
154 155 156 157 158
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
180 181 182 183 184 185 186 187
188 function Items() {
189
190 $items = new DataObjectSet();
191
192 $sourceItems = $this->sourceItems();
193
194
195
196 if($this->value) {
197 if(!$sourceItems) $sourceItems = new DataObjectSet();
198
199
200 $rows = $this->sortData(ArrayLib::invert($this->value));
201
202 if(isset($rows['new'])) {
203 $newRows = $this->sortData($rows['new']);
204
205 $i = 0;
206 foreach($newRows as $idx => $newRow){
207
208 $newRow['ID'] = "new";
209
210
211 foreach($newRow as $k => $v){
212 if($this->extraData && array_key_exists($k, $this->extraData)){
213 unset($newRow[$k]);
214 }
215 }
216
217
218 $sourceClass = $this->sourceClass;
219 $sourceItems->push(new $sourceClass($newRow));
220
221 $i++;
222 }
223 }
224 }
225
226
227 if($sourceItems) foreach($sourceItems as $sourceItem) {
228 $items->push($this->generateTableFieldItem($sourceItem));
229 }
230
231
232 if($this->showAddRow && $this->Can('add')) {
233 $items->push(new TableField_Item(null, $this, null, $this->fieldTypes, true));
234 }
235
236 return $items;
237 }
238
239 240 241 242 243 244 245
246 protected function generateTableFieldItem($dataObj) {
247
248 $form = new Form(
249 $this,
250 null,
251 $this->FieldSetForRow(),
252 new FieldSet()
253 );
254 $form->loadDataFrom($dataObj);
255
256
257 return new TableField_Item($dataObj, $this, $form, $this->fieldTypes);
258 }
259
260 261 262
263 function FieldList() {
264 return $this->fieldList;
265 }
266
267 268 269
270 function saveInto(DataObject $record) {
271
272 if(is_array($this->value)){
273 $newFields = array();
274
275
276 $value = ArrayLib::invert($this->value);
277 $dataObjects = $this->sortData($value, $record->ID);
278
279
280 if(isset($dataObjects['new']) && $dataObjects['new']) {
281 $newFields = $this->sortData($dataObjects['new'], $record->ID);
282 }
283
284
285
286 $savedObjIds = $this->saveData($dataObjects, $this->editExisting);
287
288
289 if($savedObjIds || $newFields) {
290 $savedObjIds = $this->saveData($newFields,false);
291 }
292
293
294
295
296 if($this->relationAutoSetting) {
297 $relationName = $this->Name();
298 if($record->has_many($relationName) || $record->many_many($relationName)) {
299 if($savedObjIds) foreach($savedObjIds as $id => $status) {
300 $record->$relationName()->add($id);
301 }
302 }
303 }
304
305
306 $this->value = null;
307 $items = $this->sourceItems();
308
309 FormResponse::update_dom_id($this->id(), $this->FieldHolder());
310 }
311 }
312
313 314 315 316 317 318 319 320 321
322 function FieldSetForRow() {
323 $fieldset = new FieldSet();
324 if($this->fieldTypes){
325 foreach($this->fieldTypes as $key => $fieldType) {
326 if(isset($fieldType->class) && is_subclass_of($fieldType, 'FormField')) {
327
328 $field = clone $fieldType;
329 } elseif(strpos($fieldType, '(') === false) {
330 $field = new $fieldType($key);
331 } else {
332 $fieldName = $key;
333 $fieldTitle = "";
334 $field = eval("return new $fieldType;");
335 }
336 if($this->IsReadOnly || !$this->Can('edit')) {
337 $field = $field->performReadonlyTransformation();
338 }
339 $fieldset->push($field);
340 }
341 }else{
342 USER_ERROR("TableField::FieldSetForRow() - Fieldtypes were not specified",E_USER_WARNING);
343 }
344
345 return $fieldset;
346 }
347
348 function performReadonlyTransformation() {
349 $clone = clone $this;
350 $clone->permissions = array('show');
351 $clone->setReadonly(true);
352 return $clone;
353 }
354
355 function performDisabledTransformation() {
356 $clone = clone $this;
357 $clone->setPermissions(array('show'));
358 $clone->setDisabled(true);
359 return $clone;
360 }
361
362 363 364
365 public function getField($fieldName, $combinedFieldName = null) {
366 $fieldSet = $this->FieldSetForRow();
367 $field = $fieldSet->dataFieldByName($fieldName);
368 if(!$field) {
369 return false;
370 }
371
372 if($combinedFieldName) {
373 $field->Name = $combinedFieldName;
374 }
375
376 return $field;
377 }
378
379 380 381 382 383 384 385 386 387 388
389 function saveData($dataObjects, $existingValues = true) {
390 if(!$dataObjects) return false;
391
392 $savedObjIds = array();
393 $fieldset = $this->FieldSetForRow();
394
395
396 if($this->extraData) {
397 foreach($this->extraData as $fieldName => $fieldValue) {
398 $fieldset->push(new HiddenField($fieldName));
399 }
400 }
401
402 $form = new Form($this, null, $fieldset, new FieldSet());
403
404 foreach ($dataObjects as $objectid => $fieldValues) {
405
406 if($objectid === "new") continue;
407
408
409 if($this->extraData) {
410 $fieldValues = array_merge( $this->extraData, $fieldValues );
411 }
412
413
414 if($existingValues) {
415 $obj = DataObject::get_by_id($this->sourceClass, $objectid);
416 } else {
417 $sourceClass = $this->sourceClass;
418 $obj = new $sourceClass();
419 }
420
421
422 if($this->filterField && $this->filterValue) {
423 $filterField = $this->filterField;
424 $obj->$filterField = $this->filterValue;
425 }
426
427
428 $dataFields = array();
429 foreach($fieldValues as $type => $value) {
430
431 if(is_array($this->extraData)) {
432 if(!in_array($type, array_keys($this->extraData))) {
433 $dataFields[$type] = $value;
434 }
435
436 } else {
437 $dataFields[$type] = $value;
438 }
439 }
440 $dataValues = ArrayLib::array_values_recursive($dataFields);
441
442 $hasData = false;
443 foreach($dataValues as $value) {
444 if(!empty($value)) $hasData = true;
445 }
446
447 if($hasData) {
448 $form->loadDataFrom($fieldValues, true);
449 $form->saveInto($obj);
450
451 $objectid = $obj->write();
452
453 $savedObjIds[$objectid] = "Updated";
454 }
455
456 }
457
458 return $savedObjIds;
459 }
460
461 462 463 464 465 466 467
468 function sortData($data, $recordID = null) {
469 if(!$data) return false;
470
471 $sortedData = array();
472
473 foreach($data as $field => $rowData) {
474 $i = 0;
475 if(!is_array($rowData)) continue;
476
477 foreach($rowData as $id => $value) {
478 if($value == '$recordID') $value = $recordID;
479
480 if($value) $sortedData[$id][$field] = $value;
481
482 $i++;
483 }
484
485
486 }
487
488 return $sortedData;
489 }
490
491 492 493
494 function ($extraData) {
495 $this->extraData = $extraData;
496 }
497
498 499 500
501 function () {
502 return $this->extraData;
503 }
504
505 506 507
508 function FieldHolder() {
509 Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/prototype/prototype.js');
510 Requirements::javascript(SAPPHIRE_DIR . '/thirdparty/behaviour/behaviour.js');
511 Requirements::javascript(SAPPHIRE_DIR . '/javascript/prototype_improvements.js');
512 Requirements::javascript(THIRDPARTY_DIR . '/scriptaculous/effects.js');
513 Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
514 Requirements::javascript(SAPPHIRE_DIR . '/javascript/TableListField.js');
515 Requirements::javascript(SAPPHIRE_DIR . '/javascript/TableField.js');
516 Requirements::css(SAPPHIRE_DIR . '/css/TableListField.css');
517
518 return $this->renderWith($this->template);
519 }
520
521 522 523
524 function sourceID() {
525 return $this->filterField;
526 }
527
528 function setTransformationConditions($conditions) {
529 $this->transformationConditions = $conditions;
530 }
531
532 function jsValidation() {
533 $js = "";
534
535 $items = $this->Items();
536 if($items) foreach($items as $item) {
537 foreach($item->Fields() as $field) {
538
539 $js .= $field->jsValidation($this->form->class."_".$this->form->Name());
540 }
541 }
542
543
544 $items = $this->sourceItems();
545 if($items && $this->requiredFields && $items->count()) {
546 foreach ($this->requiredFields as $field) {
547 foreach($items as $item){
548 $cellName = $this->Name().'['.$item->ID.']['.$field.']';
549 $js .= "\n";
550 if($fields->dataFieldByName($cellName)) {
551 $js .= <<<JS
552 if(typeof fromAnOnBlur != 'undefined'){
553 if(fromAnOnBlur.name == '$cellName')
554 require(fromAnOnBlur);
555 }else{
556 require('$cellName');
557 }
558 JS;
559 }
560 }
561 }
562 }
563
564 return $js;
565 }
566
567 function php($data) {
568 $valid = true;
569
570 if($data['methodName'] != 'delete') {
571 $items = $this->Items();
572 if($items) foreach($items as $item) {
573 foreach($item->Fields() as $field) {
574 $valid = $field->validate($this) && $valid;
575 }
576 }
577
578 return $valid;
579 } else {
580 return $valid;
581 }
582 }
583
584 function validate($validator) {
585 $errorMessage = '';
586 $valid = true;
587
588
589 $items = $this->Items();
590 if($items) foreach($items as $item) {
591 foreach($item->Fields() as $field) {
592 $valid = $field->validate($validator) && $valid;
593 }
594 }
595
596
597 if($this->requiredFields&&$sourceItemsNew&&$sourceItemsNew->count()) {
598 foreach ($this->requiredFields as $field) {
599 foreach($sourceItemsNew as $item){
600 $cellName = $this->Name().'['.$item->ID.']['.$field.']';
601 $cName = $this->Name().'[new]['.$field.'][]';
602
603 if($fieldObj = $fields->dataFieldByName($cellName)) {
604 if(!trim($fieldObj->Value())){
605 $title = $fieldObj->Title();
606 $errorMessage .= sprintf(
607 _t('TableField.ISREQUIRED', "In %s '%s' is required."),
608 $this->name,
609 $title
610 );
611 $errorMessage .= "<br />";
612 }
613 }
614 }
615 }
616 }
617
618 if($errorMessage){
619 $messageType .= "validation";
620 $message .= "<br />".$errorMessage;
621
622 $validator->validationError($this->name, $message, $messageType);
623 }
624
625 return $valid;
626 }
627
628 function setRequiredFields($fields) {
629 $this->requiredFields = $fields;
630 }
631
632 633 634
635 function setRelationAutoSetting($value) {
636 $this->relationAutoSetting = $value;
637 }
638
639 640 641
642 function getRelationAutoSetting() {
643 return $this->relationAutoSetting;
644 }
645 }
646
647 648 649 650 651 652
653 class TableField_Item extends TableListField_Item {
654
655 656 657
658 protected $fields;
659
660 protected $data;
661
662 protected $fieldTypes;
663
664 protected $isAddRow;
665
666 protected ;
667
668 669 670 671 672 673 674
675 function __construct($item = null, $parent, $form, $fieldTypes, $isAddRow = false) {
676 $this->data = $form;
677 $this->fieldTypes = $fieldTypes;
678 $this->isAddRow = $isAddRow;
679 $this->item = $item;
680
681 parent::__construct(($this->item) ? $this->item : new DataObject(), $parent);
682
683 $this->fields = $this->createFields();
684 }
685 686 687 688 689
690 function createFields() {
691
692 if($this->item && $this->data) {
693 $form = $this->data;
694 $this->fieldset = $form->Fields();
695 $this->fieldset->removeByName('SecurityID');
696 if($this->fieldset) {
697 $i=0;
698 foreach($this->fieldset as $field) {
699 $origFieldName = $field->Name();
700
701
702 $combinedFieldName = $this->parent->Name() . "[" . $this->ID() . "][" . $origFieldName . "]";
703
704
705 if($this->isAddRow || $this->ID() == 'new') $combinedFieldName .= '[]';
706
707
708 if(strpos($origFieldName,'.') === false) {
709 $value = $field->dataValue();
710 } else {
711
712 $fieldNameParts = explode('.', $origFieldName) ;
713 $tmpItem = $this->item;
714 for($j=0;$j<sizeof($fieldNameParts);$j++) {
715 $relationMethod = $fieldNameParts[$j];
716 $idField = $relationMethod . 'ID';
717 if($j == sizeof($fieldNameParts)-1) {
718 $value = $tmpItem->$relationMethod;
719 } else {
720 $tmpItem = $tmpItem->$relationMethod();
721 }
722 }
723 }
724
725 $field->Name = $combinedFieldName;
726 $field->setValue($field->dataValue());
727 $field->addExtraClass('col'.$i);
728 $field->setForm($this->data);
729
730
731 if(isset($this->parent->transformationConditions[$origFieldName])) {
732 $transformation = $this->parent->transformationConditions[$origFieldName]['transformation'];
733 $rule = str_replace("\$","\$this->item->", $this->parent->transformationConditions[$origFieldName]['rule']);
734 $ruleApplies = null;
735 eval('$ruleApplies = ('.$rule.');');
736 if($ruleApplies) {
737 $field = $field->$transformation();
738 }
739 }
740
741
742 $item = $this->item;
743 $value = $field->Value();
744 if(array_key_exists($origFieldName, $this->parent->fieldFormatting)) {
745 $format = str_replace('$value', "__VAL__", $this->parent->fieldFormatting[$origFieldName]);
746 $format = preg_replace('/\$([A-Za-z0-9-_]+)/','$item->$1', $format);
747 $format = str_replace('__VAL__', '$value', $format);
748 eval('$value = "' . $format . '";');
749 $field->dontEscape = true;
750 $field->setValue($value);
751 }
752
753 $this->fields[] = $field;
754 $i++;
755 }
756 }
757
758 } else {
759 $list = $this->parent->FieldList();
760 $i=0;
761 foreach($list as $fieldName => $fieldTitle) {
762 if(strpos($fieldName, ".")) {
763 $shortFieldName = substr($fieldName, strpos($fieldName, ".")+1, strlen($fieldName));
764 } else {
765 $shortFieldName = $fieldName;
766 }
767 $combinedFieldName = $this->parent->Name() . "[new][" . $shortFieldName . "][]";
768 $fieldType = $this->fieldTypes[$fieldName];
769 if(isset($fieldType->class) && is_subclass_of($fieldType, 'FormField')) {
770 $field = clone $fieldType;
771 $field->Name = $combinedFieldName;
772 } elseif(strpos($fieldType, '(') === false) {
773
774 $field = new $fieldType($combinedFieldName,$fieldTitle);
775 } else {
776 $field = eval("return new " . $fieldType . ";");
777 }
778 $field->addExtraClass('col'.$i);
779 $this->fields[] = $field;
780 $i++;
781 }
782 }
783 return new FieldSet($this->fields);
784 }
785
786 function Fields() {
787 return $this->fields;
788 }
789
790 function () {
791 $content = "";
792 $id = ($this->item->ID) ? $this->item->ID : "new";
793 if($this->parent->getExtraData()) {
794 foreach($this->parent->getExtraData() as $fieldName=>$fieldValue) {
795 $name = $this->parent->Name() . "[" . $id . "][" . $fieldName . "]";
796 if($this->isAddRow) $name .= '[]';
797 $field = new HiddenField($name, null, $fieldValue);
798 $content .= $field->FieldHolder() . "\n";
799 }
800 }
801
802 return $content;
803 }
804
805 806 807 808 809 810
811 function IsAddRow(){
812 return $this->isAddRow;
813 }
814
815 }
816
817 ?>
[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the
Silverstripe Forums to ask development related questions.
-