├── _config
└── relations.yml
├── templates
├── GridFieldRelationHandlerButtons.ss
├── GridFieldHasOneRelationHandlerItem.ss
└── GridFieldManyRelationHandlerItem.ss
├── composer.json
├── model
└── GridFieldRelationHandlerRelationList.php
├── javascript
└── GridFieldRelationHandler.js
├── gridfield
├── GridFieldHasOneRelationHandler.php
├── GridFieldManyRelationHandler.php
└── GridFieldRelationHandler.php
└── README.md
/_config/relations.yml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/templates/GridFieldRelationHandlerButtons.ss:
--------------------------------------------------------------------------------
1 |
2 | <% loop Fields %>
3 | $Me
4 | <% end_loop %>
5 |
6 |
--------------------------------------------------------------------------------
/templates/GridFieldHasOneRelationHandlerItem.ss:
--------------------------------------------------------------------------------
1 | checked<% end_if %> />
2 |
--------------------------------------------------------------------------------
/templates/GridFieldManyRelationHandlerItem.ss:
--------------------------------------------------------------------------------
1 | checked<% end_if %> <% if Disabled %>disabled<% end_if %> />
2 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "simonwelsh/gridfieldrelationhandler",
3 | "description": "This module provides two GridField components that aid in managing relationships within SilverStripe.",
4 | "type": "silverstripe-module",
5 | "license": "BSD-3-Clause",
6 | "keywords": ["silverstripe", "gridfield", "component"],
7 | "authors": [
8 | {
9 | "name": "Simon Welsh",
10 | "homepage": "http://simon.geek.nz/",
11 | "email": "simon@simon.geek.nz"
12 | }
13 | ],
14 | "require": {
15 | "silverstripe/framework": "~3.0"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/model/GridFieldRelationHandlerRelationList.php:
--------------------------------------------------------------------------------
1 | foreignKey;
13 | }
14 |
15 | public function getForeignIDFilter(RelationList $on = null) {
16 | if(!$on) {
17 | return;
18 | }
19 | return $on->foreignIDFilter();
20 | }
21 | }
22 |
23 | class GridFieldManyRelationHandler_ManyManyList extends ManyManyList {
24 | public function __construct() {
25 |
26 | }
27 |
28 | public function getJoinTable(ManyManyList $on = null) {
29 | if(!$on) {
30 | return;
31 | }
32 | return $on->joinTable;
33 | }
34 |
35 | public function getLocalKey(ManyManyList $on = null) {
36 | if(!$on) {
37 | return;
38 | }
39 | return $on->localKey;
40 | }
41 |
42 | public function getForeignKey(ManyManyList $on = null) {
43 | if(!$on) {
44 | return;
45 | }
46 | return $on->foreignKey;
47 | }
48 |
49 | public function getForeignIDFilter(RelationList $on = null) {
50 | if(!$on) {
51 | return;
52 | }
53 | return $on->foreignIDFilter();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/javascript/GridFieldRelationHandler.js:
--------------------------------------------------------------------------------
1 | jQuery(function($) {
2 | $.entwine('ss', function($) {
3 | $('.ss-gridfield .ss-gridfield-item .col-noedit').entwine({
4 | onclick: function(e) {
5 | e.stopPropagation();
6 | e.stopImmediatePropagation();
7 | }
8 | });
9 | $('.ss-gridfield .ss-gridfield-item .col-noedit input').entwine({
10 | getState: function () {
11 | return this.getGridField().getState().GridFieldRelationHandler;
12 | },
13 | setState: function(val) {
14 | this.getGridField().setState('GridFieldRelationHandler', val);
15 | },
16 | onchange: function(e) {
17 | var state = this.getState();
18 | var input = $(e.target).closest('input');
19 | if(input.hasClass('radio')) {
20 | state.RelationVal = input.val();
21 | } else if(input.hasClass('checkbox')) {
22 | if(state.RelationVal.indexOf) {
23 | if(input.is(':checked')) {
24 | state.RelationVal.push(input.val());
25 | } else {
26 | var index = state.RelationVal.indexOf(input.val());
27 | if(index != -1) {
28 | var left = state.RelationVal.slice(0, index);
29 | var right = state.RelationVal.slice(index+1);
30 | state.RelationVal = left.concat(right);
31 | }
32 | }
33 | } else if(input.is(':checked')) {
34 | state.RelationVal = [input.val()];
35 | } else {
36 | state.RelationVal = [];
37 | }
38 | }
39 | this.setState(state);
40 | }
41 | });
42 | });
43 | });
44 |
45 | if (!Array.prototype.indexOf) {
46 | Array.prototype.indexOf = function(elt /*, from*/)
47 | {
48 | var len = this.length >>> 0;
49 |
50 | var from = Number(arguments[1]) || 0;
51 | from = (from < 0)
52 | ? Math.ceil(from)
53 | : Math.floor(from);
54 | if (from < 0)
55 | from += len;
56 |
57 | for (; from < len; from++)
58 | {
59 | if (from in this &&
60 | this[from] === elt)
61 | return from;
62 | }
63 | return -1;
64 | };
65 | }
66 |
--------------------------------------------------------------------------------
/gridfield/GridFieldHasOneRelationHandler.php:
--------------------------------------------------------------------------------
1 | onObject = $onObject;
11 | $this->relationName = $relationName;
12 |
13 | $hasOne = $onObject->has_one($relationName);
14 | if(!$hasOne) {
15 | user_error('Unable to find a has_one relation named ' . $relationName . ' on ' . $onObject->ClassName, E_USER_WARNING);
16 | }
17 | $this->targetObject = $hasOne;
18 |
19 | parent::__construct(false, $targetFragment);
20 | }
21 |
22 | protected function setupState($state, $extra = null) {
23 | parent::setupState($state, $extra);
24 | if($state->FirstTime) {
25 | $state->RelationVal = $this->onObject->{$this->relationName}()->ID;
26 | }
27 | }
28 |
29 | public function getColumnContent($gridField, $record, $columnName) {
30 | $class = $gridField->getModelClass();
31 | if(!($class == $this->targetObject || is_subclass_of($class, $this->targetObject))) {
32 | user_error($class . ' is not a subclass of ' . $this->targetObject . '. Perhaps you wanted to use ' . $this->targetObject . '::get() as the list for this GridField?', E_USER_WARNING);
33 | }
34 |
35 | $state = $this->getState($gridField);
36 |
37 | $checked = $state->RelationVal == $record->ID;
38 | $field = new ArrayData(array('Checked' => $checked, 'Value' => $record->ID, 'Name' => $this->relationName . 'ID'));
39 | return $field->renderWith('GridFieldHasOneRelationHandlerItem');
40 | }
41 |
42 | protected function saveGridRelation(GridField $gridField, $arguments, $data) {
43 | $field = $this->relationName . 'ID';
44 | $state = $this->getState($gridField);
45 | $id = intval($state->RelationVal);
46 | $this->onObject->{$field} = $id;
47 | $this->onObject->write();
48 | parent::saveGridRelation($gridField, $arguments, $data);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/gridfield/GridFieldManyRelationHandler.php:
--------------------------------------------------------------------------------
1 | cheatList = new GridFieldManyRelationHandler_HasManyList;
10 | $this->cheatManyList = new GridFieldManyRelationHandler_ManyManyList;
11 | }
12 |
13 | public function getColumnContent($gridField, $record, $columnName) {
14 | $list = $gridField->getList();
15 | if(!$list instanceof RelationList) {
16 | user_error('GridFieldManyRelationHandler requires the GridField to have a RelationList. Got a ' . get_class($list) . ' instead.', E_USER_WARNING);
17 | }
18 |
19 | $state = $this->getState($gridField);
20 | $checked = in_array($record->ID, $state->RelationVal->toArray());
21 | $field = array('Checked' => $checked, 'Value' => $record->ID, 'Name' => $this->relationName($gridField));
22 | if($list instanceof HasManyList) {
23 | $key = $record->{$this->cheatList->getForeignKey($list)};
24 | if($key && !$checked) {
25 | $field['Disabled'] = true;
26 | }
27 | }
28 | $field = new ArrayData($field);
29 | return $field->renderWith('GridFieldManyRelationHandlerItem');
30 | }
31 |
32 | public function getManipulatedData(GridField $gridField, SS_List $list) {
33 | if(!$list instanceof RelationList) {
34 | user_error('GridFieldManyRelationHandler requires the GridField to have a RelationList. Got a ' . get_class($list) . ' instead.', E_USER_WARNING);
35 | }
36 |
37 | $state = $this->getState($gridField);
38 |
39 | // We don't use setupState() as we need the list
40 | if($state->FirstTime) {
41 | $state->RelationVal = array_values($list->getIdList()) ?: array();
42 | }
43 | if(!$state->ShowingRelation && $this->useToggle) {
44 | return $list;
45 | }
46 |
47 | $query = clone $list->dataQuery();
48 | try {
49 | $query->removeFilterOn($this->cheatList->getForeignIDFilter($list));
50 | } catch(InvalidArgumentException $e) { /* NOP */ }
51 | $orgList = $list;
52 | $list = new DataList($list->dataClass());
53 | $list = $list->setDataQuery($query);
54 | if($orgList instanceof ManyManyList) {
55 | $joinTable = $this->cheatManyList->getJoinTable($orgList);
56 | $baseClass = ClassInfo::baseDataClass($list->dataClass());
57 | $localKey = $this->cheatManyList->getLocalKey($orgList);
58 | $query->leftJoin($joinTable, "\"$joinTable\".\"$localKey\" = \"$baseClass\".\"ID\"");
59 | $list = $list->setDataQuery($query);
60 | }
61 | return $list;
62 | }
63 |
64 | protected function relationName($gridField) {
65 | return $gridField->getName() . get_class($gridField->getList());
66 | }
67 |
68 | protected function cancelGridRelation(GridField $gridField, $arguments, $data) {
69 | parent::cancelGridRelation($gridField, $arguments, $data);
70 |
71 | $state = $this->getState($gridField);
72 | $state->RelationVal = array_values($gridField->getList()->getIdList()) ?: array();
73 | }
74 |
75 | protected function saveGridRelation(GridField $gridField, $arguments, $data) {
76 | $state = $this->getState($gridField);
77 | $gridField->getList()->setByIdList($state->RelationVal->toArray());
78 | parent::saveGridRelation($gridField, $arguments, $data);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## GridFieldRelationHandler
2 |
3 | This module provides two [`GridField`](http://doc.silverstripe.org/framework/en/topics/grid-field) components
4 | that aid in managing relationships within [SilverStripe](http://www.silverstripe.org). The
5 | [`GridFieldHasOneRelationHandler`](#gridfieldhasonerelationhandler) component allows a `SS_List` to be used to select the value of a has_one
6 | relation and the [`GridFieldManyRelationHandler`](#gridfieldmanyrelationhandler) component manages a `RelationList`.
7 |
8 | ## Installation ##
9 |
10 | If the version of SilverStripe you are running is earlier than 3.0.3, first apply [this patch](https://github.com/silverstripe/sapphire/commit/d2b4e0df01f82fdbe613890c8ae909af404640a5) to `GridFieldPaginator`.
11 |
12 | Download this module and extract it into your site's root folder. Flush your site's manifest by visiting
13 | http://yoursite.com/?flush=1. These components are now ready for use.
14 |
15 | ## GridFieldHasOneRelationHandler ##
16 |
17 | 
18 |
19 | The `GridFieldHasOneRelationHandler` component provides radio buttons for selecting the object that the
20 | has_one points to. Its constructor takes the object the relation exists on, the name of the relation and
21 | an optional target fragment which describes the position of the save relation button.
22 |
23 | ### Example ###
24 |
25 | :::php
26 | $config->addComponent(new GridFieldHasOneRelationHandler($this, 'MainImage'));
27 |
28 | ## GridFieldManyRelationHandler ##
29 |
30 | 
31 |
32 | The `GridFieldManyRelationHandler` component provides check boxes for selecting the objects that a
33 | has_many or many_many point to. Its constructor takes an optional target fragment which describes
34 | the position of the save relation button.
35 |
36 | If your `GridField` also has a `GridFieldPaginator` component, this component must be inserted before
37 | it for the pagination to work properly.
38 |
39 | ### Example ###
40 |
41 | :::php
42 | // The second argument here ensures that this component is placed before any GridFieldPaginator
43 | $config->addComponent(new GridFieldManyRelationHandler(), 'GridFieldPaginator');
44 |
45 | ## License ##
46 |
47 | Copyright (c) 2014, Simon Welsh - simon.geek.nz
48 | All rights reserved.
49 |
50 | Redistribution and use in source and binary forms, with or without
51 | modification, are permitted provided that the following conditions are met:
52 | * Redistributions of source code must retain the above copyright
53 | notice, this list of conditions and the following disclaimer.
54 | * Redistributions in binary form must reproduce the above copyright
55 | notice, this list of conditions and the following disclaimer in the
56 | documentation and/or other materials provided with the distribution.
57 | * The name of Simon Welsh may not be used to endorse or promote products
58 | derived from this software without specific prior written permission.
59 |
60 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
61 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
62 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
63 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
64 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
65 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
66 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
67 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
68 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
69 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
70 |
71 |
--------------------------------------------------------------------------------
/gridfield/GridFieldRelationHandler.php:
--------------------------------------------------------------------------------
1 | 'Save changes',
10 | 'CANCELSAVE_RELATION' => 'Cancel changes',
11 | 'TOGGLE_RELATION' => 'Change relation status',
12 | );
13 |
14 | public function __construct($useToggle = true, $targetFragment = 'before') {
15 | $this->targetFragment = $targetFragment;
16 | $this->useToggle = $useToggle;
17 | }
18 |
19 | public function setUseToggle($useToggle) {
20 | $this->useToggle = (bool)$useToggle;
21 | return $this;
22 | }
23 |
24 | public function setColumnTitle($columnTitle) {
25 | $this->columnTitle = $columnTitle;
26 | return $this;
27 | }
28 |
29 | public function getColumnTitle() {
30 | return $this->columnTitle;
31 | }
32 |
33 | public function setButtonTitle($name, $title) {
34 | $this->buttonTitles[$name] = $title;
35 | return $this;
36 | }
37 |
38 | public function getButtonTitle($name) {
39 | if(isset($this->buttonTitles[$name])) {
40 | $value = $this->buttonTitles[$name];
41 | $key = sprintf('GridFieldRelationHandler.%s-%s', $name, str_replace(' ', '', $value));
42 | return _t($key, $value);
43 | } else {
44 | return _t('GridFieldRelationHandler.' . $name);
45 | }
46 | }
47 |
48 | protected function getState($gridField) {
49 | static $state = null;
50 | if(!$state) {
51 | $state = $gridField->State->GridFieldRelationHandler;
52 | $this->setupState($state);
53 | }
54 | return $state;
55 | }
56 |
57 | protected function setupState($state) {
58 | if(!isset($state->RelationVal)) {
59 | $state->RelationVal = 0;
60 | $state->FirstTime = 1;
61 | } else {
62 | $state->FirstTime = 0;
63 | }
64 | if(!isset($state->ShowingRelation)) {
65 | $state->ShowingRelation = 0;
66 | }
67 | }
68 |
69 | public function augmentColumns($gridField, &$columns) {
70 | $state = $this->getState($gridField);
71 | if($state->ShowingRelation || !$this->useToggle) {
72 | if(!in_array('RelationSetter', $columns)) {
73 | array_unshift($columns, 'RelationSetter');
74 | }
75 | if($this->useToggle && ($key = array_search('Actions', $columns)) !== false) {
76 | unset($columns[$key]);
77 | }
78 | }
79 | }
80 |
81 | public function getColumnsHandled($gridField) {
82 | return array('RelationSetter');
83 | }
84 |
85 | public function getColumnMetadata($gridField, $columnName) {
86 | if($columnName == 'RelationSetter') {
87 | return array(
88 | 'title' => $this->columnTitle
89 | );
90 | }
91 | return array();
92 | }
93 |
94 | public function getColumnAttributes($gridField, $record, $columnName) {
95 | return array('class' => 'col-noedit');
96 | }
97 |
98 | protected function getFields($gridField) {
99 | $state = $this->getState($gridField);
100 | if(!$this->useToggle) {
101 | $fields = array(
102 | Object::create(
103 | 'GridField_FormAction',
104 | $gridField,
105 | 'relationhandler-saverel',
106 | $this->getButtonTitle('SAVE_RELATION'),
107 | 'saveGridRelation',
108 | null
109 | )
110 | );
111 | } elseif($state->ShowingRelation) {
112 | $fields = array(
113 | Object::create(
114 | 'GridField_FormAction',
115 | $gridField,
116 | 'relationhandler-cancelrel',
117 | $this->getButtonTitle('CANCELSAVE_RELATION'),
118 | 'cancelGridRelation',
119 | null
120 | ),
121 | Object::create(
122 | 'GridField_FormAction',
123 | $gridField,
124 | 'relationhandler-saverel',
125 | $this->getButtonTitle('SAVE_RELATION'),
126 | 'saveGridRelation',
127 | null
128 | )
129 | );
130 | } else {
131 | $fields = array(
132 | Object::create(
133 | 'GridField_FormAction',
134 | $gridField,
135 | 'relationhandler-togglerel',
136 | $this->getButtonTitle('TOGGLE_RELATION'),
137 | 'toggleGridRelation',
138 | null
139 | )
140 | );
141 | }
142 | return new ArrayList($fields);
143 | }
144 |
145 | public function getHTMLFragments($gridField) {
146 | Requirements::javascript(basename(dirname(__DIR__)) . '/javascript/GridFieldRelationHandler.js');
147 | $saveRelation =
148 | $data = new ArrayData(array(
149 | 'Fields' => $this->getFields($gridField)
150 | ));
151 | return array(
152 | $this->targetFragment => $data->renderWith('GridFieldRelationHandlerButtons')
153 | );
154 | }
155 |
156 | public function getActions($gridField) {
157 | return array('saveGridRelation', 'cancelGridRelation', 'toggleGridRelation');
158 | }
159 |
160 | public function handleAction(GridField $gridField, $actionName, $arguments, $data) {
161 | if(in_array($actionName, array_map('strtolower', $this->getActions($gridField)))) {
162 | return $this->$actionName($gridField, $arguments, $data);
163 | }
164 | }
165 |
166 | protected function toggleGridRelation(GridField $gridField, $arguments, $data) {
167 | $state = $this->getState($gridField);
168 | $state->ShowingRelation = true;
169 | }
170 |
171 | protected function cancelGridRelation(GridField $gridField, $arguments, $data) {
172 | $state = $this->getState($gridField);
173 | $state->ShowingRelation = false;
174 | $state->FirstTime = true;
175 | }
176 |
177 | protected function saveGridRelation(GridField $gridField, $arguments, $data) {
178 | $state = $this->getState($gridField);
179 | $state->ShowingRelation = false;
180 | }
181 | }
182 |
--------------------------------------------------------------------------------