New York
Account created on 23 October 2009, over 14 years ago
#

Merge Requests

More

Recent comments

🇺🇸United States FatherShawn New York

True, it was never implemented but as you note the project works correctly to view one's own complete step

🇺🇸United States FatherShawn New York

This appears to have been broken at some point late in the run up to 1.1.0 as it was working for a long time

🇺🇸United States FatherShawn New York
🇺🇸United States FatherShawn New York

The save and continue function in our new block admin interface (v.1.1) is the simplest case of multi-step, although steps 2+ are all the same form.

Some additional sense can be had from the sum of interactions in that page. See HTMX Block Admin

Leaving this open but postponed. Others are welcome to add examples and invited to add them to our new documentation.

🇺🇸United States FatherShawn New York
🇺🇸United States FatherShawn New York
🇺🇸United States FatherShawn New York

FatherShawn created an issue.

🇺🇸United States FatherShawn New York

I should have branched 1.1.x off when we started. The idea is that we are using patch based branches so that we can update the htmx library in a patch update on the existing minor version while we are working on the next minor version. I didn't but it has been short enough I think that's OK. I'll make the branch now and aim to keep the strategy going forward.

🇺🇸United States FatherShawn New York
🇺🇸United States FatherShawn New York

Added with test. All tests passing.

🇺🇸United States FatherShawn New York

It turns out the correct syntax from phpstan's perspective is @covers ::stream_stat and not @covers ::stream_stat()

📌 | HTMX | HTMX Blocks
🇺🇸United States FatherShawn New York

All tests and code quality checks passing in GitLab CI.

🇺🇸United States FatherShawn New York

These are valid methods as far as I can see. Shall I remove the @covers tags?

------ ------------------------------------------------------------------------------------ 
  Line   tests/src/Kernel/CloudflareStreamWrapperTest.php                                    
 ------ ------------------------------------------------------------------------------------ 
  95     @covers value                                                                       
         \Drupal\cloudflare_stream\StreamWrapper\CloudflareStreamWrapper::stream_stat()      
         references an invalid method.                                                       
  113    @covers value                                                                       
         \Drupal\cloudflare_stream\StreamWrapper\CloudflareStreamWrapper::stream_stat()      
         references an invalid method.                                                       
  133    @covers value                                                                       
         \Drupal\cloudflare_stream\StreamWrapper\CloudflareStreamWrapper::stream_stat()      
         references an invalid method.                                                       
  156    @covers value                                                                       
         \Drupal\cloudflare_stream\StreamWrapper\CloudflareStreamWrapper::stream_close()     
         references an invalid method.                                                       
  156    @covers value                                                                       
         \Drupal\cloudflare_stream\StreamWrapper\CloudflareStreamWrapper::stream_open()      
         references an invalid method.                                                       
  180    @covers value                                                                       
         \Drupal\cloudflare_stream\StreamWrapper\CloudflareStreamWrapper::stream_metadata()  
         references an invalid method.                                                       
 ------ ------------------------------------------------------------------------------------ 
🇺🇸United States FatherShawn New York

I've had a run at fixing the lint. I'm pretty sure that all the critical methods in the streamwrapper have automated tests.

🇺🇸United States FatherShawn New York
🇺🇸United States FatherShawn New York

FatherShawn made their first commit to this issue’s fork.

📌 | HTMX | HTMX Blocks
🇺🇸United States FatherShawn New York

All coding standards issues resolved and new tests passing

🇺🇸United States FatherShawn New York

Yes - I'll write something on Monday in the Change Record :)

🇺🇸United States FatherShawn New York

I think those steps are close to what I'm thinking about. I would adjust it like so:

  1. Determine where and how to process an #htmx render array property.
  2. Agree on a couple of use cases
  3. Design a processor that transforms #ajax render array key-value pairs into #htmx render array key-value pairs. This is our BC layer.

For use cases I think we may have agreed on

Content replacement using #wrapper

I would suggest one of the commands that are similar, such as append, for the second.

🇺🇸United States FatherShawn New York

@kevinquillen At the request of @nod_ we are discussing that in 📌 [POC] Implementing some components of the Ajax system using HTMX Active

🇺🇸United States FatherShawn New York

@tim-diels I tested syncing with your changes locally and we had a Cloudflare Stream video with a name that had characters that threw an error when used as a file name. That revealed a bug in the drush error logging and the filename method of Video Sync. The most recent commit fixes that. All of your work checks out.

🇺🇸United States FatherShawn New York

Some more thoughts on the questions raised in #43 🌱 [Plan] Gradually replace Drupal's AJAX system with HTMX Active , #44 🌱 [Plan] Gradually replace Drupal's AJAX system with HTMX Active , and #45 🌱 [Plan] Gradually replace Drupal's AJAX system with HTMX Active above:

However, Drupal pages have long been dependent on a strong relationship between <head> and <body> content, with different JS/CSS bundles from page to page, for example.

I think the existing concepts of ajax_page_state ought to work

I think I do have this working with ajax_page_state in my contrib implemenation. HTMX includes a header, HX-Request with every request it makes. I have a response subscriber that only processes the response if this header was included on the request. On HTMX requests, the response is processed using logic adapted from our current AjaxResponseAttachmentsProcessor. The resulting asset data is attached to the response as JSON.

Now, you can indeed push changes to <head> using multiple response top-level element with the hx-swap-oob attribute, but that needs an ID on each modified target, which needs to pre-exist on the source page, which seems like a lot of potential trouble.

Perhaps HTMX has evolved it's capabilities as all my exposure is recent. The <script> tag containing the JSON asset data is wrapped with <div hx-swap-oob="beforeend:body"></div> as part of the response processing. The value on hx-swap-oob defines the swap strategy and target that is used for the inner HTML of the <div>. The result is a script tag appended to the end of the body. HTMX fires an event, htmx:oobAfterSwap, after it has processed and inserted our asset JSON. Currently I have a listener for this event that uses the loadjs library we already use to process and attach the assets.

which is likely to interfere with our use of behaviours

HTMX fires another event, htmx:load after each response has finished processing. I'm currently using an event listener on this event to call our Drupal.attachBehaviors(). All of this is verified in a test.

I have less experience with the JS side of our core code and @nod_ said the JS here could be improved 🌱 [Plan] Gradually replace Drupal's AJAX system with HTMX Active so please offer improvements either in 📌 [POC] Implementing some components of the Ajax system using HTMX Active or in the HTMX module issue queue .

A related issue will be history support. If we enable back/forward navigation with the hx-push-url we probably need to make it so that the cacheability headers, among others, be updated for updated pages, because the result will often not match the source. Think, for example, of a search page filled with results: the results will at least carry cache tags for the results but the original page will not.

If we did enable history support, what I think is important is that the url that is updated in the browser should be able to recreate the current display. Using the example of a search page, we commonly add query parameters to such urls containing the search terms. I don't believe that we are currently updating anything in the header on ajax responses, other than header JS included in the differential assets payload. If we find that we do want to add fully harmonizing the head section, we could explore adapting the HTMX head-support extension for our specific needs.

🇺🇸United States FatherShawn New York

Three initial thoughts:

  1. Content replacement is probably the simplest case and so one we should include in this POC
  2. I have a proof of concept for JS/CSS attachment in the HTMX contrib module.
  3. What if we plan to replace the #ajax render array key with an #htmx key? If we did that, then our BC strategy is writing a converter that rewrites the render array to equivalent values
🇺🇸United States FatherShawn New York

Thanks for the support and clear guidance @nod_ :)

I opened 📌 [POC] Implementing some components of the Ajax system using HTMX Active

🇺🇸United States FatherShawn New York

Thanks for the update. I'll leave this postposponed, and updated to be more general as a feature idea.

🇺🇸United States FatherShawn New York

I also expect that in your case, you would want to store the email address in TempStore ( user specific ) rather than configuration which would be site wide.

🇺🇸United States FatherShawn New York

I think the approach here will be to support client plugins that implement \Drupal\Core\Plugin\PluginFormInterface and conditionally include a plugin's form in \Drupal\oauth2_client\Form\Oauth2ClientForm. This would allow client plugins to define additional configuration.

Example:

  • \Drupal\user\Plugin\Action\ChangeUserRoleBase::buildConfigurationForm
  • \Drupal\action\Form\ActionFormBase line 81

I don't have space right now to add but I'm happy to review any code or work on it when I have time.

🇺🇸United States FatherShawn New York

Resolved all warnings from the code linters in the GitLab pipelines.

🇺🇸United States FatherShawn New York

Thanks for the review @tim-diels!

  1. I added the change record link to the info file and the deprecation comment tag on the hosted video source plugin.
  2. The missing description was because the description field is not enabled by default:

  3. Here's the resulting markup:

    <div id="0e31b8bd-41f3-4a71-9b09-b21836628c30">
    
      <!-- THEME DEBUG -->
      <!-- THEME HOOK: 'cloudflare_video' -->
      <!-- BEGIN OUTPUT from 'modules/cloudflare_stream/templates/cloudflare-video.html.twig' -->
    
      <stream src="redacted-id-string" preload="" style="width: 854px; height: 480px;">
        <div style="position: relative; height: 0px; width: 100%; padding-top: 56.3388%;">
          <iframe style="height: 100%; width: 100%; position: absolute; top: 0px; left: 0px; border: medium;"
                  src="https://embed.videodelivery.net/embed/iframe.fla9.3b8bfec.html?videoId=redacted-id-string"
                  allowfullscreen="true" allow="autoplay; picture-in-picture"></iframe>
        </div>
      </stream>
      <script data-cfasync="false" defer="" type="text/javascript"
              src="https://embed.videodelivery.net/embed/r4xu.fla9.latest.js?video=redacted-id-string"></script>
      <p class="description">The captain's chair</p>
    
      <!-- END OUTPUT from 'modules/cloudflare_stream/templates/cloudflare-video.html.twig' -->
    </div>
    
  4. I added a targeted exception at the case in which no media type id is passed to the drush command and none is set in sync settings. This now results in the CLI in that case:
    [error] ValueError: A media type ID was not provided and none is stored in cloudflare_stream_sync.settings in Drupal\cloudflare_stream_sync\SyncVideos->syncVideos()
🇺🇸United States FatherShawn New York

Then we need a plan and proof of concept? ...

We are working on some proofs of concept in the HTMX contrib module Brian linked in #33. The first thing was to get assets from inserted content loading using the ajax page state approach we have now. The approach I took there may be a good pattern for some of the other components of the AJAX system in core:

  1. data is returned in JSON appended to the body
  2. An event listener processes the data and makes the change.
  3. The additional assets use case responds on HTMX load, but HTMX responses can return custom events so actions in our AJAX system beyond content replacement could be paired with an event and a listener.

Since we often innovate and learn in contrib, collaborators warmly encouraged over there!

That would mean modules with their own JavaScript interacting with the AJAX API would need to re-implement the functionality so it works in HTMX mode and support both for a while, then sites could switch from one to the other over time, and we could eventually change the default for new sites. This would mean no 'bc layer' as such for the JavaScript AJAX API, it would just co-exist for a while and then be removed.

If we approached this in a similar manner to how we are dealing with the change from Annotations to Attributes for plugins, a module wouldn't have to support both but we would need to do so in core for a time. This would allow modules to change anything custom that they have done as needed without a bc layer.

📌 | HTMX | HTMX Blocks
🇺🇸United States FatherShawn New York

FatherShawn created an issue.

🇺🇸United States FatherShawn New York

Adding a note here to document a conversation in Drupal Slack with @ndf - we are not going to add the complexity of submodules but keep a standard one module structure

🇺🇸United States FatherShawn New York

I've pushed an interim branch that depends on 📌 Move the media source from cloudflare_stream_hosted_video to cloudflare_stream Needs review . I'll make an MR when that work is merged and discuss my approach at that point.

🇺🇸United States FatherShawn New York

I plan to explore addressing Tus protocol for file uploads Active in the context of creating a stream wrapper for this issue.

🇺🇸United States FatherShawn New York

Here are two merge requests. MR-30 marks the hosted video module and source as deprecated in the 2.x branch. In #2928256: Users shouldn't be able to change the media source plugin after the media type is created changing of media sources was blocked in core. The source property on Media types is a protected property. There may be ways to circumvent that but it is only intended to be changed before the type is saved. This was permitted in #2928851: Allow the media source of a media type to be changed when creating a new media type .

MR-31 creates a new source in the main module. I've adjusted the sync commands and the form for selection of the target type. I've also moved the required core version to 10.3 so that the shift from Annotations to Attributes for plugin metadata can be accommodated.

🇺🇸United States FatherShawn New York

Regular swaps in HTMX can replace or append with additional control on how and where that is executed.

Out of band swaps add additional payload and they are not limited in number. Multiple out of band swaps could insert a variety of tags in the same request.

When paired with javascript event listeners, those payloads can be processed. For example, using the out of band swap currently to add json in a script tag that describes additional CSS/JS assets needed by the inserted content, then loading those assets after the json is inserted.

It is pushing me to think differently. It's like making stew with a new set of spices.

🇺🇸United States FatherShawn New York

Thanks for the review and the notes! If they prompt any questions I'll post on the MR - love that we can do that now!

🇺🇸United States FatherShawn New York

All tests and code checks passing

🇺🇸United States FatherShawn New York

I'm exploring simplifying this solution:

  1. Still move the media source plugin to the main module and rename it.
  2. Leave the existing sub-module in place but adjust as needed for the plugin changes.

If we think that we should rename the sub-module, I'd recommend that we do that in a later release. We can notify people in the release notes to simply move the existing submodule into their custom modules.

🇺🇸United States FatherShawn New York

Tagged a 1.4 release on the unsupported 8.x-1.x branch

I'll tag a full 2.0 release after marking this issue fixed.

Thanks for the collaboration @rszrama! My original client required a PO number. After this was contributed, a feature was contributed for the file upload of a copy of the PO. The instructions should be configured for the user in the gateway config so that the tasks are clear.

🇺🇸United States FatherShawn New York

We don't need to build a service. Core has these services and we can use them in our controller. Less to do and maintain is a win!!

🇺🇸United States FatherShawn New York
🇺🇸United States FatherShawn New York

The best approach I think is a controller that renders using `\Drupal\Core\Entity\EntityViewBuilder` and it's decendents.

🇺🇸United States FatherShawn New York

Looks like we can use a dynamic entity based on the docblock from `\Drupal\Core\Entity\EntityAccessCheck`

  /**
   * Checks access to the entity operation on the given route.
   *
   * The route's '_entity_access' requirement must follow the pattern
   * 'slug.operation'. Typically, the slug is an entity type ID, but it can be
   * any slug defined in the route. The route match parameter corresponding to
   * the slug is checked to see if it is entity-like, that is: implements
   * EntityInterface. Available operations are: 'view', 'update', 'create', and
   * 'delete'.
   *
   * For example, this route configuration invokes a permissions check for
   * 'update' access to entities of type 'node':
   * @code
   * pattern: '/foo/{node}/bar'
   * requirements:
   *   _entity_access: 'node.update'
   * @endcode
   * And this will check 'delete' access to a dynamic entity type:
   * @code
   * example.route:
   *   path: foo/{entity_type}/{example}
   *   requirements:
   *     _entity_access: example.delete
   *   options:
   *     parameters:
   *       example:
   *         type: entity:{entity_type}
   * @endcode
   *
   * ...
   */

Route path: `/htmx/{entity_type}/{entity_id}/{view_mode}`

🇺🇸United States FatherShawn New York

We are already providing a service to return a slimmer response: 📌 Provide a means to return a simple html response on routes Fixed
The simple page provided by core only renders any system messages and the main content of the page. If we render and return the entity alone, that will be the main content of the page, and so that feature is solved by this work.

We may be able to generalize to a single route, but I would want to be sure that we can account for:

  1. entity access
  2. view modes

I know that if we focus an implementation on a particular entity type, that we can hand off access checks back to core. If we have to build an access solution to provide a general route, then I would favor entity specific solutions, with possibly a base controller in the main module.

🇺🇸United States FatherShawn New York

Since this proposal adds some of our own JS, I've made a commit to the feature branch that moves HTMX JS into a subdirectory, along with the documentation expected by d.o policy.

🇺🇸United States FatherShawn New York

Yes it could. It can implement any element ajax loading content. Until 📌 Leverage hx-swap-oob for assets Active is solved you will not get assets (JS/CSS) that are unique to the loaded content added to the page, so you would need to plan for that.

🇺🇸United States FatherShawn New York
🇺🇸United States FatherShawn New York

As to what can be built? I've been playing with HTMX Blocks. I wanted to create a bucket of configured blocks that were along side those configured to be placed in the regions. I've been learning how HTMX works by building the admin interface with it and I definitely started with your "fancy things happen when I click a button" so that edit forms are presented via ajax in a <dialog> element and the list of blocks is updated when an edit form is submitted.

A documentation request came in to the issue queue around the same time you posted: create tabs that load other content. They could lazy load using hx-on:load of on mouseover, or when they scroll into view...

Infinite scroll seems high on the list

Multipart forms - anything we use our could use ajax for.

🇺🇸United States FatherShawn New York

Let's focus on an initial release of basic features: v1.1

🇺🇸United States FatherShawn New York

Here's my initial list:

  1. A means to dynamically add assets needed by inserted html: 📌 Leverage hx-swap-oob for assets Active
  2. Create an HTMX Blocks sub-module that provides Drupal blocks can be configured to respond to an HTMX event (hx-on). When the event triggers, replace itself in the DOM with another block.
  3. Create an HTMX Node sub-module that provides an htmx route to render a given node in a given view mode
🇺🇸United States FatherShawn New York

Drupal ajax.js has this:

    // Allow Drupal to return new JavaScript and CSS files to load without
    // returning the ones already loaded.
    // @see \Drupal\Core\StackMiddleWare\AjaxPageState
    // @see \Drupal\Core\Theme\AjaxBasePageNegotiator
    // @see \Drupal\Core\Asset\LibraryDependencyResolverInterface::getMinimalRepresentativeSubset()
    // @see system_js_settings_alter()
    const pageState = drupalSettings.ajaxPageState;
    options.data['ajax_page_state[theme]'] = pageState.theme;
    options.data['ajax_page_state[theme_token]'] = pageState.theme_token;
    options.data['ajax_page_state[libraries]'] = pageState.libraries;

I think we can use hx-vals on the body tag for this:
The simplest is:

hx-vals='js:{"ajax_page_state[libraries]": drupalSettings.ajaxPageState.libraries}'

But it may be more secure to dynamically set this attribute on the body tag via JS before we load HTMX rather than introduce a JS execution path into the DOM

🇺🇸United States FatherShawn New York

Set the default branch in Gitlab

Production build 0.69.0 2024