d6fb97a0caf21e34e923870f2b0992dca3fa97e0
[Packages/TYPO3.CMS.git] / typo3 / sysext / lang / Resources / Public / JavaScript / LanguageModule.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 * Language module class
16 */
17 define(['jquery', 'moment', 'datatables', 'TYPO3/CMS/Backend/jquery.clearable'], function($, moment) {
18 'use strict';
19
20 var LanguageModule = {
21 me: this,
22 context: null,
23 table: null,
24 topMenu: null,
25 currentRequest: null,
26 settings: {},
27 icons: {},
28 labels: {},
29 identifiers: {
30 searchField: '.t3js-language-searchfield',
31 topMenu: 'div.menuItems',
32 activateIcon: 'span.activateIcon',
33 deactivateIcon: 'span.deactivateIcon',
34 downloadIcon: 'span.downloadIcon',
35 loadingIcon: 'span.loadingIcon',
36 completeIcon: 'span.completeIcon',
37 progressBar: 'div.progressBar',
38 progressBarText: 'div.progress-text',
39 progressBarInner: 'div.progress-bar',
40 lastUpdate: 'td.lastUpdate',
41 languagePrefix: 'language-',
42 extensionPrefix: 'extension-'
43 },
44 classes: {
45 enabled: 'enabled',
46 disabled: 'disabled',
47 processing: 'processing',
48 complete: 'complete',
49 extension: 'extensionName',
50 actions: 'actions',
51 progressBar: 'progressBar',
52 loading: 'loading',
53 lastUpdate: 'lastUpdate'
54 }
55 };
56
57 /**
58 * Initialize language table
59 */
60 LanguageModule.initializeLanguageTable = function(contextElement, tableElement) {
61 LanguageModule.context = $(contextElement);
62 LanguageModule.topMenu = $(LanguageModule.identifiers.topMenu);
63 LanguageModule.settings = LanguageModule.context.data();
64 LanguageModule.icons = LanguageModule.buildIcons();
65 LanguageModule.labels = LanguageModule.buildLabels();
66 LanguageModule.table = LanguageModule.buildLanguageTable(tableElement);
67 LanguageModule.initializeSearchField();
68 LanguageModule.initializeEventHandler();
69 };
70
71 /**
72 * Initialize translation table
73 */
74 LanguageModule.initializeTranslationTable = function(contextElement, tableElement) {
75 LanguageModule.context = $(contextElement);
76 LanguageModule.topMenu = $(LanguageModule.identifiers.topMenu);
77 LanguageModule.settings = LanguageModule.context.data();
78 LanguageModule.icons = LanguageModule.buildIcons();
79 LanguageModule.labels = LanguageModule.buildLabels();
80 LanguageModule.table = LanguageModule.buildTranslationTable(tableElement);
81 LanguageModule.initializeSearchField();
82 LanguageModule.initializeEventHandler();
83 };
84
85 /**
86 * Activate a language
87 */
88 LanguageModule.activateLanguageAction = function(triggerElement, parameters) {
89 var $row = $(triggerElement).closest('tr'),
90 locale = $row.data('locale');
91
92 if ($row.hasClass(LanguageModule.classes.processing)) {
93 LanguageModule.abortAjaxRequest();
94 }
95 LanguageModule.executeAjaxRequest(LanguageModule.settings.activateLanguageUri, {locale: locale}, function(response, status) {
96 if (status === 'success' && response.success) {
97 $row.removeClass(LanguageModule.classes.disabled).addClass(LanguageModule.classes.enabled);
98 LanguageModule.displaySuccess(LanguageModule.labels.languageActivated);
99 } else {
100 LanguageModule.displayError(LanguageModule.labels.errorOccurred);
101 }
102 });
103 };
104
105 /**
106 * Deactivate a language
107 */
108 LanguageModule.deactivateLanguageAction = function(triggerElement, parameters) {
109 var $row = $(triggerElement).closest('tr'),
110 locale = $row.data('locale');
111
112 if ($row.hasClass(LanguageModule.classes.processing)) {
113 LanguageModule.abortAjaxRequest();
114 }
115 LanguageModule.executeAjaxRequest(LanguageModule.settings.deactivateLanguageUri, {locale: locale}, function(response, status) {
116 if (status === 'success' && response.success) {
117 $row.removeClass(LanguageModule.classes.enabled).removeClass(LanguageModule.classes.complete).addClass(LanguageModule.classes.disabled);
118 LanguageModule.displaySuccess(LanguageModule.labels.languageDeactivated);
119 } else {
120 LanguageModule.displayError(LanguageModule.labels.errorOccurred);
121 }
122 });
123 };
124
125 /**
126 * Update a language
127 */
128 LanguageModule.updateLanguageAction = function(triggerElement, parameters) {
129 var $row = $(triggerElement).closest('tr'),
130 locale = $row.data('locale'),
131 $progressBar = $(LanguageModule.identifiers.progressBar, $row),
132 $lastUpdate = $(LanguageModule.identifiers.lastUpdate, $row);
133
134 $row.addClass(LanguageModule.classes.processing);
135 LanguageModule.loadTranslationsByLocale(locale, function(status, data, response) {
136 if (status === 'success') {
137 LanguageModule.setProgress($progressBar, 100);
138 LanguageModule.displaySuccess(LanguageModule.labels.updateComplete);
139 $row.removeClass(LanguageModule.classes.processing).addClass(LanguageModule.classes.complete);
140 $lastUpdate.html(LanguageModule.formatDate(response.timestamp));
141 } else if (status === 'progress') {
142 LanguageModule.setProgress($progressBar, parseFloat(response.progress));
143 } else if (status === 'error') {
144 LanguageModule.displayError(LanguageModule.labels.errorOccurred);
145 }
146 });
147 };
148
149 /**
150 * Update all active languages
151 */
152 LanguageModule.updateActiveLanguagesAction = function(triggerElement, parameters) {
153 var $activeRows = $('tr.' + LanguageModule.classes.enabled, LanguageModule.table.table().container());
154
155 LanguageModule.topMenu.addClass(LanguageModule.classes.processing);
156 $activeRows.addClass(LanguageModule.classes.processing);
157 LanguageModule.loadTranslationsByRows($activeRows, function(row, status, data, response) {
158 var $progressBar = $(LanguageModule.identifiers.progressBar, row),
159 $lastUpdate = $(LanguageModule.identifiers.lastUpdate, row);
160
161 if (status === 'success') {
162 LanguageModule.setProgress($progressBar, 100);
163 row.removeClass(LanguageModule.classes.processing).addClass(LanguageModule.classes.complete);
164 $lastUpdate.html(LanguageModule.formatDate(response.timestamp));
165 } else if (status === 'progress') {
166 LanguageModule.setProgress($progressBar, parseFloat(response.progress));
167 } else if (status === 'error') {
168 LanguageModule.displayError(LanguageModule.labels.errorOccurred);
169 } else if (status === 'finished') {
170 LanguageModule.displaySuccess(LanguageModule.labels.updateComplete);
171 LanguageModule.topMenu.removeClass(LanguageModule.classes.processing);
172 }
173 });
174 };
175
176 /**
177 * Cancel language update
178 */
179 LanguageModule.cancelLanguageUpdateAction = function(triggerElement, parameters) {
180 var $activeRows = $('tr.' + LanguageModule.classes.enabled, LanguageModule.table.table().container());
181
182 LanguageModule.topMenu.removeClass(LanguageModule.classes.processing);
183 $activeRows.removeClass(LanguageModule.classes.processing);
184 LanguageModule.abortAjaxRequest();
185 };
186
187 /**
188 * Update an extension translation
189 */
190 LanguageModule.updateTranslationAction = function(triggerElement, parameters) {
191 var $row = $(triggerElement).closest('tr'),
192 $cell = $(triggerElement).closest('td'),
193 extension = $row.data('extension'),
194 locale = LanguageModule.table.cell($cell).data().locale;
195
196 $cell.addClass(LanguageModule.classes.processing);
197 LanguageModule.loadTranslationByExtensionAndLocale(extension, locale, function(status, data, response) {
198 if (status === 'success') {
199 LanguageModule.displaySuccess(LanguageModule.labels.updateComplete);
200 $cell.removeClass(LanguageModule.classes.processing).addClass(LanguageModule.classes.complete);
201 } else if (status === 'error') {
202 LanguageModule.displayError(LanguageModule.labels.errorOccurred);
203 }
204 });
205 };
206
207 /**
208 * Build icons
209 */
210 LanguageModule.buildIcons = function() {
211 return {
212 activate: $(LanguageModule.identifiers.activateIcon, LanguageModule.context).html(),
213 deactivate: $(LanguageModule.identifiers.deactivateIcon, LanguageModule.context).html(),
214 download: $(LanguageModule.identifiers.downloadIcon, LanguageModule.context).html(),
215 loading: $(LanguageModule.identifiers.loadingIcon, LanguageModule.context).html(),
216 complete: $(LanguageModule.identifiers.completeIcon, LanguageModule.context).html(),
217 progressBar: $(LanguageModule.identifiers.progressBar, LanguageModule.context).html()
218 }
219 };
220
221 /**
222 * Build labels
223 */
224 LanguageModule.buildLabels = function() {
225 return {
226 processing: TYPO3.lang['table.processing'],
227 search: TYPO3.lang['table.search'],
228 loadingRecords: TYPO3.lang['table.loadingRecords'],
229 zeroRecords: TYPO3.lang['table.zeroRecords'],
230 emptyTable: TYPO3.lang['table.emptyTable'],
231 dateFormat: TYPO3.lang['table.dateFormat'],
232 errorHeader: TYPO3.lang['flashmessage.error'],
233 infoHeader: TYPO3.lang['flashmessage.information'],
234 successHeader: TYPO3.lang['flashmessage.success'],
235 languageActivated: TYPO3.lang['flashmessage.languageActivated'],
236 errorOccurred: TYPO3.lang['flashmessage.errorOccurred'],
237 languageDeactivated: TYPO3.lang['flashmessage.languageDeactivated'],
238 updateComplete: TYPO3.lang['flashmessage.updateComplete']
239 }
240 };
241
242 /**
243 * Build language table
244 */
245 LanguageModule.buildLanguageTable = function(tableElement) {
246 return $(tableElement).DataTable({
247 dom: 'lrtip',
248 serverSide: false,
249 stateSave: true,
250 paging: false,
251 info: false,
252 ordering: true,
253 language: LanguageModule.labels,
254 order: [[1, 'asc']]
255 });
256 };
257
258 /**
259 * Initialize translation table
260 */
261 LanguageModule.buildTranslationTable = function(tableElement) {
262 var languageCount = $(tableElement).data('languageCount'),
263 columns = [
264 {
265 render: function(data, type, row) {
266 return LanguageModule.buildImage(data.icon, data.title, data.title, data.width, data.height);
267 },
268 width: '20px',
269 orderable: false,
270 targets: 0
271 }, {
272 render: function(data, type, row) {
273 return data.title;
274 },
275 className: LanguageModule.classes.extension,
276 targets: 1
277 }
278 ];
279
280 for (var i = 0; i < languageCount; i++) {
281 columns.push({
282 render: function(data, type, row) {
283 var links = [
284 LanguageModule.buildActionLink('updateTranslation', data, LanguageModule.icons.download),
285 LanguageModule.buildLoadingIndicator(),
286 LanguageModule.buildCompleteIndicator()
287 ];
288 return links.join('');
289 },
290 className: 'dt-center',
291 targets: (i + 2)
292 });
293 }
294
295 return $(tableElement).DataTable({
296 dom: 'lrtip',
297 serverSide: false,
298 stateSave: true,
299 paging: false,
300 info: false,
301 ordering: true,
302 language: LanguageModule.labels,
303 ajax: LanguageModule.settings.listTranslationsUri,
304 order: [[1, 'asc']],
305 columnDefs: columns,
306 createdRow: function (row, data, index) {
307 var $row = $(row);
308 $row.attr('id', LanguageModule.identifiers.extensionPrefix + data[1].key);
309 $row.attr('data-extension', data[1].key);
310 }
311 });
312 };
313
314 /**
315 * Initialize search field
316 */
317 LanguageModule.initializeSearchField = function() {
318 var getVars = LanguageModule.getUrlVars();
319 var currentSearch = (getVars['search'] ? getVars['search'] : LanguageModule.table.search());
320 $(LanguageModule.identifiers.searchField)
321 .val(currentSearch)
322 .on('input', function() {
323 LanguageModule.table.search($(this).val()).draw();
324 })
325 .clearable({
326 onClear: function() {
327 if (LanguageModule.table !== null) {
328 LanguageModule.table.search('').draw();
329 }
330 }
331 })
332 .parents('form').on('submit', function() {
333 return false;
334 });
335 };
336
337 /**
338 * Initialize event handler, redirect clicks to controller actions
339 */
340 LanguageModule.initializeEventHandler = function() {
341 $(document).on('click', function(event) {
342 var $element = $(event.target);
343
344 if ($element.data('action') !== undefined) {
345 LanguageModule.handleActionEvent($element, event);
346 } else if ($element.parent().data('action') !== undefined) {
347 LanguageModule.handleActionEvent($element.parent(), event);
348 } else if ($element.parent().parent().data('action') !== undefined) {
349 LanguageModule.handleActionEvent($element.parent().parent(), event);
350 } else if ($element.parent().parent().parent().parent().data('action') !== undefined) {
351 LanguageModule.handleActionEvent($element.parent().parent().parent().parent(), event);
352 }
353 });
354 };
355
356 /**
357 * Handler for "action" events
358 */
359 LanguageModule.handleActionEvent = function(element, event) {
360 event.preventDefault();
361 var data = element.data();
362 var actionName = data.action + 'Action';
363 if (actionName in LanguageModule) {
364 LanguageModule[actionName](element, data);
365 }
366 };
367
368 /**
369 * Load translations for all extensions by given locale
370 */
371 LanguageModule.loadTranslationsByLocale = function(locale, callback, counter) {
372 counter = counter || 0;
373 var data = {locale: locale, count: counter};
374 LanguageModule.executeAjaxRequest(LanguageModule.settings.updateLanguageUri, data, function(response, status) {
375 if (status === 'success' && response.success) {
376 if (parseFloat(response.progress) < 100) {
377 callback('progress', data, response);
378 counter++;
379 LanguageModule.loadTranslationsByLocale(locale, callback, counter);
380 } else {
381 callback('success', data, response);
382 }
383 } else {
384 callback('error', data, response);
385 }
386 });
387 };
388
389 /**
390 * Load translations for all extensions by given rows
391 */
392 LanguageModule.loadTranslationsByRows = function(rows, callback) {
393 if (rows) {
394 rows = $(rows).toArray();
395 var $row = $(rows.shift()),
396 locale = $row.data('locale');
397
398 LanguageModule.loadTranslationsByLocale(locale, function(status, data, response) {
399 callback($row, status, data, response);
400 if (status === 'success') {
401 if (rows.length) {
402 LanguageModule.loadTranslationsByRows(rows, callback);
403 } else {
404 callback($row, 'finished', data, response);
405 }
406 }
407 });
408 }
409 };
410
411 /**
412 * Load translation for one extension by given locale
413 */
414 LanguageModule.loadTranslationByExtensionAndLocale = function(extension, locale, callback) {
415 var data = {extension: extension, locale: locale};
416 LanguageModule.executeAjaxRequest(LanguageModule.settings.updateTranslationUri, data, function(response, status) {
417 if (status === 'success' && response.success) {
418 callback('success', data, response);
419 } else {
420 callback('error', data, response);
421 }
422 });
423 };
424
425 /**
426 * Execute AJAX request
427 */
428 LanguageModule.executeAjaxRequest = function(uri, data, callback) {
429 var newData = {};
430 newData[LanguageModule.settings.prefix] = {
431 data: data
432 };
433 LanguageModule.currentRequest = $.ajax({
434 type: 'POST',
435 cache: false,
436 url: uri,
437 data: newData,
438 dataType: 'json',
439 success: function(response, status) {
440 if (typeof callback === 'function') {
441 callback(response, status, '');
442 }
443 },
444 error: function(response, status, error) {
445 if (typeof callback === 'function') {
446 callback(response, status, error);
447 }
448 }
449 });
450 };
451
452 /**
453 * Abort current AJAX request
454 */
455 LanguageModule.abortAjaxRequest = function() {
456 if (LanguageModule.currentRequest) {
457 LanguageModule.currentRequest.abort();
458 }
459 };
460
461 /**
462 * Display error flash message
463 */
464 LanguageModule.displayError = function(label) {
465 if (typeof label === 'string' && label !== '') {
466 top.TYPO3.Notification.error(LanguageModule.labels.errorHeader, label);
467 }
468 };
469
470 /**
471 * Display information flash message
472 */
473 LanguageModule.displayInformation = function(label) {
474 if (typeof label === 'string' && label !== '') {
475 top.TYPO3.Notification.info(LanguageModule.labels.infoHeader, label);
476 }
477 };
478
479 /**
480 * Display success flash message
481 */
482 LanguageModule.displaySuccess = function(label) {
483 if (typeof label === 'string' && label !== '') {
484 top.TYPO3.Notification.success(LanguageModule.labels.successHeader, label);
485 }
486 };
487
488 /**
489 * Build action link
490 */
491 LanguageModule.buildActionLink = function(action, parameters, content) {
492 var $link = $('<a>');
493
494 $link.addClass(action + 'Link');
495 $link.attr('data-action', action);
496 for (var name in parameters) {
497 if (parameters.hasOwnProperty(name)) {
498 $link.attr('data-' + name, parameters[name]);
499 }
500 }
501 $link.html(content);
502 return $link.wrap('<span>').parent().html();
503 };
504
505 /**
506 * Build progress bar
507 */
508 LanguageModule.buildProgressBar = function() {
509 var $span = $('<span>');
510 $span.addClass(LanguageModule.classes.progressBar);
511 $span.html(LanguageModule.icons.progressBar);
512 return $span.wrap('<span>').parent().html();
513 };
514
515 /**
516 * Build loading indicator
517 */
518 LanguageModule.buildLoadingIndicator = function() {
519 var $span = $('<span>');
520 $span.addClass(LanguageModule.classes.loading);
521 $span.html(LanguageModule.icons.loading);
522 return $span.wrap('<span>').parent().html();
523 };
524
525 /**
526 * Build complete state indicator
527 */
528 LanguageModule.buildCompleteIndicator = function() {
529 var $span = $('<span>');
530 $span.addClass(LanguageModule.classes.complete);
531 $span.html(LanguageModule.icons.complete);
532 return $span.wrap('<span>').parent().html();
533 };
534
535 /**
536 * Build image
537 */
538 LanguageModule.buildImage = function(uri, alt, title, width, heigth) {
539 var $image = $('<img>');
540 $image.attr('src', uri);
541 $image.attr('alt', alt ? alt : '');
542 $image.attr('title', title ? title : '');
543 $image.attr('style', 'width: ' + width + 'px; height: ' + heigth + 'px;');
544 var $span = $('<span>');
545 $span.addClass('typo3-app-icon');
546 $span.attr('style', 'background: none; text-align: center;');
547 $span.html($image);
548 return $span.wrap('<span>').parent().html();
549 };
550
551 /**
552 * Format date
553 */
554 LanguageModule.formatDate = function(timestamp) {
555 return moment.unix(timestamp).format(LanguageModule.labels.dateFormat);
556 };
557
558 /**
559 * Set progress bar progress
560 */
561 LanguageModule.setProgress = function(progressBar, progress) {
562 var $inner = $(LanguageModule.identifiers.progressBarInner, progressBar),
563 $text = $(LanguageModule.identifiers.progressBarText, progressBar);
564 $inner.css({width: progress + '%'});
565 $inner.attr('aria-valuenow', progress);
566 $text.text(Math.round(progress) + '%');
567 };
568
569 // Utility method to retrieve query parameters
570 LanguageModule.getUrlVars = function getUrlVars() {
571 var vars = [], hash;
572 var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
573 for (var i = 0; i < hashes.length; i++) {
574 hash = hashes[i].split('=');
575 vars.push(hash[0]);
576 vars[hash[0]] = hash[1];
577 }
578 return vars;
579 };
580
581 $(function() {
582 if ($('#typo3-language-list').length) {
583 LanguageModule.initializeLanguageTable('div.typo3-module-lang', '#typo3-language-list');
584 } else if ($('#typo3-translation-list').length) {
585 LanguageModule.initializeTranslationTable('div.typo3-module-lang', '#typo3-translation-list');
586 }
587 });
588
589 return LanguageModule;
590 });