Golang + HTMX - Creating a Go webserver / HTMX Integration / Template Fragments

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in this video we're going to explore how to use htmx with golang in order to create web applications that are interactive and dynamic but don't require us to write a lot of JavaScript code we're going to explore how to use goes HTTP module as well as the HTML template module and we're going to see how we can integrate those with htmx in this video so let's get started we're going to assume that we have no knowledge of go in this video and let's start by showing the page the way we can actually download go you can download it from this page and I'm going to leave a link below the video and once you've installed go you have access to a command line tool so if you run the go command you get some different commands that you can use here these are sub commands for example the go run command which will compile and run a go program so we have go installed what I'm going to do is go to vs code and I have a main.go file and Dot go is just the extension for go files and we're going to start populating this file in this video I'm going to go back to go and go to this documentation here this is the classic hello world example for go and I'm going to copy this code and we're going to bring that into to our Min dot go file so we declare a package called Main and we're importing a module here it's the format module and then we can use the format.printline function to print out this message hello world and that print message will occur within a main function here that we Define on line five so we have a file here that's going to print out a message to the terminal let's now see how to execute a go file we're going to bring up the terminal and we can run the go run command and then we can just pass the name of the file in this case main.go and when we run that we get the message hello world printed to the terminal now our goal in this video is to create a golang web server which is going to take requests and generate responses so we're going to bring another package from goes standard Library into this application let's go to the documentation for the HTTP package this is the package we're going to use and this contains a bunch of different functions both client and server based functions for example the client functions like get and post are going to send requests to endpoints I'll link this page below the video but we have examples here here and the function we're going to use is the listen and serve function which is going to create a web server on our backend so what I'm going to do is go back to our main.go file here and just below the format.printline function what we're going to do is create a web server here and we can do that with the http.listen and serve function and you can see that's automatically imported the net HTTP module as a parameter to listen and serve we can specify the port number in our case we're going to specify Port 8000 here and the second parameter here is a Handler function which we can just set to nil and as it says in the comment here the Handler is typically now in which case the default servmox is used now like the documentation here we're going to surround the listen and serve call with this log dot fatal function so let's go back to vs code and we can paste the log dot fatal function in here and we can surround this http.listening serve function with that log dot fatal call and what log dot fatal does as you can see in the comment it's equivalent to a print statement followed by a call to OS dot exit so it's going to print out the error and then it's going to exit from the program so if there are any issues with the listening server function for our HTTP server it's going to print out that problem and it's going to exit from the process and it's going to stop our server so if we now go back to the terminal and run this program we should start a server that's running on Port 8000 so let's run the go run command and we can pass the name of that file and you can see the program is blocking at the moment nothing is happening but we have a server that's running on Port 8000 so that's great but we need to actually Define some URLs in this server and some functions that are going to handle requests to those URLs so let's stop the server for now and let's go back to vs code we can now Define a Handler function with the HTTP handle Funk function that takes us its first argument a pattern a URL pattern which is going to be the URL for this particular function and the second parameter is of course the function itself and you can see in the comment here the second function is the Handler for that URL and I'm going to pass a Handler with a name of Handler one here that we're going to create just above the handle Funk function so let's create this variable H1 and that's going to be equal to our goal function and golang Handler functions have a particular signature we're going to pass in the http.response rater that's the first argument and we're going to give that a name of w and we're also going to pass a second argument called R and that's going to be a pointer to the http.request this is standard convention and go we don't need to worry too much about these but this is analogous to Django's request object it contains information about the request for example as you can see here the method which can be a get request or a post request and so on and the response writer is what we actually use to send the response from the golang Handler function to the client now within the body of this function I'm going to use the go i o module and we're going to use the right string function here and we're going to write a string to the response and when we hit save that's automatically going to import the io module here so the right string function takes as its first argument our writer and the second argument is this string that we want to write and it's going to write that string to the rater object that we pass as our first argument in our case we're going to pass W the response writer as the first argument and the string that we want to write here is just hello world for now and I'm going to copy this line down below and we're going to use the request structure that we have up here and we're going to access the method and we're going to print that out to the terminal as well and this demonstrates how we can access particular attributes on the structure using the request object so let's go back to the terminal and we're going to rerun the server here you can see the message hello world and we have the running server if we go to the browser and we navigate to localhost 8000. you can see the message that's output here using the io.right string function first of all we have hello world and then we have the request method which is a get request and that's coming from the second line here and if you want that on a new line we can add the new line character like that and whenever we make these changes we need to stop the server and rebuild and run that go file once we've done that we can go back to the browser and we can get the get on and new Lane so this is the basic setup of our go server we have a function called main that's the entry point to the application itself we Define a Handler function here which Maps a particular URL pattern to that Handler H1 and finally we call the http.listen and serve function that's going to start on HTTP web server at Port 8000 and then once that started we can go to our browser and we can send requests to this particular URL pattern which is then going to call the function so this is working fine at the moment but in a real application we want to return HTML templates rather than writing out these strings using the io.wright string function and when we're using htmx we are going to be returning partial HTML templates to the client so we need to understand using Google how we can render these HTML templates and return them as part of the response now I have an index.html file here and it's currently empty I'm going to use emit on vs code to fill in some boilerplates here and then within the body I'm going to print an H1 tag that says hello world and now we're going to learn how to return this template as a response for this particular Handler function now the go standard library has a template package built in and we have this here again like all of these Pages I'm going to link this below the video you can see on the overview here that the package template implements data driven templates for generating HTML output safe against code injection and if we go to the index on the left hand side you get all of the different functions available in this module and for template types we have this function called parse fails we're going to click that and we're going to see that this function will create a new template and it will pass the template definitions from the named file so it takes a set of file names as parameters and these files are HTML templates that we can pass to the pass files function in order to actually create the template object if we go back to the index we can then use another function called must which is a helper function that wraps a call to a function that returns a template or a pointer to a template so this is based demonstrated through the code itself so I'm going to go back to vs code and we're going to remove the calls to IO dot rate string and instead of that we're going to create a variable here called template we're going to set that equal to the template dot must function and to that we pass the template dot parse files function and we can then pass the name of our HTML template as you can see on the left hand side that's called index.html and if we save the file we're going to Auto Import the HTML template module from golang and as you can see we're getting this error here or this complaint from vs code that the template is declared but it's not used so whenever you declare a variable in golang if it's not used anywhere in your program you will get this error here and actually the program will be unable to compile just to quickly demonstrate that if you go back to the terminal and try and compile and run this you can see that the variable template is declared but it's not used therefore we cannot actually compile and run this program what we want to do now is take this template that we've created here and we want to write it to a response rater W and we can do that by going to a new line here and we can call the template dot execute function and we can pass the response writer to that and the second argument to this is any data we want to pass to the template you can think of this as being like context in Django for example at the moment we're not going to pass any data so we're going to set that to nil and if we save the file and go back to the terminal and try and recompile this and run the program it's going to start our server and if we go to localhost 8000 we now get the header one tag with the hello world message so now our golang Handler function is actually serving an HTML template to the client and that is then rendering the contents of that template when we go to the given URL now we're building up to adding htmx to this application but what we're going to do now is add some context to the response so let's go back to vs code now we're not going to bother setting up a database or anything too complicated in this video if you're interested in that please let me know in the comments we can do many more videos on go if you're interested but like the Django series on htmx I'm going to found a list of films in this video so I'm gonna do above the main function is I'm going to create a new type called film and we set that equal to a structure in golang and the struct can contain different fields and structs are useful for grouping together data to form records you might be familiar with them if you've used C or similar programming languages so let's say we have data on films and we want to group that data into a struct we might have the title of the film which is a string data type and we may also have the director of the film which is also a string so let's say we now have a list of these films and we want to return them to the template that we're executing here and we want to render those in the template so what we need to do is actually pass data down to the template and then within the template itself we need to access that data within the body tag and render it out to the client So within the Handler function I'm going to paste some code here we're defining a variable called films which is of type map the keys are of type string and the value is a list of films or an array of films and you can see within the map we have a key called films and that will map to these three films that we have here The Godfather Blade Runner and the thing so that's our map with a key of films that maps to these three films we can now pass that as data to our template using the template dot execute function and that is then going to pass that data down and that data is then accessible within this template now how do we actually access this data in the template now I'm going to link to a good article on how to use go templates and this is from golangforall.com I'll link that below the video but you can see that below here we have the ability to Define conditions and the templates for example using the if statement and we also have these range Cycles where you can iterate over arrays slices Maps or even channels and we're going to use the range statement to iterate over the films that we've added to the data that we're executing this template with that's the films here you can see it has a key of films which maps to an array of films so what we're going to do is go back to index.html and we're going to to use that range statement now we can use the double curly braces here and then write the range statement and then we can refer to a name that's available within our context in our case that's films and then we can go down and end that range statement below and Within These two quarterly braces we can write any logic we want and we can access fields that are defined on a film struct for example the title field so I'm going to go back to index.html and I'm going to paste the paragraph tag in here where we access the film's title and then also the director so let's go back to the terminal and we can rebuild and run our server if we now go back to the page and refresh this page you can see that as well as hello world we are now getting the data from our golang server displayed on the page we have the title of each film along with the director so this demonstrates how we can pass context or data from our go server down to the templates which are then rendered on the client side and shown to our users let's quickly go over that we create a template object here with the template.must statement that wraps a call to parse fail and pass files will just take the name of the template that we want to actually render here below this we can optionally Define some context or data that we want to pass to the template and in a real application that's probably going to come from a database in our case we've hard-coded a map that defines a list of films and then we pass that using the template.execute method to the response writer object as a second argument so that's how it's done on the server if we go to the template itself you can see that we can Loop over an array or a slice using the range statement and then we can access Fields within the given film in this case using the dot notation wrapped in these color braces if you want to know more about how these statements work in golang check out the link to that page that I mentioned before now we're going to move on and what we're going to do is add the bootstrap 5 to this application and we're going to style this page a little bit better and then after that we're going to add HDMX into the mix and show how we can use htmx and a golang context now I have the bootstrap documentation open here I'll leave a link to this below the video what we're going to do is we're going to include a link to bootstraps CDN and we can do that by clicking the CDN links tab on the right hand side and we're going to grab the CSS for bootstrap 5.3 and we're going to paste that into the index.html and we can do this in the head tag so I'm going to put a link tag in here I'm going to set the href equal to that link that we're grabbing there and that should bring bootstrap into the application what I'm then going to do is change up the body tag here I'm going to remove all of this code and I'm going to paste in some code that uses bootstrap classes to make the Styles a little bit better and divide the page into a two column layout you can see we've still got the range statement here and we're rendering out the title and the director and this time we're doing that within an unordered list and for each film in the list we are rendering an Ali tag containing the title and the director and if we scroll down here you can see I'm also defining a form that's going to allow us to add a film to the existing list and we're going to use htmx to do that and second once we've added that code what I'm actually going to do is go up to a link tag and don't forget to add the rail equals style sheet to that particular element once we've done that we can go back to the terminal and we're going to stop the server and rebuild and run let's go Application if we go back to the web page and we have a look now you can see that we have our phone list with three elements and we now have a form on the right hand side that's going to allow us to add a new film now if you want to copy the code for the stylings here I'm going to put this code on GitHub check the link in the description it's going to be in the final branch and finally now we're going to start writing some htmx so when the form on the right hand side is submitted with a new film's details what we want to do is send a post request to our go server but we want to do that with htmx so that it's going to add the new film to the existing list without refreshing the page and that keeps the page interactive it keeps the user engaged and they don't lose their position on the page now I have the htmx documentation open here I'll leave a link to this below the video we're going to copy the CDN link and we're going to go back to vs chords I'm going to paste that into the head tag at the top here just below the bootstrap see the end link so this should bring htmx into our project if we now go down to the form what we're going to do is add an HDMX attribute to the form and it's going to be the HX post attribute and we can set that equal to a URL that we want to send a post request to using htmx when the form is submitted now when you define a request method such as hxget or hxpost these methods or these requests are triggered by a specific action that happens in the Dom so we're going to send a post request to the slash add Dash film endpoint which we're going to Define in a second and the default action on a form element when we Define a method such as hxpost is the submit event if you need to override that in a particular element you can use the HX trigger attribute to do that we don't need to do that what we're seeing here is that when we submit this form using this button it's going to then send a post request using htmx to this particular you URL and that post request is going to actually be an Ajax request it's not going to refresh the page and what we're going to get back from the htmx request is HTML that we then want to swap into the document so that's the biggest paradigm shift from a normal API where we're typically returning Json data instead of Json we're going to return hypermedia HTML data and that's going to be swapped into the document at a particular element called the target so we need to actually set the target we'll do that in a second what I'm going to do is go to our go server and we're going to actually add this new URL pattern into our server implementation so let's go back to main.go and just below this Handler I'm going to create another handle function here and this time the URL is going to be the slash add film endpoint and we're going to pass a Handler function called H2 to this function as a second argument and then we need to Define that Handler function above so let's create a variable called H2 it's going to be equal to a golang function and I'm going to copy the signature of this function at the top with a response rater and also the request object as well I'm going to copy that down here and then we can write the body of this function now for now in this function let's just use a log dot print statement we're going to print out that htmx request was received and I'm going to use another log dot print statement here and I'm going to print something else we're going to use the request and we're going to get the headers from the HTTP request we can do that with the request dot header field and to get a particular HTTP header we can use the dot get function and we can pass the name of that header the key name for that header to that function and don't forget and go we need to use double quotes here the name of the header is the HX request header and this is an HTTP header that is set by htmx when it sends a request and that is going to be set to True when the request is coming from htmx so let's save this file and go back to the terminal and rerun the server and on this page here if we submit some data for example vertigo by Alfred Hitchcock if we submit that you can see the form actually disappears and if we go to the ghost server you can see that we have these messages here the htmx request has been received and we also get the print of true here and that's because the request has been sent by htmx one of the headers in the request is the HX request header and that's what we've extracted here on line 32 and the value for that header is true so this header actually gives us a way to detect when the request is coming from htmx and that may be very useful in particular situations in your application so now we know that when we submit this form it's sending a post request to the add film URL this URL then maps to this particular Handler function and it calls the handle function H2 which then logs these statements to the server now what I'm going to do is remove these two statements and what we're going to do from the request is extract the director and the name from that request so let's create a variable called title and that's going to be equal to the request which has a function called post form value and as you can see that takes a key which is a string and in our case the key is called title and the reason for that is if we go to index.html you can see we Define the input for the title here and it has a name of title so the server can extract that from the request using that name attribute on the HTML if we go back to main.go we have here a title and we're extracting it from the HTML using this post form value function and I'm going to copy that line of code down below and we can also extract the director from the request and that has a name in the HTML of director so we can grab that data that's been submitted and just to make sure this is working before we go on I'm going to print the title and director to the terminal so let's save this and we can rerun our go server here and when we go back to the page if we refresh this page and we submit some data here once that submission occurs if we go back to the server you can see the values that have been submitted are now being printed to the terminal so at this point after receiving this data in our Handler function for the title and the director you might want to then add that data to a database and normal application of course we don't have a database here so we're not going to do that now what we want to do in the context of an htmx request is return some HTML which is then going to be swapped into the Dom so what I'm going to do is remove the two calls to print line and I'm going to create an HTML string here and we're going to hard code some HTML in the go Handler function later on we're going to extract this code out to a template fragment in golang but for now what we're going to do is we're going to use the format dot sprintf function which allows us to create a string based on some formatting and the string that we want to generate and return here is this one here it's a list element for a new film and this is because we're adding a new film to the list so what we want to do is return that new list element so that it can be swapped into the Dom so I'm going to create a string here and paste that in and I'm going to change these to single quotations within the HTML string here and instead of referencing these variables with the curly brackets because we're using the Sprint IF function we can use percentage s which is going to format the data as a string and that data is going to be passed in as an argument here to this function so we're going to pass in the title and the director that we've extracted and what that's going to do is for this first formatting here it's going to place the title that we've extracted to that one and for the second one it's going to place the director and when we use the Sprinter function or the printf function we can use these types of formatting strings these are placeholders for actual values that are coming from a variable for example the title and the director once we have that HTML string what we're going to do is we're going to create a template object we can use the template.new function for that we can give a name to the template in our case we'll just give it a name of T that doesn't really matter and to that new template we can call the dot parse function and that will take text that we want to actually parse here we're going to pass in the HTML string that we've generated on the line above and this is going to return to us both a template and an error and we can discard the error by using the underscore operator we want the template though that's coming back from this function call so we're creating a list element and populating that with the title and the director and then we're passing that HTML string to the Posh function that's available on the template once we've generated the template again we can use the template dot execute function so I'm going to copy that line of code down below and we're going to remove the data we don't need any data here we're simply rendering this list element back and that's already populated with our title and our director now for more complex examples we can render an entire template file and return context to that but this is a very simple example and what we're going to do here is we're going to add a film using this form once that's been submitted it's going to hit that function it's going to generate a new list element and return that and add it into our existing list now this isn't going to completely work at the moment if we go back to the server and stop this server and rebuild once that's up and running again we can try and add vertigo by Alfred Hitchcock when we submit this you can see we get back the list element but it is actually replacing the form in the document it's not being added to the existing list it's replacing the form now we can change this Behavior by specifying an HX Target attribute to the element that's sending the request so let's go back to the template here and within the form element we have the HX post attribute we're now going to add an HX Target attribute here and when we get back that HTML from the server we want to add it to the unordered list that we have up here so I'm going to add an ID to this unordered list and it's going to have an ID of film Dash list and we can then reference this ID within our HX Target for example here by specifying film list now if we go back to our page and refresh the page if we try and add that same film here and submit that you can see that it actually replaces the entire list and that's because we are setting the target to the unordered list tag but we're replacing the entire list and that's because htmx has a default swap mechanism of inner HTML so the Target that we specify is going to take that Target which is the UL tag that you can see in the index.html and it's going to replace the entire inner HTML of that UL tag with the returned HTML from the server now again we can change this Behavior by adding another attribute to the form element in our case we want to add an attribute called HX Swap and we can set that to before end and if we go to the htmx documentation on swapping hdmax offers different ways to swap HTML into the Dom by default it's inner HTML but what we've done is change that to before end and that's going to append the content after the last child inside the target so if we go back to our template here the target is this unordered list the last child within that is going to be the last Ali tag within this range statement and the before end swap mechanism is going to take the returned content and it's going to add it in after that last Li tag so to see this behavior let's again go back to the page and we're going to refresh this page and we're going to try and add vertical to this page when we hit submit you can see that the new film has appeared at the end of the existing unordered list and that's the desired behavior that we want when we add a new film we want it to go to the end of the list so let's try and add a film called vertigo 2. you can see that that has appeared again after the last element in the unordered list so to sum up this Behavior we are sending a post request using this form to the back end that's done via htmx and what happens on the back end is we generate some HTML from the data that's been sent to the server and we return our list element to the front end the client htmx will then take that returned HTML and it's going to swap it into the Dom using the HX Target as the element that we want to swap into and the swap mechanism that we specify so we can build these kind of interactive Pages using htmx and golang and in fact this will work with any server side technology now one thing to note if we submit a form it might take some time before that data is processed by the server and then the HTML is returned to the front end this is particularly true if you're interacting with a database and perhaps entering a lot of data as part of a request response cycle we're going to look at another attribute now in htmx called HX indicator which allows you to specify an element that will have a class added to it for the duration of the request and because you're adding a class to an element for the duration of a request you can then use CSS to actually perform some sort of styling or animations based on that particular class now what I'm going to do is grab a spinner from bootstrap and I've got this page open here I'll leave a link below the video and it's these classes here such as spin on Border that we can add to a button element in order to generate these spanners so what we're going to do is go back to the index.html template here if we scroll down to the button element that's used to submit the form I'm going to replace that with some new HTML and within that button we have a span element that contains our classes to actually show the spinner and it also contains this htmx indicator class if we go back to the htmx documentation or on this you can see that this hdmax indicator class has some CSS styles for example opacity of zero and this means that by default it's invisible it will not be shown on the document but what we're going to do is we're going to set an HX indicator equal to this spinner and that's going to add this htmx request class to the spinner and that's going to change the opacity to one and that's going to use our transition to show that using a small animation I think this is better demonstrated by an example so let's go back to vs code here we also have the ID of spinner on this span and we're going to use that in the formula this is the element that contains all our htmx attributes it triggers the request to the server and it specifies the Target and the swap mechanism I'm going to add another attribute here called HX indicator and we're going to set that equal to the ID of that spinner so just to recap here we have HX indicator set to this span with the ID of spinner this span is hidden by default because it has the class of htmx indicator which sets the opacity to zero but when when the request is in Flight the HX indicator is going to basically add another class to this element here and it's going to set the opacity to 1 for the duration of the request so that's then going to show the spinner and when the request is done we're going to get another transition where the spanner is heading so let's save this file and go back to the page I'm going to try and add another film here and let's see vertigo by Alfred Hitchcock when we submit that we don't really see the spinner because the request is basically processed immediately and the response is returned there's not a lot of latency here because we don't have a real Network this is all done through localhost and we're also not interacting with any external databases or caches or anything like that so what I'm going to do is simulate some latency on the golang server if we go back to main.go and we go to this header to function that handles the htmx request when that request comes in I'm going to simulate some latency with a call to the time.sleep function and I'm going to set that to one second we can do that by using the syntax here one multiplied by time dot second let's now rebuild the go server here and we can go back to the page now when we submit this form you can see the spinner it takes one second for the response to be received the spanner then disappears and the element is swapped into the Dom if you try this with another film such as vertigo 2 you can see the same effect here so HX indicator allows us to display some feedback to the user as well the response is coming back from the server and when the response is received it's going to remove that class that it adds for the duration of the request and that's going to then set the opacity of the given element back to zero so we're nearly done with this video I want to show one more thing and that's template fragments with go if we go back to the server here and look at this particular line of code which generates the HTML string you can see we've hard-coded html text in this function on the server now this is not usually what you want to be doing on a server you don't want to be hard coding HTML and instead of this you can employ a number of different techniques for example we could create a separate template here that just contains the LI tag and we could then use the same technique that we used up here where we use the template.parse files function to actually parse that template and then pass a context to the template with the execute function so we could do that rather than hard coding the HTML in this function but we are only returning an Li tag here we're not returning any other content so to create a template Just For An Li tag might be considered Overkill now a better technique in our example might be to use template fragments in go if we go back to index.html here what we can do within a range statement where the LI tag lives as we can Define another action here in the template and that's called a block action and we can give the block a name in our case we'll call it film list element and then we can put a dot in here and what I'm going to do is tab over the LI tag and then we can end the block using the end statement here so now we have a block called film list the element we can now go back to our go Handler function here and what I'm going to do is remove this call to the HTML string and I'm actually going to remove all the lines of code below the title and director what we can do now is copy the line of code that we had up here down to the new Handler function and that's going to load the index.html template using the template.parsh files function and we're storing that in a variable called template and templates have a function that we can use that is similar to this execute method so I'm going to reference the template and this time we're going to call the execute template function and if we hover over that you can see the signature of that function it takes the rater just like the execute method and the last parameter as you can see is the data which we had before in the execute method as well but we also now have another argument here called name and the name can reference a block name in our template so we're going to see that now we're going to pass the response writer as the first argument the second argument is going to be the name of the block that we've created and that's called the film list element and the third argument is the data we want to pass to the block and in our case we have this data here where we're referencing a title and a director so we need to pass a film struct down that contains a title and a director so let's go back to the execute template function here and we're going to create a film here and populate it with the title that's equal to the title that we've pulled out of the post request on line 33 and also we're going to give a director here and set that equal to the director that we got from the request so this is going to write the contents of this block populated with this data to the response writer W let's save the fail and go back to the terminal and we're going to recompile and execute this program if we now go back to the page and submit this film by Alfred Hitchcock you can see that we get the spanner where the request is in flight and then we get the response and we can see that the film has been added to the list on the left hand side the key difference here if we go back to vs code and we look at our Handler function as that now instead of hard coding some HTML within the function itself we are now parsing the index.html template as we did above and then we are using the execute template function to render this particular block from the template along with this data that we send into that template and that's going to then return that and swap it into the Dom using HDMX so that's all for this video we've seen how to use go and set up an HTTP server along with a couple of Handler functions we've seen how we can render templates using golang and return these as part of a response and we've seen how to use statements in our templates such as this range statement and the block statement to structure our code and render data to the page on the htmx side the things we've seen how to define attributes that we can attach to HTML elements and we've seen how we can use those attributes to send requests on particular actions that happen in the Dom and then we can also swap data that's coming back from the server into the document at specific locations so thank you for watching this has been a new video most of htmx videos I've done before have been with Django and fast API this is the first try at using a language other than python if you're interested in more goal line videos please let me know in the comments and if you've enjoyed the video please like And subscribe to the channel thanks again for watching we'll see you in the next video
Info
Channel: BugBytes
Views: 152,609
Rating: undefined out of 5
Keywords:
Id: F9H6vYelYyU
Channel Id: undefined
Length: 35min 15sec (2115 seconds)
Published: Wed Jun 07 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.