Extending Toolbar Buttons0:00
Now, I know I just said that we're done with the Markdown editor. I'd like to just add something to it, but not in the way you'd think. I actually want to extend it, or make it extensible, so that we can add custom buttons to the toolbar. Because, as a developer, I'm very quickly going to get tired of writing custom Markdown for all of this stuff. In the back-end, we've just created those awesome Markdown fixtures. Why should the back-end get all of that magic, but as developers, we have to manually enter all of the data when we want to test on the front-end? That's just not fair. I think we could create a local route that would allow us to pull one of those fake fixtures, and then automatically insert it on this page at the click of a button in the toolbar. Let's see if we can make it happen. Now, the temptation here might be to go straight into the Markdown editor component, and we'll
Using Slots for Toolbar0:52
it on this page at the click of a button in the toolbar. Let's see if we can make it happen. Now, the temptation here might be to go straight into the Markdown editor component, and we'll stick a button here underneath. The problem is that that button needs to be context-aware, because it makes sense here under Create a Post, but when you're in a Comment, you don't want an autofill button in the Markdown editor. So, what we actually need to do is go one step higher. We need to be in the parent component, and we need a way to insert a button into the toolbar from there. We do that using slots. So, inside here, let's create a slot. We'll give it a name of Toolbar, and why don't we also pass the editor down? So, editor equals editor, and that will allow the parent to access this property in order to do things like check whether the button should be active. Now, we can go into that Create component,
editor, and that will allow the parent to access this property in order to do things like check whether the button should be active. Now, we can go into that Create component, and we can make use of it. So, here we have Markdown editor. Let's use the open close tag so that we can override template, and template is going to use the hashtag Toolbar, because that's the slot we want to use, and we can use the equal syntax to also pull in that editor that gets passed up to the parent. Okay, so inside here, why don't we go ahead and just grab one of these buttons, maybe the H3, drop it straight in, and check what we have on the front end. Nice. You can see already it works. Here is our new custom button right next to the original H3, and I imagine if we click this, yeah, both H3s are highlighted, meaning that our editor that's being passed up to the parent works as expected. Of course,
Adding Autofill Action2:25
right next to the original H3, and I imagine if we click this, yeah, both H3s are highlighted, meaning that our editor that's being passed up to the parent works as expected. Of course, we don't want H3. We want autofill, so let's add that. autofill, it's never going to have an active check. We can change this for an icon that makes more sense. Let's check on remix icon, perhaps page. Yeah, article, that looks good. So, RI article line, and yep, that looks very nice, and when we click it, what we don't want to do any of this, we want to call a function. Maybe we'll call that function autofill, and we need to create that function down here near the bottom. So, const autofill equals, and we should be off to the races. So, what should autofill actually do? Well, I think it needs to make an Axios request to an API local route that will grab one of those fixtures and return the title and the
Creating Local-Only Routes3:16
races. So, what should autofill actually do? Well, I think it needs to make an Axios request to an API local route that will grab one of those fixtures and return the title and the body, and then obviously autofill is going to update our form object that we have here in order to place the correct content inside, which should update the front end as well. Now, why don't we go ahead and create the route first, and then we'll come back and implement the autofill function. Again, there's a temptation here to go straight into the web.php file and just start outputting a route that will return fixture content. However, I think there's a cleaner way to do this, and we do it by jumping into the RouteServiceProvider. So, RouteServiceProvider has a boot method, and inside here on line 31, you'll see that it's loading this api.php file. It's also loading this web.php file,
provider. So, RouteServiceProvider has a boot method, and inside here on line 31, you'll see that it's loading this api.php file. It's also loading this web.php file, and those files correspond to our routes directory where we have api and web.php. We can load as many files as make sense. In our case, why don't we grab this, and we'll paste it below. We don't want any middleware because it will contain both api and web routes, but let's give it a prefix of local so that there's no conflicts in route naming, and then we'll load in a file called local.php, like so. So, now under routes, we can create a new file called local.php, and inside here, I'll create two groups. The first one is going to have the middleware of api, and the second one, I'm sure you've already guessed, is going to have a middleware of web. So, if we have any local routes that we're interested in
to have the middleware of api, and the second one, I'm sure you've already guessed, is going to have a middleware of web. So, if we have any local routes that we're interested in that should be accessible in the browser, well, they will go in the web group. Any local routes that instead are just an api call will go in this group here. Of course, we've not actually done anything in our RouteServiceProvider to state that these routes should only be available locally, but that's very simple. Let's say if this app, and there's an environment method that we can call to check if the current environment is local. So, only if the app environment is set to local will this routes file be loaded in. Now inside our route file itself, let's jump into our api group, and we can use the get method to create maybe a post content end point. I'm not going to worry about using
Extracting Post Fixtures5:39
Now inside our route file itself, let's jump into our api group, and we can use the get method to create maybe a post content endpoint. I'm not going to worry about using any form of controller, seeing as this is only for local development, and inside the closure here, this is where we need to grab one of those fixtures and then return the title and the body from that fixture to the front end in JSON. In order to do that, why don't we take another look at our PostFactory, which obviously has all of this functionality baked in here, here, and here, and we could extract this functionality out of the factory so that we can reuse it in our route and in the factory as well. Let's create a new directory under app called support where we can place any classes like this that don't really belong under any other form of namespace in our application structure.
Let's create a new directory under app called support where we can place any classes like this that don't really belong under any other form of namespace in our application structure, and then in support, let's create a little class which we could call PostFixtures. And well, let's just start extracting things. So we'll need this here. We'll grab it and we'll drop it in place, and it's going to have to be a public static function so that we can call it from outside of this class. We'll also need this private static collection of fixtures, so let's drop that in place as well. What else will we need? Well, we're going to need this functionality here where we perform the map because we'll need to return the title and the body. It would be nice to already have done that for the end user rather than having to remember to do it yourself. So we'll take that, and we can just probably
the title and the body. It would be nice to already have done that for the end user rather than having to remember to do it yourself. So we'll take that, and we can just probably chain that onto the end of getFixtures. Okay, I think that would actually work. Let's get rid of this now, and instead of returning posts, we can return postFixtures, getFixtures like so. So that should be the PostFactory updated to use this new class that we've created. Now let's go back into our local.php root file, and here we want to return postFixtures, but we only want one fixture. So let's just call getFixtures, and we'll use the random method to grab a random postFixture. And this returns a single instance, which is going to be an array which has title and body. So now that we have our root in place, we can jump back into the create.view component. Our autofill function can now make a request
Calling Autofill via Axios8:01
to be an array which has title and body. So now that we have our root in place, we can jump back into the create.view component. Our autofill function can now make a request using Axios, which by default will actually just be available at the window object level. So we can say axios.get, and we want to make a request to /local because we added that prefix in our root service provider. So /local, /post-content. And let's use the await keyword here in order to perform the request without having to use promises. And I can say const response equals await, and I'll have to add async here to allow that to work. So we await the response, and just for a moment, let's console.log out that response and see what we're getting back in the front end. Let's open up our console, click autofill, and there we go. Here's our response, and inside data we have our body,
that response and see what we're getting back in the front end. Let's open up our console, click autofill, and there we go. Here's our response, and inside data we have our body, and we have our title. If I click it again, we should have a different response. Yeah, so here's singing in the rain, here's Batman the Dark Knight. Very good. Okay, so now all that's left to do is take that response and update our form. We can say form.title equals response.data.title, and then let's copy and paste that and update it for the body as well. And hopefully now when we click autofill, look at that, we automatically set the content of the title and the body for the post with these beautiful customized markdown files so that we don't have to do any of this manually. And with the click of a button, I can come down, hit create post, and we have our post here ready to go. This is brilliant. There's just one thing
we don't have to do any of this manually. And with the click of a button, I can come down, hit create post, and we have our post here ready to go. This is brilliant. There's just one thing to be aware of. If we go back to create post and, well, let's go into our terminal where we're currently running npm run dev, and let's instead run npm run build, which will compile for production. Then we'll go back to the browser and refresh, and you can see we still have the autofill button here. Now, if I was to go into the .env file and change my APP_ENV from local to production, which is what you would do in a production environment, and then I open my console and hit the autofill button, as you'd expect, we're getting a 404. So a production user would not be able to actually make use of the autofill button because obviously that route that we created is only available in local environments. But why show them a button that they can't click? We shouldn't show this.
Hiding Autofill in Prod10:29
make use of the autofill button because obviously that route that we created is only available in local environments. But why show them a button that they can't click? We shouldn't show this button at all. In order to do this, let's create a little const inside our create component, and let's say isInProduction. And isInProduction is going to be a function, and that function will return a special reserved environment variable in Vite. It's available at import.meta.env.PROD. So if you use npm run build, this is true. But if you use npm run dev, it's going to be false, meaning that say in autofill, we could do an early return. So let's say if isInProduction, then we want to simply return early and never make the request to Axios. And of course, we can do the opposite inside our markdown editor. So where we have this custom button, I can say, please only show this if we are not currently in production. And now if we
And of course, we can do the opposite inside our markdown editor. So where we have this custom button, I can say, please only show this if we are not currently in production. And now if we go back to our terminal, let's do npm run build again. And then we'll come back to our browser and refresh. You can see, sure enough, that autofill button has disappeared. But when we go back to running npm run dev, instead, we go back to the front end and refresh, the autofill button is back. And so long as in .env, we've set the APP_ENV to local, we can click this button, and we can see that it goes back to correctly filling out the front end. The last thing I'd like to do is just extract isInProduction out of this file, because we might want to use the same check across different view files. And I don't want to have to repeat myself using the prod environment variable. It would also be nice to have a single source of truth so that
Centralizing Environment Check12:12
want to use the same check across different view files. And I don't want to have to repeat myself using the prod environment variable. It would also be nice to have a single source of truth so that we can hard code true or false for this value if we need to quickly test something in different environments. So let's go ahead and do that. I will cut this code out. And then perhaps under utilities, let's create a utility called environment.js, drop it in, we'll need to export it to make it available. And then I should be able to say import. And we want isInProduction from our environment.js utility file. Okay, hopefully, we still see the button, it still works. But if I want to, I can set this to true, so hard code. And now the button disappears. Of course, I can then return it back to import.meta.env.prod. And I get the real value, a nice single source of truth for us to be able to rely on. So there we go. It was actually incredibly
course, I can then return it back to import.meta.env.prod. And I get the real value, a nice single source of truth for us to be able to rely on. So there we go. It was actually incredibly simple, I think to extend our markdown editor to add context aware custom actions. And being able to create those local only routes in Laravel is so powerful for providing that little bit of improved developer experience that will make our lives easier as we continue working on the forum application.
