← All changes
|
src/DonationForms/ViewModels/DonationFormViewModel.php
+59
-4
4.15.1
→
4.17.0
View file →
| @@ -6,9 +6,13 @@ | ||
| 6 | 6 | use Give\Campaigns\ValueObjects\CampaignGoalType; |
| 7 | 7 | use Give\DonationForms\Actions\GenerateAuthUrl; |
| 8 | 8 | use Give\DonationForms\Actions\GenerateDonateRouteUrl; |
| 9 | 9 | use Give\DonationForms\Actions\GenerateDonationFormValidationRouteUrl; |
| 10 | +use Give\DonationForms\Actions\GetFormSkeletonData; | |
| 11 | +use Give\DonationForms\Actions\IsolateEnqueuedFormViewAssets; | |
| 12 | +use Give\DonationForms\Actions\RenderFormSkeleton; | |
| 10 | 13 | use Give\DonationForms\DataTransferObjects\DonationFormGoalData; |
| 14 | +use Give\DonationForms\Models\DonationForm; | |
| 11 | 15 | use Give\DonationForms\Properties\FormSettings; |
| 12 | 16 | use Give\DonationForms\Repositories\DonationFormRepository; |
| 13 | 17 | use Give\DonationForms\ValueObjects\GoalType; |
| 14 | 18 | use Give\Framework\Blocks\BlockCollection; |
| @@ -46,9 +50,18 @@ | ||
| 46 | 50 | /** |
| 47 | 51 | * @var bool |
| 48 | 52 | */ |
| 49 | 53 | private $previewMode; |
| 54 | + | |
| 50 | 55 | /** |
| 56 | + * The skeleton markup for this render, or empty when there is none to show. | |
| 57 | + * | |
| 58 | + * @since 4.17.0 | |
| 59 | + * | |
| 60 | + * @var string | |
| 61 | + */ | |
| 62 | + private $skeleton = ''; | |
| 63 | + /** | |
| 51 | 64 | * @since 4.1.0 |
| 52 | 65 | */ |
| 53 | 66 | private DonationFormGoalData $donationFormGoalData; |
| 54 | 67 | |
| @@ -144,8 +157,15 @@ | ||
| 144 | 157 | wp_strip_all_tags(give_get_option('custom_form_styles', '')) |
| 145 | 158 | ); |
| 146 | 159 | |
| 147 | 160 | wp_enqueue_style('givewp-base-form-styles'); |
| 161 | + | |
| 162 | + // The skeleton prints in the body before the app bundles load, so its styles go in the head with the rest. | |
| 163 | + if ($this->skeleton) { | |
| 164 | + wp_register_style('givewp-form-skeleton-styles', false); | |
| 165 | + wp_add_inline_style('givewp-form-skeleton-styles', (new RenderFormSkeleton())->css()); | |
| 166 | + wp_enqueue_style('givewp-form-skeleton-styles'); | |
| 167 | + } | |
| 148 | 168 | } |
| 149 | 169 | |
| 150 | 170 | /** |
| 151 | 171 | * @since 3.0.0 |
| @@ -274,19 +294,24 @@ | ||
| 274 | 294 | ]; |
| 275 | 295 | } |
| 276 | 296 | |
| 277 | 297 | /** |
| 298 | + * @since 4.17.0 print the form's skeleton inside the root and announce the shell to the embedding page. | |
| 278 | 299 | * This is the order of loading: |
| 279 | 300 | * 1. Enqueue global styles from WP. |
| 280 | 301 | * - This ensures template compatability with global WP css variables as needed. Loads before our templates, so they can use things like global font-family, etc. |
| 281 | 302 | * 2. Enqueue our donation form specific scripts & styles. |
| 282 | 303 | * - We will let WP handle the actual printing depending on how they were enqueued. |
| 283 | - * 3. Call the specific WP functions wp_print_styles() and wp_print_head_scripts() | |
| 304 | + * 3. Pin the script and style queues to what we enqueued. | |
| 305 | + * - The print functions below fire actions that other plugins enqueue from, so the queues | |
| 306 | + * have to be constrained before printing rather than trusted. | |
| 307 | + * 4. Call the specific WP functions wp_print_styles() and wp_print_head_scripts() | |
| 284 | 308 | * - This will only print the styles and scripts that are enqueued within our route - so we don't have to dequeue a bunch of stuff. |
| 285 | - * 4. Manually echo our window data and root div for our React app to consume | |
| 286 | - * 5. Finally, call the specific WP function wp_print_footer_scripts() | |
| 309 | + * 5. Manually echo our window data and root div for our React app to consume | |
| 310 | + * 6. Finally, call the specific WP function wp_print_footer_scripts() | |
| 287 | 311 | * - This will only print the footer scripts that are enqueued within our route. |
| 288 | 312 | * |
| 313 | + * @since 4.16.7 Isolate the printed assets from anything enqueued after the form has been prepared | |
| 289 | 314 | * @since 4.14.3 Escape HTML attributes for classNames property |
| 290 | 315 | * @since 3.20.0 Adds class for form design |
| 291 | 316 | * @since 3.11.0 Sanitize customCSS property |
| 292 | 317 | * @since 3.0.0 |
| @@ -292,8 +317,10 @@ | ||
| 292 | 317 | * @since 3.0.0 |
| 293 | 318 | */ |
| 294 | 319 | public function render(): string |
| 295 | 320 | { |
| 321 | + $this->skeleton = $this->previewMode ? '' : $this->renderSkeleton(); | |
| 322 | + | |
| 296 | 323 | $this->enqueueGlobalStyles(); |
| 297 | 324 | |
| 298 | 325 | $this->enqueueFormScripts( |
| 299 | 326 | $this->donationFormId, |
| @@ -299,8 +326,10 @@ | ||
| 299 | 326 | $this->donationFormId, |
| 300 | 327 | $this->designId() |
| 301 | 328 | ); |
| 302 | 329 | |
| 330 | + (new IsolateEnqueuedFormViewAssets())(); | |
| 331 | + | |
| 303 | 332 | ob_start(); |
| 304 | 333 | wp_print_styles(); |
| 305 | 334 | wp_print_head_scripts(); |
| 306 | 335 | ?> |
| @@ -321,10 +350,14 @@ | ||
| 321 | 350 | ?> |
| 322 | 351 | |
| 323 | 352 | <div data-theme="light" id="root-givewp-donation-form" |
| 324 | 353 | data-iframe-height |
| 325 | - class="<?= esc_attr(implode(' ', $classNames)) ?>"></div> | |
| 354 | + class="<?= esc_attr(implode(' ', $classNames)) ?>"><?= $this->skeleton ?></div> | |
| 326 | 355 | |
| 356 | + <?php if ($this->skeleton): ?> | |
| 357 | + <script>parent.postMessage({type: 'givewp-embed-shell', height: document.documentElement.scrollHeight}, '*');</script> | |
| 358 | + <?php endif; ?> | |
| 359 | + | |
| 327 | 360 | <?php |
| 328 | 361 | wp_print_footer_scripts(); |
| 329 | 362 | |
| 330 | 363 | echo ob_get_clean(); |
| @@ -329,8 +362,30 @@ | ||
| 329 | 362 | |
| 330 | 363 | echo ob_get_clean(); |
| 331 | 364 | |
| 332 | 365 | exit(); |
| 366 | + } | |
| 367 | + | |
| 368 | + /** | |
| 369 | + * The skeleton RenderFormSkeleton draws, printed inside the root so the iframe shows the form's | |
| 370 | + * shape as soon as its HTML and head styles arrive, before the app bundles load. createRoot() | |
| 371 | + * replaces it with the form. The inline script that follows the root tells the embedding page | |
| 372 | + * the shell has painted and how tall it is; the payload is that height and nothing else, so it | |
| 373 | + * is addressed to any origin and the embed authenticates the message by its origin and source | |
| 374 | + * instead. Both are skipped for an unknown design, where there is nothing to show, and in the | |
| 375 | + * builder preview. | |
| 376 | + * | |
| 377 | + * @since 4.17.0 | |
| 378 | + */ | |
| 379 | + private function renderSkeleton(): string | |
| 380 | + { | |
| 381 | + $form = DonationForm::find($this->donationFormId); | |
| 382 | + | |
| 383 | + if (!$form) { | |
| 384 | + return ''; | |
| 385 | + } | |
| 386 | + | |
| 387 | + return (new RenderFormSkeleton())((new GetFormSkeletonData())($form)); | |
| 333 | 388 | } |
| 334 | 389 | |
| 335 | 390 | /** |
| 336 | 391 | * Loads scripts in order: [Registrars, Designs, Gateways, Block] |