Goal: Inline Form Validation
The video introduces real-time validation for a new article form to provide instant feedback while typing. This improves user experience by avoiding unnecessary full form submissions.
Frontend Refactoring and Event Handling
The form is refactored into reusable components, and keydown events are debounced to trigger validation requests. Event bubbling is leveraged to handle input changes at the form level rather than per field.
Validation Endpoint and Backend Setup
A new validation endpoint is created to process form data asynchronously. The backend reuses existing validation logic and returns updated form elements with validation errors using server-sent events (SSE).
Dynamic DOM Patching with DataStar
DataStar is used to patch specific DOM elements based on IDs or selectors. Proper element targeting and patch modes ensure that only intended parts of the form are updated.
Rich Text Editor Reinitialization
The EasyMDE editor breaks after DOM replacement, so it is reinitialized via a globally exposed JavaScript function. Change events are manually dispatched to keep the editor content synchronized with form state.
Switch to Data Binding and JSON Payloads
The form transitions from traditional form submissions to DataStar signals with data-bind attributes. The backend adapts to JSON input, enabling more precise state-driven validation.
Conditional Validation Rules
Validation logic is duplicated at the payload level to allow optional fields during typing. This prevents premature validation errors for empty fields while maintaining stricter validation during final submission.
Create Article Flow with SSE Redirects
The create controller integrates SSE-based validation and conditional redirects. On validation failure, errors are returned and displayed; on success, the user is redirected to the edit page with appropriate feedback.
Next Steps
The same real-time validation and SSE-driven updates will be applied to the edit article page to maintain consistency across the application.
Let's get some form inline validation up and running. And I want to start by focusing on the new article form or page, because right now we only get validation back when it will be submit our request. It would be nice to have some instant feedback on typing out so we don't submit unnecessarily. It's also an experience more users, or most users are used to now.
So to do that, we are going to be taking all of the meat of the form here and say Timbal, article, form, elements. We're going to accept field props. Then we're going to dump everything in there. And then we're simply going to say, article, form, elements, and pass props.
Great. Clean that up a little bit. Now, we want to submit our request every time we start typing into the field and then have our validation rules run. And then return a response, which is going to be this article form elements that showcases any errors that might be. For that, we are going to be needing a new endpoint. So before we finish up the frontend,
implementation here, I'm going to jump into routes and let's create it. Just create it here and say validate article data. You still want the admin prefix and slash validate article data at an equal sign. And then we can go back into our
app in the template file here. Now, since the events from these inputs bubble up, we don't need to specify any event listeners or we don't need to add anything to each element here. We could technically add listeners to the input field, to the text area, and then send a request from there. But since everything bubbles up to the
to a higher little because of JavaScript, we can just define it here. We are gonna be using two data on attributes here because we're gonna doing something a little bit similar as to what we did with Flash messages where we have this data on interval. We now wanna have a state of method post here instead of action.
We will say data on, and then we can use our key down event. And we wanna debounce it by, let's say 500 milliseconds. We can go even lower 200 milliseconds. And we wanna debounce it to the new route that we created. And that was validate.
the import routes, value date, article data. Just refresh the LSP here. Right. So we are now dealing with an action. And if you look up actions here, you need to use the post action and we do it by add post endpoint. Then if you go a little bit further, you can see we have a little bit down. We have this here, content
type so we can send the content type to be form instead of JSON, which is what Datastar will use as a default. So we can basically just do this one change until Datastar to use a form request, and then we don't really need to change anything else. So let's start there. First thing, we need to specify the method. And we are going to say add post.
Then we need to provide the route inside of this parameter, uh, quotations, rotation marks and quotation marks. And then say content type is you need to say content type in brackets. Right. Like this, right? Yes. And then we can say content type.
And now whenever we will press a input field, if I go in here, open my development tools, you can see we submit a quest to an endpoint that doesn't exist yet because you don't have the controller. But this is because the key down event on this input field bubbles up to the form element that then grabs it and sends it to the back end.
You can see it since all the fields. So we can basically validate the complete state of an article. Great. We also need, and I'm just going to grab all of this to specify an unsubmit. And that will basically be the exact same thing as here. We are specifying a post request again. And this is
basically what we want. We have two options here because data style works with IDs on locating what to replace. So if we wrap this component in an ID, so we do something like def, just def ID, article, article elements, close it and just return this from the backend.
Data Star will automatically figure out what to replace because it has this ID here. I don't really wanna break the styling because we need to add basically the same styles as what we have here for it to look the same. So we're gonna be dealing with the selection in the backend. And to do that, we can specify our ID on the form and call it, let's just call it article form.
There we go. And then we can jump into our, oh, I'm jumping in the wrong notes here. Then we can jump into our controller and create the validate article data controller. All right, so I'm in the controller.gov file here and underneath the article form payload, I'm gonna create a new method on the controller struct called validate article data. And accept the ego.
context, and then I'm gonna say pretty much, pretty much what we have here. We're gonna grab the elements out of the request because it's still just form request we are sending. Then we will say error equals to models
And now we need to grab this validate from models that we haven't exported yet. So if we jump into models and change this to be an uppercase, so we export it, then we go into here, save, here and save, simply because I use the LSB to change all the references. Right, now,
We should have access to validate if I restart. And validate needs to be called with struct. We just pass in a models.article data. So we reuse the validation rules we set. And can I grab some, let me grab this. And everything stays the same. Then we specify
Again, we have the validation errors like this. Here, we then say, if error does not equal null, then we try to grab the errors as and say, error validation errors. And if it's not a validation error, we return
error because then something else went wrong. Give that a save. Then we want to grab an instance of the data star new SSE passing the response and request. And then we send the patch element table or we use the patch element
temple helper method on the SSE instance we just created. So we can say two. Let me just close it completely here and return the error. And in here we want to say two article fields props. We pass the payload and we pass the validation errors like this. Close it off.
And why commit this? Because you also need to save views and use a new article form elements, elements component that we just created. Now, if I just return nil here, we should technically be ready. Oh, we need to register the new route as well. So validate and
is called the same here, valid date. Let's try and jump in, give this a save. And now when I start typing, do we not see anything? We are seeing some errors here. No, yes, right. I am forgetting what I just said at the beginning here. So basically what is happening here is that we are trying to send
this component down, but this component does not have an ID, so DataStar does not know what to look for and what to replace. Luckily, they ship with some helper methods, so we can say with selector ID, and we know this is article form, and we can also say DataStar with mode, and then we do with mode inner,
So we replace everything inside of the element with this ID. Now I go back in. I give this a refresh. And you can see here, our validation is working. If we go back, it correctly identifies when the title is valid or not. But that's also two other things. You notice that our editor is gone now.
And if I go down here, give everything a refresh and type here, we get a validation error on the title, even though we haven't submitted anything. And that's because we're using the model layers validation, where we always require title to be present. That is not necessarily what we want right now. We just want to indicate to ourselves if something is valid or not. So in a second, we are going to duplicate, we're going to be
breaking the dry principle and duplicating itself a little bit because we don't want to include JSON tags on the article or we don't want to change the article model. We don't want to change the validation rules there. So we want to add this to the article data payload that we form payload that we have in the controller layer. But first let's fix this editor here being broken. And to fix
The editor, we need to be able to call it to call this function that we are automatically calling when we load this file. So what we want to do is say window init easy mde, set it equal to this function. So now on the window object, this is something that get exposed through JavaScript, we can call this function and reinitialize the editor.
But we also need to do a little bit more work because DataStar is not aware of the value that is inside of this ECMDE editor. It doesn't get exposed in the same way as it does when we just submit the request in a regular way. So we need to say window. ECMDE instance code mirror on. Let me say on.
change, then we do a little arrow function here, close that, then we say text area value equal to window, easy, easy mde instance and then value, and then we say text area, dispatch events, new events, input, and then bubbles,
True. And close that. And now we can access this function to initialize the editor through the window object. And we can also grab out the values that are inside this editor from the text area that we initialize the editor on. Right. Go back into admin.
And then we want to say in the new article here that we're gonna be using the data init. And then we can call JavaScript, just a regular JavaScript and say init, easy mde. Let's do that, jump back, refresh. And I misspelled something in the, what did I misspell?
We have code windowEasyMDE instance. I'm just gonna take it from my notes here. That's probably easier. Let's try again. Go back into admin. EasyMDE. We still have an issue. Okay. That does not make
since what is the issue here? Is it because I'm not completing all of this? So inside this function we say const text area and grab it from this one right here. Yes, then we want to say let current value equal to text
area value, right? And the element becomes the text area. And notice that this is just a function, so we could accept an argument to the function. So we don't only target this article content here if we want to reuse the form. But for our use case right now, this is more than sufficient. Then we can say initial value is current value.
Right, now are we okay? We are still not okay. Amazing. Right, I need to say window, easyMDE instance, equal to new, easyMDE. Come on. Hey, there we go. Now we are good. So we still have that whenever we type in here,
the form is not showing up. That is because we are returning this entire thing that includes the editor. So what we can do is that we can from the controller that we just created right here, we can execute a script on the server side using data style again. So we can say SSE, execute script, window, and then we use the same init, ec, mde,
call the function and that will then do exactly what we're doing here but when we return the response. So let's try again. You can see now we are getting the validation and we are also keeping the editor and we get validation as expected. Great.
we still see that whenever we type into the field, we get this validation on the title, even though we don't text, or even though we are not writing into the title. So let's fix that by duplicating ourselves a little bit. And also, let's start by using data star signals instead of using a form request as we have doing so far. Okay, so we are gonna be
modifying all of our input fields here. So instead of name, we're going to say data bind name and data bind name here. The editor, we also can say data bind name and data bind name here and data bind name here. This all stays the same. Let's remove
this, give that a save, and now whenever we type into the field, we will see our request is still being submitted, but we are no longer submitting a form, a type form, we are sending this text slash event stream. So to proper
Extract the values out. We need to go back into our controller. And then we need to change this to be JSON. Now we give it a refresh. You can still have the validation happening. And we are not auto
Right, so something is a little bit off with the input form. I'll fix that in a second. But now we are still doing the validation. And everything is working as expected. We just need to fix that. This one gets validated even though it shouldn't be validated. Right, to do that, we will add validation to this article form payload here. So we can have this omit
empty value or tag put on each of these. So we don't trigger an unnecessary evaluation, let's call it. So we say value date omit empty. This one is min equals five and max equal 100. Let me just grab all of this, add it here and also add it here.
Add it after the years like this. And the max value here was 255. And what was the, this one was greater than five. And then instead of using the model article data struct, simply pass payload.
And now, if we go in, we should be able to type in excerpt and we see nothing is happening in title because it's empty and it can be empty. It's only when we start typing into title that it correctly updates. And again, if you delete everything, it also doesn't show now because it's an empty field. Right. What we need to do
Next is to deal with the creation. When we create an article, we still want to submit. And there we want to actually apply the validation rules from the model layer, because this is where we actually insert data into the database. But we also still want to be able to correctly update the UI and show some indications of what went wrong. Or if everything went right, we want to redirect to the edit article page and show
flash message to ourselves that we created the article. So let's update the create article controller. All right, so make this work. We need to grab this SSE setup that we have here. You can pass it here. Now we will actually need the article that comes back. And then we wanna say, actually wanna say exactly what we do
up here. So if we have an validation error, we want to return the article form elements with the errors. And we also want to take this execute script call and pass it here. So that is if something goes wrong, we still show the errors and indicate to ourselves what went wrong. But if everything goes right,
We can redirect using SSE, and now we only need to pass the route. And we want to pass it to edit article page, where we need to say strings, re, string, is a string replace, strings replace, strings replace. Where we want to replace the ID param with article ID string and the first
instance. So now if we go back and we refresh, let's create a valid article. So let's say something like how to do integration tests. An article about integration tests. We set this to publish and press save. We should not be allowed to publish the article.
And nothing happens. Right. Why did nothing happen? We are submitting the request. Right. And we are sending it to this one. So let me check router. Create article page is correct. This one goes to the right one. So admin.
Right, we are submitting to validate both times. That's not correct. We just submit to create article. If I do it again, clear this out and press save. It also refers to place, of course. So how to, let's just grab what we had before. Tutorial on how to create integration tests in Golang.
set it to publish, clear this out, press save, and now we get the content cannot be empty when publishing. But if we add some values here, let me just grab this, paste this, and say save, we get redirected to the edit article page that is not really working now, and that's because we need to update it to this new setup.
that we just created. So in the next episode, we will repeat what we just did for the new article page and then apply the same thing to our edit article page.