 Hey, everyone. Welcome back to another Flask series. In this tutorial, I will be showing you how you can build a CRUD app using Flask as your back-end and HTMX to make AJAX requests without a single line of JavaScript. So a couple of things before we start. I'll be using Flask version 3 for this tutorial. So if you're using any other version of Flask, things may or may not break. Also, you don't have to know anything about Flask or HTMX to get started with this tutorial. The only good prerequisite that you might want to be familiar with is Bootstrap. And that's the library that we'll use for styles because we don't want to be wasting time writing CSS. And even that's not mandatory. So if you don't know Bootstrap, don't fret. You can still follow along. As we're starting with a quick demo of what you can do with this final app that we're going to make by the end of the day. So first of all, create, you can create new posts. And then you click on that. And you see that we've created a new post you can edit. And then you can also delete. There we go. There will also be server side validation. So as a mock like demonstration, I've made it so that if you have numbers in the title, you get a red flag. And this is server side. And I can prove it to you by going over to my console. And for now, we have one endpoint call to this post slash validate endpoint. I'm going to take away the error. And you can see that since the last call, we've made a new call to the server to validate the title. Now the interesting part of this whole thing is not that we can do it because we can easily make code to make do all of this in react create react have in just like five minutes. But the interesting thing is that all of this is being done without a single line of JavaScript written, I have not written a single line of JavaScript for this to work. And if I click on this edit post element here, you can see an inspector, we have this hx dash get property. And what this property is doing is it's specifying an endpoint to the server. So whenever I click on this button, it actually makes a get request to the server and the server will return HTML code back to the client. The client will take that and render that inside of this in place of this component, depending on what my target is, I can specify targets of what elements to replace on getting the response from the server to demonstrate. I'll just go and click on that and copy the value. So go to the server's IP address, which is just local host. And I'll just slash and then the endpoint. You can see that we actually are getting back an HTML partial. If I view the page source, you can see that it's just a partial component. It's not a full HTML document. It doesn't have the standard HTML structure that you might expect. There's no head tags. There's no body tags. That's why it's highlighted in red because Firefox isn't happy that we're sending them invalid HTML, so to say. And right now, this is taken out of context. It's being rendered in a new page and that new page doesn't contain all the style files like styles.css. And that's why it looks this ugly. It's unstyled. But if I go back to my main page, I click edit, you see that this is actually the same component to input files to input fields submit, cancel and delete post button and exact same copy here. In fact, if I click inspect and I inspect this element that that is being rendered here, I can go and copy our HTML. So I'm copying HTML from the inspector of this page that it is being limited zoom out here so you can see what page I'm on. So it's this main page here. And I'm going to go put it in VS code. And you can compare side by side that this code is actually the exact same code as the code that we're getting as a response from the server. Do you see how interesting and exciting this is now you get full control over what goes on the user's browser and what renders and this is all done on the server side and your server isn't sending JSON data like in the traditional REST API. It's like actually sending actual HTML. Now let's jump straight into coding. First thing to do is set up our flask web server. And one thing to note here is that I'll be using flask version three, if you're using any other version of flask that isn't version three, things may or may not break. So how to install flask is you would go to your your empty folder here into pip install flask with a capital F. Once that command is done running, you have flask installed. You would go to the flask quick start page and copy the simple Hello World example. You can make an app.py here, or I prefer to call my file server.py to indicate that it is in fact a server that we can run. And you would run the server using this command. So if you were using app.py, you can ignore this middle part here that says dash dash app server, if you're using any other name that isn't server.py, you want to rename this to be whatever the file name is. And then you would do run dash dash debug allows us to automatically detect changes in the server code. So whenever I make changes to the server code, the server detects that and automatically restarts by itself. So I don't have to keep restarting the server. Every single time I make a change. And by default, flask is hosted on port 5000 for the development server. And I prefer to have my server on port 3000. And that's why I have that command specified there. Now that my server is running, I will go to the server's address. And we see our simple Hello World example here. If I view the page source is just a p tag that says Hello World, which matches exactly what we have here, while we're returning to our function that is right under the app dot route decorator of the root directory, the root path. The first thing I'll do is go to the bootstrap documentation site and copy the boilerplate code required for bootstrap to run. What this is is it's just an HTML template. And bootstrap has kind very kindly added the link to the bootstrap style sheet. And also bootstrap JavaScript, if we want to ever use any bootstrap JavaScript functionality like models or alerts. So I'll just copy this. And in place of this Hello World, I can make a string that has multiple lines and just paste that in. And we can go to our local host and refresh it and can see that we get our style Hello World, I can start adding some bootstrap components here. For example, I can make a button that says click me. Now obviously, this button is going to be unstyled for now because in order to use bootstrap buttons, I can actually search up in the documentation to actually get styles on our buns, we have to give the buns some classes CSS classes and the bootstrap CSS classes for buns are BTN for the first class and the second class should be BTN dash color name. So let's try doing primary color on our bun by giving it class of BTN and BTN dash primary and that should make it a blue button. And there we go. Now this worked and all but it presents us with a very unique problem or problem is that now we have mixed in HTML code with our server side Python code and that is a big no, no. And to fix that problem, Flask has very kindly included a templating engine inside of Flask that will allow us to use templates called the template engine is called Jinja. And how Jinja works is that we first have to create a templates directory. And then here I can make a dot index dot HTML file. I'll take all this HTML code here, take it out and put in this dedicated HTML file. And in here, I will import a render template function from Flask. So now in our Hello World, I can return render template. Whoops, I can spell today. And the parameter that we're going to pass into this function is going to be the path to our HTML file relative to the root of the templates folder. So it's called index dot HTML. And that's what I'll be putting in here. To prove that now works, I will go back to my index dot HTML file, I'll change the color of the button to button dash danger, that should make it a red button and refresh and there we go. So next thing I want to introduce you to is called macros. Now macros is kind of like functions having Python functions inside of your HTML file. So say for example, I don't like this being here clogging up my index dot HTML file, I want this to be somewhere else in a different file and the different function dedicated function, perhaps that contains the head. So what I'll do is I'll make a new file in my templates folder, I'll call it underscore macros dot HTML. The reason why I've chose to call this file underscore macros is because in flask or in templating engines in general, there is a convention that says if you prefix your file name with an underscore, it means that file is not to be rendered as a standalone HTML file. It's just a partial that shouldn't be ever rendered publicly. So here in this macros dot HTML file, I'll create macro. Did you see what I just did there? I used something called flask flask snippets. I did F macro just tab it gave me instantly the expression that I needed and that is coming from this one extension that I installed called flask snippets right there. So if you want to code as efficient as I do, you might want to install this extension here that allows you to very quickly make these expressions. So in the way these expressions work is first I have I name my macro, I can name it head. So notice how I have parentheses, this is exactly like a function in Python, right? And here I should actually change the spaces and then using spaces to spaces. There we go. And in here, my block of code is going to be my HTML from my head here in index dot HTML. I'll take that copy and paste it here. And so now in my index dot HTML, I can use flask expression. And in this expression, I'll just call the head function. Now that's not gonna work. Because if I refresh the page, head is undefined. As you can see here, flask isn't like liking that all too much. That's because we have to import our function just like in Python. Before I go ahead and do that, I just want to introduce you to the concept of flask expressions. So flask expressions are basically expressions in your HTML that are wrapped by these double curly braces here, I can in these expression blocks, I can write simple Python code. For example, I can do one plus one, I go back to my page, I refresh it. Oh, yeah, head is undefined because flask sees this, even though it's commented out in HTML, it's not actually commented out to flask, because flask will attempt to render all the HTML that is in your HTML HTML code. So in order to make this a comment, you want to use this syntax. So you want to open the tag using curly brace hash and then close it the same way. Now it's going to be an actual comment to flask templating engine itself. So refresh that you can see it's two. Now let's go back and think of how we can actually import this head from my other macros.html file. So to do that, I'll just do f import. So that's the syntax. You want to import underscore macros.html as macros. So this will this is relative to the root of templates folder. And here I can do macros.head. So now if I refresh the page, you see that we get our styles back, I can view the page source, you can see that our head is actually there. It's as if it wasn't all and not in one file as if it was all just in one file. So to the browser, it's invisible. All of this is going on in the back end in the server. Okay, now that we have our head macro, I want to be a little bit greedier. I want to make another macro. Now this other macro is going to be our nav bar. And if you can see this preview down here, that's the nav bar I'll be making. And I'm not going to just make this nav bar on spot right now, because there's already nav bars made for us in bootstrap. If you would just go to the bootstrap documentation site, there's a bunch of nav bar here is that we can copy the code for and paste it into our project. So in this code pen here, I have already customized the bootstraps nav bar to be a simple nav bar that we can use in our project. Now I'm going to copy and paste this code into our project. But don't be afraid that if you don't understand this code, it doesn't really matter. This is irrelevant to our project and our HTML and fast knowledge, you don't have to know this you can make your own nav bar. I'm just putting this in so it looks nice. Alright, so don't be afraid if you don't know what this is doing. It doesn't really matter. It's just the styles and bootstrap. Right. So I'm going to do a fat F macro, call his nav bar, and we paste this in here. Okay, now that we have our nav bar macro, can go back to my index dot HTML here. And I can include my macro here using f expression. Whoops. So we do macros dot nav bar. And refresh that you can see that my nav bar appears. I don't see the posts and users links. It's because I have to zoom out a little bit to see those those two appear. And that's part of bootstrap responsiveness. But one thing I don't like about the page right now as it is is that this component here is like stuck to the top of the page. I don't like how there's no space between the nav bar and this hello world text. So what I'm going to do is make a container here. So make divs and wrap all of that inside of the divs. And then we'll have a class name of container and margin top five. These classes come from bootstrap. So if I refresh the page, you can see that we have our nice looking container. Now again, as usual, if you had no idea why just did there, don't worry about it. It's just the styles. It's not important to htmx or fast. I'm going to move on now and make a new route at slash posts. Right now, it's not found because we haven't defined that route yet and server dot pi. But I want to make that route and link this link in nav bar over to the slash posts. So first, I'm going to go back to server dot pi. I'm going to copy over that function, make a new one. And in the decorator, I'm going to do slash posts to say that this function is for slash posts. I'll rename the function as well to be posts. I'll rename hello world to be home. And here I'm going to render the template for posts slash index dot html. And that means that I have to go into my templates folder and make a new folder called posts. And I have to make an index dot html file inside of that posts folder. The question now is then what should I put in this index dot html file. Now, of course, we could go back to our previous index dot html file, copy the contents and paste it over here. And here I'll change something I'll say posts, posts page, I'll get rid of that button. And so if I go to slash posts, you see that I have posts page, I get rid of the slash posts go back to root and that's our root page. Now, if you've been programming for a long enough time, there should instantly be bells ringing in your head. Do not repeat yourself. I've just copied and pasted code. That's a big no no. DRY, right? We shouldn't be copying and pasting the same code because then it's bad for maintainability if in the future I want to change something like for example, down here, I have to go hunt around for all of the files that I copied and pasted to and do make all of the changes everywhere. So let me introduce you to this big brain move that you can do in a flask. It's called templates. I'm going to go and make a new base dot HTML file here in my templates directory. I'm going to copy and paste the code one last time I promise into this base dot HTML file. And then I'm going to go and just make a new folder called home because we already have a folder called posts for a post route. So there's no reason for us to not make another folder for our homepage. And I'm going to move the index dot HTML for a homepage into the home folder. And now that I've done that, I have to remember to go to server dot pie and update the route here to the home slash index dot HTML. Let's go to home dot index dot HTML. And we remove all of the code there. And same goes for posts. So I've cleared these two index dot HTML files. And only the base dot HTML has all of the HTML code in one place. Well, that was so dumb. You might be you might be asking or saying because now if I refresh the page, it's going to be blank. Same goes for my post page. It's going to be blank because there's nothing in my HTML files. And to that I say, don't worry, you can go back to our index dot HTML files. We do something called F extends. There we go. And now instead of extending layout template, I have to change that into an actual HTML file. So relative to the root of the templates folder, I should extend base dot HTML, which is this file that we just made right here. And I'm going to do the same thing for my home folder, my home index dot HTML, I'm sorry. Alright, so I'm going to go back to the page. And now you see that the HTML files are back. But that's still really stupid, you might be saying because my homepage and my post page are still going to be the same HTML code. The homepage still says posts. And the post page also says post. So we can't be doing that. And to that I say, don't worry, let's just go back to the base dot HTML file, which is now the parent file for the two index dot HTML file files under it. And we just have to identify the part of this code that is different between the two files. So we have to identify which part of this base dot HTML file is going to remain the same. And which part will not be the same. And that's really easy because I know that the nav bar should stay the same. The headers should be the same. The body and the closing tags should all be the same. Only this part inside of our container is changing between the different files. And for that, I'm gonna make something called a block. Let me just tidy up this empty space here. I'm gonna make a flask block. So flask block. So that's what a block look like. We just have to name our block. We can name it anything I want. We can name it content for this example. So once I've made this block, I can go to my children files and I can just override the blocks in the parent file. So let's do f block again. Remember that we named the block content in our parent. So I have to do the same thing here in my base in my HTML files. So I'm just gonna make this this is posts. Because this is in the post directory. I do the same thing in home by call this home. So now I will refresh the page for posts. You see it says posts here. If I go back home, it says home. Now before I move on to the next thing, I just want to show you a really cool neat little trick that you can use with templates. So here I'm going to go back to my parent base HTML file. And instead of having just a blank content block, I'm gonna make something in here. You say this is the default content in base HTML. Now obviously, if I go and refresh the page, nothing's gonna happen because this entire block is being replaced and overwritten by the blocks in the children files. But my children files, I have access to this function in the flask template called super. Now for example, now that I'm in my homepage, I can make a flask expression here and call the super function. And what this does is it calls the parents content block before it calls our own block, or I could just move it down here like this and we'll call we'll call our own block first and call the parent block after. But I'm going to put leave it like this. We refresh this and you can see that the parent block gets called inside of our child block. So we can have default elements inside of our base HTML and we can call that default element inside of the children elements. So yeah, that's just one small little thing I want to show you that that's really cool. So moving on now, I want to go up and fix up this nav bar here, because right now we can see that I'm clicking on those links, but it's not sending me anywhere and that's really annoying that I have to keep coming to the URL bar and typing my address in and everything like that. I want to introduce you to this new superpower that flasks has given to us. And that is this function called URL for which can import from flask. Now this function generates us a URL string based on the name of the function that we pass it in and the name of the function depends on the name of the function that handles that route. So for example, here I'm gonna just make a print statement to print URL for and we can print out the URL for the posts route. Now note that the argument that you're putting in here is a string. It's a name it's the name of the function of the route. It's not the name of the route itself. It's the name of the function. So if I were to change this to right, so this URL for would be your URL for posts and not URL for right. So let me show you that in action. I'll pull up my server. I'll be watching for this print statement here. In fact, maybe I'll concatenate some string there to make it more obvious. Alright, so I'm going to go and visit my home page. And it's if I go to the terminal, you should be able to see that we have printed out the URL route for the function name that we've specified in the URL for parameter. Now, I'm going to change this back to posts, because our demonstration is over. And it will make use of the URL for function, not in the server.py file, but instead in our macros.html. Remember that we made our nav bar in this file. So instead of having my flask anchor here, by the way, I don't know if you've noticed I named the project flask x because it stands for flask plus htmx. Really clever idea, isn't it? Anyway, let's get back to work. Remember I was talking about the URL for function earlier. Well, we can use that inside of our html files using our flask expression. Again, we use the double curly braces. We call the URL for function. This is called home, because that's what we named the route handler for the home page. Copy that, paste it here, and we named this URL for posts, because that's what this is called, right? Now that's done, I'll refresh the page here. First, I'll pop over to the source code. Let's look for a nav bar. So that's our nav bar. Notice that our href, our hrefs has been replaced with the actual routes. So the home is just slash and posts is slash posts. So if I were to click on the links, you can see that it's actually working perfect. Okay, now that we have our posts page, let's actually go ahead and make the components for the individual posts themselves. So I've made I've written some code here for the post components. It's actually really simple. It's just a card in bootstrap. And it has two divs and it has the title and h5s, a paragraph tag that contains the post content and the button that does nothing for now it's the edit post button. And if you want to read more on how I made this using the CSS classes, you can actually just go to bootstrap documentation. Basically, what does the styles is the class names here, those are the things that do the magic trick. So I'll just copy over this component. And let's head back to our code. So obviously, the file of interest would be the posts slash index dot html, that's our post page html file, to change up the indents using two spaces. And here, I'll just take the my post code, and I'll just post code, my the code for my post, I'll paste it here. And I'm going to because now if I save the page and refresh that, you'll see one post, but I want to make multiple. So just clone that down a couple of times, and space it out nicely. And now I refresh that and you'll see that we have three posts. That's okay. And that's awesome. But right now the post titles, they're looking very repetitive. Post title, post title, post content, post contents all the same, right? So I've sneakily added a new module into our Python project called DB, it's a mock database. And in here, I have some code for a sample database. It's not don't worry, it's not a real database right now, it's just a Python object dictionary or whatever you want to call it. It's a, it's a dictionary of posts. And I've the reason why I'm calling this DB thing a module is because I created this empty underscore underscore in it underscore underscore dot pi file, which informs Python that this is module that we can import from our other modules. So what that means is that I can go to my server dot pi, and I can do a from dot DB, which is basically just this directory, that's what the dot is dot DB, import sample DB. So now we have our sample DB module imported in this main server dot pi file, I can just go to my post function, I'll just print DB for now. Can I do that? Why is it it's not defined all right, it's called sample underscore DB, not just DB. Okay, so now I can pull my terminal up and refresh this page. And there we go, that's the print statement module, htmx quickstart dot DB dot sample DB, that's not really comprehensive enough for me, because I don't want to actually be just pointing the module under print. Let's let's print this database object out. So I can do dot DB. And let's just get the posts key, because why not refresh the page. And you see that we print our actual database in like somewhat JSON format. Okay, so but why is this good for us? Well, the reason this is good is because now we have our actual database, our mock database, so we don't have to hard code the post contents and the post titles, because instead, extract that information from the database. Let's just very briefly walk you through some of the helper or utility methods or functions that we have down here. Well, I'd say you don't have to worry too much about understanding how these functions work, because think of it as an abstraction layer kind of thing, right, because you can very just very easily replace this database with any other database out there, they should provide you with the same functionality, for example, get posts, you're just returning the DB key of posts, which is just this array here, the list of posts, and or get post by post ID. So we just iterate through the list of posts, we check each individual post, and we check we compare the ID of each individual post with the given post ID in the arguments. And if we see that, oh, they match, then we return the post object. It's really simple. And there's functions to add new posts to update existing posts, and to delete a post based on the post ID. If you want to copy the code, don't bother pausing the video and copying it word for word character for character on your keyboard. It's all the codes and GitHub just go to YouTube description go download the code from my GitHub repository for this project. And I say you don't even really have to worry about this database. It's just meant to serve as like a mock API for a database that we're going to implement in the future using something external Firebase MongoDB SQL anything like that. And for that reason, I'm going to collapse all of the functions, the function definitions here. So all you got to know is we have these functions. And we also know what the functions expects in the arguments. And now we can actually use the database in our server dot pi. So first things first, I will first get all of the posts that are in our database in this function. So I'll make a variable called posts. And it's equal to sample DB dot get posts, because that's what we have provided here in our sample DB. And then so how are we going to access the post data inside of our templates? So we have our post template and that's our template, right? So we have to somehow render the data in here and replace these mock title and contents using our our data from the database. So the way I can do that is I can pass in arguments in my render template here, I can say posts equals to posts. And now I'm in this template, I have access to the posts object. So I'll just very quickly prove to you that we have we do indeed have access to the posts. So I'll just use my flask expression symbols, I'll just put posts in there. And we'll see what renders in the browser. So there you go, you have a string, a string representation of the post dictionary, the post object. And now that we have that, we can very easily iterate through these posts one by one and make a card for each post using for loops. So the way we would do that is we would use a for loop expression in flask, it's just press black curly brace percentage for element in collection, blah, blah, blah. So here, obviously, I'll name the element to be post and the collection should be posts. Let's just print out the post title for now. So post dot title. And if I were to go back to my sample database, we see that it should theoretically it should start printing HTML is awesome. And then this title and then this title. So you refresh that and you see that we do indeed get three titles that we should probably separate into their own separate lines. There we go. Now, the next obvious problem is that we want these titles to be in here in the individual cards themselves. We don't want them to be just floating there on top of everything. So what I'm going to do is just take this out. And I'll take an individual card item, and I'll put it inside of the four nested brackets or block, and I'll get rid of all of the other posts, because now we only need code for one post and a for loop will figure out the rest for for us. So now in each block, we have access to a post object, which then you can extract the title and contents from using our flask expression notations. So we do that post.content. And remember, this is all coming from our sample database, our posts items have IDs, titles and contents. And then yeah, let's refresh the page. And there we go, we see that we have three posts. If I wanted more or I want less, I could just modify my database and it will all be automatically actually all be automatically generated. I'm gonna just copy paste this ID before hello world. It is 3pm and I want to sleep refresh and we get our last post automatically, despite not having written any fast code at all or HTML, it's all automatic. So that was my really brief introduction to flask and what you can do with the flask templating engine. In the next part, and yes, I'm putting all of this in the next part, I'll show you how you can edit posts by clicking on the button, because right now it doesn't do anything. I'll teach you how to do dynamic stuff, create, read, update and delete right now we're just reading. I'll show you how you can update and delete posts in the next part because we have to use htmx for these functionalities. While you don't necessarily have to use htmx, you can always use regular JavaScript or regular forms and get post restful API conventions. But the whole point is htmx can help us save a lot of time and makes everything much simpler. It gives you a lot of customization and it will build the final project and flesh fully flesh everything out by then we are finally ready to start working on our htmx component of the website. So previously, where we left off, the edit post button does nothing. And now I want to change and fix that. So the first step would be to install htmx. And you can go to the htmx documentation website and copy the this link right here that links you to the CDN script tags. And we're going to include this script tag on top of all of our pages. So the logical place to put that is, of course, it being the base.html inside of the header. And the head has a macro for it. So I'm just going to go to the underscore macros.html file and right under the bootstrap CDN. I'm putting that there. So that's the CDN for bootstrap. And that's the CDN for htmx. All right, now that we've installed htmx, we now have access to a whole bunch of html attributes on all of our html components. And these attributes include these here that you can see in the htmx documentation site. But I'm just going to show you, I'm going to give you a taste of what htmx is capable of. Let's put it that way. So first things first, let's jump into our code. And this is the code for the posts page. I'm not going to touch the for loop for simplicity sake, I'm going to make a new section here temporarily to demonstrate something. Add p tags, say hi, replace me, please. And refresh the browser, you see that I can target that in the in the elements console, you can see that we have a div in the p tag that says hi, replace me, please. So now what I'm going to do is I'm going to replace this div with new html from our server dynamically without refreshing the whole page without writing a single line of JavaScript using htmx. Now the traditional way to do a refresh of an element in the page would be to change this into like a button or an anchor that makes a get request to the server. And the server should return the entire html page for the entire browser window. And the browser would just take that html and wipe out all the current page contents and just replace the entire page with this new page that it's just gotten from the server. Now, the idea behind htmx is if you can replace and target single individual html components and replace those individual components instead of having to refresh the whole page. Why do I have to go through the process of doing a full page refresh when this is obviously more efficient? So to demonstrate that, I'm going to change these paragraph tags to be a button. And so that's the button component. I'm going to use the htmx attribute here called hx get. And I will just make a get request to our server and tell the server to give me some html and I'll take that html and replace this the inner html content of this button. I know that was a mouthful, but you'll understand once I implement this. So for that, I'm going to have to go to my server.py and make a new route here. So I'm going to name that slash test. I'll just rename this function to be test. So rendering a template, I can render some random string here that says Hello, world. So usually in html, you would just do slash test. That's the route to our function here in this server.py file. Because we're using Flasker and be using URL4, like we've mentioned in the previous part, but I'm just going to leave it like this for now and see if it works. If it doesn't work, I'll come back and replace that with URL4. So here it says Hi, replace me please. I'm going to click on that and you see that the page didn't refresh the whole pages stayed the same, except for this one html DOM node here. This button node, the inner html content changed to Hello, world. So I'm just going to change this to URL4 now. You can change this to test because that's the name of the function and you see that it works the same. I'm going to do something a little bit more fun this time. I'm going to go to my server.py, import random from Python. That's a Python standard library that can use to generate random numbers. And here I'll just say your random number is and then we add a random number between zero and 100. So I'm going to click on that. See that says my random number is 81. I'm going to click on that. You can see that every single time I click it, it gives me a different number. So just a quick recap. What this button is doing is it's making an HTTP get request because I used the get keyword to get verb in here. So it makes a get request to the server and the server returns it some html code, which it takes and just replaces this part, the inside of the button. So basically by default, htmx replaces the inner html of html DOM nodes that we specify the action verbs on. But say that I wanted the button to just entirely disappear. I do not want the user to be able to click on the button multiple times. So essentially I want to get rid of this button and I want to replace the entire button itself, not just the insides of the button. I can use hx swap for this and I can say by default swap should be swapping inner html. So if I save that refresh that you see that the behavior is the same. If I change this to outer html though. Now when I click on the button, the button straight up disappears. Did you see that? It's really cool. So I can look at inspector. You see that our div now just contains plain text. It doesn't contain any buttons and nothing like that. So yeah, that's pretty cool. But here's the thing. I want more there's you can do even more with this. For example, if I made a paragraph tag, this should be gone. So now in my div I have paragraph tags that says this should be gone. And when I click on the button, it replaces the button. So now when I want this paragraph tag to disappear to when I click on a button. So I do not want to just be replacing the button itself. I want to actually replace the entire div. I want to target a different, I want to target the parent component of the button. Let's put it that way. I want to target the parent and I don't want to just target itself. And to do that I can actually specify hx-target this. So I specified the target to be this parent component of the button. If I replace this, and you see that now it actually replaces the entire div and my div just straight up just vanished along with the button. And all we're left with is this plain text just floating in the middle of our html. Now that we've taken a look at the basics of html, we can actually go about solving our edit post problem. So if we want to click on this button and we want it to be able to fetch the edit form from the server and then replace the contents of this post with the form, the html for the form, for the edit form. So to do that I'm first going to first build out the UI elements using bootstrap for the form. And I'm going to just actually do it here up here separate from the for loop so to keep things simple. So yeah I'll first have two divs and those are just styles. So that's what it looks like. It's just an empty card and I'm going to have inside of this card, I'm going to have a form and inside of this form I'm going to have an input for the title. So if I refresh that you can see that it's the input for the title and I'm going to have another input for the content. You can see the difference is we have a font size of four and font size of six. So this is a smaller input for our description and I'm going to have another container here. It's actually invisible because there's nothing in here. I'm going to have to add a button here for you to be able to see it. So that's the submit button. I'm going to have another button for cancel and one last button for delete and that would be all the way here on the right side and that's because we have, let's see what I do here. Oh yeah, that behavior is coming from this margin start auto. It's a class from Bootstrap which basically just expands the margin automatically on this last button here so that there's like a long a really long margin that stretches all the way to this front part and pushes the delete post button all the way to the right side. Now what you're going to notice is that all of our elements in this form are kind of stuck together and I want to possibly style that a little bit more so it looks nicer and for that I'm going to have to write some custom CSS and it is now time for me to introduce you guys to the static files convention in Flask. So I'm going to make a styles.css file but where do I do that? So I'm going to first make a folder called static and this folder contains all of the static files such as images or CSS files and I'm going to just make something called styles.css in here. And to include this in my project because right now if I refresh the page it's that styles.css isn't going to show up it's like if I open up the head you don't see any links to my my static styles.css file so I'm going to go to my macros here again and just in my head macro I'm going to just add a link to a stylesheet. Now github co-pilot has very kindly auto-completed my code for me here but let's go through it and see what it's actually doing. So we have href and href is using the url4 function from Flask very handy and now instead of just passing in like what we used to do we would pass in the name of a function in our server.py file for example home or posts and the url4 function will go out and look for that route that that's associated to that function but there's this special reserved route name called static which just points to the static folder and then here we can specify the file name which is relative to the root of this static directory and for us it's going to be styles.css now do I verify that this is working um oh very easy I just say I can just do like um something color red sure why not I'm going to go refresh the page and now if I go to my inspector let's go to our head we can we should be able to see there we go link rel stylesheet and then the href is right there so if I go to the sources tab here I can see that my browser is actually reading and seeing the static folder being served by the flask development server which should contain all of our public files and that is very nice so um I'm actually going to replace this with an actually useful style so um let's see so we're going to style this so that it uh it adds a new it adds margin to every element so what I'm going to do is make a new class called children margin bottom like one and it should okay so this is a css selector to select all of the children of the target component and give all of the children uh margin of one rem so I'm going to put this to the form class we can say class equals to it's actually so refreshing being able to just type class instead of class name coming from a react the world of react so um children that's why I'm that's why kind of like html makes a lot it makes me feel like I'm coming back to just vanilla html instead of just using jsx like you would with react or like spelled or view so let's refresh that and see that it's not working hmm I realize that I've uh I've been a big dummy this whole time and I have two class attributes and that first class attribute is overriding the second one so if I remove the first class attribute and now that's working so yeah this is actually just a short short way of doing things instead of going through each of the inputs and adding a margin to each and each and every one of them individually I just add this class to the parent and this css selector just automatically goes through all of the children nodes and apply this style to every one of them automatically all right moving on we should now make a route in our api server um to make it so that we have a route to fetch the edit form html and we should take this we should ideally probably take this html and put it somewhere else and not just have a dangling edit form inside of our production page so let's go ahead and make a new route called post slash edit slash let's see if get a copa pilot is smart enough to do this and it turns out it is so this is one way of how you can make dynamic routes in flask we name this the edit post so this string is basically telling flask that we want to listen for we want to watch out for routes that match this so slash post slash edit and there should be one last part this last this last bit from the url should be an integer and we can accept the integer from our edit post so for now i'm just gonna return an empty string can print post id to our browser to our server console and i'm gonna just make a manual call slash post slash edit edit i'm actually curious what happens if you don't specify the last bit in the url it says not found 404 which is very good because the server expects us to have one last last bit that should be an integer so if i have like a string there also return not found if i put an integer here then now it's no longer not found it's just an empty page i'm really sorry if the url bar is really small you might have to zoom in to see this i don't know if there's a way for me to like zoom in so you can see the url bar basically that's what i'm typing in here so that's the link and then that's the dynamic bit of the url and there we go we've printed that out into the console 301847 which is really good which is what this post id thing is doing so if you remember from our previous part of this crash course i have this function in my database utility file called getPost and it gets the post object by the post id so i could just say post equals to get post sample db dot getPost get up copa is so smart you all so i'm gonna do that and let's print post there we go and now it should be none because there is there are no posts with this id so i have to go to my whoops what did i just do i have to go to my database here and let's look at our sample so we have a we have four posts of id's one two three and four so i should probably change this to edit slash two maybe so whoops i'll put that in the url bar and of course nothing shows up in the browser because we're returning an empty string but shouldn't be none oh yeah it's saying none probably because my database here expects this to be an integer yeah it's probably expects this oh it expects this to be a string okay see this is actually a string in my database object i'm using the equal equals operator so if i'm trying to compare this isn't python isn't javascript you all okay python doesn't equate string one to the integer one they're not the same ones so maybe i should change this to like string instead and now if i do that i actually get my post and not just none all right let's actually do something useful with this post data instead of just printing it to the console so i am gonna go to my posts.html i'm gonna take that code out so that we don't render that floating uh this don't render that floating edit edit form in the middle of nowhere i'm gonna go and make a folder here called underscore partials underscore just means that oh this isn't uh the files in here are just their partial html files they shouldn't be rendered as as standalone html just say edit dot html here put this in here can i just format document there we go so in here i'm gonna use my i'm gonna be using my flask expressions again actually wait before i do that um maybe let's not do that maybe let's just show how this works with html first so in here this edit post button doesn't do anything right now i can do like an hx git equals to url4 what's the name of our route it's called edit post um oh here i should be i should return render template posts ah i caught i finally caught github copilot lacking here it should be post partials edit dot html and here url4 would be it's called um edit post so okay so here's the thing now i don't think this would work because the edit post url4 function it expects so basically expects this to be something like slash post slash edit slash post id here post id here right so if i just did url4 post it would just be like this it doesn't have the last bit in the url i'm just i'm actually curious what happens if i do this so you cannot build url endpoint for edit post because we forgot to specify values post id very very very very smart so now i just have to specify post id equals to and that's uh that's because i have this post id argument here post id equals to so we have our post title post content i'm pretty sure we also have access to post id so now if i click on edit post and you see that actually did something albeit not something not something that we wanted to happen but it did something still nonetheless did you figure out what the problem was well if you didn't here's what happened when we clicked on edit post it actually made a request i can show that pull that up it made a request to our post slash edit endpoint which hit this function which returned this template and it just took this entire code in here and just dumped it and replaced the inner html of this button so uh i should actually target this so you see that this whole thing is actually a button and that's why you're seeing this blue background that's actually part of the button and it took the html and put everything inside of the button as children components so that's not very good well we can fix that and remember how we fixed it using uh hx target and h and hx swap in the previous example so notice that we have a card and a card body here and that matches this card and this card body so what we can do is we can essentially just replace this entire div we can replace the entire div using outer html and it replaces the entire div with this div which is card uh mt5 card body see card mt5 card body so what we're gonna do is it's hx target equals to this and then hx swap equals to uh well see if i did inner html or if i just neglected to put that there at all it's going to just replace the inside of this card but we don't want that to happen we want it to replace the entire thing including itself that's why i'm going to have this outer html target here and now click on edit post and hopefully it works one jarring problem that is immediately obvious is that you'll see that for every post i click edit on it actually just puts existing title and existing content in there as the initial values we can fix that because in our edit post route handler we're already passing in the post object as a data object to the render template function which renders this edit.html file so we technically have access to the post information in here so i can replace the existing title using flask expression and i can say post.title if i can spell it properly and here should be post.content and refresh this so now you see it's actually replacing the titles with the correct titles of each individual post now here comes the fun part click on edit post i'm going to just delete this when i click on submit it just sends us back i can see the default behavior of htmx is to send this send a request a get request back to the original endpoint and we just have it just appends all of the body in the url parameters so let's go ahead and modify the behavior to actually modify the post in the database so i'm going to come to my post edit post route handler here and right now we're just handling a get request let's also add options here so we can handle get requests as well as other request types such as the put request so the put request is one of the other rest apis verbs so here in postman you can see that we have a bunch of rest api verbs put requests are one of them and in this case we we're just going to be sending our data using the url encoded form method which if you don't know if you don't know what that is don't i wouldn't worry too much about it i'll show you just in the minute what exactly i mean by that so this is my edit form and by default the forms behavior is to send the form data based on the input fields here back to the server using the url encoded method i just showed you earlier in postman so now we can we can specify that we don't want to be sending get requests back to the server instead on my submit button i will just add this hx dash so usually we do get but this time i'm gonna do an hx put which specifies that i'm gonna send in i'm gonna be sending in new data and i want to i want you to replace my old post data with all of this new data i'm sending to you right now and url 4 is your usual edit post because it's going to the same url it's just using a different uh rest verb a different kind of request instead of a get request we're doing a put request as usual we need to provide the post id so in here how do i differentiate uh how do i know if i'm i'm receiving a get request or a put request so there's this object that you can get from flask um so it's called request which you can import from flask using from flask import request so you say if request dot method equals the put and here this is just default get request and then this is the put request handler so if the request coming in is a put request we just there's no is there an update post function i don't remember if i wrote that function no i didn't um i should probably do that um so we we pass in the post id and then we pass in okay so i'm just going to comment this out for now i'm going to return empty strings i'm going to go to the console i'm going to print out this request dot form so in our request object we also instead on top of just having access to this dot method i also have access to dot form which gives me all of the form data that i'm sending in so for now i'm just going to go back to my posts in the edit post we say hello there hit enter and right now the default default behavior is kicking in we're replacing the inner html of the button with the response from the server which is an empty string but we'll fix that in a minute but now if i go to the console you can see that i am actually getting a dictionary that contains all of the data for this new post object i just realized that my camera is kind of blocking the view so let me get rid of that just for a second you can see we have our title content in our console bring my beautiful face back in so now i will see if should i do this on camera should i well with the help of co-pilot we can do it we can get anything done within just seconds so i'm just make update post there we go um there's a spacing also yeah so global db that's using this database object we say for post in post we say id equals to post id uh then we update the content and we should also update the title right uh yeah we should update title and content so post title equal to title and this should work so now in my server i instead of just printing that out i can do sample db dot update post post id that's the target and then we can get our request dot form now get up co-pilot could be maybe suggesting us to just do form like that right using dictionary the dictionary access modifier but this would give you an error because we should be using the dot get method instead of these square brackets so i should probably destructure this into multiple lines title equals to that content equals to that and then we can say title equals title and then content equals to content there we go and we can continue returning an empty string for now just i just want to see if this works so edit post more exclamation points hit enter so now i'm going to refresh the page and you see that we have indeed updated our server now obviously this is just temporary it's because our database is stored in ram so if i restart the server it's just going to revert back to its original database but that's a problem for tomorrow you can use other like actual databases like mongo db and stuff like that next thing i'm going to fix is the fact that when i click on edit post yes it does work but when i hit enter it just disappears my submit button and the form stays here and i have to reload the page to get this back so i'm going to make an api endpoint that returns this inner html for the default card view and then when i click submit it should render the default card view back and we actually already have the UI code for that inside of our post index dot html i just have to take this out and put it in its own file here in partials i can call show dot html put this in here spacing should be two spaces format okay and then here obviously we have to replace this with something and or i could just leave this in for now i'll replace that later and we're trying to access post and we have to get past that data object so let's make our endpoint in the server.py file so we just do post slash yep string post id and post and just should be post id so post equals to sample db dot get post and get post by post id and we render template so again we want to render underscore partials slash show should probably rename this function to be show post this should be singular and so in my edit here instead instead of let's think about right okay so we want to just replace this entire edit form so i can just set the hx target to be this and hx swap actually i'll set this hx swap to be here in the button here hx swap equals to or actually doesn't even doesn't really matter as long as it's within the same parent i can say hx swap equals to outer html because look at our show code right our show code has this card with margin top five and then we have the body inside of it it's pretty much the same structure that's why i want to replace the entire this entire div i don't want to be just replacing the inside of it that's why i have this hx swap is outer html and edit post see if it works fingers crossed enter hmm so i've realized my mistake here basically what's happening is when i hit enter inside of my form it's triggering the form submit action which is basically just looking at the submit button and it's seeing this hx put action and this basically just comes into this edit post because remember this is actually specifying the edit post route so i'm in this function and i check and i see that the method is put and i'm returning empty string uh and that's why my post disappears when i edit it so that disappears because i'm specifying the target to be outer html and what do i replace the outer html with well i'm replacing it with an empty string that's why it disappears so if i were to actually edit something here it disappears but if i refresh the page you can see the edit actually happened because all of this code here it they ran and this last line is just that thing we have to fix um so i don't want to be rewriting code so i can actually just call this show post function manually and just passing them post id now this is meant to be a route handler but i could also just call it like it's a regular python function it still returns the html from this render template function call so if i click on edit i say edit me hit enter and perfect just to have a little bit of fun i'm gonna edit more posts um and you see that they all work i am awake it's 3 p.m and i want to wake up and it's working beautiful also there's just one small thing i want to fix before moving on so if you remember from earlier i have actually copied and pasted code from my post index.html to this new file called show.html and essentially the code is a one-for-one copy a hundred percent same it's the code to display the cards for our individual posts here and that's breaking a very big rule in programming called dry do not repeat yourself because in the future if i want to make a small change this one post i don't know if i'll trust i would trust myself to remember to look in these multiple places to replace all of the code and it's not only tedious it's also unreliable so to fix this problem i am going to leverage on htmx's feature called load polling and this is basically a method we can use to customize when an htmx action gets called so in all of the previous examples we've seen we have this hx get element on buttons and the default behavior if we don't specify a trigger is for htmx to call that action when we click on the button so previously all of our previous examples we are uh not specifying a trigger which means htmx triggers when the button gets clicked but now we can do something special and you can customize when the htmx actions should be triggered in this case it's triggered on load so whenever this div element gets loaded into the DOM or in the browser it just calls the the action automatically without having us to click on any buttons and now going back to our example we see that we already have an endpoint in my server to fulfill requests for requests to show individual individual posts and we have the html already in its own separate little file there so all we have to do is replace the div with a custom htmx trigger that triggers a call to this show post route on load and so i don't have to repeat myself using this same exact same code so instead i can just actually just go to htmx documentation and copy this and then instead of get getting this endpoint i'll customize my own endpoint using url for show post an id equals to post id and this is very much like hold on why is there an extra semicolon there i don't think i need all of that there we go so this is very much like if i go to edit here so if i click on submit yeah so it's very much like this submit button that we've done in the edit post but instead of fetching the edit post endpoint and giving the post id that way we're actually just doing the same thing but we're using the show post endpoint directly while in the previous example we would call the edit post route and the edit post route will manually return a show post um function this time we're just going straight for the show post function because you're not trying to edit anything here and so i'm going to save this and see if this works i'm actually not sure post id yeah this should be post id not just id so we see that it does indeed work it just takes a while for everything to load in and that's because we have this delay of one second here if i change this the five seconds you'd see that it's going to take even longer it's going to take an eternity actually for everything to load in there we go so just by deleting this delay because we don't need it i can refresh the page and you see that this is instantaneous if i go and look at the page source you can see that by default the posts are actually empty divs that have no content in them it's only just got these htmx attributes that tells htmx to automatically fetch the post contents individually on load which is amazing now one thing you're going to notice is that if i click on multiple edit post buttons or on multiple posts if i click on the cancel button of one of these posts you would expect only that one post to revert back to its original form but when i actually go and do that you'll see that all of the post i have expanded just collapses back to its original form and that's because in our edit form here the button doesn't actually have an action and so it falls back to the default action of just refreshing the entire page and essentially if i go to my console you can see that the entire post gets all of the posts gets refetched from scratch and the browser renders the entire page all over again and that's the call there and just appends the form information into the url as query parameters this is a very quick and easy fix because we've already done our previous code for the show post functionality inside of our divs here so all i have to do is click on that hx get action and just put it here on my button and just yeah and because we already have our target and hx swap attributes specified in the parent component we don't have to specify that again in this button because hmax is smart enough to see that it doesn't see anything in its own class in its own component so it's going to traverse up to the parents until they until it eventually finds these two html attributes of interest and automatically takes that value for what it is so now if i just refresh the page i'm going to take away the url parameters here click on edit post on these couple of posts click on cancel and you see that it works perfectly now and doesn't just refresh the entire page okay so now i'm done with the cancel button i'm going to finally work on this delete post button right now if i click on it you see that it just does that same thing it was doing with the cancel button it refreshes the entire page and it pins the query parameters to the url which isn't the behavior that i wanted for this delete button so as usual the first step would be to create a new api endpoint for to handle this delete functionality i'm just going to steal the code from my edit show my show post function i'm going to append this last part to the url called slash delete so notice that the dynamic part of our url doesn't necessarily have to be the last part of the url it could also be in the middle and i'll rename this function delete post so instead of just doing post equals to db.get post i'll just do a sample db.delete post post id and so that's because i already have this function in my database utility now don't get frightened by this syntax here in python that's just a list comprehension it's kind of like dot filter in javascript so it returns true as long as the id of the post we're trying to iterate through is not equal to the current id that we're getting in the argument of the function and if it is it happens to be the same post as the one provided in the argument just straight up just ignores that and doesn't include that in our list so yeah dot filter it's just basically dot filter id not equals to id post id not equals to id and now we have that we can return so here's a trick what should we return now if we go to the edit dot html here this is the delete post button and this is the button that we're gonna put our html htmx actions on so if i just return an empty string and i just put that action here hx-get we can change this to hx-delete in the future if you really wanted to we just have to change like methods equals to delete like this and you can do hx-delete instead but it's kind of unnecessary in this example because we're already specifying that this route is made specifically for this one purpose to delete posts so i can just leave it like that for now so we just do an hx-delete we all for delete post and then post id equals to post dot id and so now that i have this this button is actually an element inside of this div and because we're not providing an hx-swap or hx-target attribute to this button it's going to go and look at its parent and see that its parent doesn't have any of those attributes either so it's going to go even further go to this parent which is the form which still it doesn't still still doesn't see hx-swap or hx-target so it goes even further and all the way until it sees this and now we have our target which is this and then swap is outer html so we're going to use this button to fetch the response from the server this this response from the server is going to override this entire outer html of this parent div and that response just happens to be an empty string so i'll refresh this and if i just delete a post you see that that post gets deleted i only have three posts now if i do that one more time we only have two posts i can hardly refresh the page and you see it's persistent and so that means that the change has been reflected in the database now ideally you're going to write like checks to make sure each user can only delete their own posts and also you want to make sure that the post id exists because if i try to delete a post that doesn't exist that should throw an error but that's beyond the scope of this tutorial because i'm just trying to teach html here now i'm going to go to the html documentation there's an example here called delete row so if i click on delete you see that there's a prompt that ensures i really want to delete this post before deleting it and you see that there's that fading animation and that's actually surprisingly simple to implement we don't even have to write a single javascript line for this to work like usually you would have to do window.prompt or window.confirm right but here it's just the simple matter of adding this one hx confirm attribute here that says are you sure so i can show that to you right now by going to my edit form and here in my delete button my delete post button just say hx confirm are you sure you want to delete this post and then let's refresh the page so edit post click on that you see that we get this nice prompt it just there's like no effort required and that's just really convenient if you're a back-end developer you don't want to mess with any front-end javascript stuff and that's why hmx is so amazing so let's go ahead and delete that post now we're left with one very lonely post well because my database is stored in ram I can just go and restart the server like that and all of my posts should come back because all of them are hard-coded in this one file and that thing resets because it's stored in ram yeah so final thing is this animation this nice looking animation of the fading out as we delete the post as the post gets banished to the void we want to be able to visually represent that so our users can more easily understand what is going on with our interface because right now if I just clicked on this delete posting it's hard to tell what just happened because it all happened so quickly how do we do that well so whenever swapping is happening you can specify swap to take up a duration of one second here in the hx swap attribute and then htmx automatically applies this htmx swapping css class to the element so we can make use of this in our code I'll just copy this and remember earlier we had our styles.css file in the static directory I'm just going to get rid of the tr and td elements here I just want to deal with this one class by itself opacity zero transition classic typical css stuff now that was easy the only problem is that we still have to specify that we want our swap to be a duration of one second and the problem with that is you'll very quickly see that our swap is actually here in the parent div and this swap attribute applies not just to this one delete button it also applies to my submit and cancel buttons so if I were to do like a swap one second here and I refresh the page you see that when I click on edit post right I click on cancel or if I click on like submit let's change this you see that it swaps it has a delay of one second and fades out everything no it doesn't matter it doesn't care if it's a submit or cancel or delete post button so in order to fix that let's take away the swap one second from this parent hx swap so remember I said earlier that the hmx goes up and traverses the parent nodes until it sees this hx swap attribute well it doesn't have to do that if I straight up just put an hx swap attribute here directly in the button that way it instantly knows this overrides the parent's hx swap attribute doesn't have to traverse the parents to find the hx swap for this one specific delete post button for the other buttons yes they still traverse to the parent but for this one delete this specific delete button I can specify swap to be one second and I keep this here in the parent so let's try that out so edit post cancel is working submit is also working but if I click on delete post it prompts us to confirm and I click okay and now that's perfect