Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Contains class \core\output\inplace_editable
19
 *
20
 * @package    core
21
 * @category   output
22
 * @copyright  2016 Marina Glancy
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
namespace core\output;
27
 
28
use templatable;
29
use renderable;
30
use lang_string;
31
use pix_icon;
32
 
33
/**
34
 * Class allowing to quick edit a title inline
35
 *
36
 * This class is used for displaying an element that can be in-place edited by the user. To display call:
37
 * echo $OUTPUT->render($element);
38
 * or
39
 * echo $OUTPUT->render_from_template('core/inplace_editable', $element->export_for_template($OUTPUT));
40
 *
41
 * Template core/inplace_editable will automatically load javascript module with the same name
42
 * core/inplace_editable. Javascript module registers a click-listener on edit link and
43
 * then replaces the displayed value with an input field. On "Enter" it sends a request
44
 * to web service core_update_inplace_editable, which invokes the callback from the component.
45
 * Any exception thrown by the web service (or callback) is displayed as an error popup.
46
 *
47
 * Callback {$component}_inplace_editable($itemtype, $itemid, $newvalue) must be present in the lib.php file of
48
 * the component or plugin. It must return instance of this class.
49
 *
50
 * @package    core
51
 * @category   output
52
 * @copyright  2016 Marina Glancy
53
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
54
 */
55
class inplace_editable implements templatable, renderable {
56
 
57
    /**
58
     * @var string component responsible for diplsying/updating
59
     */
60
    protected $component = null;
61
 
62
    /**
63
     * @var string itemtype inside the component
64
     */
65
    protected $itemtype = null;
66
 
67
    /**
68
     * @var int identifier of the editable element (usually database id)
69
     */
70
    protected $itemid = null;
71
 
72
    /**
73
     * @var string value of the editable element as it is present in the database
74
     */
75
    protected $value = null;
76
 
77
    /**
78
     * @var string value of the editable element as it should be displayed,
79
     * must be formatted and may contain links or other html tags
80
     */
81
    protected $displayvalue = null;
82
 
83
    /**
84
     * @var string label for the input element (for screenreaders)
85
     */
86
    protected $editlabel = null;
87
 
88
    /**
89
     * @var string hint for the input element (for screenreaders)
90
     */
91
    protected $edithint = null;
92
 
93
    /**
94
     * @var pix_icon icon to use to toggle editing
95
     */
96
    protected $editicon = null;
97
 
98
    /**
99
     * @var bool indicates if the current user is allowed to edit this element - set in constructor after permissions are checked
100
     */
101
    protected $editable = false;
102
 
103
    /**
104
     * @var string type of the element - text, toggle or select
105
     */
106
    protected $type = 'text';
107
 
108
    /**
109
     * @var string options for the element, for example new value for the toggle or json-encoded list of options for select
110
     */
111
    protected $options = '';
112
 
113
    /**
114
     * Constructor.
115
     *
116
     * @param string $component name of the component or plugin responsible for the updating of the value (must declare callback)
117
     * @param string $itemtype type of the item inside the component - each component/plugin may implement multiple inplace-editable elements
118
     * @param int $itemid identifier of the item that can be edited in-place
119
     * @param bool $editable whether this value is editable (check capabilities and editing mode), if false, only "displayvalue"
120
     *              will be displayed without anything else
121
     * @param string $displayvalue what needs to be displayed to the user, it must be cleaned, with applied filters (call
122
     *              {@link format_string()}). It may be wrapped in an html link, contain icons or other decorations
123
     * @param string $value what needs to be edited - usually raw value from the database, it may contain multilang tags
124
     * @param lang_string|string $edithint hint (title) that will be displayed under the edit link
125
     * @param lang_string|string $editlabel label for the input element in the editing mode (for screenreaders)
126
     * @param pix_icon|null $editicon icon to use to toggle editing
127
     */
128
    public function __construct($component, $itemtype, $itemid, $editable,
129
            $displayvalue, $value = null, $edithint = null, $editlabel = null, ?pix_icon $editicon = null) {
130
        $this->component = $component;
131
        $this->itemtype = $itemtype;
132
        $this->itemid = $itemid;
133
        $this->editable = $editable;
134
        $this->displayvalue = $displayvalue;
135
        $this->value = $value;
136
        $this->edithint = $edithint;
137
        $this->editlabel = $editlabel;
138
        $this->editicon = $editicon;
139
    }
140
 
141
    /**
142
     * Sets the element type to be a toggle
143
     *
144
     * For toggle element $editlabel is not used.
145
     * $displayvalue must be specified, it can have text or icons but can not contain html links.
146
     *
147
     * Toggle element can have two or more options.
148
     *
149
     * @param array $options toggle options as simple, non-associative array; defaults to array(0,1)
150
     * @return self
151
     */
152
    public function set_type_toggle($options = null) {
153
        if ($options === null) {
154
            $options = array(0, 1);
155
        }
156
        $options = array_values($options);
157
        $idx = array_search($this->value, $options, true);
158
        if ($idx === false) {
159
            throw new \coding_exception('Specified value must be one of the toggle options');
160
        }
161
        $nextvalue = ($idx < count($options) - 1) ? $idx + 1 : 0;
162
 
163
        $this->type = 'toggle';
164
        $this->options = (string)$nextvalue;
165
        return $this;
166
    }
167
 
168
    /**
169
     * Sets the element type to be a dropdown
170
     *
171
     * For select element specifying $displayvalue is optional, if null it will
172
     * be assumed that $displayvalue = $options[$value].
173
     * However displayvalue can still be specified if it needs icons and/or
174
     * html links.
175
     *
176
     * If only one option specified, the element will not be editable.
177
     *
178
     * @param array $options associative array with dropdown options
179
     * @return self
180
     */
181
    public function set_type_select($options) {
182
        if (!array_key_exists($this->value, $options)) {
183
            throw new \coding_exception('Options for select element must contain an option for the specified value');
184
        }
185
        if (count($options) < 2) {
186
            $this->editable = false;
187
        }
188
        $this->type = 'select';
189
 
190
        $pairedoptions = [];
191
        foreach ($options as $key => $value) {
192
            $pairedoptions[] = [
193
                'key' => $key,
194
                'value' => $value,
195
            ];
196
        }
197
        $this->options = json_encode($pairedoptions);
198
        if ($this->displayvalue === null) {
199
            $this->displayvalue = $options[$this->value];
200
        }
201
        if ($this->editicon === null) {
202
            $this->editicon = new pix_icon('t/expanded', (string) $this->edithint);
203
        }
204
        return $this;
205
    }
206
 
207
    /**
208
     * Sets the element type to be an autocomplete field
209
     *
210
     * @param array $options associative array with dropdown options
211
     * @param array $attributes associative array with attributes for autoselect field. See AMD module core/form-autocomplete.
212
     * @return self
213
     */
214
    public function set_type_autocomplete($options, $attributes) {
215
        $this->type = 'autocomplete';
216
 
217
        $pairedoptions = [];
218
        foreach ($options as $key => $value) {
219
            $pairedoptions[] = [
220
                'key' => $key,
221
                'value' => $value,
222
            ];
223
        }
224
        $this->options = json_encode(['options' => $pairedoptions, 'attributes' => $attributes]);
225
        return $this;
226
    }
227
 
228
    /**
229
     * Whether the link should contain all of the content or not.
230
     */
231
    protected function get_linkeverything() {
232
        if ($this->type === 'toggle') {
233
            return true;
234
        }
235
 
236
        if (preg_match('#<a .*>.*</a>#', $this->displayvalue) === 1) {
237
            return false;
238
        }
239
 
240
        return true;
241
    }
242
 
243
    /**
244
     * Export this data so it can be used as the context for a mustache template (core/inplace_editable).
245
     *
246
     * @param \renderer_base $output typically, the renderer that's calling this function
247
     * @return array data context for a mustache template
248
     */
249
    public function export_for_template(\renderer_base $output) {
250
        if (!$this->editable) {
251
            return array(
252
                'displayvalue' => (string)$this->displayvalue
253
            );
254
        }
255
 
256
        if ($this->editicon === null) {
257
            $this->editicon = new pix_icon('t/editstring', (string) $this->edithint);
258
        }
259
 
260
        return array(
261
            'component' => $this->component,
262
            'itemtype' => $this->itemtype,
263
            'itemid' => $this->itemid,
264
            'displayvalue' => (string)$this->displayvalue,
265
            'value' => (string)$this->value,
266
            'edithint' => (string)$this->edithint,
267
            'editlabel' => (string)$this->editlabel,
268
            'editicon' => $this->editicon->export_for_pix(),
269
            'type' => $this->type,
270
            'options' => $this->options,
271
            'linkeverything' => $this->get_linkeverything() ? 1 : 0,
272
        );
273
    }
274
 
275
    /**
276
     * Renders this element
277
     *
278
     * @param \renderer_base $output typically, the renderer that's calling this function
279
     * @return string
280
     */
281
    public function render(\renderer_base $output) {
282
        return $output->render_from_template('core/inplace_editable', $this->export_for_template($output));
283
    }
284
}