|
36 | 36 | */ |
37 | 37 | class Visualizer_Elementor_Widget extends \Elementor\Widget_Base { |
38 | 38 |
|
| 39 | + /** |
| 40 | + * Register all Elementor-related hooks for the Visualizer widget. |
| 41 | + * |
| 42 | + * Called once from index.php on the plugins_loaded action (after Elementor |
| 43 | + * itself is confirmed present). Each inner hook fires at its normal time in |
| 44 | + * the WordPress lifecycle, so timing is identical to registering them |
| 45 | + * directly in visualizer_launch(). |
| 46 | + * |
| 47 | + * @return void |
| 48 | + */ |
| 49 | + public static function register_hooks() { |
| 50 | + // Register the widget with Elementor's widget manager. |
| 51 | + add_action( |
| 52 | + 'elementor/widgets/register', |
| 53 | + function ( $widgets_manager ) { |
| 54 | + $widgets_manager->register( new self() ); |
| 55 | + } |
| 56 | + ); |
| 57 | + |
| 58 | + // Register the Visualizer icon for the Elementor widget panel. |
| 59 | + add_action( |
| 60 | + 'elementor/editor/after_enqueue_styles', |
| 61 | + function () { |
| 62 | + $icon_url = VISUALIZER_ABSURL . 'images/visualizer-icon.svg'; |
| 63 | + wp_add_inline_style( |
| 64 | + 'elementor-icons', |
| 65 | + '.visualizer-elementor-icon { display:inline-block; width:1em; height:1em; background:url("' . esc_url( $icon_url ) . '") no-repeat center/contain; }' |
| 66 | + ); |
| 67 | + } |
| 68 | + ); |
| 69 | + |
| 70 | + // Enqueue Visualizer scripts inside the Elementor preview iframe. |
| 71 | + // Elementor serves the preview iframe as a shell page and injects widget HTML via |
| 72 | + // JavaScript (innerHTML), so wp_enqueue_script calls inside render() never reach the |
| 73 | + // iframe. We load all chart render libraries here so they are available when |
| 74 | + // elementor-widget-preview.js triggers visualizer:render:chart:start. |
| 75 | + add_action( |
| 76 | + 'elementor/preview/enqueue_scripts', |
| 77 | + function () { |
| 78 | + do_action( 'visualizer_enqueue_scripts' ); |
| 79 | + |
| 80 | + // ChartJS render library. |
| 81 | + if ( ! wp_script_is( 'numeral', 'registered' ) ) { |
| 82 | + wp_register_script( 'numeral', VISUALIZER_ABSURL . 'js/lib/numeral.min.js', array(), Visualizer_Plugin::VERSION, true ); |
| 83 | + } |
| 84 | + if ( ! wp_script_is( 'chartjs', 'registered' ) ) { |
| 85 | + wp_register_script( 'chartjs', VISUALIZER_ABSURL . 'js/lib/chartjs.min.js', array( 'numeral' ), null, true ); |
| 86 | + } |
| 87 | + wp_enqueue_script( 'visualizer-render-chartjs-lib', VISUALIZER_ABSURL . 'js/render-chartjs.js', array( 'chartjs', 'visualizer-customization' ), Visualizer_Plugin::VERSION, true ); |
| 88 | + |
| 89 | + // Google Charts render library. |
| 90 | + wp_enqueue_script( 'visualizer-google-jsapi', '//www.gstatic.com/charts/loader.js', array(), null, true ); |
| 91 | + wp_enqueue_script( 'visualizer-render-google-lib', VISUALIZER_ABSURL . 'js/render-google.js', array( 'visualizer-google-jsapi', 'visualizer-customization' ), Visualizer_Plugin::VERSION, true ); |
| 92 | + |
| 93 | + // DataTable render library + styles. |
| 94 | + if ( ! wp_script_is( 'visualizer-datatables', 'registered' ) ) { |
| 95 | + wp_register_script( 'visualizer-datatables', VISUALIZER_ABSURL . 'js/lib/datatables.min.js', array( 'jquery' ), Visualizer_Plugin::VERSION, true ); |
| 96 | + } |
| 97 | + wp_enqueue_script( 'visualizer-render-datatables-lib', VISUALIZER_ABSURL . 'js/render-datatables.js', array( 'visualizer-datatables', 'visualizer-customization' ), Visualizer_Plugin::VERSION, true ); |
| 98 | + wp_enqueue_style( 'visualizer-datatables', VISUALIZER_ABSURL . 'css/lib/datatables.min.css', array(), Visualizer_Plugin::VERSION ); |
| 99 | + |
| 100 | + // D3 render library (AI charts). |
| 101 | + $d3_renderer_asset = VISUALIZER_ABSPATH . '/classes/Visualizer/D3Renderer/build/index.asset.php'; |
| 102 | + if ( file_exists( $d3_renderer_asset ) && ! wp_script_is( 'visualizer-d3-renderer', 'registered' ) ) { |
| 103 | + $d3_asset = include $d3_renderer_asset; |
| 104 | + wp_register_script( |
| 105 | + 'visualizer-d3-renderer', |
| 106 | + VISUALIZER_ABSURL . 'classes/Visualizer/D3Renderer/build/index.js', |
| 107 | + array_merge( $d3_asset['dependencies'], array( 'jquery' ) ), |
| 108 | + $d3_asset['version'], |
| 109 | + true |
| 110 | + ); |
| 111 | + } |
| 112 | + if ( wp_script_is( 'visualizer-d3-renderer', 'registered' ) ) { |
| 113 | + wp_enqueue_script( 'visualizer-d3-renderer' ); |
| 114 | + wp_localize_script( |
| 115 | + 'visualizer-d3-renderer', |
| 116 | + 'vizD3Renderer', |
| 117 | + array( |
| 118 | + 'iframeJsUrl' => VISUALIZER_ABSURL . 'classes/Visualizer/D3Renderer/build/iframe.js', |
| 119 | + ) |
| 120 | + ); |
| 121 | + } |
| 122 | + |
| 123 | + // Elementor widget preview handler — uses frontend/element_ready hook. |
| 124 | + wp_enqueue_script( 'visualizer-elementor-preview', VISUALIZER_ABSURL . 'js/elementor-widget-preview.js', array( 'jquery', 'elementor-frontend' ), Visualizer_Plugin::VERSION, true ); |
| 125 | + |
| 126 | + // Prevent Elementor's editor-preview CSS from hiding our widget. |
| 127 | + // Elementor marks widgets without a content_template() as elementor-widget-empty |
| 128 | + // and adds display:none to .elementor-widget-empty when the panel is hidden |
| 129 | + // (.elementor-editor-preview on <body>). Our widget renders async (Google Charts |
| 130 | + // loads via callback), so the empty class is always present. |
| 131 | + wp_add_inline_style( |
| 132 | + 'visualizer-datatables', |
| 133 | + '.elementor-editor-preview .elementor-widget-visualizer-chart.elementor-widget-empty { display: block !important; }' |
| 134 | + ); |
| 135 | + } |
| 136 | + ); |
| 137 | + } |
| 138 | + |
39 | 139 | /** |
40 | 140 | * Get widget name. |
41 | 141 | * |
@@ -239,7 +339,7 @@ protected function render() { |
239 | 339 | // via elementor/preview/enqueue_scripts; injecting render-facade.js would add |
240 | 340 | // a second visualizer:render:chart:start trigger causing duplicate renders. |
241 | 341 | foreach ( wp_scripts()->queue as $handle ) { |
242 | | - if ( 0 === strpos( $handle, 'visualizer-render-' ) |
| 342 | + if ( ( 0 === strpos( $handle, 'visualizer-render-' ) || 'visualizer-d3-renderer' === $handle ) |
243 | 343 | && 'visualizer-render-google-lib' !== $handle |
244 | 344 | && 'visualizer-render-chartjs-lib' !== $handle |
245 | 345 | && 'visualizer-render-datatables-lib' !== $handle ) { |
@@ -301,6 +401,12 @@ protected function render() { |
301 | 401 | 'library' => $library, |
302 | 402 | ); |
303 | 403 |
|
| 404 | + // D3/AI charts store their rendering code in post meta — include it so |
| 405 | + // elementor-widget-preview.js can pass it to the D3 renderer. |
| 406 | + if ( 'd3' === $library ) { |
| 407 | + $chart_entry['code'] = get_post_meta( $chart_id, Visualizer_Module_AIBuilder::CF_D3_CODE, true ); |
| 408 | + } |
| 409 | + |
304 | 410 | // Elementor injects widget HTML via innerHTML, so <script type="text/javascript"> |
305 | 411 | // tags never execute in the preview iframe. Instead embed the chart data in a |
306 | 412 | // JSON script element — it is preserved through innerHTML but not executed. |
|
0 commit comments