Created
July 8, 2020 14:04
-
-
Save mortenson/1a4b61f85fcf9a618519fc231c1c6b6e to your computer and use it in GitHub Desktop.
Drupal single file components livewire demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!-- | |
This is a complex but realistic example of how you might use frameworks like | |
Alpine.js and dom-diffing libraries like morphdom to create components that | |
are re-rendered server-side. This example is heavily influenced by Livewire. | |
In production, it might make more sense for just the dynamic part of the | |
component to be AJAX-ified, in this case that would probably mean making the | |
autocomplete results their own component. There are lots of ways to do this! | |
--> | |
<template> | |
<div x-data='alpineAutocomplete($el)' class="aautocomplete"> | |
<label for="aautocomplete">{{ 'Cat Breed' | t }}</label> | |
<input type="text" x-model="autocomplete" x-on:input.debounce="onInput()" x-on:focus="open = true" x-on:blur.debounce="open = false" id="aautocomplete" autocomplete="false" name="aautocomplete"> | |
<div x-show="open" class="aautocomplete__results"> | |
{% if results is empty %} | |
<div class="aautocomplete__empty">No results.</div> | |
{% endif %} | |
{% for result in results %} | |
<a @click="onResult($event)" class="aautocomplete__result">{{ result }}</a> | |
{% endfor %} | |
</div> | |
</div> | |
</template> | |
<script> | |
function alpineAutocomplete($el) { | |
return { | |
autocomplete: "", | |
open: false, | |
onResult: function($event) { | |
// Update our model when a result is selected. | |
this.autocomplete = $event.target.innerText | |
}, | |
onInput: function() { | |
// Clear results if there's no text. | |
if (this.autocomplete.length == 0) { | |
$el.children[2].innerHTML = ''; | |
return; | |
} | |
// Fetch newly rendered element. This generically works with all | |
// components and could be abstracted. | |
fetch(Drupal.url('alpine-autocomplete/' + this.autocomplete + '?_sfc_raw=1')) | |
.then(function (response) { | |
if (!response.ok) { | |
return; | |
} | |
response.text().then(function (text) { | |
// Use morphdom to apply patch of changes. | |
morphdom($el, text, { | |
onBeforeElUpdated: function(fromEl, toEl) { | |
if (fromEl.isEqualNode(toEl)) { | |
return false; | |
} | |
return true; | |
} | |
}); | |
}); | |
}); | |
} | |
} | |
} | |
</script> | |
<style> | |
.aautocomplete { | |
position: relative; | |
} | |
.aautocomplete__results { | |
max-height: 200px; | |
min-width: 200px; | |
position: absolute; | |
top: 100%; | |
left: 0; | |
overflow-y: scroll; | |
} | |
.aautocomplete__result { | |
cursor: pointer; | |
display: block; | |
} | |
</style> | |
<?php | |
$prepareContext = function (&$context) { | |
// Since "autocomplete" is user controlled, we need some validation. | |
if (!empty($context['autocomplete']) && is_string($context['autocomplete'])) { | |
$cat_breeds = _sfc_example_get_cat_breeds(); | |
$context['results'] = array_filter($cat_breeds, function ($cat_breed) use ($context) { | |
return strpos(strtoupper($cat_breed), strtoupper($context['autocomplete'])) !== FALSE; | |
}); | |
} | |
}; | |
$dependencies = [ | |
'sfc_example/alpine', | |
'sfc_example/morphdom', | |
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sfc_example.alpine_autocomplete: | |
path: '/alpine-autocomplete/{autocomplete}' | |
defaults: | |
_controller: '\Drupal\sfc\Controller\ComponentController::build' | |
component_id: 'example_alpine_autocomplete' | |
requirements: | |
_access: 'TRUE' |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment