This question already has answers here:
blazor variable argument passing to onclick function
(3 answers)
Closed 6 months ago.
I'm creating an application with Blazor where I like to have a checkbox group. I have a list of options to display and I use a simple for for it
#for (int i = 0; i < Choices?.Count(); i++)
{
<div class="checkbox-option">
<input type="checkbox" id="Name" name="Name" value="#(i + 1)"
#onchange="eventArgs => { CheckChanged(i, eventArgs.Value); } ">
<label for="#($"{ElementData.Name}-{i}")">#Choices[i]</label>
</div>
}
#code {
void CheckChanged(int index, object checkValue)
{
var i = item;
}
}
I expect in the index variable to have a different value for each option. I was surprised when I started to debug and the parameter index has always the same value and it is the number of Choices.
I can't understand where the problem is.
Update
If I change the code introducing a variable where to store the number i it is working as expected
#for (int i = 0; i < Choices?.Count(); i++)
{
var rtn = i;
<div class="checkbox-option">
<input type="checkbox" id="Name" name="Name" value="#(i + 1)"
#onchange="eventArgs => { CheckChanged(rtn, eventArgs.Value); } ">
<label for="#($"{ElementData.Name}-{i}")">#Choices[i]</label>
</div>
}
#code {
void CheckChanged(int index, object checkValue)
{
var i = item;
}
}
You are missing a vital piece of knowledge.
Your code produces a RenderFragment, it doesn't actually render the component. Your component just stacks the RenderFragment onto the Renderer's queue. The Renderer services that queue when it gets thread time and updates the UI.
See this answer (and several others) for an explanation - For loop not returning expected value - C# - Blazor
Normal practice is to do what you've done in your second code block, or just use a foreach.
Related
"Error CS0103 The name '__builder' does not exist in the current context"
Here is my code that causes the error:
#code {
public void Calc()
{
for (int i = 0; i < 55; )
{
<div class="alert-info">
<h3>This is the number" #i</h3>
</div>
}
}
}
You can't render HTML in #code or #functions block. You can do this in a #{ } code bock.
As per ltwlf's answer, HTML markup is not allowed within the #Code block. I received the same error, but I was positive that I had no HTML within my #code block. However, after careful review I noted that there were HTML comment tags <!-- ... --> inside my #code block. This displays correctly as green comments and is easily missed - Therefore carefully review your #code block for any HTML markup if you get this error.
The correct answer is at this SO link.
In short you cannot mix markup with C# code inside #code block.
Consider make a component for reusing the markup.
Members of the component class are defined in one or more #code blocks. In #code blocks.
Component members are used in rendering logic using C# expressions that start with the # symbol. For example, a C# field is rendered by prefixing # to the field name.(more info)
The RenderFragment delegate must accept a parameter called __builder of type RenderTreeBuilder so that the Razor compiler can produce rendering instructions for the fragment.
Sample1:
#using Microsoft.AspNetCore.Components.Rendering
#{ load1(__builder); }
#code {
void load1(RenderTreeBuilder __builder)
{
#for (int i = 0; i < 1; i++)
{
<div class="alert-info">
<h3>This is the number #i</h3>
</div>
}
}
}
Sample2:
<button class="btn btn-success" #onclick="()=>Calc()">Action</button>
#((MarkupString)myMarkup)
#code {
string myMarkup = "";
void Calc()
{
#for (int j = 0; j < 55; j++)
{
myMarkup += getMarkup(j);
}
}
string getMarkup(int j) {
var myMarkup = "<div class='alert-info'>";
myMarkup += $"<h3>This is the number {j}</h3>";
myMarkup += "</div>";
return myMarkup;
}
}
In order to use the "onclick" within a razor page you must prefix it with the # symbol.
Event Handling
I have an array of names, and if a user try to update one of them and is duplicate, I want to do something (error message) The problem is that is always duplicate. Pname will be changed on every keypress. I am not sure how to store the initial array and to compare with it.
<input
v-model="Pname"
type="text"
class="form-control"
/>
for(let element of this.customer_names){
if(this.Pname == element.name){
duplicateValue = +1;
}
}
You can do something as simple as:
if(this.customer_names.indexOf(this.Pname) != -1) {
// there is a duplicate somewhere
}
Put that code in your change/key-up event listener
You can use #blur like this:
<input
v-model="Pname"
type="text"
class="form-control"
#blur="findDuplicate"
>
function findDuplicate () {
if(this.customer_names.indexOf(this.Pname) != -1) {
// There is a duplicate
}
}
So, by this when you click outside, after you are done with typing, it will run that findDuplicate function.
I'm developing an app that has different license types, and according to the license we need to disable/enable inputs.
One way is to put a conditional :disabled for each input but that's a lot of work and error prone, since we might forget to put it on some inputs.
I thought of using a directive like v-disable-all that searches for all inputs under the container and adds disabled to them.
I was wandering if there is a better solution or if there is already a solution like this?
I ended up creating this directive:
import Vue from "vue";
Vue.directive("disable-all", {
// When all the children of the parent component have been updated
componentUpdated: function(el, binding) {
if (!binding.value) return;
const tags = ["input", "button", "textarea", "select"];
tags.forEach(tagName => {
const nodes = el.getElementsByTagName(tagName);
for (let i = 0; i < nodes.length; i++) {
nodes[i].disabled = true;
nodes[i].tabIndex = -1;
}
});
}
});
I'am coming a bit late, but there is an attribute on the HTML element called "disabled", which ... disable every input in the tree.
<fieldset :disabled="!canEdit">
...
</fieldset>
canEdit could be a computed property or anything you want.
You can do something like this:
let elems = document.getElementById('parentDiv').getElementsByTagName('input');
This will give you all the inputs inside a parent, then you can run a simple for loop to loop over them and set each one to disabled.
Something like this:
for(let i = 0; i < elems.length; i++) {
elems[i].disabled = true;
}
Hope this helps set you on the right path.
let elems = document.getElementById('someid').getElementsByTagName('input');
console.log(elems);
for(let i = 0; i < elems.length; i++) {
elems[i].disabled = true;
}
<html>
<body>
<div id="someid">
<input type="text">
<input type="text">
<input type="text">
<input type="text">
<input type="text">
</div>
</body>
</html>
Now you just need to wrap your fields inside <v-form :disabled="variable"></v-form>
I'm having trouble getting Aurelia to iterate over a map where the keys are strings (UUIDs).
Here is an example of the data I'm getting from an API running somewhere else:
my_data = {
"5EI22GER7NE2XLDCPXPT5I2ABE": {
"my_property": "a value"
},
"XWBFODLN6FHGXN3TWF22RBDA7A": {
"my_property": "another value"
}
}
And I'm trying to use something like this:
<template>
<div class="my_class">
<ul class="list-group">
<li repeat.for="[key, value] of my_data" class="list-group-item">
<span>${key} - ${value.my_property}</span>
</li>
</ul>
</div>
</template>
But Aurelia is telling me that Value for 'my_data' is non-repeatable.
I've found various answer by googling, but they have not been clearly explained or incomplete. Either I'm googling wrong or a good SO question and answer is needed.
As another resource to the one supplied by ry8806, I also use a Value Converter:
export class KeysValueConverter {
toView(obj) {
if (obj !== null && typeof obj === 'object') {
return Reflect.ownKeys(obj).filter(x => x !== '__observers__');
} else {
return null;
}
}
}
It can easily be used to do what you're attempting, like this:
<template>
<div class="my_class">
<ul class="list-group">
<li repeat.for="key of my_data | keys" class="list-group-item">
<span>${key} - ${my_data[key]}</span>
</li>
</ul>
</div>
</template>
The easiest method would be to convert this into an array yourself (in the ViewModel code)
Or you could use a ValueConverter inside repeat.for as described in this article Iterating Objects
The code...
// A ValueConverter for iterating an Object's properties inside of a repeat.for in Aurelia
export class ObjectKeysValueConverter {
toView(obj) {
// Create a temporary array to populate with object keys
let temp = [];
// A basic for..in loop to get object properties
// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/for...in
for (let prop in obj) {
if (obj.hasOwnProperty(prop)) {
temp.push(obj[prop]);
}
}
return temp;
}
}
/**
* Usage
* Shows how to use the custom ValueConverter to iterate an objects properties
* aka its keys.
*
* <require from="ObjectKeys"></require>
* <li repeat.for="prop of myVmObject | objectKeys">${prop}</li>
*/
OR, you could use the Aurelia Repeat Strategies provided by an Aurelia Core Team member
You'd have to import the plugin into your app.
Then you'd use it using the pipe syntax in your repeat.for....like so....
<div repeat.for="[key, value] of data | iterable">
${key} ${value.my_property}
</div>
I have an object wich has two arrays of objects, like this:
Interests = {
MostInterests: [],
DistinctInterests: []
};
I also have an input that when is changed, uses a function to search elements in the Interests.DistinctInterests, but It looks like the change.delegate="function()" is taking a long time to trigger.
<input ref="textsearch" change.delegate="searchInterest($event.target.value)" type="text" />
searchInterest(value){
console.log('SEARCH');
this.searchedInterests = [];
var i = 0, j = 0;;
var upperValue = value.toUpperCase();
for(i = 0 ; i < this.Interests.DistinctInterests.length ; i++){
if(this.Interests.DistinctInterests[i].normalizedName.indexOf(upperValue) !=-1){
this.searchedInterests[j] = this.Interests.DistinctInterests[i];
j++;
}
}
console.log('END SEARCH');
}
The goal is update the view with the elements in this.searchedInterests, which contains items that match the searched text.
I don't know if It is an Aurelia problem or a javascript performance. I have aldo tried with $.each() function.
PS: the list contains 50 elements.
The change event is only fired when a change to the element's value is committed by the user.
Think of commited as CTRL+Z steps
This is the reason your function took more time to execute: it just wasn't called.
Instead, by using the input event, your function will get called every time the value changes.
<input ref="textsearch" input.delegate="searchInterest($event.target.value)" type="text" />