UNPKG

7.23 kBJavaScriptView Raw
1/**
2 * @license Copyright (c) 2003-2023, CKSource Holding sp. z o.o. All rights reserved.
3 * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4 */
5/**
6 * @module media-embed/ui/mediaformview
7 */
8import { ButtonView, FocusCycler, LabeledFieldView, View, ViewCollection, createLabeledInputText, submitHandler } from 'ckeditor5/src/ui';
9import { FocusTracker, KeystrokeHandler } from 'ckeditor5/src/utils';
10import { icons } from 'ckeditor5/src/core';
11// See: #8833.
12// eslint-disable-next-line ckeditor5-rules/ckeditor-imports
13import '@ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css';
14import '../../theme/mediaform.css';
15/**
16 * The media form view controller class.
17 *
18 * See {@link module:media-embed/ui/mediaformview~MediaFormView}.
19 */
20export default class MediaFormView extends View {
21 /**
22 * @param validators Form validators used by {@link #isValid}.
23 * @param locale The localization services instance.
24 */
25 constructor(validators, locale) {
26 super(locale);
27 const t = locale.t;
28 this.focusTracker = new FocusTracker();
29 this.keystrokes = new KeystrokeHandler();
30 this.set('mediaURLInputValue', '');
31 this.urlInputView = this._createUrlInput();
32 this.saveButtonView = this._createButton(t('Save'), icons.check, 'ck-button-save');
33 this.saveButtonView.type = 'submit';
34 this.saveButtonView.bind('isEnabled').to(this, 'mediaURLInputValue', value => !!value);
35 this.cancelButtonView = this._createButton(t('Cancel'), icons.cancel, 'ck-button-cancel', 'cancel');
36 this._focusables = new ViewCollection();
37 this._focusCycler = new FocusCycler({
38 focusables: this._focusables,
39 focusTracker: this.focusTracker,
40 keystrokeHandler: this.keystrokes,
41 actions: {
42 // Navigate form fields backwards using the <kbd>Shift</kbd> + <kbd>Tab</kbd> keystroke.
43 focusPrevious: 'shift + tab',
44 // Navigate form fields forwards using the <kbd>Tab</kbd> key.
45 focusNext: 'tab'
46 }
47 });
48 this._validators = validators;
49 this.setTemplate({
50 tag: 'form',
51 attributes: {
52 class: [
53 'ck',
54 'ck-media-form',
55 'ck-responsive-form'
56 ],
57 tabindex: '-1'
58 },
59 children: [
60 this.urlInputView,
61 this.saveButtonView,
62 this.cancelButtonView
63 ]
64 });
65 }
66 /**
67 * @inheritDoc
68 */
69 render() {
70 super.render();
71 submitHandler({
72 view: this
73 });
74 const childViews = [
75 this.urlInputView,
76 this.saveButtonView,
77 this.cancelButtonView
78 ];
79 childViews.forEach(v => {
80 // Register the view as focusable.
81 this._focusables.add(v);
82 // Register the view in the focus tracker.
83 this.focusTracker.add(v.element);
84 });
85 // Start listening for the keystrokes coming from #element.
86 this.keystrokes.listenTo(this.element);
87 const stopPropagation = (data) => data.stopPropagation();
88 // Since the form is in the dropdown panel which is a child of the toolbar, the toolbar's
89 // keystroke handler would take over the key management in the URL input. We need to prevent
90 // this ASAP. Otherwise, the basic caret movement using the arrow keys will be impossible.
91 this.keystrokes.set('arrowright', stopPropagation);
92 this.keystrokes.set('arrowleft', stopPropagation);
93 this.keystrokes.set('arrowup', stopPropagation);
94 this.keystrokes.set('arrowdown', stopPropagation);
95 }
96 /**
97 * @inheritDoc
98 */
99 destroy() {
100 super.destroy();
101 this.focusTracker.destroy();
102 this.keystrokes.destroy();
103 }
104 /**
105 * Focuses the fist {@link #_focusables} in the form.
106 */
107 focus() {
108 this._focusCycler.focusFirst();
109 }
110 /**
111 * The native DOM `value` of the {@link #urlInputView} element.
112 *
113 * **Note**: Do not confuse it with the {@link module:ui/inputtext/inputtextview~InputTextView#value}
114 * which works one way only and may not represent the actual state of the component in the DOM.
115 */
116 get url() {
117 return this.urlInputView.fieldView.element.value.trim();
118 }
119 set url(url) {
120 this.urlInputView.fieldView.element.value = url.trim();
121 }
122 /**
123 * Validates the form and returns `false` when some fields are invalid.
124 */
125 isValid() {
126 this.resetFormStatus();
127 for (const validator of this._validators) {
128 const errorText = validator(this);
129 // One error per field is enough.
130 if (errorText) {
131 // Apply updated error.
132 this.urlInputView.errorText = errorText;
133 return false;
134 }
135 }
136 return true;
137 }
138 /**
139 * Cleans up the supplementary error and information text of the {@link #urlInputView}
140 * bringing them back to the state when the form has been displayed for the first time.
141 *
142 * See {@link #isValid}.
143 */
144 resetFormStatus() {
145 this.urlInputView.errorText = null;
146 this.urlInputView.infoText = this._urlInputViewInfoDefault;
147 }
148 /**
149 * Creates a labeled input view.
150 *
151 * @returns Labeled input view instance.
152 */
153 _createUrlInput() {
154 const t = this.locale.t;
155 const labeledInput = new LabeledFieldView(this.locale, createLabeledInputText);
156 const inputField = labeledInput.fieldView;
157 this._urlInputViewInfoDefault = t('Paste the media URL in the input.');
158 this._urlInputViewInfoTip = t('Tip: Paste the URL into the content to embed faster.');
159 labeledInput.label = t('Media URL');
160 labeledInput.infoText = this._urlInputViewInfoDefault;
161 inputField.on('input', () => {
162 // Display the tip text only when there is some value. Otherwise fall back to the default info text.
163 labeledInput.infoText = inputField.element.value ? this._urlInputViewInfoTip : this._urlInputViewInfoDefault;
164 this.mediaURLInputValue = inputField.element.value.trim();
165 });
166 return labeledInput;
167 }
168 /**
169 * Creates a button view.
170 *
171 * @param label The button label.
172 * @param icon The button icon.
173 * @param className The additional button CSS class name.
174 * @param eventName An event name that the `ButtonView#execute` event will be delegated to.
175 * @returns The button view instance.
176 */
177 _createButton(label, icon, className, eventName) {
178 const button = new ButtonView(this.locale);
179 button.set({
180 label,
181 icon,
182 tooltip: true
183 });
184 button.extendTemplate({
185 attributes: {
186 class: className
187 }
188 });
189 if (eventName) {
190 button.delegate('execute').to(this, eventName);
191 }
192 return button;
193 }
194}