7f7eae8895181abd8a4fcd60434c083783cefa74
[Packages/TYPO3.CMS.git] / typo3 / sysext / rtehtmlarea / htmlarea / plugins / MicrodataSchema / microdata-schema.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2012 Stanislas Rolland <typo3(arobas)sjbr.ca>
5 * All rights reserved
6 *
7 * This script is part of the TYPO3 project. The TYPO3 project is
8 * free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * The GNU General Public License can be found at
14 * http://www.gnu.org/copyleft/gpl.html.
15 * A copy is found in the textfile GPL.txt and important notices to the license
16 * from the author is found in LICENSE.txt distributed with these scripts.
17 *
18 *
19 * This script is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * This copyright notice MUST APPEAR in all copies of the script!
25 ***************************************************************/
26 /**
27 * Microdata Schema Plugin for TYPO3 htmlArea RTE
28 */
29 HTMLArea.MicrodataSchema = Ext.extend(HTMLArea.Plugin, {
30 /*
31 * This function gets called by the class constructor
32 */
33 configurePlugin: function (editor) {
34 /*
35 * Registering plugin "About" information
36 */
37 var pluginInformation = {
38 version : '1.0',
39 developer : 'Stanislas Rolland',
40 developerUrl : 'http://www.sjbr.ca/',
41 copyrightOwner : 'Stanislas Rolland',
42 sponsor : 'SJBR',
43 sponsorUrl : 'http://www.sjbr.ca/',
44 license : 'GPL'
45 };
46 this.registerPluginInformation(pluginInformation);
47 /*
48 * Registering the buttons
49 */
50 var buttonList = this.buttonList, buttonId;
51 for (var i = 0, n = buttonList.length; i < n; ++i) {
52 var button = buttonList[i];
53 buttonId = button[0];
54 var buttonConfiguration = {
55 id : buttonId,
56 tooltip : this.localize(buttonId + '-Tooltip'),
57 iconCls : 'htmlarea-action-' + button[2],
58 action : 'onButtonPress',
59 context : button[1]
60 };
61 this.registerButton(buttonConfiguration);
62 }
63 },
64 /*
65 * The list of buttons added by this plugin
66 */
67 buttonList: [
68 ['ShowMicrodata', null, 'microdata-show']
69 ],
70 /*
71 * Default configuration values for dialogue form fields
72 */
73 configDefaults: {
74 combo: {
75 editable: true,
76 selectOnFocus: true,
77 typeAhead: true,
78 triggerAction: 'all',
79 forceSelection: true,
80 mode: 'local',
81 valueField: 'name',
82 displayField: 'label',
83 helpIcon: true,
84 tpl: '<tpl for="."><div ext:qtip="{comment}" style="text-align:left;font-size:11px;" class="x-combo-list-item">{label}</div></tpl>'
85 }
86 },
87 /*
88 * This function gets called when the editor is generated
89 */
90 onGenerate: function () {
91 // Create the types and properties stores
92 this.typeStore = Ext.StoreMgr.lookup('itemtype');
93 if (!this.typeStore) {
94 this.typeStore = new Ext.data.JsonStore({
95 autoDestroy: false,
96 autoLoad: true,
97 fields: [{ name: 'label'}, { name: 'name'}, { name: 'comment'}, { name: 'subClassOf'}],
98 listeners: {
99 load: {
100 fn: this.addMicrodataMarkingRules,
101 scope: this
102 }
103 },
104 root: 'types',
105 storeId: 'itemtype',
106 url: this.editorConfiguration.schemaUrl
107 });
108 } else {
109 this.addMicrodataMarkingRules(this.typeStore);
110 }
111 this.typeStore = Ext.StoreMgr.lookup('itemprop');
112 if (!this.propertyStore) {
113 this.propertyStore = new Ext.data.JsonStore({
114 autoDestroy: false,
115 autoLoad: true,
116 fields: [{ name: 'label'}, { name: 'name'}, { name: 'comment'}, { name: 'domain'}, { name: 'range'}],
117 listeners: {
118 load: {
119 fn: this.addMicrodataMarkingRules,
120 scope: this
121 }
122 },
123 root: 'properties',
124 storeId: 'itemprop',
125 url: this.editorConfiguration.schemaUrl
126 });
127 } else {
128 this.addMicrodataMarkingRules(this.propertyStore);
129 }
130 },
131 /*
132 * This function adds rules to the stylesheet for language mark highlighting
133 * Model: body.htmlarea-show-language-marks *[lang=en]:before { content: "en: "; }
134 * Works in IE8, but not in earlier versions of IE
135 */
136 addMicrodataMarkingRules: function (store) {
137 var styleSheet = this.editor.document.styleSheets[0];
138 store.each(function (option) {
139 var selector = 'body.htmlarea-show-microdata *[' + store.storeId + '="' + option.get('name') + '"]:before';
140 var style = 'content: "' + option.get('label') + ': "; font-variant: small-caps;';
141 var rule = selector + ' { ' + style + ' }';
142 if (!Ext.isIE) {
143 try {
144 styleSheet.insertRule(rule, styleSheet.cssRules.length);
145 } catch (e) {
146 this.appendToLog('onGenerate', 'Error inserting css rule: ' + rule + ' Error text: ' + e, 'warn');
147 }
148 } else {
149 styleSheet.addRule(selector, style);
150 }
151 return true;
152 }, this);
153 },
154 /*
155 * This function gets called when a button was pressed.
156 *
157 * @param object editor: the editor instance
158 * @param string id: the button id or the key
159 *
160 * @return boolean false if action is completed
161 */
162 onButtonPress: function (editor, id, target) {
163 // Could be a button or its hotkey
164 var buttonId = this.translateHotKey(id);
165 buttonId = buttonId ? buttonId : id;
166
167 switch (buttonId) {
168 case 'ShowMicrodata':
169 this.toggleMicrodata();
170 break;
171 default :
172 break;
173 }
174 return false;
175 },
176 /*
177 * Toggles the display of microdata
178 *
179 * @param boolean forceMicrodata: if set, microdata is displayed whatever the current state
180 *
181 * @return void
182 */
183 toggleMicrodata: function (forceMicrodata) {
184 var body = this.editor.document.body;
185 if (!HTMLArea.DOM.hasClass(body, 'htmlarea-show-microdata')) {
186 HTMLArea.DOM.addClass(body,'htmlarea-show-microdata');
187 } else if (!forceMicrodata) {
188 HTMLArea.DOM.removeClass(body,'htmlarea-show-microdata');
189 }
190 },
191 /*
192 * This function builds the configuration object for the Microdata fieldset
193 *
194 * @param object element: the element being edited, if any
195 * @param object configured properties for the microdata fields
196 *
197 * @return object the fieldset configuration object
198 */
199 buildMicrodataFieldsetConfig: function (element, properties) {
200 var typeStore = Ext.StoreMgr.lookup('itemtype');
201 var propertyStore = Ext.StoreMgr.lookup('itemprop');
202 var itemsConfig = [];
203 this.inheritedType = 'none';
204 var parent = element.parentNode;
205 while (parent && !/^(body)$/i.test(parent.nodeName)) {
206 if (parent.getAttribute('itemtype')) {
207 this.inheritedType = parent.getAttribute('itemtype');
208 break;
209 } else {
210 parent = parent.parentNode;
211 }
212 }
213 var selectedType = element && element.getAttribute('itemtype') ? element.getAttribute('itemtype') : 'none';
214 var selectedProperty = element && element.getAttribute('itemprop') ? element.getAttribute('itemprop') : 'none';
215 itemsConfig.push({
216 xtype: 'displayfield',
217 itemId: 'currentItemType',
218 fieldLabel: this.getHelpTip('currentItemType', 'currentItemType'),
219 style: {
220 fontWeight: 'bold'
221 },
222 value: this.inheritedType
223 });
224 itemsConfig.push(Ext.applyIf({
225 xtype: 'combo',
226 fieldLabel: this.getHelpTip('itemprop', 'itemprop'),
227 hidden: this.inheritedType === 'none',
228 itemId: 'itemprop',
229 store: propertyStore,
230 value: selectedProperty,
231 width: ((properties['itemprop'] && properties['itemprop'].width) ? properties['itemprop'].width : 300)
232 }, this.configDefaults['combo']));
233 itemsConfig.push({
234 itemId: 'itemscope',
235 fieldLabel: this.getHelpTip('itemscope', 'itemscope'),
236 listeners: {
237 check: {
238 fn: this.onItemScopeChecked,
239 scope: this
240 }
241 },
242 style: {
243 marginBottom: '5px'
244 },
245 checked: element ? (element.getAttribute('itemscope') === 'itemscope') : false,
246 xtype: 'checkbox'
247 });
248 itemsConfig.push(Ext.applyIf({
249 xtype: 'combo',
250 fieldLabel: this.getHelpTip('itemtype', 'itemtype'),
251 hidden: element && !element.getAttribute('itemscope'),
252 hideMode: 'visibility',
253 itemId: 'itemtype',
254 store: typeStore,
255 value: selectedType,
256 width: ((properties['itemtype'] && properties['itemtype'].width) ? properties['itemtype'].width : 300)
257 }, this.configDefaults['combo']));
258 return {
259 xtype: 'fieldset',
260 itemId: 'microdataFieldset',
261 title: this.getHelpTip('', 'microdata'),
262 defaultType: 'textfield',
263 defaults: {
264 labelSeparator: ':'
265 },
266 items: itemsConfig,
267 labelWidth: 100,
268 listeners: {
269 afterrender: {
270 fn: this.onMicroDataRender,
271 scope: this
272 }
273 }
274 };
275 },
276 /*
277 * Handler invoked when the Microdata fieldset is rendered
278 */
279 onMicroDataRender: function (fieldset) {
280 this.fieldset = fieldset;
281 var typeStore = Ext.StoreMgr.lookup('itemtype');
282 var index = typeStore.findExact('name', this.inheritedType);
283 if (index !== -1) {
284 // If there is an inherited type, set the label
285 var inheritedTypeName = typeStore.getAt(index).get('label');
286 this.fieldset.find('itemId', 'currentItemType')[0].setValue(inheritedTypeName);
287 // Filter the properties by the inherited type, if any
288 var propertyCombo = this.fieldset.find('itemId', 'itemprop')[0];
289 var selectedProperty = propertyCombo.getValue();
290 // Filter the properties by the inherited type, if any
291 this.filterPropeties(this.inheritedType, selectedProperty);
292 }
293 },
294 /*
295 * Handler invoked when the itemscope checkbox is checked/unchecked
296 *
297 */
298 onItemScopeChecked: function (checkbox, checked) {
299 this.fieldset.find('itemId', 'itemtype')[0].setVisible(checked);
300 this.synch
301 },
302 /*
303 * Filter out properties not part of the selected type
304 */
305 filterPropeties: function (type, selectedProperty) {
306 var typeStore = Ext.StoreMgr.lookup('itemtype');
307 var propertyStore = Ext.StoreMgr.lookup('itemprop');
308 if (propertyStore.realSnapshot) {
309 propertyStore.snapshot = propertyStore.realSnapshot;
310 delete propertyStore.realSnapshot;
311 propertyStore.clearFilter(true);
312 }
313 var index,
314 superType = type,
315 superTypes = [];
316 while (superType) {
317 superTypes.push(superType);
318 index = typeStore.findExact('name', superType);
319 if (index !== -1) {
320 superType = typeStore.getAt(index).get('subClassOf');
321 } else {
322 superType = null;
323 }
324 }
325 var superTypes = new RegExp( '^(' + superTypes.join('|') + ')$', 'i');
326 propertyStore.filterBy(function (property) {
327 // Filter out properties not part of the type
328 return superTypes.test(property.get('domain')) || property.get('name') === 'none';
329 });
330 // Make sure the combo list is filtered
331 propertyStore.realSnapshot = propertyStore.snapshot;
332 propertyStore.snapshot = propertyStore.data;
333 var propertyCombo = this.fieldset.find('itemId', 'itemprop')[0];
334 propertyCombo.clearValue();
335 propertyCombo.setValue(selectedProperty);
336 },
337 /*
338 * Set microdata attributes of the element
339 */
340 setMicrodataAttributes: function (element) {
341 var comboFields = this.fieldset.findByType('combo');
342 Ext.each(comboFields, function (field) {
343 var itemId = field.getItemId();
344 var value = field.getValue();
345 switch (itemId) {
346 case 'itemprop':
347 case 'itemtype':
348 element.setAttribute(itemId, (value === 'none') ? '' : value);
349 break;
350 }
351 }, this);
352 var itemScopeField = this.fieldset.find('itemId', 'itemscope')[0];
353 if (itemScopeField.getValue()) {
354 element.setAttribute('itemscope', 'itemscope');
355 } else {
356 element.removeAttribute('itemscope');
357 element.removeAttribute('itemtype');
358 }
359 },
360 /*
361 * This function gets called when the toolbar is updated
362 */
363 onUpdateToolbar: function (button, mode, selectionEmpty, ancestors, endPointsInSameBlock) {
364 if (this.getEditorMode() === 'wysiwyg' && this.editor.isEditable()) {
365 switch (button.itemId) {
366 case 'ShowMicrodata':
367 button.setInactive(!HTMLArea.DOM.hasClass(this.editor.document.body, 'htmlarea-show-microdata'));
368 break;
369 default:
370 break;
371 }
372 }
373 }
374 });