| 1 |
/* global elementorFrontend, jQuery */ |
| 2 |
/** |
| 3 |
* Elementor preview handler for Visualizer charts. |
| 4 |
* |
| 5 |
* @since 3.11.16 |
| 6 |
*/ |
| 7 |
// Guard against the script being injected more than once into the preview iframe. |
| 8 |
if ( ! window.visualizerElementorPreview ) { |
| 9 |
window.visualizerElementorPreview = true; |
| 10 |
|
| 11 |
( function ( $ ) { |
| 12 |
'use strict'; |
| 13 |
|
| 14 |
/** |
| 15 |
* Poll until `condition()` returns true, then call `callback`. |
| 16 |
* Gives up after `maxAttempts` × 100 ms. |
| 17 |
*/ |
| 18 |
function waitFor( condition, callback, maxAttempts ) { |
| 19 |
maxAttempts = maxAttempts === undefined ? 50 : maxAttempts; |
| 20 |
if ( condition() ) { |
| 21 |
callback(); |
| 22 |
return; |
| 23 |
} |
| 24 |
if ( maxAttempts <= 0 ) { |
| 25 |
return; |
| 26 |
} |
| 27 |
setTimeout( function () { |
| 28 |
waitFor( condition, callback, maxAttempts - 1 ); |
| 29 |
}, 100 ); |
| 30 |
} |
| 31 |
|
| 32 |
/** |
| 33 |
* Given the Elementor widget wrapper element, extract chart data from the |
| 34 |
* embedded JSON script element and trigger chart rendering. |
| 35 |
*/ |
| 36 |
function renderWidget( widgetEl ) { |
| 37 |
var $scope = $( widgetEl ); |
| 38 |
var $dataEl = $scope.find( 'script.visualizer-chart-data[type="application/json"]' ); |
| 39 |
|
| 40 |
if ( ! $dataEl.length ) { |
| 41 |
return; |
| 42 |
} |
| 43 |
|
| 44 |
var elementId = $dataEl.attr( 'data-element-id' ); |
| 45 |
var chartEntry; |
| 46 |
|
| 47 |
try { |
| 48 |
chartEntry = JSON.parse( $dataEl.text() ); |
| 49 |
} catch ( e ) { |
| 50 |
return; |
| 51 |
} |
| 52 |
|
| 53 |
if ( ! elementId || ! chartEntry ) { |
| 54 |
return; |
| 55 |
} |
| 56 |
|
| 57 |
window.visualizer = window.visualizer || {}; |
| 58 |
window.visualizer.charts = window.visualizer.charts || {}; |
| 59 |
window.visualizer.charts[ elementId ] = chartEntry; |
| 60 |
|
| 61 |
// Build the viz object that render-google.js / render-chartjs.js expect. |
| 62 |
// is_front:true tells render-google.js to call renderChart(id) for just |
| 63 |
// this element rather than render() for all charts. |
| 64 |
var viz = $.extend( {}, window.visualizer, { id: elementId, is_front: true } ); |
| 65 |
|
| 66 |
function doTrigger() { |
| 67 |
$( '#' + elementId ).removeClass( 'viz-facade-loaded' ); |
| 68 |
$( 'body' ).trigger( 'visualizer:render:chart:start', viz ); |
| 69 |
} |
| 70 |
|
| 71 |
if ( chartEntry.library === 'google' || chartEntry.library === 'GoogleCharts' ) { |
| 72 |
// render-google.js bails silently when typeof google !== 'object'. |
| 73 |
// Poll until the Google Charts loader script has executed. |
| 74 |
waitFor( function () { return typeof google === 'object'; }, doTrigger ); |
| 75 |
} else { |
| 76 |
doTrigger(); |
| 77 |
} |
| 78 |
} |
| 79 |
|
| 80 |
/** |
| 81 |
* Scan a subtree for visualizer-chart widgets and render each one. |
| 82 |
*/ |
| 83 |
function scanAndRender( root ) { |
| 84 |
var $root = $( root ); |
| 85 |
|
| 86 |
if ( $root.is( '[data-widget_type="visualizer-chart.default"]' ) ) { |
| 87 |
renderWidget( root ); |
| 88 |
} |
| 89 |
|
| 90 |
$root.find( '[data-widget_type="visualizer-chart.default"]' ).each( function () { |
| 91 |
renderWidget( this ); |
| 92 |
} ); |
| 93 |
} |
| 94 |
|
| 95 |
$( document ).ready( function () { |
| 96 |
var observer = new MutationObserver( function ( mutations ) { |
| 97 |
// Collect widget elements to render, de-duplicating within the batch. |
| 98 |
var toRender = []; |
| 99 |
var seen = window.WeakSet ? new WeakSet() : null; |
| 100 |
var seenList = []; |
| 101 |
|
| 102 |
// Enqueue a widget element for rendering, skipping duplicates. |
| 103 |
function enqueue( el ) { |
| 104 |
if ( ! el ) { |
| 105 |
return; |
| 106 |
} |
| 107 |
if ( seen ) { |
| 108 |
if ( seen.has( el ) ) { |
| 109 |
return; |
| 110 |
} |
| 111 |
seen.add( el ); |
| 112 |
toRender.push( el ); |
| 113 |
return; |
| 114 |
} |
| 115 |
if ( seenList.indexOf( el ) !== -1 ) { |
| 116 |
return; |
| 117 |
} |
| 118 |
seenList.push( el ); |
| 119 |
toRender.push( el ); |
| 120 |
} |
| 121 |
|
| 122 |
mutations.forEach( function ( mutation ) { |
| 123 |
mutation.addedNodes.forEach( function ( node ) { |
| 124 |
if ( node.nodeType !== 1 ) { |
| 125 |
return; |
| 126 |
} |
| 127 |
|
| 128 |
// Only react when chart data was injected, not when chart |
| 129 |
// rendering (SVG / canvas) mutates the DOM — that would loop. |
| 130 |
var hasData = ( node.matches && node.matches( 'script.visualizer-chart-data[type="application/json"]' ) ) || |
| 131 |
( node.querySelector && node.querySelector( 'script.visualizer-chart-data[type="application/json"]' ) ); |
| 132 |
if ( ! hasData ) { |
| 133 |
return; |
| 134 |
} |
| 135 |
|
| 136 |
// Node is the widget wrapper or contains one (new widget added). |
| 137 |
if ( $( node ).is( '[data-widget_type="visualizer-chart.default"]' ) ) { |
| 138 |
enqueue( node ); |
| 139 |
} |
| 140 |
$( node ).find( '[data-widget_type="visualizer-chart.default"]' ).each( function () { |
| 141 |
enqueue( this ); |
| 142 |
} ); |
| 143 |
|
| 144 |
// Node is inner content of an existing widget (chart switched). |
| 145 |
// Look upward for the widget wrapper. |
| 146 |
enqueue( $( node ).closest( '[data-widget_type="visualizer-chart.default"]' )[ 0 ] ); |
| 147 |
} ); |
| 148 |
} ); |
| 149 |
|
| 150 |
toRender.forEach( function ( el ) { |
| 151 |
renderWidget( el ); |
| 152 |
} ); |
| 153 |
} ); |
| 154 |
|
| 155 |
observer.observe( document.documentElement, { childList: true, subtree: true } ); |
| 156 |
|
| 157 |
// Handle widgets already present on initial load. |
| 158 |
scanAndRender( document.body ); |
| 159 |
} ); |
| 160 |
|
| 161 |
// Register Elementor's element_ready hook as a secondary trigger. |
| 162 |
// Called both immediately (in case elementorFrontend is already initialised) |
| 163 |
// and on the init event (in case it fires after this script loads). |
| 164 |
// The guard prevents double-registration if both code paths fire. |
| 165 |
var elementorHookRegistered = false; |
| 166 |
function registerElementorHook() { |
| 167 |
if ( elementorHookRegistered ) { |
| 168 |
return; |
| 169 |
} |
| 170 |
if ( typeof elementorFrontend !== 'undefined' && elementorFrontend.hooks ) { |
| 171 |
elementorFrontend.hooks.addAction( |
| 172 |
'frontend/element_ready/visualizer-chart.default', |
| 173 |
function ( $scope ) { renderWidget( $scope[ 0 ] ); } |
| 174 |
); |
| 175 |
elementorHookRegistered = true; |
| 176 |
} |
| 177 |
} |
| 178 |
registerElementorHook(); |
| 179 |
window.addEventListener( 'elementor/frontend/init', registerElementorHook ); |
| 180 |
}( jQuery ) ); |
| 181 |
} // end visualizerElementorPreview guard |
| 182 |
|