Jump to content

Add defer attribute to Alpine.js script loaded by Inputfield module


elabx
 Share

Recommended Posts

Hi! 

I am adding Alpine.js to work with an Inputfield I'm building:

public function renderReady(Inputfield $parent = null, $renderValueMode = false){
  $this->config->scripts->add("https://unpkg.com/alpinejs@3.10.2/dist/cdn.min.js");
  parent::renderReady($parent, $renderValueMode);
}

But seems to be that it requires the "defer" attribute to work correctly, otherwise I get a lot of errors from Alpine. 

From what I understand, the way the AdminThemeUikit loads the scripts is done here  (wire/modules/AdminTheme/AdminThemeUikit/_head.php):

// line 55
foreach($scripts as $file) {
	echo "\n\t<script type='text/javascript' src='$file'></script>";
}

I also tried hooking into the AdminThemeUikit::renderFile to basically edit the output after finding the line where the alpine.js is loaded, but it happens multiple times if I setup different fields of the same type in the page (and maybe too hackish anyway??)

Does anyone think it's worth making a pull request to make this more configurable? Maybe a hookable AdminThemeUikit:renderScriptTag($filename, $options) method in AdminTheme? I feel it makes sense to be able to add this attributes, since defer is now sort of standardized from what I read around the internet. 

foreach($scripts as $file) {
    $adminTheme->renderScriptTag($file);
}

I tried looking into initializing Alpine components any other way but had no success figuring out how to, yet 🙃

Thanks for further answers!

Link to comment
Share on other sites

Padloper 2 uses Alpine.js (and htmx) in lots of places, including Process modules and Inputfields.  Without defer, as you point out, Alpine.js goes funky. My solution is to use inline scripts. ProcessWire does so itself, in several places. For Process modules, I have a method like below that I use to inject Alpine.js script where I need it:

<?php

namespace ProcessWire;

private function getInlineScripts() {
  // @note: need to load alpine as 'defer'
  $url = $this->wire('config')->urls->$this;
  $alpinejs = "{$url}vendors/scripts/alpinejs/alpinejs.3.2.4.min.js";
  $out = "<script src='{$alpinejs}'defer></script>\n";
  return $out;
}

It might not look pretty for some people but your browser doesn't care and it doesn't bother me either .

For Inputfields, I have this:

<?php

namespace ProcessWire;

public function ___render() {
  // .....some code

  /** @var PageArray $value */
  $value = $this->attr('value');
  $form = $this->buildForm($value);
  $out = '';
  //----------
  // ...etc

  //----------
  // MAIN render content
  $out .= $form->render();
  // -------
  /** @var str $preloadInlineAssets */
  $preloadInlineAssets = $this->renderPreloadInlineAssets();
  if (!empty($preloadInlineAssets)) {
    $out .= $preloadInlineAssets;
  }

  //----------
  // APPEND any configs for this Inputfield that might be needed by JavaScript
  // E.G. translated strings.
  $out .= $this->renderJavaScriptConfigs();

  //-------------
  return $out;
}

private function renderPreloadInlineAssets(){
  // ...some code: conditions, etc
  // ----
  $out = "<script src='{$source}'{$defer}></script>\n";
  return $out;
}

 

14 hours ago, elabx said:

Does anyone think it's worth making a pull request to make this more configurable? Maybe a hookable AdminThemeUikit:renderScriptTag($filename, $options) method in AdminTheme? I feel it makes sense to be able to add this attributes, since defer is now sort of standardized from what I read around the internet. 

I've been meaning to make a request to have this handled by config->scripts->add(), e.g.

<?php

namespace ProcessWire;

$config->scripts->add($alpinejs, ['defer' => true, 'integriy'='some_hash', type="module"]);

 

  • Like 1
Link to comment
Share on other sites

5 minutes ago, kongondo said:

It might not look pretty for some people but your browser doesn't care and it doesn't bother me either .

Love that 😄 

RockFrontend uses this syntax which I think is simpler and better than array syntax, because you don't have to think about all the custom attributes one might need. It's simply a suffix as a plain string:

$rockfrontend->scripts()->add("foo/bar.js", "defer");

// the last part is simply a string, so you can do anything you need
->add("https://code.jquery.com/jquery-3.6.1.min.js", 'defer integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ=" crossorigin="anonymous"');

 

  • Like 1
Link to comment
Share on other sites

3 hours ago, kongondo said:
$config->scripts->add($alpinejs, ['defer' => true, 'integriy'='some_hash', type="module"]);

1000% something like this would be really nice! 

3 hours ago, kongondo said:

My solution is to use inline scripts. ProcessWire does so itself, in several places. For Process modules, I have a method like below that I use to inject Alpine.js script where I need it:

Do you keep track of where you have injected it somehow? I has previously solved loading it with the following hook on an Inputfield:

$this->addHookAfter('AdminTheme::getExtraMarkup', function ($e) {
  $parts = $e->return;
  $parts['head'] .= "<script defer src='https://unpkg.com/alpinejs@3.10.2/dist/cdn.min.js'></script>";
  $e->return = $parts;
});

But then noticed Alpine got injected multiple times if I used the field in a repeater for example, within a page with the same field. 

Link to comment
Share on other sites

3 hours ago, elabx said:

Do you keep track of where you have injected it somehow?

I haven't had the need yet.

3 hours ago, elabx said:

But then noticed Alpine got injected multiple times if I used the field in a repeater for example, within a page with the same field. 

Yeah, I see your point.

 

3 hours ago, elabx said:

I has previously solved loading it with the following hook on an Inputfield:

Would hooking into page edit form solve the multiple injections issue or does each repeater come with its own page edit form? Just wondering.

3 hours ago, elabx said:

@kongondo just out of curiosity, do you know what it would take if we wanted to share the same AlpineJS source for our modules? Have a ModuleJS derived AlpineJS module? 

Gut feeling is that this could work. Would it be just for loading Alpine.js?

Link to comment
Share on other sites

4 hours ago, elabx said:

But then noticed Alpine got injected multiple times if I used the field in a repeater for example, within a page with the same field. 

I think it should be enough to just remove the hook once it got triggered? It's definitely better to add the script in the renderReady method rather than hooking buildForm or something, because in buildForm() you don't know whether your alpine field will be rendered or not (or you'd have to check that yourself and don't forget about nested fields etc...).

<?php
$this->addHookAfter('AdminTheme::getExtraMarkup', function ($e) {
  $parts = $e->return;
  $parts['head'] .= "<script defer src='https://unpkg.com/alpinejs@3.10.2/dist/cdn.min.js'></script>";
  $e->return = $parts;
  $e->removeHook(null); // add this line
});
  • Like 1
  • Thanks 1
Link to comment
Share on other sites

39 minutes ago, kongondo said:

You are the man @bernhard! I don't recall ever seeing this API!

Haha, I think I've never actually used that myself, but I read about it some day in the blog and it sounded really useful and I think it would have been really useful in some of my hooks where I solved it differently (more complicated I guess) 😄 

  • Like 1
Link to comment
Share on other sites

  • 2 weeks later...

Thank you both for your answers! 

I tried to remove the hook like you suggested but the script still gets appended twice in the presence of two of the same field 😑

Also added the request here:

https://github.com/processwire/processwire-requests/issues/468

Also made the edit here:

https://github.com/elabx/processwire/commit/9a261c18a232845d92abe8e411fa48a381563dfd

I also dig your version @bernhard , like, if I want defer without value, this kinda looks weird?

array( [ 'defer' => ''] )

 

Link to comment
Share on other sites

@kongondo

Also managed to load AlpineJS like this:

https://github.com/elabx/AlpineJS

Although only works with the edited core version files mentioned above lol

Then on the Inputfield on init(), I can do:

$this->modules->AlpineJS

This was copied from: https://github.com/somatonic/JqueryDataTables

Only adding the composer dependency since that's how I want to manage it through the Inputfield actually using Alpine

  • Like 1
Link to comment
Share on other sites

6 hours ago, elabx said:

I also dig your version @bernhard , like, if I want defer without value, this kinda looks weird?

array( [ 'defer' => ''] )

Yeah sometimes good old strings are just a whole lot easier/faster to write. $scripts->add("foo.js", "defer") vs. $scripts->add("foo.js", ["defer" => ""]);

Why not add support for strings in your PR? if(is_string($suffix)) ... elseif(is_array($suffix)) ... else ...

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...