Test Setup Failed
Push — dev ( df7ea0...1d5d0f )
by
unknown
03:09
created

TreeWidget::prepareJs()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 9
rs 9.6666
cc 3
eloc 6
nc 3
nop 0
1
<?php
2
3
namespace devgroup\JsTreeWidget\widgets;
4
5
use Yii;
6
use yii\base\InvalidConfigException;
7
use yii\base\Widget;
8
use yii\helpers\Html;
9
use yii\helpers\Json;
10
use yii\helpers\Url;
11
use yii\web\JsExpression;
12
use yii\helpers\ArrayHelper;
13
use yii\web\View;
14
15
/**
16
 * JsTree widget for Yii Framework 2
17
 */
18
class TreeWidget extends Widget
19
{
20
21
    const TREE_TYPE_ADJACENCY = 'adjacency';
22
    const TREE_TYPE_NESTED_SET = 'nested-set';
23
24
    public $treeType = self::TREE_TYPE_ADJACENCY;
25
    /**
26
     * @var array Enabled jsTree plugins
27
     * @see http://www.jstree.com/plugins/
28
     */
29
    public $plugins = [
30
        'wholerow',
31
        'contextmenu',
32
        'dnd',
33
        'types',
34
        'state',
35
    ];
36
37
    /**
38
     * @var array Configuration for types plugin
39
     * @see http://www.jstree.com/api/#/?f=$.jstree.defaults.types
40
     */
41
    public $types = [
42
        'show' => [
43
            'icon' => 'fa fa-file-o',
44
        ],
45
        'list' => [
46
            'icon' => 'fa fa-list',
47
        ],
48
    ];
49
50
    /**
51
     * Context menu actions configuration.
52
     * @var array
53
     */
54
    public $contextMenuItems = [];
55
56
    /**
57
     * Various options for jsTree plugin. Will be merged with default options.
58
     * @var array
59
     */
60
    public $options = [];
61
62
    /**
63
     * Route to action which returns json data for tree
64
     * @var array
65
     */
66
    public $treeDataRoute = null;
67
68
    /**
69
     * Translation category for Yii::t() which will be applied to labels.
70
     * If translation is not needed - use false.
71
     */
72
    public $menuLabelsTranslationCategory = 'app';
73
74
    /**
75
     * JsExpression for action(callback function) on double click. You can use JsExpression or make custom expression.
76
     * Warning! Callback function differs from native jsTree function - it consumes only one attribute - node(similar to contextmenu action).
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 141 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
77
     * Use false if no action needed.
78
     * @var bool|JsExpression
79
     */
80
    public $doubleClickAction = false;
81
82
    /** @var bool|array route to change parent action (applicable to Adjacency List only) */
83
    public $changeParentAction = false;
84
85
    /** @var bool|array route to reorder action */
86
    public $reorderAction = false;
87
88
    /** @var bool plugin config option for allow multiple nodes selections or not */
89
    public $multiSelect = false;
90
91
    /**
92
     * @inheritdoc
93
     */
94
    public function init()
95
    {
96
        self::registerTranslations();
97
        parent::init();
98
    }
99
100
    public static function registerTranslations()
101
    {
102
        Yii::$app->i18n->translations['jstw'] = [
103
            'class' => 'yii\i18n\PhpMessageSource',
104
            'basePath' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'messages',
105
        ];
106
    }
107
108
    /**
109
     * @inheritdoc
110
     */
111
    public function run()
112
    {
113
        if (!is_array($this->treeDataRoute)) {
114
            throw new InvalidConfigException("Attribute treeDataRoute is required to use TreeWidget.");
115
        }
116
117
        $options = [
118
            'plugins' => $this->plugins,
119
            'core' => [
120
                'check_callback' => true,
121
                'multiple' => $this->multiSelect,
122
                'data' => [
123
                    'url' => new JsExpression(
124
                        "function (node) {
125
                            return " . Json::encode(Url::to($this->treeDataRoute)) . ";
126
                        }"
127
                    ),
128
                    'success' => new JsExpression(
129
                        "function (node) {
130
                            return { 'id' : node.id };
131
                        }"
132
                    ),
133
                    'error' => new JsExpression(
134
                        "function ( o, textStatus, errorThrown ) {
135
                            alert(o.responseText);
136
                        }"
137
                    )
138
                ]
139
            ]
140
        ];
141
142
        // merge with contextmenu configuration
143
        $options = ArrayHelper::merge($options, $this->contextMenuOptions());
144
145
        // merge with attribute-provided options
146
        $options = ArrayHelper::merge($options, $this->options);
147
148
        $options = Json::encode($options);
149
150
        $this->getView()->registerAssetBundle('devgroup\JsTreeWidget\widgets\JsTreeAssetBundle');
151
152
        $doubleClick = '';
153
        if ($this->doubleClickAction !== false) {
154
            $doubleClick = "
155
            jsTree_{$this->getId()}.on('dblclick.jstree', function (e) {
156
                var node = $(e.target).closest('.jstree-node').children('.jstree-anchor');
157
                var callback = " . $this->doubleClickAction . ";
158
                callback(node);
159
                return false;
160
            });\n";
161
        }
162
        $treeJs = $this->prepareJs();
163
        $this->getView()->registerJs("
164
        var jsTree_{$this->getId()} = \$('#{$this->getId()}').jstree($options);
165
        $doubleClick $treeJs", View::POS_READY);
166
        return Html::tag('div', '', ['id' => $this->getId()]);
167
    }
168
169
    /**
170
     * @return array
171
     */
172
    private function contextMenuOptions()
173
    {
174
        $options = [];
175
        if (count($this->contextMenuItems) > 0) {
176
            if (!in_array('contextmenu', $this->plugins)) {
177
                // add missing contextmenu plugin
178
                $options['plugins'] = ['contextmenu'];
179
            }
180
181
            $options['contextmenu']['items'] = [];
182
            foreach ($this->contextMenuItems as $index => $item) {
183
                if ($this->menuLabelsTranslationCategory !== false) {
184
                    $item['label'] = Yii::t($this->menuLabelsTranslationCategory, $item['label']);
185
                }
186
                $options['contextmenu']['items'][$index] = $item;
187
            }
188
        }
189
        return $options;
190
    }
191
192
    /**
193
     * Prepares js according to given tree type
194
     *
195
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
196
     */
197
    private function prepareJs()
198
    {
199
        switch ($this->treeType) {
200
            case self::TREE_TYPE_ADJACENCY :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
201
                return $this->adjacencyJs();
202
            case self::TREE_TYPE_NESTED_SET :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
203
                return $this->nestedSetJs();
204
        }
205
    }
206
207
    /**
208
     * @return string
209
     */
210
    private function adjacencyJs()
211
    {
212
        $changeParentJs = '';
213
        if ($this->changeParentAction !== false) {
214
            $changeParentUrl = is_array($this->changeParentAction) ? Url::to($this->changeParentAction) : $this->changeParentAction;
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 132 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
215
            $changeParentJs = <<<JS
216
             jsTree_{$this->getId()}.on('move_node.jstree', function(e, data) {
217
                var \$this = $(this);
218
                $.get('$changeParentUrl', {
219
                        'id': data.node.id,
220
                        'parent_id': data.parent
221
                    }, "json")
222
                    .done(function (data) {
223
                        if ('undefined' !== typeof(data.error)) {
224
                            alert(data.error);
225
                        }
226
                        \$this.jstree('refresh');
227
                    })
228
                    .fail(function ( o, textStatus, errorThrown ) {
229
                        alert(o.responseText);
230
                    });
231
                return false;
232
            });
233
JS;
234
        }
235
236
        $reorderJs = '';
237
        if ($this->reorderAction !== false) {
238
            $reorderUrl = is_array($this->reorderAction) ? Url::to($this->reorderAction) : $this->reorderAction;
239
            $reorderJs = <<<JS
240
            jsTree_{$this->getId()}.on('move_node.jstree', function(e, data) {
241
                var params = [];
242
                $('.jstree-node').each(function(i, e) {
243
                    params[e.id] = i;
244
                });
245
                $.post('$reorderUrl',
246
                    {'order':params }
247
                    , "json")
248
                    .done(function (data) {
249
                        if ('undefined' !== typeof(data.error)) {
250
                            alert(data.error);
251
                        }
252
                        \$this.jstree('refresh');
253
                    })
254
                    .fail(function ( o, textStatus, errorThrown ) {
255
                        alert(o.responseText);
256
                    });
257
                return false;
258
            });
259
JS;
260
        }
261
        return $changeParentJs . "\n" . $reorderJs . "\n";
262
    }
263
264
    /**
265
     * @return string
266
     */
267
    private function nestedSetJs()
268
    {
269
        $js = "";
270
        if (false !== $this->reorderAction || false !== $this->changeParentAction) {
271
            $action = $this->reorderAction ?: $this->changeParentAction;
272
            $url = is_array($action) ? Url::to($action) : $action;
273
            $js = <<<JS
274
                jsTree_{$this->getId()}.on('move_node.jstree', function(e, data) {
275
                    var \$this = $(this),
276
                        \$parent = \$this.jstree(true).get_node(data.parent),
277
                        \$oldParent = \$this.jstree(true).get_node(data.old_parent),
278
                        siblings = \$parent.children || {};
279
                    $.post('$url', {
280
                            'node_id' : data.node.id,
281
                            'parent': data.parent,
282
                            'position': data.position,
283
                            'old_parent': data.old_parent,
284
                            'old_position': data.old_position,
285
                            'is_multi': data.is_multi,
286
                            'siblings': siblings
287
                         }, "json")
288
                         .done(function (data) {
289
                            if ('undefined' !== typeof(data.error)) {
290
                                alert(data.error);
291
                            }
292
                            \$this.jstree('refresh');
293
                         })
294
                         .fail(function ( o, textStatus, errorThrown ) {
295
                            alert(o.responseText);
296
                         });
297
                    return false;
298
                });
299
JS;
300
        }
301
        return $js . "\n";
302
    }
303
}
304