Django and HTMX #21 - WebSocket Notifications with the HTMX WebSocket Extension and django-channels

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in this video we're going to use Django and htmx along with web sockets to build an application that allows the server to push notifications to front-end clients we're going to see how to set up Django channels in this video in order to deal with websocket connections and we're going to see how we can use htmx's websocket extension to connect to the server we'll then see how to use Django and Django channels to push websocket messages to the clients finally we're going to see how the htmx websocket extension handles the messages from the server and puts content into the Dom and will see other client-side tasks such as clearing the notifications as well so let's get started so for this video we have a GitHub repository here which I'll link below the video on the master Branch we have the starter code and there's also a final Branch here that contains the final code at the end of the video so what you should do to get started is clone this master branch and then we can go to vs code where I've already got that cloned and we can run the python manage.pi migrate command and that will generate our database once you've generated your database what we're going to do is we're going to run the development server here and we're going to have a look at the application from the starter code now as you can see in requirements.txt we have this package called Django oloth installed and this package basically provides you out of the box with registration login and logout functionality we're going to use that in this video because the goal of the application is going to be when we sign up for the application it's going to send notifications to all existing users so let's go to our page here and you can see we have a very basic page it contains this message here and there's a sign up and login Link at the top and these are Django oloth forms the very basic you can style them up if you want to but we're not going to do that in this video at the top right you can see we have a notification Bell at the moment we have no notifications but later in the video what we hope to do is when a new user signs up and we run this page without refreshing or without doing anything we're going to be getting a notification because the Django server is going to push that websocket data to the client and when it does we hope to see that no notification at the top right so let's get started with the video the first thing that we're going to do is go to Django channels documentation the installation section gives you this command to run to install Django channels along with the asgi server Daphne I'm going to make the text a little bit larger here so what we're going to do once you've installed those we're going to add Daphne and channels to installed apps so let's go to our project I've already installed these we're going to go to settings.pi and if we scroll down to installed apps at the top of these installed apps we're going to add the Daphne ASCII server and if we go down to the bottom just underneath the Olaf applications we're going to add channels to the installed apps now if we save settings.pi you can see at the bottom we get a command error and that's because we haven't set the asgi application setting because we are using an asynchronous asgi server we need to set this and point it to the application object so if we scroll down to the bottom of this settings file we're going to add that asgi application setting and we're going to point it to our asgi DOT python you can see that on the left can say that's under the project folder which is called htmx underscore websockets so what we're going to do is we're going to go to the project folder hdmax websockets we are going to the asgi file and it's going to be the application object so this setting just tells Django where the asga application object lives what we're now going to do is actually go to that file and we're going to change the settings here in this file and to do that again we're going to reference the documentation so let's go back to Django channels and you can see that in the asgi dot python what we're going to do is Define this protocol type router so let's grab the import and we're going to copy that into asgi.pi at the top so from channels.writting we've imported the protocol type router and then underneath the application object what we're going to do is instantiate the protocol type return let's copy this code from the documentation I'm going to paste that down here and we can change the name of the application that's being referred to to application so let's go through this file very quickly we are setting the Django settings module on line 5 here underneath that we instantiate the as the application and then on line 9 we instantiate a protocol type router and we're setting a key within that dictionary to http and pointing that to our application here now HTTP is the default protocol for Django applications what we're going to do a bit later on in this video is add another key called websocket and that key is going to determine how websocket connections are handled when they arrive at the server but for now we're just going to leave it at HTTP but that's why this is called a protocol type router it takes the protocol and it determines what happens when a connection with that protocol is received so let's save the asgi.pi file and we're going to restart the Django development server now once that starts running you can see at the bottom it's started in asgi Daphne server so when you run the Run server command and you have an asgi application Daphne takes control of the server and it allows asynchronous operations to be performed and we're going to need that when we use Django channels in this video so we're now going to move on to htmx's websocket client we're going to install that and use that in our application education but before we do that let's once again go over the goal of this video If we go back to the application when a user signs up to the application we want Django to push a websocket message out to all clients and then that will show as a notification at the top right here so what we're going to do is use the htmx websocket extension and that's going to be used to instantiate a connection from the client to our Django server so let's go to the documentation for this websocket extension for htmx as you can see at the top it enables bi-directional communication with websocket servers directly from HTML now in order to use this extension we need to add this script to our base template so let's go to the templates directory and go to our base template and you can see we've got a few by default here we've got htmx and Tailwind along with Tailwind elements just underneath that we're going to add another one and that's for the htmx websocket extension let's paste that into the base.html's head tag and then we can go back to the documentation so if we scroll down a little bit here we have a Dev tag and it's got an hx-ext attribute we've not seen this one before on this channel but basically when you're using an extension with htmx this is how you can Define that that extension is being used in your application and as well as that we have another attribute here WS connect and that determines the endpoint with which the client will establish a websocket connection with the server so let's copy these two attributes and we're going to go back to vs code and in the base.html I'm just going to add these to the body tag so let's paste them in here now we're going to change the end point here that's been connected to we're not defining our chat room here what we're going to do is add another endpoint and I'm going to paste one in here it's the slash WS slash notifications now it's considered a good practice with Django channels if you prefix your websocket URLs with this slash WS it can make deploying channels projects a little bit easier so once we've added that to the body tag let's go back to our page and we're going to refresh this page and if we go back to Django and look at the terminal you can see that there's a web socket can connection we're trying to connect to a Django by websocket but we're getting a disconnect and an error message and that's because at the moment we do not have an endpoint here for this websocket connection so the next step in this application is that we need to create something at this endpoint that's able to handle the websocket connection and in Django channels that is called a consumer so we're going to go to the documentation here and I'll link this page below the video we're going to scroll down here and as it says we're going to create a new file called consumers.pi and that's going to be within the code application so let's create that file just now and within this file we're going to create a subclass of the websocket consumer from Django channels so we're going to copy this line of code and we're going to paste it at the top of consumers.pi from channels.generic.websocket we import the websocket consumer and then we can create a class below that and that class can then handle websocket connections from the client and it can handle sending and receiving messages so let's create a subclass it's called the notification consumer and that will inherit from the websocket consumer and if it go back to the documentation for the consumer you can see that it has certain methods on that class now we need a connect and disconnect method here so we're going to copy these and paste them into our consumer and the connect method as you can imagine that's what's called when a client first connects to the websocket consumer and that calls the self.accept method in order to accept the websocket connection you can also reject the connection which we'll see at the end of the video and we also have a disconnect method that's called when a client disconnects from the consumer and we're going to add some code here later on as well now let's go back to the documentation for this page and if we scroll down we can see more details here we can see that this websocket consumer class is a synchronous class now channels also support writing asynchronous consumers for greater performance but these are a bit more difficult to handle we could do this in this video but we're just going to stick with the synchronous version of these consumers now if we scroll down just below that section the next step in this video is we need to create a routing configuration so what we're going to do is we're going to create this fail called routing dot pi and again that's going to be within the core application if we go back to the documentation what we're going to do is basically Define websocket URL patterns very similar to the normal URL stop python but instead of djangle views these websocket URLs are going to connect to the consumer classes so let's go back to our new routing.pi file and we're going to paste this code in here you can see we have websocket URL patterns and this URL here it matches what we defined in the base.html for the htmx websocket extension that's trying to connect to slash WS slash notifications so we Define that URL here in the websocket URL patterns and we are connecting that to our notification consumer and that's the class that we've just defined here in the consumers.pi file and finally in the routing.pi file we're calling the as asgi function that's a static function on a consumer in Jungle channels and that will allow it to handle that connection and if you've worked the Django class based views before this is very similar to the as view function so let's see save the routing dot Pi fail and go back to the documentation if we scroll down you can see that the next step here is to point the main ASCII configuration at this new routing module so what we're going to do is take a lot of the code from this file here and we're going to paste that into the asgate.pi file so let's copy all this code here and this is defining a bunch of imports from Django channels we're going to paste these into the asgate.pi file at the top now before we had the protocol type router and the get ASCII application we're now importing the auth middleware stack the URL router and the allowed host's origin validator so these are quite verbose classes but we're going to see what some of them are doing in a second let's go back to the documentation the next thing we're going to do is import the routing module you can see that this is done here we're going to do that underneath the application object we're going to import from the code application the routing module and the final thing we're going to do is we're going to remove this comment from the protocol type router and we're going to add a new protocol and that's the websocket protocol so let's add that as a a key to this dictionary here and the value for this we're going to get from the documentation let's copy this code here that's defined for the websocket key and we'll paste it in here and all we need to do here is change the chat application to the core application that we have in our project and what this is doing is it's taking these websocket URL patterns and it's giving them to what's called a URL router and Django channels and that's used to expose certain attributes from the URL like parameters to the consumer class and we're also using the auth middleware stack which adds the user to What's called the scope we're going to see that later in the video you don't need to know the final details of this you just need to copy the code from the documentation and once you've done that you can save the asgi.pi file and close it we're not going to go back to that fail in this video now let's run the Django development server again and go back to our page and we're going to refresh this page and we're going to see if we get our websocket connection between the client and our Django server if we go back to the terminal you can see that we have the websocket handshake and we are actually getting a connection so now that we're getting the connection what are we actually going to try and do with our consumer class well when the client initiates a connection with our server we want to accept that connection we're already doing that but what we also want to do is when our user signs up to our application and they can do that with this form here what we want to do is we want to broadcast that message as a notification to all users that are on the page and then those users will receive the data from the websocket from the server and they will then show a message on the front end and that will be handled by the htmx websocket client so that's what we're going to try and Achieve now one important detail about Django channels if we go back to the consumer class each client that creates our websocket connection with the Django channels consumer will receive their own instance of the consumer what we need to do is track each of these instances they're called channels we need to track them in what's called a grip in Django channels so what we're going to do is go back to Django channels documentation here and we're going to scroll down and there's a section on enabling Channel layers A Channel layer as it says here is kind of a communication system it allows multiple consumers to talk to each other and to also talk to other parts of Django and that's important if you're building something like a chat room if we go back to the consumer file here each individual client that wants to join the chat room they'll get their own instance of this consumer if you wanted to broadcast a message to all people in the chat room we need to track which consumers belong to the chat room and that's done using something called grips but to do this we need this channel layer to be enabled in Django channels this section here gives us a bit more information about this concept A Channel layer provides the following abstractions it provides a channel which is a mailbox where messages can be sent every channel has a name and anyone who has the name of the channel can send a message to the channel but there's also the concept of a group and that's a bunch of related channels and a group also has a name and anyone who has the name of a group can add or remove channels to the group but more importantly they can send messages to all of the connected channels within the group and that's how you would broadcast a websocket connection from server to a bunch of clients using this concept of a group I hope that makes sense I'm going to dive into all of this in more detail in a dedicated Django channels course later on but for now let's go back to the application if we scroll down here what we now need to do is add a setting called Channel underscore layers to our settings.pi file and you can see that for this channel here what's being used is redis now we're not going to use redis in this video but there is an alternative if you're using Channel layers if you go to the channel layers section of the documentation and if we go to the configuration section you can see that as well as the redis channel layer you can also use an in-memory Channel layer now the in memory layer is good for development and for this video but in production you would definitely want to use redis but what we are going to do is simply copy the setting here for channel layers we're going to go to settings.pi again and at the bottom underneath the asgate application we're going to paste the configuration for the in memory Channel layer and because it's in memory it doesn't require any additional setup so it's very easy to use and development so once you've added that you can save the settings file and we're going to go back to consumers.pi and within the connect method we're going to find the name of the channel so what we're going to do is print self.channel underscore name and when you add Channel layers that basically is a property that's added to the class or the consumer so let's add that and go back to the front end of our application and refresh this page and if we do that and go back to the back end you can see that we have this string here that's being printed that's the name of the channel and that is unique to this specific consumer if we go back to the page and refresh we're going to disconnect from that websocket connection and we're going to reinstantiate the connection when the page reloads we're going to go back to the server and we can see that we get a different ID here we got a different name so the channel name is unique for each instance of a consumer so every client that connects is going to have their own channel name what we want to do is add that channel name to a group and the grip is an abstraction that allows us to connect multiple different consumers together so that we can then broadcast and receive messages from so the next step in this video is to create this group and what we're going to do is add each channel that we get in the consumer from a new connection we're going to add that to the group now if we go back to the documentation and we go back to the original page we were walking through here which was the tutorial part two what we're going to do is scroll down and you can see that we have this code here in the connect method that's used to join a new channel to a grip when a connection is established so what we're going to do is copy that and we're going to paste it into our connect method now you can see that we are calling the self.channellayer.group add function and we are going to provide a name of a group and we're going to provide that the channel name to that function now we've already seen the channel name we printed it out at the top that's the unique name given to an instance of a consumer what we need to do is Define the name of the group that we're going to create here so just above the async to sync function I'm going to Define a variable called group name and I'm going to Cache that on the class itself and the grip is going to be called user Dash notifications and then we're going to pass this variable to the group AD function function so let's change the code from the documentation here and we're going to reference the group name and the final thing we need to do is import the async to sync function if we go back to the documentation you can see that that's coming from the asgi ref package so basically what that does it takes an asynchronous function such as the group AD and it allows it to be executed in a synchronous context so we're going to bring that in here remember the websocket consumer is a synchronous class so let's go over the code and this connect method so that we understand what's Happening Here We Define a group called user notifications and then what we're doing is we're using the self.channellayer.grip add function and that allows us to add the channel name which is unique to the group that we have here and it's called user notifications so when we get the first connection from a websocket client to the server it's going to add this channel to the group and then another user comes in with the connection and it's also going to add that user to the group as well so for each websocket connection we're adding it to this group and then finally we call the self.accept method and that accepts the connection now we need to do something similar on the disconnect method instead of grip add there's another function if we go back to the documentation you can see it's called group discard and again we provide to that the name of the group and the name of the channel and that will basically remove that channel from the group so let's copy this code again I'm going to paste that into our disconnect method and again we'll change the name of the group to the name that we have cached on the class itself.group name so now we have a system where when a user connects they're added to a group and when the disconnect when the web socket is terminated they are removed from that group now the benefit of doing this is that we can now send a message to a group and that will then be sent to all of the clients that are part of that group so we can now broadcast a message to multiple people in our application and we can trigger that from the server to the client now we want to send this notification to all users when we get a new user signing up to our application so what we're going to do is use Django signals to find out when a user has signed up and then when we get that sign up we're going to send a message through the websocket to all of our clients to tell them the name of the Eurozone that has signed up so let's now go to our core application and we're going to create a signals.pi file and let's go to Django's documentation here there's a page on signals I'll link that in the description of this video signals allow you to perform actions when events happen in the Django ecosystem and the event that we are interested in is here on the right hand side it's the post save event and this signal is called for a given model when the save function is finished so let's go back to the signals.pi file and I'm going to bring a few Imports in at the top first of all the settings module we're also going to bring the post save signal in and the receiver method and we're going to go down and we're going to use the receiver method as a decorator here the first argument to that is the signal name which is post save and the second argument is the source or rather the sender and that's the model that is sending the signal when the save method has been finished and that's going to be equal to the settings.auth user model now the other user model is defined in the settings.pi file and this comes from the starter code it refers to a code.user model and if we go to models.pi you can see we have the user and it's inheriting from the abstract user and this is our default user model in this application because of the setting that we've specified here so within our signals file this is the model that when it's finished saving I.E the post save signal it's going to trigger this function that we're about to write so let's call this function send notifications on sign up and these notification functions take some arguments we have the sender the instance itself that's the user model that has sent the signal and we also have a Boolean and that's whether or not the user is created in the database or whether it's just being saved and updated so let's now write the logic for this function we're going to test if the user has been created because we only want to inform and send notifications when a new user has signed up we don't want to do it whenever they save and change any of the data so if the user has been created what we want to do here is trigger a note application to all of the consumers or the channels in our user notification group so that's what we're going to try and do but before we do that let's go to the documentation for Django channels we're going to go to the channel layers section and we're going to go to this bottom part here it's called using outside of consumers and this tells us how we can send to the channel layer from outside of a consumer class we don't have access to the self.channel there but instead what we can do is use this get channel layer function so we're going to copy this import at the top here and we're going to bring it into our signals.pi file we'll paste that below the Django Imports so this is a function that we can call to get access to the channel layer so let's create a variable here called Channel layer and we're going to set that equal to calling this function and it will return the layer to us so we can use that to send notifications now the group that we want to send these notifications to is called user notification so we'll Define a variable here called group name and finally what we're going to do here is Define an event and that's going to be a dictionary of key value pairs the first one is an important one it's called tape and the value of this key is going to be user underscore joined and what that's actually going to do is it's going to call a function on our consumer that's called user joined we're going to Define that in a second and see exactly how that works let's go back to our event payload here and we can Define any key value pairs we want but what I'm going to do is Define a key called text and that's going to be equal to the instance of the user who has signed up and after they sign up they're going to have a username so we're going to attach that as the text key in this dictionary now the final thing we need to do is underneath all that code we need to actually call the channel layers group send method now like the two channel layer methods we see here called Group ads and group discard we also need to wrap this in the async to sync function so if we go back through the signals.pi at the top I'm going to import from asgirif that function and then just below the code here in the signal what we're going to do is Define the async desync function and we're going to use the channel layers group send method and we need to pass to group send then name of the group that we're going to send the messages to and we also need to define the payload which is the event dictionary that we have above now like I said earlier this type key in the event called user join that determines a function that's called or a method that's called on the consumer class so what we're going to need to do now is within the consumer we're going to call or Define rather a function called user joint and this is an instance method so again it will take self as the first argument and the second argument is the event that we're actually sending to this function so that's basically the payload that we've attached here as the second parameter to group send that's going to be this dictionary here so we're able to extract the username from that dictionary so let's go back to consumers.pi and what we want to do in the body of this function is called the web sockets send method and that send method is going to send data to our client in other words all clients that are part of the group and the text that we want to send is equal to the event dictionaries text key and actually this should be text underscore data and that's because the data we're sending over the web socket as Text data so let's save that and what I'm going to do now is open two browsers and we're going to test this out so we have these two browsers open here as you can see one of them is an incognito mode what I'm going to do in the second browser is I'm going to go to the sign up page and we're going to try and sign up as a user so I've filled in some details here but on the first browser I'm going to go to the developer tools and if we expand this out and go to the network tab here we hope to see a websocket message appearing in this network Tab and it's been sent from Django to our client so let's go back to the other browser and we're going to sign up and we're not seeing anything appearing here and that's because I've not actually registered the signal that we've created what we need to do in order to do that is go to the App Store Python and within the core config we Define a function called ready and that will take self as an argument and within this application we need to register our signal simply by importing them into this ready function so from the current application we import the signals module if we save that and we try this again hopefully it's going to work on the left we have the page where the client is connected to our websocket on the back end and on the right we're going to fill in some user details and sign up so we have on the right a user called Dwight if we click sign up and go to this notification websocket here if we look at the messages you can see that the username has been transmitted from a server to the front end to the client and that's been done through a websocket connection that's handled by Django channels and is instantiated on the front end using the HDMX websocket plugin now the next step here is to actually return HTML content because as you should know if you've watched any other videos HDMX deals with HTML content and it swaps that into the Dom so what we want to do is actually take this notification bar and we want to swap some content into that whenever a notification is filed to the front end so let's go back to vs code and within our consumers.pi file this user joined method will send the username to the front end instead of sending that as text Data what we want to do is actually send HTML which is then swapped in to a notification bar so because we're returning HTML at the top I'm going to import the get template function from django.template.loader and at the bottom we're going to remove this line of code and we're going to create a variable called HTML and we're going to call that get template function and that takes as an argument the template that we want to get and then we can call the dot render function and pass a context to that template so it's going to be a keyword argument of context and we're going to create a username key here and that's going to map to the events text key and remember that the event is coming from the signal and the text key defines the username of the user who has just signed up to our application so that's going to return the HTML for this particular template that we're going to create in just a second and the only other thing we need to do in this function is called the self.send method to broadcast this to our users and the text data in this case is going to be equal to that HTML from the get template function now of course we need to create the notification.html template so within the templates that directory and the core directory we're going to create a new file called notification.html and that will be in our partials directory and the notification template is basically just going to be something similar to what we have on Tailwind elements for the alerts page I'm going to link this below the video but you can get the code for these here I'm going to paste a modified version into our template so let's paste that in here and what we're going to do is we're going to note a couple of attributes here firstly we have a notification drop down that's the ID of the dev wrapper and that also has an attribute called HX swap out of band and that's set to after begin now what we're going to do is go back to the htmx websocket documentation and there's a section here called receiving messages from a websocket and that's exactly what we're doing we're sending HTML data that's going to be received on the client by this extension and as it says here content is sent down from the websocket will be parsed as HTML and swapped in by the ID property and it uses the same logic as out of band swap so let's go back to the template we have this ID called notif application drop down if we go through the nav bar that defines the notification section at the top of the page here let's go back to navbar.html we can search in this template for the ID that we're going to actually use here it's called notification drop down and it belongs to this UL tag and this UL tag defines the list elements that appear underneath this Bell this notification icon so we want to perform an out-of-band swap that's going to put the content from the server into this bill and it's going to do it after the start of the bill if we go back to the notification template here the HX swap out of band property is set to after begin so what it's going to do is for this bill here it's going to find the UL containing all the list elements and it's going to drop the new drop down after the beginning of this UA also at the top of the list I think it's easier to see this if we actually implement it so let's do that now if we go back to the template for a notification the spans text says the username has joined and remember we're attaching that username as context so what we hope to see is that when a new or signs up this content is then swapped into the dorm and it appears under this notification icon so let's test that out again we're going to go back to two browsers and on the right hand side what we're going to do is we're going to log out of this application as the current user I'm going to sign up as a new user now on the left if you look at the Bell here you see that we have no notifications at the moment if we go to the right hand side and sign up here you can see that we get this notification appearing on the left hand side and the websocket has sent that HTML from the server to the client and the htmx websocket plugin has accepted that and swapped it into the Dom and it's done it after the beginning of this list this UL tag now we can do this again we can sign up another user so I'm going to sign out with this guy and we're going to create a new user and when we hit sign up again we expect this notification to appear in the list and it's going to be at the beginning at the top of the list so let's sign up with this new user Robert and you can see we get the new notification so that's the main part of this application we are now connecting the front end and the back end with a websocket connection and were able to send messages from the server to the client which then updates the UI when it receives these messages so our websocket system is working but we're going to perform a few extra tasks in this video firstly anyone can see these notifications including users that are not logged in we only want to show these notifications to users that are actually signed into an application so what we're going to do is go back to consumers.pi and we're going to add a few lines of code that are going to make sure that unauthenticated users do not see these notifications now at the top of the connect method we can get the user who's sending this request and we're going to Cache that as self.user and that's going to be equal to itself.scope which is a dictionary and we can get the user key from that dictionary now self.scope is something that is available on the consumer class and the user key gives us back the user who's sending the request now we can actually get this user key because we go to asgi.pi we've added the auth middleware stack from Django channels and that adds this user key to the scope and allows us to fetch the user from the request so we have access to the user now within the connect method and we can write an if statement here and we can check if the user is not authenticated so it's going to be if not self.user DOT is authenticated so if the user is not authenticated what we're going to do is we're going to reject the connection and on the consumer class if you want to reject the connection you can call the self.close method and then we'll just return out of the function so if the user is not authenticated we're going to close the websocket connection and we're not going to allow them to establish that connection otherwise we'll just progress through this function as we did before but we also need to add a bit of code to the disconnect method as well remember in the disconnect method we're calling the group discard method and we're removing this channel name from the group but we only need to do that if the user has been authenticated and has been added to the group so what we're going to do is add another if statement here we're going to check if the user is authenticated in which case if they are authenticated we know they were added to the group and we can then call the group discard method now we need to do this because because if you try and remove a channel from that group that doesn't exist it will cause an error so by doing this we're checking to see whether the user is in the group in the first place and then we can remove them when they disconnect so that's some additional code on the back end on Django that will stop unauthenticated users accessing the websocket connection what we're now going to do is go through the client side again and what we're going to do is write code to check these notifications and display the number of notifications in this bill now to see this in action let's go to the navbar.html and I've left a comment somewhere here which is this one here is the number of notifications books and this is a span tag and if we scroll into the text content and let's say we put the number five there if we run the server and refresh this page we should see the number five appears at this bill here so we can then get some visual feedback on how many notifications we have now what we're going to do is actually make this Dynamic but we need to write JavaScript code to do that so let's go back to our template here and we're going to remove the number five from that HTML and we're going to go back to the restart HTML and at the bottom we're going to write a script tag here and within this script tag we're going to grab the span that contains the number it's got an ID of notification number so let's go back to our script and we're going to write a variable called number span that's going to be equal to document.getelementbyid and to that we're going to pass the ID of the span which is notification number so now we have access to that element now what we need to do here is increment the number in that by one every time we get back a websocket message telling us a new user has been added so what we're going to do here is in the document we're going to go to the body element which remember if we scroll up contains our htmx websocket connection and to that we're going to add an event listener and we're going to listen for an event that's defined by htmx's websocket client so let's get back to their documentation now if we scroll down you can see there's an events section here I'll link this below the video now one of the events is this one here htmx WS after message and this event is triggered when a message has been completely processed by HTML MX and that's what we want to actually listen for in our document body and then when we get that message we can perform the action of updating the notification number so let's go back to vs code and the event listener is going to take that particular event from the htmx websocket plugin and when we get the event what we're going to do is call this callback function and we're going to create a variable here called number of notifications and that's going to be equal to the number span that we got on line 36 above here we're going to get the inner HTML for that particular element and that's going to either give us a number if we have a number there what it's going to give us nothing if we haven't got anything and remember by default if we go back to the navbar.html that span does not contain anything by default so when we first do this it's not going to have a number and this particular line of code here is going to give us nothing so in the case that it gives us nothing we're going to write an if statement and we're going to see if the number of notifications is null in that case we know that we're dealing with the first notification so we're going to set numberspan.inner HTML and we're going to set that equal to 1 now on the case that we have a number already in there in other words it's not null we're going to write an else block here I'm going to say numberspan dot inner HTML and that's going to be equal to the number of notifications but we need to parse that as an integer so we'll call the percent function and we'll pass number of notifications and then we can add one to that so let's test this out now if we go back to our page here and we refresh this page and I'm going to add another browser here so let's say we have a user on the left who is signed into the application and we have another user trying to sign up on the right when we hit the sign up button you can see we now get this notification that tells us how many notifications there are and it also gives us the content of that notification if we then go back to the page here and try and sign up with another user and let's call this user Steve when we hit sign up we get the notification but actually the element is not updating with the number two so let's inspect the console here and we'll try and figure out what's gone wrong we've got a reference error number is not defined so let's get back to our code here and this is my mistake obviously I've not called that the right way variable it should be number span and that refers to the span that's going to show the number and in the else block that was incorrect so that did not fire correctly let's try this again if we sign up with one user we get the notification saying one we then try and sign up with another when we submit the second form you can see that it now updates and it has two in there and this will keep updating if we add new notifications so we now have visual feedback as to the number of notifications that we have the final thing to do in this long video is to implement the clear all button when we hit this we want to clear all of the notifications and remove the number from the Icon so let's go back to our base.html and we're going to work on that in this script tag as well and again if we go back to the nav bar at the bottom we have this button here it's or rather it's an anchor tag and it says clear all it has an ID of clear button so we're going to copy that and once we've copied that we go back to the base.html and again at the top here I'm going to get a reference to that button we'll call it clear button it's going to be document.getelement by ID and we'll pass the ID to that function once we've got the element by its ID what we're going to do is add an event listener to that element and the event that we're listening for is the click event and when we receive that we will fire our callback function and what we need to do here is remove all of the notifications and also remove the number so let's go back to our navbar.html and what we have here if we search for that notification drop down remember that's the UL tag that contains all of the list elements for each notification now what we need to do is remove all of those except from the list element that contains the clear all button so to do that what we're going to do is go back to our partial that's returned by htmx and that returns our Dev so what we need to do is get all of the div tags that live underneath this UL element and we can remove them from the Dom so let's go to base.html within the event listener we're going to get a reference to the notification drop down by calling get element by ID so that's the drop down then we need to get all of the divs that live within that drop down so let's call this variable notifications and it's going to be equal to drop down and we'll call the query selector all method to get all of the divs underneath that this gives us back all of these notifications that you can see in the UI so for example Harry 2 and Harry here these are both notifications they're Dev tags that live underneath the UL tag so what we now need to do is remove them from the Dom so what we're going to do is call the notifications dot for each function and we can pass for each notification a callback function and all we need to do for that callback function is called the notifications remove method and this is just a method in the document object model that will remove an element from the Dom once we've done that we are basically done with the notifications we only need to also update this number here if we remove the notifications we don't want to see the number it should then be removed from that span so let's get back to our JavaScript and this is the last thing we're going to do and it's this number span here that shows the number so we're going to copy the name of that variable and we can set the inner HTML in that span to an empty string and that's all we need to do here here so let's test this out and refresh the page again we're going to sign up with this user here and we see the notification on the left and then I'm going to sign up with another user and when we sign up with that user we now have two notifications and you see both of the users all we want to do now is clear these by hitting this button if we do that you can see that the notification Bell has no longer got the number and we also don't have the notifications listed here so that is what successfully and if we sign up with another user when we hit sign up that whole system is still working so we're able to get notifications from the server display them on the page with the htmx websocket extension and we're also able to clear these notifications from our page with this clear all button so this is quite a dynamic system it's been built up using Django and Django channels on the back end and using HTML and htmx on the front end and we're interfacing with a websocket and the back end is sending the front end these messages and htmx is doing what it does best and the front end is swapping that content into the dorm and we get the appearance of a dynamic page that's updating based on the server generated notifications so this has been a long video thank you very much for watching I hope you've learned something from the video if you've enjoyed it please like And subscribe to the channel if you're interested in more content on Django channels in the future please let me know as well and we'll see you in the next video
Info
Channel: BugBytes
Views: 16,640
Rating: undefined out of 5
Keywords:
Id: FcVwDEcu6K0
Channel Id: undefined
Length: 40min 15sec (2415 seconds)
Published: Mon Feb 13 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.