Is there a way to send more than one parameter to a WinJS.Binding.converter() function? Consider the following data and output:
{ contactName: "Tara Miller", mainNumber: "555-405-6190", alternateNumber: "555-209-1927" },
{ contactName: "Bryan Bond", alternateNumber: "555-574-4270" },
{ contactName: "Jenna Siever", mainNumber: "555-843-8823", alternateNumber: "555-799-5424" },
Here is the HTML. The MyData.chooseBestNumber converter function is used to display either a person's main phone number or the words "no main number" if they don't have a main number:
<div id="listViewTemplate" data-win-control="WinJS.Binding.Template">
<div class="contactCard">
<div data-win-bind="innerText: contactName"></div>
<div data-win-bind="innerText: mainNumber MyData.chooseBestNumber"></div>
</div>
</div>
Here is the JS defining the converter function:
WinJS.Namespace.define("MyData", {
chooseBestNumber: WinJS.Binding.converter(function (mainNumber) {
if (mainNumber) return mainNumber;
else return "no main number";
}),
});
Below is what I'd ultimately like to be able to do...passing more than one parameter into the converter function so that I can return either the main number (if it is defined), the alternate number (as a fallback), or a message (if all else fails):
WinJS.Namespace.define("MyData", {
chooseBestNumber: WinJS.Binding.converter(function (mainNumber, alternateNumber) {
if (mainNumber) return mainNumber;
else if (alternateNumber) return alternateNumber;
else return "no phone numbers";
}),
});
Is it possible to send more than one parameter to a WinJS.Binding.converter() function?
You can actually bind your phone number div to the this keyword which will effectively bind the innerText property of that div to the entire model object. That way in your converter, you'd have access to the whole model.
So your updated code would look like this:
HTML
<div id="listViewTemplate" data-win-control="WinJS.Binding.Template">
<div class="contactCard">
<div data-win-bind="innerText: contactName"></div>
<div data-win-bind="innerText: this MyData.chooseBestNumber"></div>
</div>
</div>
JavaScript Converter
WinJS.Namespace.define("MyData", {
chooseBestNumber: WinJS.Binding.converter(function (model) {
if (model && model.mainNumber) return mainNumber;
else if (model && model.alternateNumber) return alternateNumber;
else return "no main number";
}),
});
Related
<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.
Having problems implementing the locator lookup method depending on its parent in POM
Example of DOM (roughly):
<div class="lessons">
<div [data-test="lesson"]>
<div class="lesson__info">
<div ...>
<h2 [data-test="lessonTitle"]>FirstLesson</h2>
<div class"lesson__data">
<div [data-test="lessonDataButton"]>
<div class"lesson__controls">
<div [data-test="lessonStartButton"]>
<div [data-test="lesson"]>
<div class="lesson__info">
<div ...>
<h2 [data-test="lessonTitle"]>SecondLesson</h2>
<div class"lesson__data">
<div [data-test="lessonDataButton"]>
<div class"lesson__controls">
<div [data-test="lessonStartButton"]>
Example of my POM:
import { Selector, t } from 'testcafe'
class Page {
constructor() {
this.lesson = Selector('[data-test="lesson"]')
this.lessonDataBtn = Selector('[data-test="lessonDataButton"]')
this.lessonStartBtn = Selector('[data-test="lessonStartButton"]')
this.lessonTitle = Selector('[data-test="lessonTitle"]')
}
async getLessonButton(title, lessonButton) {
const titleLocator = this.lessonTitle.withText(title);
const currentLesson = this.lesson.filter((node) => {
return node.contains(titleLocator())
}, { titleLocator });
const buttonSelector = currentLesson.find((node) => {
return node === lessonButton();
}, { lessonButton });
return buttonSelector;
}
In my test I'm trying to click "lessonDataButton" in specific lesson filtered by its "title":
await t.click(await schedule.getLessonButton(testData.lesson.data.title, page.lessonDataBtn))
It works correctly only for first occurrence of "lessonDataBtn" on page, but if I try to find the same button in second lesson - it will be an error:
The specified selector does not match any element in the DOM tree.
> | Selector('[data-test="lesson"]')
| .filter([function])
| .find([function])
I created an example using the code samples you provided and got a different error:
1. The specified selector does not match any element in the DOM tree.
| Selector('[data-test="lesson"]')
| .filter([function])
> | .find([function])
But I believe the case is the same: the lessonButton() call in the filter function of the find method of the currentLesson selector will always return the first node of the set. A straightforward solution is to search for the button directly with the css selector: const buttonSelector = currentLesson.find('[data-test="lessonDataButton"]');. You also can get rid of filter functions completely:
getLessonButton (title) {
return this.lessonTitle.withText(title)
.parent('[data-test="lesson"]')
.find('[data-test="lessonDataButton"]');
}
Here this sortable function is return emplty string
Index.cshtml
<div id="sortable" class="col-lg-9 col-md-9">
#foreach (var chart in Model.Charts)
{
<div class="col-lg-4 col-md-6 hidden" id=#string.Format("chartNumber{0}", chart.ChartOrder)>
<figure class="highcharts-figure1">
<div id=#string.Format("container{0}", chart.ChartOrder)></div>
</figure>
</div>
}
</div>
Javascript
$("#sortable").sortable({
containment: 'parent',
cursor: 'move',
scroll: false,
update: function () {
var order = $("#sortable").sortable('serialize');
console.log(order); // emplty string
}
});
I want that when user update the charts order then new array should be formed so that i can send this array to the Post function so that i can store the sequence of the charts.
But here this sortable function is return empty string, so need help
I want that when user update the charts order then new array should be formed so that I can send this array to the Post function so that I can store the sequence of the charts.
In API documentation of Sortable plugin, we can find:
It works by default by looking at the id of each item in the format "setname_number", and it spits out a hash like "setname[]=number&setname[]=number".
So to achieve your requirement, please modify the code like below.
Set Id of item as setname_number
<div class="col-lg-4 col-md-6" id=#string.Format("setname_{0}", chart.ChartOrder)>
Get new item order
update: function () {
//specify `key` based on your actual requirement
var order = $("#sortable").sortable("serialize", { key: "sort" });
console.log(order);
}
Test Result
I have a v-for loop with vue.js on a SPA and I wonder if it's posible to set a variable at the beginning and then just print it everytime you need it, because right now i'm calling a method everytime i need to print the variable.
This is the JSON data.
{
"likes": ["famiglia", "ridere", "caffè", "cioccolato", "tres leches", "ballare", "cinema"],
"dislikes":["tristezze", "abuso su animali", "ingiustizie", "bugie"]
}
Then I use it in a loop:
<template>
<div class="c-interests__item" v-for="(value, key) in interests" :key="key" :data-key="key" :data-is="getEmotion(key)" >
// NOTE: I need to use the variable like this in different places, and I find myself calling getEmotion(key) everythime, is this the way to go on Vue? or there is another way to set a var and just call it where we need it?
<div :class="['c-card__frontTopBox', 'c-card__frontTopBox--' + getEmotion(key)]" ...
<svgicon :icon="getEmotion(key) ...
</div>
</template>
<script>
import interests from '../assets/json/interests.json'
... More imports
let emotion = ''
export default {
name: 'CInfographicsInterests',
components: {
JSubtitle, svgicon
},
data () {
return {
interests,
emotion
}
},
methods: {
getEmotion (key) {
let emotion = (key === 0) ? 'happy' : 'sad'
return emotion
}
}
}
</script>
// Not relevanty to the question
<style lang='scss'>
.c-interests{...}
</style>
I tried adding a prop like :testy="getEmotion(key)" and then { testy } with no luck...
I tried printing { emotion } directly and it doesn't work
So, there is anyway to acomplish this or should i stick calling the method every time?
Thanks in advance for any help.
It's not a good idea to use methods inside a template for non-user-directed actions (like onClicks). It's especially bad, when it comes to performance, inside loops.
Instead of using a method, you can use a computed variable to store the state like so
computed: {
emotions() {
return this.interests.map((index, key) => key === 0 ? 'happy' : 'sad');
}
}
This will create an array that will return the data you need, so you can use
<div class="c-interests__item"
v-for="(value, key) in interests"
:key="key" />`
which will reduce the amount of times the item gets re-drawn.
I have the following scenario. A form has a few inputs and under some of them there are hints like "you don't have to fill this field" etc. Now I want the regular validation messages to replace those hints if a validation error appears. When the field is valid again the hint doesn't have to show up again (I wouldn't mind if it showed up though).
Is it possible to achieve that using the standard ValidationMessageFor helper?
I guess I could patch something up using JS, since I'm already monitoring the element which contains validation message for class changes (using http://meetselva.github.io/attrchange/), so I can change the color of a whole control group on validation error.
In this case I would just need to show\hide the hint depending on whether the validation error is visible or not.
In the end the solution I used looks like this. I encapsulated each input paired with validation message inside a 'control-group' div. Then I used attrchange to monitor validation span changes.
In View:
<div class="control-group">
#Html.TextBoxFor(model => model.Something)
<p class="help-block">This is a hint.</p>
#Html.ValidationMessageFor(model => model.Something)
</div>
In css:
.control-group.error .help-block
{
display:none;
}
In JS:
function UpdateControlGroupErrorState(valmsg) {
valmsg = $(valmsg);
var controlGroup = valmsg.closest('.control-group');
if (controlGroup.find('.field-validation-error').length > 0) {
if (!controlGroup.hasClass("error")) {
controlGroup.addClass("error");
}
}
else {
if (controlGroup.hasClass("error")) {
controlGroup.removeClass("error");
}
}
}
function SetupGroupValidate(validators) {
validators.each(function () {
UpdateControlGroupErrorState(this);
$(this).attrchange({
trackValues: true,
callback: function (e) {
if (e.attributeName == "class") {
UpdateControlGroupErrorState(this);
}
}
});
});
}
$(function () {
var validators = $('.control-group span[data-valmsg-for]');
SetupGroupValidate(validators);
});