KnockoutJS: Access index of item in array from within the JavaScript template - indexing

I populate a list from an array using KnockoutJS:
<div data-bind:"foreach: list">
<input type="text" data-bind="value: myText" />
</div>
function ViewModel() {
self.list = ko.observableArray([
new listItem("sample text")
]);
};
function listItem (text) {
this.myText = text;
};
I can assign an id to the individual instances of my input like so
<input data-bind="attr: { id: $index } ...
How do I access this index from within my listItem function? I want to be able to do something like
function listItem (text) {
this.myText = text;
this.index = $index;
};
in order to use this for further processing.

You could create a custom binding that sets your property to the index, it would look something like:
ko.bindingHandlers.setIndex = {
init: function(element, valueAccessor, allBindings, data, context) {
var prop = valueAccessor();
data[prop] = context.$index;
}
};
This assumes that you are dealing with objects in your array. You would use it like:
<ul data-bind="foreach: items">
<li data-bind="setIndex: 'myIndex', text: name"></li>
</ul>
So, this copies the $index observable on to your object with the property name that you specify. Sample: http://jsfiddle.net/rniemeyer/zGmcg/
Another way that you can do this outside of bindings (this is how I used to do it before $index) is to subscribe to changes to the observableArray and repopulate an index each time.
Here is what an extension to an observableArray might look like:
//track an index on items in an observableArray
ko.observableArray.fn.indexed = function(prop) {
prop = prop || 'index';
//whenever the array changes, make one loop to update the index on each
this.subscribe(function(newValue) {
if (newValue) {
var item;
for (var i = 0, j = newValue.length; i < j; i++) {
item = newValue[i];
if (!ko.isObservable(item[prop])) {
item[prop] = ko.observable();
}
item[prop](i);
}
}
});
//initialize the index
this.valueHasMutated();
return this;
};
You would then use it like:
this.myItems = ko.observableArray().indexed('myIndexProp');
Here is a sample: http://jsfiddle.net/rniemeyer/bQD2C/

Related

Can't return empty value to input box on vue

I want made a validation for input to be number only, whenever someone input a string, the input box will be cleared.
First, I made a method function on $event like this (ps. I use props)
<BaseInput
:value="nama"
#update="nama = ruled($event)"
label="Nama"
type="type"
/>
and this is the method, I use RegExp to check if the $event value is number. When it's false then I return $event value to empty string.
ruled(event) {
console.log(event)
var intRegex = new RegExp(/[0-9]/);
var data = intRegex.test(event)
if(!data) {
alert("Value Must Number")
event = ""
console.log('masuk if' + data)
}
return event
}
but it didn't clear the input box, anyone know why it happened ?
As Creative Learner said, I must clearing input box in child component only, so I did this on my component child
<template>
<input
:value="value"
:placeholder="label"
#input="$emit('update', ruled($event))"
/>
</template>
And this is the methods:
methods: {
ruled(event) {
//console.log(event)
var val = event.target.value
if(event.target.type == "number"){
var intRegex = new RegExp(/[0-9]/);
var intdata = intRegex.test(val)
if(intdata == false) {
error = "Value must contain number"
//alert("Value must contain number")
return event.target.value = ""
}
}
return val
}
}
great thanks to Creative Learner who made me understand
Suggestions :
Instead of #update you have to use #keypress or #change.
You can use v-model for two-way data binding.
Working Demo :
new Vue({
el: '#app',
data: {
nama: ''
},
methods: {
ruled(event) {
var intRegex = new RegExp(/^\d+$/);
var data = intRegex.test(this.nama);
if (!data) {
alert("Value must contain number");
this.nama = "";
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input
v-model="nama"
#change="ruled($event)"
label="Nama"
type="text"
/>
</div>

How to maintain original array sort order with Vue.js

I'm using v-for with Vue.js (v2.6.12) to display entries in an object e.g.
{
"12345": {
name: "foo",
isAccepted: true,
},
"56789": {
name: "bar",
isAccepted: false,
}
}
HTML:
<div v-for="item in sortMyItems(items)" v-bind:key="item.id">
<span>{{ item.name }}</span>
<span>{{ item.isAccepted }}</span>
</div>
Sort method in VM:
methods: {
sortMyItems: function(items) {
var accepted = [];
var rejected = [];
for (var id in items) {
var item = items[id];
if (item.isAccepted) {
accepted.push(item);
} else {
rejected.push(item);
}
}
return accepted.concat(rejected);
}
}
It's important to me to maintain the object structure of items in the model, which is why I'm doing it this way. The problem I have is that when the isAccepted property of any of the items in my data structure change, Vue re-renders the items that the sort order reflects the new ordering. I understand that this is a very useful feature of Vue, but in my case I really don't want this to happen. I want the sort order to be maintained the way it is after sortMyItems is first called. Is there a way to tell Vue to not monitor changes or just not re-render e.g. v-once
As far as I understood your question:
You could call sortMyItems(items) in the created Lifecycle Hook and store the result in a property of data.
Then, you can iterate over that property in your v-for:
export default {
data() {
return {
sortedData: [];
}
},
created() {
this.sortedData = sortMyItems(items)
}
}

How to Call Function on Each dom-repeat Element

I am trying to call a function on each element inside of a dom-repeat template.
<dom-repeat items="[[cart]]" as="entry">
<template>
<shop-cart-item id="item"></shop-cart-item>
</template>
</dom-repeat>
...
checkStatus() {
this.$.item.doSomething();
}
How can I call doSomething on each element?
You can iterate through nodes like:
checkStatus() {
const forEach = f => x => Array.prototype.forEach.call(x, f);
forEach((item) => {
if(item.id == 'cartItem') {
console.log(item);
item.doSomething(); // call function on item
}
})(this.$.cartItems.childNodes)
}
You can add an on-tap event in the loop. In order to observe which item you clicked look into model property :
<dom-repeat items="[[cart]]" as="entry">
<template>
<!-- need to give dynamic id for each item in dome-repeat -->
<shop-cart-item id="[[index]]" on-tap = 'checkStatus'></shop-cart-item>
</template>
</dom-repeat>
...
checkStatus(status) {
console.log(status.model) // you can get index number or entry's properties.
this.$.item.doSomething();
}
EDIT:
So as per comment of #Matthew, if need to call a function in one of element's function in dom-repeat first give a dynamic id name as above then:
checkStatus(status) {
this.shadowRoot.querySelector('#'+ status.model.index).doSomething();
}

Vuejs Updating v-for by reference

So I have a simple v-for and each item in the v-for has a #click on it
<result-index>
<li v-for="(result, index) in results" #click="getReult(results[index])">
{{ result.name }}
</li>
</result-index>
Now, my method for getResult just assigns that result to a result data:
methods: {
getResult: function(result) {
// when the child <result-update> updates this, it updates fine, but it doesn't update the v-for reference of this.
this.result = result;
}
}
Now I have another component that get the data for that result and display it:
<result-index>
<li v-for="(result, index) in results" #click="getReult(results[index])">
{{ result.name }}
</li>
<result-update v-if="result" v-model="result">
//... here is a form to access the result and update it
</result-update>
</result-index>
In my result-update I am updating via the index and value like so:
methods: {
update(e) {
this.$emit("input", //data here...);
},
}
watch: {
value: function() {
this.form = this.value;
},
},
created() {
this.form = __.cloneDeep(this.value);
}
Which update the parent result fine (the one we used the #click on), but not the v-for reference of that result, so how can I update the v-for reference of the result when it changes in , also please note, it is not possible for me to put the inside the v-for due to the css design of this, it needs to be seperate from the ...
When this.result = result, this.result points to one address of the memory.
When <result-update v-if="result" v-model="result"> receives input event then assign new value to this.result, it will make this.result = newValue (actually point to another address of the memory for newValue), so it will not change the value for result[index] as you expected.
Check below demo:
const test = {result: []}
let value1 = ['a']
console.log('org', test)
test.result = value1 // assign with new array
console.log('Replace the whole array', test)
value1[0] = 'b' // assign new value to first element of value1
console.log('Update one item of the array', test) //test.result and value1 point to same address of the memory
The solution:
You can save the index for current <result-index>, then change the value by this.results[index].
So adjust your codes to below then should work fine.
For the template of component <result-index>, change it to:
<result-index>
<li v-for="(result, index) in results" #click="getReult(index)">
{{ result.name }}
</li>
</result-index>
For the method=getResult inside component <result-index>, change it to:
methods: {
getResult: function(index) {
this.selected = index;
}
}
Inside the parent component, change the template to:
<result-update v-if="selected >= 0" v-model="results[selected]">
//... here is a form to access the result and update it
</result-update>

How to use domProps in render function?

here is a custom select component, it works, but I just can not understand some part of the code,
jsFiddle
Vue.component("myselect", {
props: ['option'],
render: function (createElement) {
var self = this
var items = []
for (var i = 0; i < 16; i++) {
items.push(createElement('option', { attrs: { value: i } }, i))
}
return createElement('select', {
domProps: { value: self.option.value }, // v-bind:value = this binds the default value
on: {
input: function (event) {
console.log(event.target.value)
}
}
}, items)
}
})
this sets the default value of select to option.value, is it <select value='2'>, but the html select tag uses <option selected>, looks like magic to me.
domProps refers to element properties, not attributes.
Think of it as something like this...
document.getElementById('mySelect').value = 'Two'
<select id="mySelect">
<option>One</option>
<option>Two</option>
<option>Three</option>
<option>Four</option>
</select>
When you set the value property on a select element, it selects the option with the corresponding value (at least in Firefox and Chrome).