Syntax for data binding with Index - vue.js

Stuck on what is most likely just a syntax issue. I want to replace the '1' in 'play1' with the v-for index.
<tr v-for="index in 5">
<td>{{player1.round1.play1}}</td>
<td>{{player2.round1.play1}}</td>
</tr>
I tried many variations of {{player1.round1.play + index}} with no success.

<tr v-for="index in 5">
<td>{{player1.round1['play'+index]}}</td>
<td>{{player2.round1['play'+index]}}</td>
</tr>
within the double-curlies of the vue template, the content is handled as javascript.
when looking up an object in javascript you can either pass the key using the dot notation or the bracket syntax.
For example, if you have an object such as this:
const objectA = {
objectB: {
objectC: {
}
}
};
you can look up objectC either using dot notation:
objectA.objectB.objectC
or using brackets:
objectA['objectB']['objectC']
note that when you are using brackets, that you have to use a simple type, a number or a string (technically symbols are also accepted, but let's not worry about that right now). The bracket syntax does however allow you to use a variable in order to access an object, like so:
let b='objectB';
let c='C';
objectA[b]['object' + c];
objectA[b][`object${c}`];
knowing this, you can then use that to access the right object inside your vue template like this:
<td>{{player1.round1['play'+index]}}</td>
or, using template literals:
<td>{{player2.round1[`play${index}`]}}</td>

Related

Can I pass a computed property and an object inside a v-bind:class in Vuejs?

I want to understand if I can do something like this, because I am trying but only getting erros... Forgive my bad english.
HTML file would be something like
<p :class="{mycss: isActive}, myComputedProperty" > My text </p>
and the component file would have something like
export default {
data () {
return {
isActive: true
}
},
computed: {
myComputedProperty () {
// do something
}
}
}
class value is an expression. If it doesn't make sense in raw JavaScript, it doesn't make sense there. Here comma operator is used, so the expression evaluates to myComputedProperty, and {mycss: isActive} part is discarded.
The format for combined class value is documented:
:class="[{mycss: isActive}, myComputedProperty]"
Since computed values are involved, defining the whole class object as a computed will result in cleaner template code.
I think the error is in your HTML - the comma is probably the cause. There are lots of ways to format strings, but this is one option:
<p :class="isActive ? 'mycss ' + myComputedProperty : myComputedProperty" > My text </p>

vue does not recover from me specifying a non existing location for v-model

When I have a textarea like
<textarea v-model="foo.abc.text"></textarea>
and either foo or foo.abc does not exist yet then
vue removes either parts of the DOM or is giving me a blank page.
It does never recover.
That alone is annoying, regardless of if I am using a debug version of vue or not.
If I try to use an approach that I have been advised to use earlier like
<textarea v-model="foo?.abc?.text"></textarea>
then I am still out of luck, I presume that I get a "rvalue" using those question marks and what I need rather is a variable location.
How do I, with as little trickery as possible, allow v-model to exist later on even if it doesnt exist now (late binding)?
Just shape your data accordingly and initialize it with empty values:
data(){
return {
foo: {
abc: {
text: ''
}
}
}
}
You can later populate it e.g. with the result of api call, but it's still better to initialize data properly
I would suggest going the :value + #input way. It allow more control over the input model, and does not require hiding it.
<textarea :value="!!foo && foo.abc.text" #input="(val) => !!foo && (foo.abc.text = val)" />
You can even hook in a validator:
<textarea
:value="!!foo && foo.abc.text"
#input="(val) => !!foo && (foo.abc.text = val)"
:rules="v => !v && 'The object has not been initialised'"
/>
I found a solution I can live with and then I got a comment in the same direction:
Conditionally showing the textarea.
v-if seems to do it but it falls under the "trickery" category, I think (angularjs would be more relaxed).
<textarea v-if="foo!=null" v-model="foo.abc"></textarea>
The symptom to hiding components if something is not all correct is not the best part of vue.js. Better show them and color them red.

vue.js: How can I create a dynamic key in v-for when key is a date?

I am struggling to figure out how to create a :key value for my v-for loop & I believe that is why the order of my list(s) are random.
This is what my template looks like:
<div v-for="(items, index) in documents">
<h1>{{ index }}</h1> // 2020-06-02
...
<table class="table">
<tr>
<th>Name</th>
</tr>
<tr v-for="(item, index) in items" :key="index">
<td>{{ item.name }}</td> // Foo
</tr>
</table>
</div>
My documents prop looks like this:
{
2020-06-01: ['name': 'Foo', ...],
2020-06-02: ['name': 'Bar', ...],
2020-06-03: ['name': 'Baz', ...],
}
My props are returning the documents in the correct order, but they are not shown on screen in the correct order. For example, I'll see 2020-06-03, 2020-06-01, 2020-06-02 or a different combination. I think it's because I don't have a :key so Vue doesn't know how to order things.
Since I am using the index in my outter loops, I'm not sure how to get a unique key.
What I think I need is something like:
<div v-for="(items, date, index) in documents" :key="index">...</div>
But, that's not really how it works.
I've also tried something like:
<div v-for="(items, index) in documents" :key="`i-${index}`">...</div>
No luck there either.
Thank you for any suggestions!
EDIT
Thank you for your feedback! Here's a few notes.
I'm getting documents from another source. I'm passing it is as props to my component. Currently, I'm using a computed property to try to alter the actual object; maybe add a key or something. No luck just yet...
Just as you've stated, it is coming in as an object. Each key of the object is a date. Each date, then has an array that I'm also looping through.
Here is what documents looks like (also added a screenshot). You can see that the dates are in order. The props are straight from my external service, so things look great so far.
{
2020-06-01: ['name': 'Foo', ...],
2020-06-02: ['name': 'Bar', ...],
2020-06-03: ['name': 'Baz', ...],
}
[
you can go with index2 or whatever else you would like
I tried making index be date just to help keep things straight and yeah.. I tried adding a third argument in the v-for just in case there was an unknown secret I could tap into (sadly no) :)
the format of your prop is messing me up mostly.
Same here, I'm just not able to understand why the order looks good in my props, but when looping through things, its pretty random.
show what you want the table to look like
This is the result I'm trying to accomplish.
<h2>2020-06-01</h2>. // this is the "missing" index (outer loop)
<table>
<tr>
<th>Name</th>
<th>Email</th>
</tr>
<tr> // this is the array that exists within each date
<td>Foo</td>
<td>foo#example.com</td>
</tr>
</table>
Edit/Solution
Thank you so much #Goofballtech! Your answer really helped me think about what I had and directions to try. Here's what I ended up with. sortedDocuments is a computed property; this.documents is a reference to the documents props that is getting passed in.
sortedDocuments() {
const data = this.documents;
return Object.keys(data)
.map((date) => {
return {
date: date,
data: data[date],
}
})
.sort(function (a, b) {
let dateA = new Date(a.date);
let dateB = new Date(b.date);
return dateA - dateB;
})
.reverse();
},
:key is used to watch for changes, not necessarily for order.
2 things.
if you want to maintain a specific order the general solution is to use an array. Order of object (although there is a bit of method to the madness) cannot necessarily be counted upon.
I believe you should used a computed property to modify these as you require then iterate over that array instead of using the object directly for what it seems you are trying to do. Will be back will an edit adding code in a moment.
Edit1 -
General information about indexes in v-for. The word index is not some magic keyword. If you have nested v-for you can do this:
<div v-for="(stuff, index) in stuffing">
display stuff here, index is your key
<div v-for="otherStuff, skdjdfhfyjsksj) in theOtherThings">
skdjdfhfyjsksj is your key value here
</div>
</div>
index is shown in examples to be clear but you can go with index2 or whatever else you would like there. It's getting passed as the second argument and that's where the magic happens.
Back to the base issue...So the format of your prop is messing me up mostly. Sorting the existing entries is straight forward but there really isn't a key:value array in javascript. Those are objects. So where are you getting the documents prop from originally? Is that something you are creating to pass down or is it coming that way from an external source?
sorting of the dates can be done like this in computed, this doesn't include the rest of the data yet as I'm not sure if you need to convert those on the fly or if we can change the prop before it gets here. I changed your document arrays to objects because the code would never show anything with that data formatted as an array as it complained about a missing ] since it did not expect the array key:value formatting.
https://jsfiddle.net/goofballtech/hmr6uw2a/65/
computed: {
sortedDates(){
return Object.keys(this.documents)
.sort((a,b) => a > b ? 1 : 0)
}
},
Final request for this edit, can you show what you want the table to look like? Having that context would help in finding the optimal data structure to achieve the results you need.
Edit2 -
Assuming the object can be an array of objects, and there is potential to have more than one name/email per date array. The below would work.
It all hinges on those funky arrays though.
Might be easier for you to copy the raw return from the API and just go in in to sterilize the data so i can see how the arrays are formatted.
https://jsfiddle.net/goofballtech/hmr6uw2a/141/
<div v-for="(docDate, index) in sortedDates" :key="index">
<h3>
{{docDate.date}}
</h3>
<table class="table">
<tr>
<th>Name</th>
<th>Email</th>
</tr>
<tr v-for="(item, index2) in docDate.data.length" :key="index2">
<td>{{documents[docDate.date][index2].name}}</td>
<td>{{documents[docDate.date][index2].email}}</td>
</tr>
</table>
</div>

Aurelia: Deleting array elements when changed to empty value

I have an array of strings bound to input elements:
<div repeat.for="i of messages.length">
<input type="text" value.bind="$parent.messages[i]">
</div>
I need to delete an element when the input content is deleted, without using dirty-checking.
This sounds easy - just delete the element which has empty value from the input.delegate handler, unfortunately this does not work due to an Aurelia bug #527. Here's a gist that tries this approach: https://gist.run/?id=e49828b236d979450ce54f0006d0fa0a
I tried to work around the bug by using queueTask to postpone deleting the array element, to no avail. And since the devs closed the bug because according to them it is a duplicate of a completely unrelated issue I guess it is not getting fixed anytime soon.
I am out of ideas how to implement this, so any suggestions are welcome.
Absolutely no need for any kind of dirty checking here! :)
Here's a working demo for your scenario: https://gist.run/?id=20d92afa1dd360614147fd381931cb17
$parent isn't needed anymore. It was related to pre-1.0 Aurelia versions.
If you use a variable instead of array indexes, you can leverage two-way data-binding provided by the input.
<template>
<div repeat.for="msg of messages">
<input type="text" value.bind="msg" input.delegate="onMessageChanged(msg, $index)">
</div>
</template>
So, your onChange event could be simplified like this:
msg holds the actual value of your current input.
i index will be used for deletion.
export class App {
messages = ['Alpha','Bravo','Charlie','Delta','Echo'];
onMessageChanged(msg, i){
if (!msg || msg.length === 0) {
this.messages.splice(i, 1);
}
}
}
There was a related question about a similar problem. This answer might give you more details about the main idea.
Ok, so the solution to this is not to use the buggy (in this case) aurelia 2-way binding, but to use 1-way binding and set the value from the input.delegate handler:
https://gist.run/?id=2323c09ec9da989eed21534f177bf5a8
The #marton answer seems to work at first sight, but it actually disables 2-way binding, so any changes to the inputs are not copied to the array. But it gave me an important hint how to solve the issue.
The equivalent of this html code:
<div repeat.for="msg of messages">
<input type="text" value.bind="msg">
</div>
is this:
for (let msg of messages) {
msg = 'something else'; // note: this does not change the contents of the array
}
See issue #444 for more details
Hence, this forces one-way binding. To fix this in the #marton solution, we only have to change the value from the input.delegate handler:
onMessageChanged(msg, i){
if (!msg || msg.length === 0) {
this.messages.splice(i, 1);//delete the element
}
else {
this.messages[i] = msg;//change the value
}
}

How to pass extra variables and parameters to jQote2?

I have a couple Javascript functions which concatenates a URL which I set to the variable c. I then try to pass that variable into jQote2:
$.get("emptyReq.tpl", function(tmpl,c) {
$("#reqs").jqoteapp(tmpl, result, c);
});
In emptyReq.tmpl, I'm doing the following:
<tr id="row">
<td name="id" class="id"><%= this.FormattedID %></td>
<td name="name" class="name"><%= this._refObjectName %></td>
<td name="state" class="state"><%= this.ScheduleState %></td>
<td name="owner" class="owner"></td>
</tr>
I've tried a couple of variations (this.c and c) and I've also tried different variables, but I'm not able to get the URL to display correctly.
c is labeled as undefined in the console, and the URL ends up being something like: http://127.0.0.1/xampp/py2/undefined instead of the actual c which is something like https://rally1.rallydev.com/slm/rally.sp#/2735190513d/detail/userstory/4599269614
Is there a way to pass the parameters properly? Or am I supposed to do the concatenation in the .tmpl file itself?
Here is what I've been using as a reference: jQote Reference.
rishimaharaj,
the jqoteapp method's third paramter is used to change the template tag (<% ... %> by default) on a per call basis. If you need to pass additional data to your template you have two options:
Make c a global variable (I wouldn't recommend that, though)
Copy c's value to the data parameter (recommended):
Please be aware that the copying needs to take into account of what type your
templating data is, i.e. a single object is handled different from an array of
objects!
$.get("emptyReq.tpl", function(tmpl,c) {
var data;
// 'result' seems to be a global var, thus making a copy is a good idea.
// Copying needs to take into account the type of 'result'
if ( Object.prototype.toString(result) === '[object Array]' ) {
data = result.slice(0);
} else {
data = [$.extend({}, result)];
}
// Now it is safe to add 'c' to the wrapping array. This way
// we won't override any homonymous property
data.c = c;
// Call the processing with our local copy
$("#reqs").jqoteapp(tmpl, data);
});
Once you've changed this, you will be able to access c through the templating
lamda's inherent property data:
<tr id="row">
... <a href="<%= data.c %>"><%= this.FormattedID ...
</tr>
Regards,
aefxx