9562ded5362c608adebfb3183cf2f22df572a2eb
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / Resources / Public / JavaScript / Plugins / MicrodataSchema.js
1 /*
2 * This file is part of the TYPO3 CMS project.
3 *
4 * It is free software; you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License, either version 2
6 * of the License, or any later version.
7 *
8 * For the full copyright and license information, please read the
9 * LICENSE.txt file that was distributed with this source code.
10 *
11 * The TYPO3 project - inspiring people to share!
12 */
13
14 /**
15 * Microdata Schema Plugin for TYPO3 htmlArea RTE
16 */
17 define([
18 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Plugin/Plugin',
19 'TYPO3/CMS/Rtehtmlarea/HTMLArea/UserAgent/UserAgent',
20 'TYPO3/CMS/Rtehtmlarea/HTMLArea/DOM/DOM',
21 'TYPO3/CMS/Rtehtmlarea/HTMLArea/Util/Util',
22 'jquery',
23 'TYPO3/CMS/Backend/Modal',
24 'TYPO3/CMS/Backend/Severity'
25 ], function (Plugin, UserAgent, Dom, Util, $, Modal, Severity) {
26
27 var MicrodataSchema = function (editor, pluginName) {
28 this.constructor.super.call(this, editor, pluginName);
29 };
30 Util.inherit(MicrodataSchema, Plugin);
31 Util.apply(MicrodataSchema.prototype, {
32
33 /**
34 * This function gets called by the class constructor
35 */
36 configurePlugin: function (editor) {
37
38 /**
39 * Registering plugin "About" information
40 */
41 var pluginInformation = {
42 version : '1.0',
43 developer : 'Stanislas Rolland',
44 developerUrl : 'http://www.sjbr.ca/',
45 copyrightOwner : 'Stanislas Rolland',
46 sponsor : 'SJBR',
47 sponsorUrl : 'http://www.sjbr.ca/',
48 license : 'GPL'
49 };
50 this.registerPluginInformation(pluginInformation);
51
52 /**
53 * Registering the buttons
54 */
55 var button = this.buttonList[0];
56 var buttonId = button[0];
57 var buttonConfiguration = {
58 id : buttonId,
59 tooltip : this.localize(buttonId + '-Tooltip'),
60 iconCls : 'htmlarea-action-' + button[2],
61 action : 'onButtonPress',
62 context : button[1]
63 };
64 this.registerButton(buttonConfiguration);
65 return true;
66 },
67
68 /**
69 * The list of buttons added by this plugin
70 */
71 buttonList: [
72 ['ShowMicrodata', null, 'microdata-show']
73 ],
74
75 itemtype: [],
76 itemprop: [],
77 filteredProperties: [],
78 /**
79 * This function gets called when the editor is generated
80 */
81 onGenerate: function () {
82 var self = this;
83 $.ajax({
84 url: this.editorConfiguration.schemaUrl,
85 dataType: 'json',
86 success: function (response) {
87 self.itemtype = response.types;
88 self.itemprop = response.properties;
89 self.addMicrodataMarkingRules('itemtype', response.types);
90 self.addMicrodataMarkingRules('itemprop', response.properties);
91 }
92 });
93 },
94 /**
95 * This function adds rules to the stylesheet for language mark highlighting
96 * Model: body.htmlarea-show-language-marks *[lang=en]:before { content: "en: "; }
97 * Works in IE8, but not in earlier versions of IE
98 *
99 * @param {String} category
100 * @param {Array} items
101 */
102 addMicrodataMarkingRules: function (category, items) {
103 var styleSheet = this.editor.document.styleSheets[0];
104 $.each(items, function (_, option) {
105 var selector = 'body.htmlarea-show-microdata *[' + category + '="' + option.name + '"]:before';
106 var style = 'content: "' + option.label + ': "; font-variant: small-caps;';
107 var rule = selector + ' { ' + style + ' }';
108 try {
109 styleSheet.insertRule(rule, styleSheet.cssRules.length);
110 } catch (e) {
111 this.appendToLog('onGenerate', 'Error inserting css rule: ' + rule + ' Error text: ' + e, 'warn');
112 }
113 });
114 },
115
116 /**
117 * This function gets called when a button was pressed.
118 *
119 * @param {Object} editor The editor instance
120 * @param {String} id The button id or the key
121 * @return {Boolean} false if action is completed
122 */
123 onButtonPress: function (editor, id) {
124 // Could be a button or its hotkey
125 var buttonId = this.translateHotKey(id);
126 buttonId = buttonId ? buttonId : id;
127 switch (buttonId) {
128 case 'ShowMicrodata':
129 this.toggleMicrodata();
130 break;
131 }
132 return false;
133 },
134
135 /**
136 * Toggles the display of microdata
137 *
138 * @param {Boolean} forceMicrodata If set, microdata is displayed whatever the current state
139 */
140 toggleMicrodata: function (forceMicrodata) {
141 var body = this.editor.document.body;
142 if (!Dom.hasClass(body, 'htmlarea-show-microdata')) {
143 Dom.addClass(body,'htmlarea-show-microdata');
144 } else if (!forceMicrodata) {
145 Dom.removeClass(body,'htmlarea-show-microdata');
146 }
147 },
148 /**
149 * This function builds the configuration object for the Microdata fieldset
150 *
151 * @param {Object} element The element being edited, if any
152 * @param {Object} properties configured properties for the microdata fields
153 * @return {Object} the fieldset configuration object
154 */
155 buildMicrodataFieldsetConfig: function (element, properties) {
156 var $fieldset = $('<fieldset />');
157 var typeStore = this.itemtype;
158 var propertyStore = this.filteredProperties.length > 0 ? this.filteredProperties : this.itemprop;
159 this.inheritedType = 'none';
160 var parent = element.parentNode;
161 while (parent && !/^(body)$/i.test(parent.nodeName)) {
162 if (parent.getAttribute('itemtype')) {
163 this.inheritedType = parent.getAttribute('itemtype');
164 break;
165 } else {
166 parent = parent.parentNode;
167 }
168 }
169 var selectedType = element && element.getAttribute('itemtype') ? element.getAttribute('itemtype') : 'none';
170 var selectedProperty = element && element.getAttribute('itemprop') ? element.getAttribute('itemprop') : 'none';
171
172 $fieldset.append(
173 $('<h4 />', {'class': 'form-section-headline'}).text(this.localize('microdata')),
174 $('<div />', {'class': 'form-group'}).append(
175 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('currentItemType', 'currentItemType')),
176 $('<div />', {'class': 'col-sm-10'}).append(
177 $('<p />', {'class': 'form-control-static'}).text(this.inheritedType)
178 )
179 )
180 );
181
182 var $itemPropSelect = $('<select />', {name: 'itemprop', 'class': 'form-control'});
183 this.attachItemProperties($itemPropSelect, propertyStore, selectedProperty);
184
185 $fieldset.append(
186 $('<div />', {'class': 'form-group'}).append(
187 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('itemprop', 'itemprop')),
188 $('<div />', {'class': 'col-sm-10'}).append(
189 $itemPropSelect
190 )
191 ).toggle(this.inheritedType !== 'none')
192 );
193
194 $fieldset.append(
195 $('<div />', {'class': 'form-group col-sm-12'}).append(
196 $('<div />', {'class': 'checkbox'}).append(
197 $('<label />').append(
198 $('<span />').html(this.getHelpTip('itemscope', 'itemscope'))
199 ).prepend(
200 $('<input />', {type: 'checkbox', name: 'itemscope'})
201 .prop('checked', element ? (element.getAttribute('itemscope') === 'itemscope') : false)
202 .on('click', $.proxy(this.onItemScopeChecked, this))
203 )
204 )
205 )
206 );
207
208 var $itemTypeSelect = $('<select />', {name: 'itemtype', 'class': 'form-control'});
209 for (var i = 0; i < typeStore.length; ++i) {
210 var attributeConfiguration = {
211 value: typeStore[i].name
212 };
213
214 if (typeStore[i].name === selectedType) {
215 attributeConfiguration.selected = 'selected';
216 }
217
218 $itemTypeSelect.append(
219 $('<option />', attributeConfiguration).text(typeStore[i].label)
220 );
221 }
222 $fieldset.append(
223 $('<div />', {'class': 'form-group'}).append(
224 $('<label />', {'class': 'col-sm-2'}).html(this.getHelpTip('itemtype', 'itemtype')),
225 $('<div />', {'class': 'col-sm-10'}).append(
226 $itemTypeSelect
227 )
228 ).toggle(!(element && !element.getAttribute('itemscope')))
229 );
230
231 this.onMicroDataRender($fieldset);
232
233 return $fieldset;
234 },
235 attachItemProperties: function($select, properties, selectedProperty) {
236 $select.empty();
237
238 for (var i = 0; i < properties.length; ++i) {
239 var attributeConfiguration = {
240 value: properties[i].name
241 };
242
243 if (properties[i].name === selectedProperty) {
244 attributeConfiguration.selected = 'selected';
245 }
246
247 $select.append(
248 $('<option />', attributeConfiguration).text(properties[i].label)
249 );
250 }
251 },
252 /**
253 * Handler invoked when the Microdata fieldset is rendered
254 *
255 * @param {Object} $fieldset
256 */
257 onMicroDataRender: function ($fieldset) {
258 this.fieldset = $fieldset;
259 var typeStore = this.itemtype,
260 index = -1;
261
262 for (var i = 0; i < typeStore.length; ++i) {
263 if (typeStore[i].name === this.inheritedType) {
264 index = i;
265 break;
266 }
267 }
268
269 if (index !== -1) {
270 // If there is an inherited type, set the label
271 var inheritedTypeName = typeStore[index].label;
272 this.fieldset.find('[name="currentItemType"]').val(inheritedTypeName);
273
274 // Filter the properties by the inherited type, if any
275 var propertyCombo = this.fieldset.find('[name="itemprop"]');
276 var selectedProperty = propertyCombo.val();
277
278 // Filter the properties by the inherited type, if any
279 this.filterProperties(this.inheritedType, selectedProperty);
280 }
281 },
282 /**
283 * Handler invoked when the itemscope checkbox is checked/unchecked
284 *
285 * @param {Event} e
286 */
287 onItemScopeChecked: function (e) {
288 this.fieldset.find('[name="itemtype"]').closest('.form-group').toggle($(e.currentTarget).prop('checked'));
289 },
290 /**
291 * Filter out properties not part of the selected type
292 */
293 filterProperties: function (type, selectedProperty) {
294 var self = this,
295 typeStore = this.itemtype,
296 propertyStore = this.itemprop,
297 index = -1,
298 superType = type,
299 superTypes = [];
300
301 if (this.filteredProperties.length > 0) {
302 this.filteredProperties = [];
303 }
304
305 while (superType) {
306 superTypes.push(superType);
307 for (var i = 0; i < typeStore.length; ++i) {
308 if (typeStore[i].name === superType) {
309 index = i;
310 break;
311 }
312 }
313 if (index !== -1) {
314 superType = typeStore[index].subClassOf;
315 } else {
316 superType = null;
317 }
318 }
319 superTypes = new RegExp( '^(' + superTypes.join('|') + ')$', 'i');
320
321 $.each(propertyStore, function() {
322 // Filter out properties not part of the type
323 if (superTypes.test(this.domain) || this.name === 'none') {
324 self.filteredProperties.push(this);
325 }
326 });
327
328 // Make sure the combo list is filtered
329 var propertyCombo = this.fieldset.find('[name="itemprop"]');
330 this.attachItemProperties(propertyCombo, this.filteredProperties, selectedProperty);
331 },
332
333 /**
334 * Set microdata attributes of the element
335 */
336 setMicrodataAttributes: function (element) {
337 if (this.fieldset) {
338 var comboFields = this.fieldset.find('select');
339 for (var i = comboFields.length; --i >= 0;) {
340 var field = $(comboFields[i]);
341 var itemId = field.attr('name');
342 var value = field.val();
343 switch (itemId) {
344 case 'itemprop':
345 case 'itemtype':
346 element.setAttribute(itemId, value === 'none' ? '' : value);
347 break;
348 }
349 }
350 var itemScopeField = this.fieldset.find('[name="itemscope"]');
351 if (itemScopeField) {
352 if (itemScopeField.prop('checked')) {
353 element.setAttribute('itemscope', 'itemscope');
354 } else {
355 element.removeAttribute('itemscope');
356 element.removeAttribute('itemtype');
357 }
358 }
359 }
360 },
361
362 /**
363 * This function gets called when the toolbar is updated
364 */
365 onUpdateToolbar: function (button, mode, selectionEmpty, ancestors, endPointsInSameBlock) {
366 if (this.getEditorMode() === 'wysiwyg' && this.editor.isEditable()) {
367 switch (button.itemId) {
368 case 'ShowMicrodata':
369 button.setInactive(!Dom.hasClass(this.editor.document.body, 'htmlarea-show-microdata'));
370 break;
371 default:
372 break;
373 }
374 }
375 }
376 });
377
378 return MicrodataSchema;
379 });