Move DOM Elements with x-teleport
I'm going to show you three new features that were recently added to Alpine.js. Let's dive in.
Alright, so I have this basic page up right now. It has super simple source code and I'm loading in Alpine.js through a CDN.
The first feature I'd like to talk about is the x-teleport directive. Let's go ahead and create a new div and I'll just add in a title here: x-teleport.
And a div with an x-data attribute signifying that we have Alpine.js state. This will just be open: false. And under here we'll add in a button and whenever you click that button, it's going to toggle that open value so open equals whatever the opposite of open is. And we'll use that to toggle a modal.
So now let's add a div in here and we'll say x-show="open". So we'll display this if open is set to true and we'll style it up a bit. And here's where our modal contents would be.
Now let's see what this looks like in the browser. We can hit this toggle modal here and it will display the modal as expected. But this might not be where I want the markup to be in the entire document. Realistically, for a modal, I would want it to live somewhere toward the end of the document, like before the closing body tag.
But if I was to go ahead and move this and then refresh, there's no longer any interactivity because this element now lives outside of our Alpine.js state. So that's where x-teleport comes in.
So instead, we can create a template and say x-teleport="body". Now by adding this in and refreshing, if we were to click this toggle again, we see our element appear differently this time. And inspecting the page, we can see that the element now lies right before the closing body tag.
So that's what teleport does. It moves blocks of markup that are inside of this template to right before the closing tag of the element that you specify in this attribute. And the attribute works like a query selector, so instead of body, we could say something like #teleport and give an element the id of teleport. And now refreshing and toggling that modal again, if we inspect it, we can see the markup for this modal is appearing right before the closing tag of the element with the id of teleport.
And even if we were to add content before this closing tag, if we refresh the page and toggle the modal, it still shows up after the markup, right before the closing tag.
And fun fact, this actually used to be a plugin called Portal but was recently added to the core framework.
Generate Unique IDs with $id and x-id
Alright, moving on. Let's create another div section. And the next feature that we're going to be talking about is a magic element called $id.
Basically, it works to solve the question: how can I easily create unique names for my elements? So let's say, for instance, that we had a form and inside of this, we had two elements. And I wanted to generate a unique ID for each of these. Now, I could hardcode that in because we only have two elements here, but that could get complicated with more.
So instead, what I'm going to do is use $id. So we'll bind id to $id and it's a method whose argument is a string that constitutes the beginning of the IDs that are generated. So let's use 'form-input'. And we'll do the same thing to the other input.
Now if we go to our browser and refresh, let's take a look at what IDs these elements have. Alright, so this has the ID of form-input-1 and this has the ID of form-input-2. So it basically just uses the string that we provided, form-input, and tacks on an integer after it depending on the position of the element.
But now what if we wanted to attach labels to these inputs? So we do label and bind for to the same id: $id('form-input'). And we'll do the same thing to this input.
Now if we refresh... oh. Let's give this a class of text-left. Alright, now let's inspect this and see what's going on here. Now... okay, the label is for form-input-1, but the ID of the form is form-input-2. And in our other group, it's form-input-3 and form-input-4. So we have a problem that because we're using the same form-input on the id, it's generating one, two, three, and four, when we want one-one and two-two.
Luckily, there's a directive called x-id that constitutes a group of IDs in this particular element. So under this div, these are all form-input IDs and they should all match. And likewise with this group.
So now refreshing, what we should see are two groups of form inputs. These consisting of form-input-1 and these consisting of form-input-2. Let's inspect our element. And that's exactly what we see here.
You can also use the magic $id element in a loop. So let's add a list here and we'll give it some data. items is an array and I'll just generate some random items. Okay, so inside of this list, let's run a loop. template, x-for="item in items", and we'll have to bind a key to it, and we'll use the item.id. And now we can create a list item and say the id is $id('list-item'). And we'll add in the text as item.name.
So now refreshing our page, we see our list items here. And if we were to inspect them, we see they have the ID of list-item-1, two, and three.
Now, $id does have a second argument that you can pass in and it serves as a custom suffix for the generated ID. So if we were to pass in our item.id here and refresh the page, taking a look at these elements, we see they have the ID of list-item-1-4, list-item-2-5, and list-item-3-6. But that's not really what we want. We wanted these all to be kind of coherent, just having the 4, 5, and 6 tacked on the end of it.
Well, like what we showed above, each of these is generated without a group underneath of it, but we can fix that in the main ul element by calling x-id and passing in 'list-item'. So now these should all be grouped under a single list item. And that's what we see here. It's still prefixed with 1 but at least it's list-item-1-4, list-item-1-5, and list-item-1-6.
Access Component State with $data
Alright, moving on to our last feature. We'll create one more div for this, and it's another magic element called $data.
This is a pretty simple one, but it's a decently powerful addition. So to demonstrate it, let's create a div here and add in some data for it. We'll say greeting is 'hello', language: 'english'. And underneath of this, we'll create a select with the model being the greeting, and give it a few options.
Alright, nested in this, we'll create another div that has data attached to it, creating more state in Alpine. And we'll say name is 'Andrew'. And under this we'll generate a button and when it's clicked we'll call a function called sayHello and pass in that magic $data object.
Now let's go ahead and create that script down here toward the end of the document. And let's see what we get if we just console log out the data.
So going back to our browser and refreshing, let's open up our console and click 'Say Hello'. So we get back this proxy object that appears as though it's empty. But the magic comes from when we use an attribute on that object. So let's alert out data.greeting and data.name.
So now if we click this 'Say Hello' button, we get back a pop-up that says 'hello Andrew'. And changing our greeting to something else, we get that attribute back immediately.
What $data does is give you access to all of the state available to you whenever it was passed in. So because we are accessing the $data object here, we have the state contained here as well as the state contained here available to us. And you can access that directly as an attribute on the object or instead of giving data as an argument, you can deconstruct the object and only pull out what you need. So we could say greeting and name and replace these with just greeting and name.
So even though the state contains a language attribute, we are not using that and don't need to pull it in. And it still works just as expected. So now in methods in our HTML, we don't have to necessarily pass in a lot of data attributes; we can let our declared functions handle that instead.
Conclusion
Alright, that's about it for this video. Plan on using any of these new features in your own project or have any opinions on them? Let's hear about it in the comments below.