I am using RactiveJS for my templates. I have some nested data like this:
var view = new Ractive({
data: {
items: [
{ size: 1 }
{ size: 3 }
{ size: 4 }
]
}
});
How can I display the sum of item sizes in my template? This depends on the size of each individual item but also on the items array (e.g. items are added/removed).
Here is a fiddle which achieves what you want by using Ractive's computed properties. Would you consider this denormalization of data?
computed: {
sum: function () {
var items = this.get( 'items' );
return items.reduce(function(prev, current) {
return prev + current.size;
}, 0)
}
You can track the sum using an observer. This has the advantage of not having to reiterate the entire array each time a value changes. (see http://jsfiddle.net/tL8ofLtj/):
oninit: function () {
this.observe('items.*.size', function (newValue, oldValue) {
this.add('sum', (newValue||0) - (oldValue||0) );
});
}
I found one solution, but it's not optimal because it denormalizes data in the view.
var view = new Ractive({
data: {
items: [
{ size: 1 }
{ size: 3 }
{ size: 4 }
],
sum: 0
},
oninit: function () {
this.observe('items items.*.size', function () {
this.set('sum', _.reduce(this.get('items'), function (memo, item) {
return memo + item.size;
}, 0));
});
}
});
And then in the template I can just use {{sum}}
Related
I am trying to recreate a real example of my code.
In my real code, this line is actually a component that will fetch an endpoint every few seconds, and fetch a random array of "n" length, myData it will contain these fetch.
<div v-for="item in addingData(myData)"> <!-- in My real code, "myData" should be the answer of an endpoint, is an setInterval, returns data like [{id:1},{id:2}] -->
{{ item.id }}
</div>
I am simulating that the response changes in myData with the help of setTimeOut
mounted() {
setTimeout(() => {
console.log('First data');
this.myData = [{ id: 3 }, { id: 2 }, { id: 1 }];
setTimeout(() => {
console.log('second data');
this.myData = [{ id: 4 }, { id: 4 }];
setTimeout(() => {
console.log('Third data');
this.myData = [];
}, 3000);
}, 3000);
}, 2000);
},
I am trying to make that every time I receive data in myData, the list of the concatenation of the received data is shown without having repeated data. That's why every time I receive data, that calls the function addingData(myData) that will do this data concatenation.
I'm using the function v-for="item in addingData(myData) and auxData is the variable that will do this concatenation.
why when there is new data, the addingData function is called 2 times and how can I prevent it?
in terms of performance this should be the output in the console.log:
what causes this re-rendering and how can I avoid it?
this is my live code:
https://stackblitz.com/edit/vue-l7gdpj?file=src%2FApp.vue
<template>
<div id="app">
<div v-for="item in addingData(myData)">
{{ item.id }}
</div>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue';
export default {
name: 'App',
data() {
return {
myData: [],
auxData: [],
};
},
mounted() {
setTimeout(() => {
console.log('First data');
this.myData = [{ id: 3 }, { id: 2 }, { id: 1 }];
setTimeout(() => {
console.log('second data');
this.myData = [{ id: 4 }, { id: 4 }];
setTimeout(() => {
console.log('Third data');
this.myData = [];
}, 3000);
}, 3000);
}, 2000);
},
methods: {
addingData(getDataFetch) {
console.log('Entering AddingData', getDataFetch);
if (getDataFetch.length !== 0) {
if (this.auxData.length === 0) {
//Adding initial data
this.auxData = getDataFetch;
} else {
//prevent duplicated values
getDataFetch.forEach((item) => {
const isNewItem = this.auxData.find((itemAux) => {
return item.id === itemAux.id;
});
if (!isNewItem) {
//adding new data
this.auxData.unshift(item);
}
});
}
} else {
//if there is not data, return []
return this.auxData;
}
},
},
};
</script>
As per my understanding, You want to combined the unique objects in to an array getting from multiple API calls and show them into the template using v-for. If Yes, You can achieve that by using computed property.
As you are updating the myData every time you are getting response, You can push the unique objects into a separate array and then return that array using a computed property.
Live Demo :
new Vue({
el: '#app',
data: {
combinedData: [],
myData: []
},
mounted() {
setTimeout(() => {
console.log('First data');
this.myData = [{ id: 3 }, { id: 2 }, { id: 1 }];
this.pushData(this.myData)
setTimeout(() => {
console.log('second data');
this.myData = [{ id: 4 }, { id: 4 }];
this.pushData(this.myData)
setTimeout(() => {
console.log('Third data');
this.myData = [];
this.pushData(this.myData)
}, 3000);
}, 3000);
}, 2000);
},
methods: {
pushData(data) {
data.forEach(obj => {
if (!JSON.stringify(this.combinedData).includes(JSON.stringify(obj))) {
this.combinedData.push(obj)
}
});
}
},
computed: {
finalData() {
return this.combinedData
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="item in finalData">
{{ item.id }}
</div>
</div>
in terms of performance this should be the output in the console.log
In terms of performance, you should use as few reactive data as possible, especially if your object has many properties. I would modify auxData directly.
this.addingData([{ id: 3 }, { id: 2 }, { id: 1 }]);
Simplified addingData
addingData(getDataFetch) {
// It's faster to get the id-s first
let itemDict = new Set(this.auxData.map((m) => m.id));
getDataFetch.forEach((item) => {
if (!itemDict.has(item.id)) {
this.auxData.unshift(item);
itemDict.add(item.id);
}
});
},
And iterate over it
<div v-for="item in auxData">
{{ item.id }}
</div>
Also watching object list can also cause performance issues. It should be used on primitive values.
Example on StackBlitz
Looks like you should be using v-for with auxData as that's what you're updating using the result of your API call (myData). As your API sends you new results, use a watcher to run a function whenever a new update is made to then also update auxData
updated stackblitz
watch: {
myData(newData, oldData) {
console.log('Entering AddingData', newData);
if (newData.length !== 0) {
if (this.auxData.length === 0) {
this.auxData = newData;
} else {
newData.forEach((item) => {
const isNewItem = this.auxData.find((itemAux) => {
return item.id === itemAux.id;
});
if (!isNewItem) {
this.auxData.unshift(item);
}
});
}
}
},
},
<div v-for="item in auxData">
{{ item.id }}
</div>
I'm trying to make a quick shopping cart with on existing project.
My list items is already is generated by php and I get work with html elements like that :
const billets = document.querySelectorAll(".card-billet");
var products = [];
billets.forEach(billet => {
products.push({
title: billet.querySelector('.card-billet-title').textContent,
price: billet.dataset.price,
qty: billet.querySelector('select[name="billet_quantity"]').value
});
});
const App = {
data() {
return {
items: products
}
},
watch: {
items: function () {
console.log("watched");
},
},
computed: {
total: function () {
console.log(this.items)
let total = 0.00;
this.items.forEach(item => {
total += (item.price * item.qty);
});
return total;
}
}
}
Vue.createApp(App).mount('#checkoutApp')
This works but only on page load but I'm trying to change the total when my select quantity changeno.
I'm a bit lost to achieve this, should I use watch but on what ? Or anything else ?
Finally I found how to achieve this, the problem was that my array was out of the vue instance so can't be updated.
I simplified the code like this :
const App = {
data() {
return {
items: []
}
},
methods: {
onChange: function (e) {
// console.log(this.items)
this.items = [];
document.querySelectorAll(".card-billet").forEach(billet => {
this.items.push({
title: billet.querySelector('.card-billet-title').textContent,
price: billet.dataset.price,
qty: billet.querySelector('.card-billet-qty-selector').value
});
});
}
},
computed: {
total: function () {
// console.log(this.items)
let total = 0.00;
this.items.forEach(item => {
total += (item.price * item.qty);
});
return total;
}
}
}
Vue.createApp(App).mount('#checkoutApp')
I am using json-rule-engine .
https://www.npmjs.com/package/json-rules-engine
I am having a student list which have name and their percentage, Also I have business rule the percentage should be greater thank or equal to than 70 . so I want to print all students name those have percentage more than 70
here is my code
https://repl.it/repls/AlienatedLostEntropy#index.js
student list
const students = [
{
name:"naveen",
percentage:70
},
{
name:"rajat",
percentage:50
},
{
name:"ravi",
percentage:75
},
{
name:"kaushal",
percentage:64
},
{
name:"piush",
percentage:89
}
]
rule
engine.addRule({
conditions: {
all: [
{
fact: "percentage",
operator: "greaterThanInclusive",
value: 70
}
]
},
onSuccess(){
console.log('on success called')
},
onFailure(){
console.log('on failure called')
},
event: {
type: "message",
params: {
data: "hello-world!"
}
}
});
code
https://repl.it/repls/AlienatedLostEntropy#index.js
any update
The json-rules-engine module takes data in a different format. In your Repl.it you have not defined any facts.
Facts should be:
let facts = [
{
name:"naveen",
percentage:70
},
[...]
Also, the module itself doesn't seem to process an array of facts. You have to adapt it to achieve this. This can be done with:
facts.forEach((fact) => {
engine
.run(fact)
[...]
Finally, the student data is found inside the almanac. You can get these values with: results.almanac.factMap.get('[name|percentage|age|school|etc]').value
Here is the updated Repl.it: https://repl.it/#adelriosantiago/json-rules-example
I might have submitted a completely unrelated answer, but here goes. Since the students object is an array, you could just loop through it and then use an if else statement.
for (let i = 0; i < students.length; i++) {
if (students[i].percentage >= 70) {
console.log(students[i].name);
}
}
Sorry if this is incorrect!
Here is a working example.
Counting success and failed cases
const { Engine } = require("json-rules-engine");
let engine = new Engine();
const students = [
{
name:"naveen",
percentage:70
},
{
name:"rajat",
percentage:50
},
{
name:"ravi",
percentage:75
},
{
name:"kaushal",
percentage:64
},
{
name:"piush",
percentage:89
}
]
engine.addRule({
conditions: {
all: [{
fact: 'percentage',
operator: 'greaterThanInclusive',
value: 70
}]
},
event: { type: 'procedure_result'}
})
let result = {success_count : 0 , failed_count : 0}
engine.on('success', () => result.success_count++)
.on('failure', () => result.failed_count++)
const getResults = function(){
return new Promise((resolve, reject) => {
students.forEach(fact => {
return engine.run(fact)
.then(() => resolve())
})
})
}
getResults().then(() => console.log(result));
I have a table generated using an array of objects. I am having a hard time figuring out how to use computed properties to filter the array of objects. I am using VuE.js. I'm not sure how to properly use the filter() in my computed properties to filter the table.
new Vue({
el:"#app",
data: () => ({
search:'',
programs: [],
editableKeys: ['date', 'company', 'funding', 'funded', 'recruit', 'program'],
}),
created () {
this.getPrograms();
},
methods: {
getPrograms() {
axios.get("https://my-json-server.typicode.com/isogunro/jsondb/Programs").then(response => {
this.programs = response.data.map(program => ({
...program,
isReadOnly: true,
dropDowns: ["Apple","Google"]
}));
}).catch(error => {
console.log(error);
});
},
editItem (program) {
program.isReadOnly = false
},
saveItem (program) {
program.isReadOnly = true
console.log(program)
alert("New Value: "+program.company)
alert("Previous Value: "+program.company)
},
bgColor (program) {
return program.funded === program.funding ? 'yellow' : 'white'
},
formatDate(program){
var formatL = moment.localeData().longDateFormat('L');
var format2digitYear = formatL.replace(/YYYY/g,'YY');
return moment(program.Date).format(format2digitYear);
},
updateField(program){
console.log(program)
alert(program)
}
},
computed: {
searchContents(){
this.programs.filter(this.search === )//??? not sure how to filter correctly
}
}
})
Here's the pen
Computed properties have to return a value, and you can use it as same as data and props. So what you need to do is return the result of the filter. For the case with no search option, you can return raw programs without the filter.
The computed property will be like this:
(if you filter the programs by it's funding attribute.)
computed: {
searchContents(){
if (this.search === '') {
return this.programs
}
return this.programs.filter(x => this.search === x.funding)
}
}
Then you can use that computed property in v-for:
<tr v-for="(program, index) in searchContents">
In knockout JS I want to find out 1st duplicate object from my collection and return that object as modal. I have to check for 1st duplicate object from first array aginst 2nd Array based on my condition. Tried _findWhere & _.Some & _.each nothing worked. Can someone help
Here -- MyMainModal is my Moda which will have multiple objects
self.dupRecord= function (MyMainModal) {
var Modaldata= ko.mapping.toJS(MyMainModal);
return _.some(Modaldata, function (MD1) {
return _.some(Modaldata, function (MD2) {
if ((MD1.ID!== MD2.Id) &&
(MD1.Name === MD2.name));
});
});
};
How about incorporating the check for first duplicate into the mapping? Something like:
function Child(data) {
ko.mapping.fromJS(data, {}, this);
};
var model = {
children: [{
id: '1',
name: 'Billy'
}, {
id: '2',
name: 'Susy'
}]
};
var mapping = {
children: {
key: function(data) {
return ko.utils.unwrapObservable(data.id);
},
create: function(options) {
console.log('creating ' + options.data.name, options.parent);
var newChild = new Child(options.data);
if(options.parent.firstDuplicate() === undefined)
options.parent.children().forEach(function(child) {
if(child.name() === newChild.name())
options.parent.firstDuplicate([child, newChild]);
});
return newChild;
},
update: function(options) {
console.log(' updating ' + options.data.name);
return options.target;
}
}
};
var vm = {
children: ko.observableArray(),
firstDuplicate: ko.observable()
};
ko.mapping.fromJS(model, mapping, vm);
ko.applyBindings(vm);
model.children.push({
id: 3,
name: 'Billy'
});
setTimeout(function() {
console.log('--remapping--');
ko.mapping.fromJS(model, mapping, vm);
}, 2000);
I read that as, "if we're not updating the record, potentially set the first duplicate." Here's a fiddle: http://jsfiddle.net/ge1abt6a/