Skip to content

Commit

Permalink
2.11.0
Browse files Browse the repository at this point in the history
  • Loading branch information
bencroker committed Aug 20, 2024
1 parent e66cee8 commit 7a1d138
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 170 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Release Notes for Sprig Core

## 2.11.0 - 2024-08-20

### Changed

- The `sprig.registerJs(js)` function now executes the registered JavaScript after htmx settles, and is now the recommended way of outputting JavaScript in Sprig components.
- Components no longer render markup added via `{% html %}`, `{% css %}` and `{% js %}` tags during Sprig requests.

## 2.10.2 - 2024-08-12

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "putyourlightson/craft-sprig-core",
"description": "A reactive Twig component framework for Craft.",
"version": "2.10.2",
"version": "2.11.0",
"type": "craft-module",
"license": "mit",
"require": {
Expand Down
95 changes: 94 additions & 1 deletion src/base/Component.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@

use Craft;
use craft\base\Component as BaseComponent;
use craft\helpers\Html;
use craft\helpers\Json;
use craft\helpers\StringHelper;
use craft\web\View;
use putyourlightson\sprig\services\ComponentsService;
use putyourlightson\sprig\Sprig;

abstract class Component extends BaseComponent implements ComponentInterface
{
Expand Down Expand Up @@ -50,7 +54,7 @@ public static function getIsRequest(): bool
*/
public static function getIsInclude(): bool
{
return !self::getIsRequest();
return !static::getIsRequest();
}

/**
Expand Down Expand Up @@ -186,6 +190,22 @@ public static function refresh(bool $refresh = true): void
Craft::$app->getResponse()->getHeaders()->set('HX-Refresh', $refresh ? 'true' : '');
}

/**
* Registers JavaScript code to be output. This method takes care of registering the code in the appropriate way depending on whether it is part of an include or a request.
*
* @since 2.11.0
*/
public static function registerJs(string $js): void
{
if (static::getIsInclude()) {
Craft::$app->getView()->registerJs($js, View::POS_END);

return;
}

Sprig::$core->requests->registerJs($js);
}

/**
* Specifies how the response will be swapped.
* https://htmx.org/reference#response_headers
Expand All @@ -204,6 +224,26 @@ public static function retarget(string $target): void
Craft::$app->getResponse()->getHeaders()->set('HX-Retarget', $target);
}

/**
* Swaps a template out-of-band. Cyclical requests are mitigated by prevented the swapping of unique components multiple times in the current request, including the initiating component.
* https://htmx.org/attributes/hx-swap-oob/
*
* @since 2.9.0
*/
public static function swapOob(string $selector, string $template, array $variables = []): void
{
if (static::getIsInclude()) {
return;
}

$value = Sprig::$core->requests->getOobSwapValue($selector, $template, $variables);
if ($value === null) {
return;
}

Sprig::$core->requests->registerHtml($value, 'innerHTML:' . $selector);
}

/**
* Triggers client-side events.
* https://htmx.org/headers/hx-trigger/
Expand All @@ -228,4 +268,57 @@ public static function triggerEvents(array|string $events, string $on = 'load'):
Craft::$app->getResponse()->getHeaders()->set($header, $events);
}
}

/**
* Triggers a refresh event on the provided selector. If variables are provided then they are appended to the component as hidden input fields. Cyclical requests are mitigated by prevented the triggering of unique components multiple times, including the initiating component.
*
* @since 2.9.0
*/
public static function triggerRefresh(string $selector, array $variables = []): void
{
if (static::getIsInclude()) {
return;
}

$triggerRefreshSources = Sprig::$core->requests->getValidatedParam('sprig:triggerRefreshSources') ?? [];
if (in_array($selector, $triggerRefreshSources)) {
return;
}

$id = Sprig::$core->requests->getValidatedParam('sprig:id');
$triggerRefreshSources[] = '#' . $id;
$variables['sprig:triggerRefreshSources'] = Craft::$app->getSecurity()->hashData(Json::encode($triggerRefreshSources));

foreach ($variables as $name => $value) {
$values[] = Html::hiddenInput($name, $value);
}

$html = implode('', $values);
Sprig::$core->requests->registerHtml($html, 'beforeend:' . $selector);
Sprig::$core->requests->registerJs('htmx.trigger(\'' . $selector . '\', \'refresh\')');
}

/**
* Triggers a refresh event on all components on load.
* https://github.com/putyourlightson/craft-sprig/issues/279
*
* @since 2.3.0
*/
public static function triggerRefreshOnLoad(string $selector = ''): void
{
if (static::getIsRequest()) {
return;
}

$selector = $selector ?: '.' . ComponentsService::SPRIG_CSS_CLASS;
$js = <<<JS
fetch('/actions/users/session-info', {headers: {'Accept': 'application/json'}}).then(() => {
for (const component of htmx.findAll('$selector')) {
htmx.trigger(component, 'refresh');
}
});
JS;

Craft::$app->getView()->registerJs($js);
}
}
70 changes: 53 additions & 17 deletions src/services/RequestsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
use craft\helpers\Html;
use craft\helpers\Json;
use craft\helpers\StringHelper;
use craft\web\View;
use putyourlightson\sprig\base\Component as BaseComponent;
use putyourlightson\sprig\Sprig;
use yii\web\BadRequestHttpException;

/**
Expand Down Expand Up @@ -44,6 +43,13 @@ class RequestsService extends Component
*/
private array $js = [];

/**
* The components that initiated out-of-band swaps in the current request.
*
* @var string[]|null
*/
private ?array $oobSwapSources = null;

/**
* Returns allowed request variables.
*/
Expand Down Expand Up @@ -153,7 +159,7 @@ public function getRegisteredHtml(): string
// Execute the JS after htmx settles, at most once.
$js = implode(PHP_EOL, $this->js);
$content = <<<JS
document.addEventListener('htmx:afterSettle', function() {
document.body.addEventListener('htmx:afterSettle', function() {
$js
}, { once: true });
JS;
Expand All @@ -175,40 +181,54 @@ public function getRegisteredHtml(): string
}

/**
* Registers HTML code to be output. This method takes care of registering the code depending on whether it is part of an include or a request.
* Registers HTML code to be output.
*
* @since 2.11.0
*/
public function registerHtml(string $html, string $swapSelector): void
{
if (BaseComponent::getIsInclude()) {
Craft::$app->getView()->registerHtml($html);

return;
}

$this->html[$swapSelector][] = $html;
}

/**
* Registers JavaScript code to be output. This method takes care of registering the code depending on whether it is part of an include or a request.
* Registers JavaScript code to be output.
*
* @since 2.11.0
*/
public function registerJs(string $js): void
{
if (BaseComponent::getIsInclude()) {
Craft::$app->getView()->registerJs($js, View::POS_END);

return;
}

// Trim any whitespace and ensure it ends with a semicolon.
$js = StringHelper::ensureRight(trim($js, " \t\n\r\0\x0B"), ';');

$this->js[] = $js;
}

/**
* Returns the value for the out-of-band swap from a rendered template if it exists, otherwise a rendered string.
*/
public function getOobSwapValue(string $selector, string $template, array $variables = []): ?string
{
if (Craft::$app->getView()->resolveTemplate($template) === false) {
return Craft::$app->getView()->renderString($template, $variables);
}

if (in_array($selector, $this->getOobSwapSources())) {
return null;
}

$this->oobSwapSources[] = $selector;

return Craft::$app->getView()->renderTemplate($template, $variables);
}

/**
* Resets the components that initiated out-of-band swaps in the current request.
*/
public function resetOobSwapSources(): void
{
$this->oobSwapSources = [];
}

/**
* Validates if the given data is tampered with and throws an exception if it is.
*/
Expand Down Expand Up @@ -240,4 +260,20 @@ private function getIsVariableAllowed(string $name): bool

return true;
}

/**
* Returns the components that initiated out-of-band swaps in the current request, including the original component.
*/
private function getOobSwapSources(): array
{
if ($this->oobSwapSources === null) {
$this->oobSwapSources = [];
$id = Sprig::$core->requests->getValidatedParam('sprig:id');
if ($id) {
$this->oobSwapSources[] = '#' . $id;
}
}

return $this->oobSwapSources;
}
}
Loading

0 comments on commit 7a1d138

Please sign in to comment.