handle errors with HTMX - htmx

<form
class="" id="form" hx-post="/add/" hx-swap="afterbegin" hx-target="#big_list" hx-trigger="submit">
<input type="text" name="langue1" >
<input type="text" name="langue2">
<div id="errors"></div>
<button type="submit">GO</button>
</form>
<div id="big_list">
.....
</div>
I have a big list in #big_list, and I want my #form appends only one row when submitted.
How with htmx, can I handle errors and show message in #errors ?

I created this solution so you can use hx-target-error = to define which HTML will be displayed after a failed request
document.body.addEventListener('htmx:afterRequest', function (evt) {
const targetError = evt.target.attributes.getNamedItem('hx-target-error')
if (evt.detail.failed && targetError) {
document.getElementById(targetError.value).style.display = "inline";
}
});
document.body.addEventListener('htmx:beforeRequest', function (evt) {
const targetError = evt.target.attributes.getNamedItem('hx-target-error')
if (targetError) {
document.getElementById(targetError.value).style.display = "none";
}
});

If your code raises the errors (validation?), you can change target and swap behavior with response headers.
Response.Headers.Add("HX-Retarget", "#errors");
Response.Headers.Add("HX-Reswap", "innerHTML");
If you want to return a status other than 200, you have to tell htmx to accept it.
4xx would normally not do a swap in htmx. In case of validation errors you could use 422.
document.body.addEventListener('htmx:beforeOnLoad', function (evt) {
if (evt.detail.xhr.status === 422) {
evt.detail.shouldSwap = true;
evt.detail.isError = false;
}
});
It works in htmx 1.8.
If you want to remove the error message on then next sucessfull request, you could use hx-swap-oob. Out of band elements must be in the top level of the response.
So the response could look like this:
<div>
your new row data...
</div>
<div id="errors" hx-swap-oob="true"></div>
Update
You can now use the new powerful extension multi-swap to swap multiple elements arbitrarily placed and nested in the DOM tree.
See https://htmx.org/extensions/multi-swap/

Although it doesn't follow REST principles, you might consider using an swap-oob to report your error back to your user. For example, your request might return a (slightly misleading) status 200, but include content like this:
<div id="errors" hx-swap-oob="true">
There was an error processing your request...
</div>
If it's important to follow REST more precisely, then you'll want to listen to the htmx:responseError event, as mentioned by #guettli in his previous answer.

Related

In Razor Pages, how can I preserve the state of a checkbox which is not part of a form?

I have page that has checkbox that is used to expand/collapse some part of the page. This is client-side logic done in JavaScript.
I want to preserve the state of this checkbox for this particular page. Can Razor Pages do this automatically?
I tried by adding bool property with [BindProperty(SupportsGet = true)] in PageModel but it doesn't work - when I check the checkbox and reload (HTTP GET) the checkbox is always false.
Guessing that this toggle feature is user-specific, and that you want to persist their choice over a number of HTTP requests, I recommend setting a cookie using client-side code, which is user- or more accurately device-specific and can persist for as long as you need, and can be read on the server too.
https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie
https://www.learnrazorpages.com/razor-pages/cookies
I want to preserve the state of this checkbox for this particular page. Can Razor Pages do this automatically?
No, since you don't send it to the backend it will not show it.
As Mike said, it better we could store it inside the client cookie or storage.
More details, you could refer to below codes:
<p>
<input type="checkbox" id="cbox1" checked="checked">
<label >This is the first checkbox</label>
</p>
#section scripts{
<script>
$(function(){
var status = getValue();
if(status === "true"){
$("#cbox1").attr("checked","checked");
}else{
$("#cbox1").removeAttr("checked");
}
})
$("#cbox1").click(function(){
var re = $("#cbox1").is(":checked")
alert(re);
createItem(re);
});
function createItem(value) {
localStorage.setItem('status', value);
}
function getValue() {
return localStorage.getItem('status');
} // Gets the value of 'nameOfItem' and returns it
console.log(getValue()); //'value';
</script>
}

Calling a function not show anything

have a vue file, i call a function but not show nothing, when i call the same function in a button and a console log, the result is correct.
<div v-if="this.aux==1" >
Gama materiales/Productos/<a class="ruta" id="cedi1">{{this.sucursal()}}</a>
<a id="cedi" class="rutan" v-if="this.id_padre!=0" v-once v-bind:href="'/productos?'+'regreso='+0+'&suc='+this.url">{{this.sucursal()}}</a>
<a class="ruta" v-if="this.id_padre!=0" >/{{this.subcategoria()}}</a> <--- this is the function
</div>
i tried remove the v-if and same, this is the function
subcategoria(){
let me=this;
axios
.get("/catalogo/subcategoria/"+this.id_padre)
.then((response) => {
this.allData = response.data;
console.log('datos', this.allData[0].nombre);
});
return this.allData[0].nombre;
},
this are the result of console.log
The last text "datos cemento" is the result of the function
and this is the result
enter image description here
Try something like
<a #click="subcategoria">click me</a>
But you should know that doing that kind of thing is not recommended. A link is to move to a page. A button is used to make actions.

Load partial view through controller on Search button click

I am working on an ASP.NET Core 2.1 MVC app using razor. I have searchQuery.cshtml and a (individually working perfectly) viewQuery.cshtml pages. In my searchQuery page, I let user enter queryId and on clicking "Search" button I want to run the action of ViewQuery that displays the results in viewQuery.cshtml and show the viewQuery below the search button area.
I am not good working with Ajax or so. On Search btn click, I call the viewQuery Get action thru ajax. In the button click, I pass the entered queryId of type int. But, when I load searchQuery page, it throws null exception for passing the queryId. I searched few hous, but didn't get any solution.
searchQuery.cshtml UPDATED
<div>
<div class="col-md-6">
<dl class="dl-horizontal">
<dt>
#Html.DisplayNameFor(model => model.QueryId)
</dt>
<dd>
<input asp-for="QueryId" class="form-control" />
</dd>
</dl>
</div>
<input type="submit" value="Show" />
<!-- CHANGE IN CALL -->
Search
</div>
<div class="modal fade" id="myModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
×
<h3 class="modal-title">Query Answer</h3>
</div>
<div class="modal-body" id="myModalBodyDiv">
</div>
<div class="modal-footer">
Ok
</div>
</div>
</div>
</div>
<script>
function ShowResult() {
// Retrieve queryId
var queryId = $("#QueryId").val();
// DisplayS PROPERLY
alert("Entered ID " + queryId);
// TRIED '/query/viewQuery' ALSO
$.ajax({
type: 'GET',
url: '../query/viewQuery',
data: { queryId: queryId },
success: function (response) {
alert(response); // **DISPLAYS [Object: object]**
$("#myModalBodyDiv").html(response);
$('#myModal').modal("show");
}, error: function (response) {
alert("Error: " + response);
}
});
}
</script>
My ViewQuery action in controller UPDATED
[Route("[controller]/viewQuery/{queryId:int}")]
public async Task<IActionResult> ViewQuery(int queryId)
{
// Retrieve Data from api using HttpClient
....
return PartialView("ViewQuery", qVM); // PartialView(qVM); //View(qVM);
}
Search Query Action UPDATED
[Route("searchQuery")] // customer/searchQuery
public IActionResult SearchQuery()
{
return View();
}
Can anyone please help me how do I achieve my goal. Simple - a text box were user enters queryId. A button on click, want to pass the entered queryId, call a GET action on controller and get the response. Finally show the response below the search button. I was just trying with the above modal dialog, I prefer text and not dialog.
Try & isolate the issue.
Instead of using model.QueryId in the searchQuery.cshtml, simply hardcode any reference to "modelid" - that way at least you are eliminating the possibility that Model is null on that page. Then instead of onclick="ShowResult(#Model.QueryId)"> , hard code some known id instead of #Model.QueryId. Then debug to see if your ViewQuery action method id hit. If the method is hit, then you can take it from there.
Also, I noticed that your jquery calls may need to be modified:
Instead of: $('myModalBodyDiv').html(response); it should probably be $('#myModalBodyDiv').html(response); (the "#" is missing ..) - same for $('myModal').
You can use Partial Pages(ViewQuery page) , in your searchQuery page , you could use Ajax to call server side action with parameter ID . On server side , you can query the database with ID and return PartialView with models :
[HttpPost]
public IActionResult Students (StudentFilter filters)
{
List students = Student.GetStudents(filters);
return PartialView("_Students", students);
}
Then in success callback function of Ajax , you can load the html of partial view to related area in page using jQuery :
success: function (result) {
$("#searchResultsGrid").html(result);
}
You can click here and here for code sample if using MVC template . And here is code sample if using Razor Pages .

Render form after method completion in VueJS

I am facing a problem with my page with VueJS. It's a page for different translations of the website. It has a dropdown on the top for the language selection that once switched will update the fields with the current language.
The problem starts when it loads, because my form is like this:
<form id="trForm">
...
<input type="text" name="header_title" class="form-control" v-model="translations.header.header_title" />
...
</form>
It's trying to access these attributes before the method returns any data, but somehow it will still show the data once it is complete, but it becomes troublesome when I try to switch the language, it won't because of this problem and also, if I do the following:
<form id="trForm">
...
<input type="text" name="header_title" v-if="translations.header" class="form-control" v-model="translations.header.header_title" />
...
</form>
on each field, those that aren't populated will display no field at all for a new input value. I tried something like translations.features || '', but no success.
I also tried to put on the parent block a condition that if the loading is false will display the form, but since the page is loaded first than the method is executed, it will always be false for the first microsecond.
methods: {
fetchTranslations(e) {
let vm = this;
vm.loaded = false;
$.get('/ajax/admin/translations', { 'locale': e }).done((data) => {
if (data.success) {
vm.translations = JSON.parse(data.translations.translation);
vm.loaded = true;
} else {
toastr.error('Something went wrong');
}
});
},
Please, what do I do? It'd be good to show the form after there is data.
Introduce a new variable, e.g. loaded that defaults to false
Use this variable as a v-if condition on the form
In the callback of your data fetch, set loaded to true.

dynamic change doesn't work on vuejs input

i'm working on a vue file and have a form :
<div class="input-group">
<span class="input-group-addon">Montant</span>
<input type="number" class="form-control" v-model="amount" v-bind:value="pattern['value']"]>
</div>
my tab pattern is loaded like that :
var request = $.ajax({
url: '{{ path ('home') }}promos/pattern/'+value,
})
request.success(function(data){
if(data['pattern']==='yes'){
this.pattern=data[0];
alert(this.pattern['value']);
}
})
and my instance :
var instance = new Vue({
el: "#General",
data: {
[...]
pattern: []
}
and the request is made evertyime i do 'action a'. I have the right alert with the value i want everytime i do 'action a' but the input stays at 0 and won't dynamically change.
Something is wrong with your code. Firstly, let's look at your ajax request:
request.success(function(data){
if(data['pattern']==='yes'){
this.pattern=data[0];
alert(this.pattern['value']);
}
})
What is the form of your data response? Because you are checking something with data['pattern'], and then you are trying to associate to this.pattern something that you call data[0]
Then, as stated in #thanksd answer, you are referencing a wrong this in your ajax callback, you need to create a self variable:
var self = this
var request = $.ajax({
url: '{{ path ('home') }}promos/pattern/'+value,
})
request.success(function(data){
if(data['pattern']==='yes'){
self.pattern=data[0];
alert(this.pattern['value']);
}
})
Finally, you write:
<input type="number" class="form-control" v-model="amount" v-bind:value="pattern['value']"]>
So there are a few mistakes here. Firstly, you have a ] at the end of the line that has nothing to do here.
Secondly, you are using v-bind:value, this is not something that is going to be responsive. If you want this input to be responsive, you should use v-model and set the value of amount when you want to change the input value.
Hope this helps
Three things:
The this in your success handler is not referencing the Vue instance. You need to set a reference outside the scope of the handler and use that instead.
You can't chain a success callback to jQuery's ajax method in the first place. It's defined as a property in the parameter object passed to the call. (Maybe you copied code over wrong?)
You need to get rid of v-model="amount" if you want the input's value to reflect the value bound by v-bind:value="pattern"
Your code should look like this:
let self = this; // set a reference to the Vue instance outside the callback scope
var request = $.ajax({
url: '{{ path ('home') }}promos/pattern/'+value,
success: function(data) { // success handler should go in the parameter object
if (data['pattern']==='yes') {
self.pattern=data[0];
alert(this.pattern['value']);
}
}
})