PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 4.0.8
Visualizer – Tables & Charts Manager with Built-in AI Generator v4.0.8
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 All 149 releases
← All changes | js/frame.js +366 -23 3.10.124.0.8 View file →
@@ -7,11 +7,12 @@
7 7 /* global vizHaveSettingsChanged */
8 8
9 9 (function ($) {
10 10 $(window).on('load', function(){
11 - // scroll to the selected chart type.
12 - if($('label.type-label.type-label-selected').length > 0) {
13 - $('label.type-label.type-label-selected')[0].scrollIntoView();
11 + let chart_select = $('#chart-select');
12 + if(chart_select.length > 0){
13 + // scroll to the selected chart type.
14 + $('#chart-select')[0].scrollIntoView();
14 15 }
15 16 });
16 17
17 18 $(document).ready(function () {
@@ -34,8 +35,9 @@
34 35 });
35 36 }
36 37
37 38 function onReady() {
39 + initAccessibility();
38 40 initTabs();
39 41
40 42 // open the correct source tab/sub-tab.
41 43 var source = $('#visualizer-chart-id').attr('data-chart-source');
@@ -71,8 +73,9 @@
71 73
72 74 $('.type-radio').change(function () {
73 75 $('.type-label-selected').removeClass('type-label-selected');
74 76 $(this).parent().addClass('type-label-selected');
77 + updateTypeAriaChecked();
75 78 });
76 79
77 80 // collapse other open sections of this group
78 81 $(document).on('click', '.viz-group-title', function () {
@@ -93,15 +96,18 @@
93 96 * and then show the chart as it was
94 97 * let's close the LHS window and show the chart that is hidden
95 98 */
96 99 $('body').trigger('visualizer:change:action');
100 + updateGroupAriaState($(this));
97 101 });
98 102
99 103 // collapse other open subsections of this section
100 104 $(document).on('click', '.viz-section-title', function () {
101 105 var grandparent = $(this).parent().parent();
102 - grandparent.find('.viz-section-title.open ~ .viz-section-items').hide();
103 - grandparent.find('.viz-section-title.open').removeClass('open');
106 + grandparent.find('.viz-section-title.open').not(this).each(function () {
107 + $(this).removeClass('open').siblings('.viz-section-items').hide();
108 + });
109 + updateSectionAriaState($(this));
104 110 });
105 111
106 112 $('#view-remote-file').click(function () {
107 113 var url = $(this).parent().find('#vz-schedule-url').val();
@@ -144,9 +150,9 @@
144 150 $('#cancel-button').click(function () {
145 151 $('#cancel-form').submit();
146 152 });
147 153
148 - init_type_vs_library();
154 + init_available_chart_list();
149 155
150 156 $('.viz-abort').on('click', function(e){
151 157 e.preventDefault();
152 158 window.parent.postMessage('visualizer:mediaframe:close', '*');
@@ -153,27 +159,216 @@
153 159 });
154 160
155 161 }
156 162
157 - function init_type_vs_library() {
158 - var $data = $('select.viz-select-library').attr('data-type-vs-library');
159 - if(typeof $data === 'undefined' || $data.length === 0){
163 + function initAccessibility(){
164 + // Chart type picker: treat tiles as radios with keyboard support.
165 + var $typePicker = $('#type-picker');
166 + if($typePicker.length){
167 + $typePicker.attr('role', 'radiogroup');
168 + $('.type-label').each(function(){
169 + var $label = $(this);
170 + var $input = $label.find('input[type="radio"]');
171 + $label.attr('tabindex', '0').attr('role', 'radio');
172 + if($input.length){
173 + $label.attr('aria-checked', $input.is(':checked') ? 'true' : 'false');
174 + }else{
175 + $label.attr('aria-disabled', 'true');
176 + }
177 + });
178 + $(document).on('keydown', '.type-label', function(e){
179 + if(e.key === 'Enter' || e.key === ' '){
180 + e.preventDefault();
181 + var $label = $(this);
182 + var $input = $label.find('input[type="radio"]');
183 + if($input.length){
184 + $input.prop('checked', true).trigger('change');
185 + }
186 + }
187 + });
188 + }
189 +
190 + // Collapsible groups: add button roles + keyboard activation.
191 + $('.viz-group-title').each(function(index){
192 + var $title = $(this);
193 + $title.attr('role', 'button').attr('tabindex', '0');
194 + var $content = $title.siblings('.viz-group-content');
195 + if($content.length){
196 + if(!$content.attr('id')){
197 + $content.attr('id', 'viz-group-content-' + index);
198 + }
199 + $title.attr('aria-controls', $content.attr('id'));
200 + updateGroupAriaState($title);
201 + }
202 + });
203 + $(document).on('keydown', '.viz-group-title', function(e){
204 + if(e.key === 'Enter' || e.key === ' '){
205 + e.preventDefault();
206 + $(this).trigger('click');
207 + }
208 + });
209 +
210 + // Subsections in source panel.
211 + $('.viz-section-title').each(function(index){
212 + var $title = $(this);
213 + $title.attr('role', 'button').attr('tabindex', '0');
214 + var $items = $title.siblings('.viz-section-items');
215 + if($items.length){
216 + if(!$items.attr('id')){
217 + $items.attr('id', 'viz-section-items-' + index);
218 + }
219 + $title.attr('aria-controls', $items.attr('id'));
220 + updateSectionAriaState($title);
221 + }
222 + });
223 + $(document).on('keydown', '.viz-section-title', function(e){
224 + if(e.key === 'Enter' || e.key === ' '){
225 + e.preventDefault();
226 + $(this).trigger('click');
227 + }
228 + });
229 + }
230 +
231 + function updateTypeAriaChecked(){
232 + $('.type-label').each(function(){
233 + var $label = $(this);
234 + var $input = $label.find('input[type="radio"]');
235 + if($input.length){
236 + $label.attr('aria-checked', $input.is(':checked') ? 'true' : 'false');
237 + }
238 + });
239 + }
240 +
241 + function updateGroupAriaState($title){
242 + if(!$title || $title.length === 0){
160 243 return;
161 244 }
162 - var $typeVsLibrary = JSON.parse( $('select.viz-select-library').attr('data-type-vs-library') );
163 - // disable all unsupported libraries for the chart type.
164 - $('input.type-radio').on('click', function(){
165 - enable_libraries_for($(this).val(), $typeVsLibrary);
245 + var isOpen = $title.parent().hasClass('open');
246 + $title.attr('aria-expanded', isOpen ? 'true' : 'false');
247 + }
248 +
249 + function updateSectionAriaState($title){
250 + if(!$title || $title.length === 0){
251 + return;
252 + }
253 + var isOpen = $title.hasClass('open');
254 + $title.attr('aria-expanded', isOpen ? 'true' : 'false');
255 + }
256 +
257 + /**
258 + * Initialize/Update the available chart list based on their supported renderer libraries.
259 + *
260 + * @returns {void}
261 + */
262 + function init_available_chart_list() {
263 + const rendererSelect = document.querySelector('select.viz-select-library');
264 + if ( ! rendererSelect ) {
265 + return;
266 + }
267 +
268 + const rendererTypesMappingData = rendererSelect?.getAttribute('data-type-vs-library');
269 + if( ! rendererTypesMappingData ) {
270 + return;
271 + }
272 +
273 + /** @type {Record<string, ('GoogleCharts' | 'ChartJS' | 'DataTable')[]>} */
274 + const rendererTypesMapping = JSON.parse( rendererTypesMappingData );
275 +
276 + document.querySelectorAll('input.type-radio').forEach( chartTypeRadio => {
277 +
278 + // Init the lib based on the default selected chart type.
279 + if ( chartTypeRadio.checked ) {
280 + const chartType = chartTypeRadio.value;
281 + if ( ! chartType ) {
282 + return;
283 + }
284 +
285 + const selectedRenderer = rendererSelect.value;
286 + if ( ! rendererTypesMapping[chartType]?.includes( selectedRenderer ) ) {
287 + disable_renderer_select_placeholder();
288 + rendererSelect.value = rendererTypesMapping[chartType][0]
289 + }
290 + }
291 +
292 + chartTypeRadio.addEventListener('click', (event) => {
293 + // Check if the chart type is supported by the selected renderer. If not, select the first supported renderer.
294 + const chartType = event.target.value;
295 + if ( ! chartType ) {
296 + return;
297 + }
298 +
299 + const selectedRenderer = rendererSelect.value;
300 + if ( ! rendererTypesMapping[chartType]?.includes( selectedRenderer ) ) {
301 + disable_renderer_select_placeholder();
302 + rendererSelect.value = rendererTypesMapping[chartType][0]
303 + }
304 + });
166 305 });
167 306
307 + // Update the chart list on user interaction.
308 + rendererSelect.addEventListener('change', (event) => {
309 + disable_renderer_select_placeholder();
310 + toggle_renderer_type( event.target.value );
311 + toggle_chart_types_by_render( event.target.value );
312 + });
313 +
314 + }
315 +
316 + /**
317 + * Disable the placeholder option in the renderer select.
318 + */
319 + function disable_renderer_select_placeholder() {
320 + const placeholderOption = document.querySelector('#library-select-placeholder');
321 + if ( placeholderOption && placeholderOption.disabled === false ) {
322 + placeholderOption.disabled = true;
323 + }
324 + }
325 +
326 + /**
327 + * Toggle the renderer type class based on the given renderer type.
328 + *
329 + * The class is used to style the chart type picker based on the given renderer library.
330 + *
331 + * @param {('GoogleCharts' | 'ChartJS' | 'DataTable')} rendererType The renderer type to toggle the class.
332 + */
333 + function toggle_renderer_type( rendererType ) {
334 + const typePicker = document.querySelector('#type-picker');
335 + if ( ! typePicker ) {
336 + return;
337 + }
338 +
339 + typePicker.classList.remove('lib-GoogleCharts', 'lib-ChartJS', 'lib-DataTable');
340 + typePicker.classList.add(`lib-${rendererType}`);
341 + }
342 +
343 + /**
344 + * Toggle chart types based on the given renderer type.
345 + *
346 + * @param {('GoogleCharts' | 'ChartJS' | 'DataTable')} rendererType The renderer type to filter the chart types.
347 + */
348 + function toggle_chart_types_by_render( rendererType ) {
349 + document.querySelectorAll('.type-box').forEach( typeBox => {
350 + const hasLib = typeBox.classList.contains(`type-lib-${rendererType}`);
351 + typeBox.classList.toggle('viz-hidden', ! hasLib );
352 +
353 + // Reset unavailable chart type selection.
354 + const chartOption = typeBox.querySelector('input[type="radio"]');
355 + if ( ! hasLib && chartOption?.checked ) {
356 + chartOption.checked = false;
357 + }
358 + });
359 +
168 360 enable_libraries_for($('input.type-radio:checked').val(), $typeVsLibrary);
169 361 }
170 362
171 363 function enable_libraries_for($type, $typeVsLibrary) {
172 364 $('select.viz-select-library option').addClass('disabled').attr('disabled', 'disabled');
365 + $('select.viz-select-library option .premium-label').remove();
366 + $('select.viz-select-library option').append('<span class="premium-label"> (PREMIUM)</span>');
173 367 var $libs = $typeVsLibrary[$type];
174 368 $.each($libs, function( i, $lib ) {
175 369 $('select.viz-select-library option[value="' + $lib + '"]').removeClass('disabled').removeAttr('disabled');
370 + $('select.viz-select-library option[value="' + $lib + '"] .premium-label').remove();
176 371 });
177 372 $('select.viz-select-library').val( $('select.viz-select-library option:not(.disabled)').val() );
178 373 }
179 374
@@ -181,9 +376,9 @@
181 376 $('.visualizer-permission').chosen({
182 377 width : '50%',
183 378 search_contains : true
184 379 });
185 -
380 +
186 381 $('.visualizer-permission-type').each(function(x, y){
187 382 var type = $(y).attr('data-visualizer-permission-type');
188 383 var child = $('.visualizer-permission-' + type + '-specific');
189 384 if($(y).val() === 'all'){
@@ -190,9 +385,9 @@
190 385 child.next('div.chosen-container').hide();
191 386 return;
192 387 }
193 388 });
194 -
389 +
195 390 $('.visualizer-permission-type').on('change', function(evt, params) {
196 391 var type = $(this).attr('data-visualizer-permission-type');
197 392 var child = $('.visualizer-permission-' + type + '-specific');
198 393 child.empty();
@@ -235,9 +430,9 @@
235 430 lineWrapping: true,
236 431 dragDrop: false,
237 432 matchBrackets: true,
238 433 autoCloseBrackets: true,
239 - extraKeys: {"Ctrl-Space": "autocomplete"},
434 + extraKeys: {"Shift-Space": "autocomplete"},
240 435 hintOptions: { tables: table_columns }
241 436 });
242 437
243 438 // force refresh so that the query shows on first time load. Otherwise you have to click on the editor for it to show.
@@ -245,9 +440,9 @@
245 440 cm.refresh();
246 441 });
247 442
248 443 cm.focus();
249 -
444 +
250 445 // update text area.
251 446 cm.on('inputRead', function(x, y){
252 447 cm.save();
253 448 });
@@ -256,9 +451,9 @@
256 451 // from the editor. Let's force this.
257 452 $('body').on('visualizer:db:query:update', function(event, data){
258 453 cm.save();
259 454 });
260 -
455 +
261 456 // clear the editor.
262 457 $('body').on('visualizer:db:query:setvalue', function(event, data){
263 458 cm.setValue(data.value);
264 459 cm.clearHistory();
@@ -273,8 +468,10 @@
273 468
274 469 function init_filter_import() {
275 470 $( '#db-filter-save-button' ).on( 'click', function(){
276 471 $('#vz-filter-wizard').submit();
472 + $( '#vz-wp-sync-options' ).hide();
473 + $( '#vz-wp-sync-btn' ).attr( 'aria-expanded', false );
277 474 });
278 475 }
279 476
280 477 function init_db_import(){
@@ -281,8 +478,36 @@
281 478 $( '#visualizer-db-query' ).css("z-index", "-1").hide();
282 479
283 480 init_db_import_component();
284 481
482 + var settings_button = document.querySelector( '#settings-button' );
483 + if ( settings_button ) {
484 + settings_button.addEventListener( 'click', function( event ){
485 + $('body').trigger('visualizer:db:query:update', {});
486 + if( $( '#db-chart-button' ).attr( 'data-current' ) !== 'filter' || $( '.visualizer-db-query' ).val().length === 0 ){
487 + return;
488 + }
489 +
490 + event.preventDefault();
491 + event.stopImmediatePropagation();
492 +
493 + var query_saved = false;
494 + var resume_save = function(){
495 + query_saved = true;
496 + settings_button.click();
497 + };
498 +
499 + $('body').one( 'visualizer:render:currentchart:update', resume_save );
500 + $( '#thehole' ).one( 'load', function(){
501 + $('body').off( 'visualizer:render:currentchart:update', resume_save );
502 + if ( ! query_saved ) {
503 + $( '#db-chart-button' ).trigger( 'click' );
504 + }
505 + } );
506 + $( '#db-chart-button' ).trigger( 'click' );
507 + }, true );
508 + }
509 +
285 510 $('#visualizer-query-fetch').on('click', function(e){
286 511
287 512 $('body').trigger('visualizer:db:query:update', {});
288 513 if($('.visualizer-db-query').val() === ''){
@@ -287,9 +512,9 @@
287 512 $('body').trigger('visualizer:db:query:update', {});
288 513 if($('.visualizer-db-query').val() === ''){
289 514 return;
290 515 }
291 -
516 +
292 517 start_ajax($('#visualizer-db-query'));
293 518 $('.db-wizard-results').empty();
294 519 $('.db-wizard-error').empty();
295 520 $.ajax({
@@ -326,9 +551,9 @@
326 551 filter_button.attr( 'data-current', 'chart' );
327 552 $( '#canvas' ).css("z-index", "1").show();
328 553 });
329 554
330 - $('#content').css('width', 'calc(100% - 300px)');
555 + $('#content').css('width', 'calc(100% - 350px)');
331 556 if( $(this).attr( 'data-current' ) === 'chart'){
332 557 $(this).val( $(this).attr( 'data-t-filter' ) );
333 558 $(this).html( $(this).attr( 'data-t-filter' ) );
334 559 $(this).attr( 'data-current', 'filter' );
@@ -347,13 +572,29 @@
347 572 $( '#db-chart-save-button' ).trigger('click');
348 573 }
349 574 } );
350 575
576 + $( document ).on( 'click', '#vz-db-sync-btn', function(){
577 + var $options = $( '#vz-db-sync-options' );
578 + var isOpen = $options.is( ':visible' );
579 + $options.toggle();
580 + $( this ).attr( 'aria-expanded', ! isOpen );
581 + } );
582 +
583 + $( document ).on( 'click', '#vz-wp-sync-btn', function(){
584 + var $options = $( '#vz-wp-sync-options' );
585 + var isOpen = $options.is( ':visible' );
586 + $options.toggle();
587 + $( this ).attr( 'aria-expanded', ! isOpen );
588 + } );
589 +
351 590 $( '#db-chart-save-button' ).on( 'click', function(){
352 591 // submit only if a query has been provided.
353 592 if($('#db-query-form .visualizer-db-query').val().length > 0){
354 593 $('#viz-db-wizard-params').val($('#db-query-form').serialize());
355 594 $('#vz-db-wizard').submit();
595 + $( '#vz-db-sync-options' ).hide();
596 + $( '#vz-db-sync-btn' ).attr( 'aria-expanded', false );
356 597 }else{
357 598 $('#canvas').unlock();
358 599 }
359 600 });
@@ -375,9 +616,9 @@
375 616 heightStyle: 'content',
376 617 active: false,
377 618 collapsible: true
378 619 });
379 -
620 +
380 621 // open the accordions by default if they are indicated with the 'open' class.
381 622 $('.visualizer-json-subform .viz-substep.open').each(function(i, e){
382 623 $('.visualizer-json-subform').accordion( "option", "active", i );
383 624 });
@@ -596,9 +837,9 @@
596 837 };
597 838
598 839 // show column visibility button only when more than 6 columns are found (including the Label column)
599 840 if($(element + ' table.viz-editor-table thead tr th').length > 6){
600 - $.extend( settings, {
841 + $.extend( settings, {
601 842 dom: 'Bt',
602 843 buttons: [
603 844 {
604 845 extend: 'colvis',
@@ -679,5 +920,107 @@
679 920 });
680 921
681 922 return $(this);
682 923 };
683 -})(jQuery);
924 +
925 + // Telemetry
926 + function getChartID() {
927 + const urlParams = new URLSearchParams(window.location.search);
928 + return urlParams.get('chart') ?? '';
929 + }
930 +
931 + const groupId = getChartID();
932 +
933 + const chartTypes = document.querySelector('#viz-types-form')
934 + if( chartTypes ) {
935 + document.querySelector('.push-right[type=submit]')?.addEventListener('click', async function (event) {
936 + if ( typeof window.tiTrk !== 'undefined' ) {
937 + event.preventDefault();
938 + try {
939 + const formData = new FormData(document.querySelector('#viz-types-form'));
940 + const savedData = Object.fromEntries(formData);
941 +
942 + tiTrk?.with('visualizer')?.add({
943 + feature: 'chart-create',
944 + featureComponent: 'saved-data',
945 + featureData: {
946 + type: savedData?.['type'],
947 + library: savedData?.['chart-library']
948 + },
949 + groupId
950 + });
951 +
952 + // Do not make the user to wait too long for the event to be uploaded.
953 + const timer = new Promise((resolve) => setTimeout(resolve, 500));
954 + await Promise.race([timer, tiTrk?.uploadEvents()]);
955 + } catch (e) {
956 + console.warn(e);
957 + }
958 + $('#viz-types-form').submit();
959 + }
960 + });
961 + }
962 +
963 + [
964 + {
965 + selector: '#editor-button',
966 + featureComponent: 'source-manual-data'
967 + },
968 + {
969 + selector: '#vz-import-file',
970 + featureComponent: 'source-import-csv-file'
971 + },
972 + {
973 + selector: '#vz-save-schedule',
974 + featureComponent: 'source-import-csv-remote'
975 + },
976 + {
977 + selector: '#visualizer-json-fetch',
978 + featureComponent: 'source-import-json-remote'
979 + },
980 + {
981 + selector: '#existing-chart',
982 + featureComponent: 'source-import-chart'
983 + },
984 + {
985 + selector: '#filter-chart-button',
986 + featureComponent: 'source-import-wordpress',
987 + },
988 + {
989 + selector: '#woo-chart-button',
990 + featureComponent: 'source-import-woocommerce'
991 + },
992 + {
993 + selector: '#db-chart-button',
994 + featureComponent: 'source-import-database'
995 + }
996 + ].forEach(({ selector, featureComponent }) => {
997 + document.querySelector(selector)?.addEventListener('click', function () {
998 + window?.tiTrk?.with('visualizer')?.set('used-source',{
999 + feature: 'chart-edit-used-source',
1000 + featureComponent,
1001 + groupId
1002 + });
1003 + });
1004 + });
1005 +})(jQuery);
1006 +
1007 +
1008 +document.querySelector('#viz-copy-shortcode')?.addEventListener('click', function() {
1009 + var copyText = document.querySelector('#viz-shortcode')?.value;
1010 + if ( !copyText ) {
1011 + return;
1012 + }
1013 +
1014 + navigator.clipboard.writeText(copyText);
1015 + alert(visualizer.l10n.copied);
1016 +});
1017 +
1018 +// Connect Chart Backend Title Info Panel with the field from the settings form.
1019 +const interactiveBackendTitleField = document.querySelector( '#viz-backend-name' );
1020 +const backendTitleSave = document.querySelector( '#settings-form input[name="backend-title"]' );
1021 +
1022 +if ( interactiveBackendTitleField && backendTitleSave ) {
1023 + interactiveBackendTitleField.addEventListener('input', function() {
1024 + backendTitleSave.value = this.value;
1025 + });
1026 +}