Related
I am new to Vue and although I could find way around most problems I've encountered, this one has been bugging me for last two days and just cannot find solution. Any help much appreciated, thanks in advance!
I've got following code:
<template>
<header><h1>HomeShop</h1></header>
<main>
<section>
<Item-Card
v-for="(item, index) in items"
:key="index"
:item="item.item"
:check="item.check"
#item-checked="checkClicked(index, item.id, items)"
#item-deleted="deleteClicked(item.id, items)"
/>
<Add-New-Item #item-submited="newItemCard" />
</section>
</main>
<button #click="$log(JSON.stringify(items, null, 1))">Log</button>
</template>
<script setup>
import { reactive, toRefs } from "vue";
import axios from "axios";
import _ from "lodash";
//data + explicit expression
const state = reactive({
items: [],
});
const { items } = toRefs(state);
//fce
const checkClicked = (index, id, items) => {
if (items[index].check === false) {
items[index].check = true;
items[index].checkTime = _.now();
items = _.sortBy(items, ["check", "checkTime"])
console.log(JSON.stringify(items, null, 1));
axios.patch("http://localhost:3000/items/" + id, { check: true });
} else {
items[index].check = false;
delete items[index].checkTime;
axios.patch("http://localhost:3000/items/" + id, { check: false });
}
};
I pass my data object array (items) as argument to checkClicked method. This function changes check status and adds checkTime timestamp based on which I sort the array (using lodash method sortBy). From within the checkClicked method I log the items array with expected correct result:
[
{
"item": "2",
"check": false,
"id": 2
},
{
"item": "3",
"check": false,
"id": 3
},
{
"item": "4",
"check": false,
"id": 4
},
{
"item": "5",
"check": true,
"id": 5,
"checkTime": 1671719753796
},
{
"item": "1",
"check": true,
"id": 1,
"checkTime": 1671719755363
}
]
However, when I log items from outside the method (the custom function $log at template) I get following result:
[
{
"item": "1",
"check": true,
"id": 1,
"checkTime": 1671719755363
},
{
"item": "2",
"check": false,
"id": 2
},
{
"item": "3",
"check": false,
"id": 3
},
{
"item": "4",
"check": false,
"id": 4
},
{
"item": "5",
"check": true,
"id": 5,
"checkTime": 1671719753796
}
]
How do I manipulate the reactive data object array items from inside the function checkClicked? My intention is to sort the data array each time timestamp checkTime is added (that is what the checkClicked function does).
My understanding is that passing the data object array items as argument into function creates separate instance of the array, that is why I am getting two different results while loging the array. However I cannot find solution how to manipulate the real items from inside the function checkClicked.
You are right. You are only changing the parameter variable items in the function. See my other answer for the clarification.
Pay also attention to the following:
When you assign a new value to your reactive proxy object, you can lose the reactivity.
Here it is:
items = _.sortBy(items, ["check", "checkTime"])
I guess, this line could also remove reactivity from the array.
There are ways to fix it, by passing the array through vue reactivity system again.
But, this causes too much work for vue to full recalculate the array items and is not efficient.
My way to provide sorted results is to use a Computed Property.
Like this:
<Item-Card v-for="(item, index) in sortedItems">
and then
const sortedItems = computed(() => {
return _.sortBy(items, ["check", "checkTime"])
})
The advantage of using a computed property is that, it will be recalculated by Vue Reactivity System only when your items array changes.
It looks like you only change the local variable items in the checkClicked() function. I have build a test playground to check it.
You have to change the line items = []; to this.items = []; if you want to change the property, not the parameter variable.
See my second answer for the right sorting solution.
const { ref, reactive, createApp, toRefs } = Vue;
const data = [
{
"item": "1",
"check": true,
"id": 1,
"checkTime": 1671719755363
},
{
"item": "2",
"check": false,
"id": 2
},
{
"item": "3",
"check": false,
"id": 3
}
]
const App = {
methods: {
clear(items) {
items = [];
}
},
setup() {
//data + explicit expression
const state = reactive({
items: data,
});
const { items } = toRefs(state);
return {
items
}
}
}
const app = createApp(App)
app.mount('#app')
<div id="app">
<div v-for="(item, index) in items"
:key="index"
:item="item.item"
:check="item.check"> #{{index}}: {{item.item}}
</div>
<button #click="clear(items)">clear</button>
</div>
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js">
</script>
sending update with solution I got at another forum. I feel dumbed I would not figured that out in two days on my own, whatever. Although advice from Tolbxela can be right track it brings as well more problems so in the end I prefer the more cleaner way:
"I can reference the state declared within . i.e. don’t pass it as a value to the function."
Thats it. Simply not passing the data object array into function, just reference it right away from within the function. So the correct code goes like this:
<template>
<header><h1>HomeShop</h1></header>
<main>
<section>
<Item-Card
v-for="(item, index) in items"
:key="item.id"
:item="item.item"
:check="item.check"
#item-checked="checkClicked(index, item.id)"
#item-deleted="deleteClicked(item.id, items)"
/>
<Add-New-Item #item-submited="newItemCard" />
</section>
</main>
<button #click="$log(JSON.stringify(sortedItems, null, 1))">Log</button>
</template>
<script setup>
import { reactive, toRefs, computed } from "vue";
import axios from "axios";
import _ from "lodash";
//data + explicit expression
const state = reactive({
items: [],
});
const { items } = toRefs(state);
//fce
const checkClicked = (index, id) => {
if (state.items[index].check === false) {
state.items[index].check = true;
state.items[index].checkTime = _.now();
state.items = _.sortBy(state.items, ["check", "checkTime"])
I'm using OpenUI5. Using the formatter.js, I have formatted some text in my view.
But my formatter is called 3 times:
When I bind the model to panel control: oPanel.setModel(oModel, "data");
both sBirthday and sFormat are undefined.
After onInit() is finished and the view is rendered:
sBirthday is valorized correctly and sFormat is undefined
Again: both sBirthday and sFormat ara valorized correctly.
Why does this happen? Is it correct?
The app get an error, because the ageDescription() in the formatter, can't manage undefined values.
formatter.js
sap.ui.define([], function () {
"use strict";
return {
ageDescription : function (sBirthday, sFormat) {
do.something();
var sFromMyBd = moment(sBirthday, sFormat).fromNow();
do.something();
return sAge;
}
}
});
main.view.xml
<mvc:View
controllerName="controller.main"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Panel id="user-panel-id">
<Input id="name-input-id" enabled="false" value="{data>/user/name}" />
<Label text="{i18n>age}: " class="sapUiSmallMargin"/>
<Label text="{
parts: [
{path: 'data>/user/birthday'},
{path: 'data>/user/dateFormat'}
],
formatter: '.formatter.ageDescription' }"/>
</Panel>
</mvc:View>
Main.controller.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/json/JSONModel",
"model/formatter"
], function (Controller, JSONModel, formatter) {
"use strict";
return Controller.extend("controller.main", {
formatter: formatter,
onInit: function () {
var oModel = new JSONModel();
var oView = this.getView();
oModel.loadData("model/data.json");
var oPanel = oView.byId("user-panel-id");
oPanel.setModel(oModel,"data");
do.something();
},
});
});
data.json
{
"user": {
"name": "Frank",
"surname": "Jhonson",
"birthday": "23/03/1988",
"dateFormat": "DD/MM/YYYY",
"enabled": true,
"address": {
"street": "Minnesota street",
"city": "San Francisco",
"zip": "94112",
"country": "California"
}
}
}
Set the model to the view only when the data request is completed:
onInit: function() {
const dataUri = sap.ui.require.toUri("<myNamespace>/model/data.json");
const model = new JSONModel(dataUri);
model.attachEventOnce("requestCompleted", function() {
this.getView().setModel(model);
}, this);
// ...
},
This ensures that the formatter is called only once (invoked by checkUpdate(true) which happens on binding initialization; see below), and no further changes are detected afterwards.
Additionally (or alternatively), make the formatter more defensive. Something like:
function(value1, value2) {
let result = "";
if (value1 && value2) {
// format accordingly ...
}
return result;
}
Why does this happen?
View gets instantiated.
onInit of the Controller gets invoked. Here, the file model/data.json is requested (model is empty).
Upon adding the view to the UI, UI5 propagates existing parent
models to the view.
Bindings within the view are initialized, triggering checkUpdate(/*forceUpdate*/true)src in each one of them.
Due to the forceUpdate flag activated, change event is fired, which forcefully triggers the formatters even if there were no changes at all:
[undefined, undefined] → [undefined, undefined]. - 1st formatter call
Fetching model/data.json is now completed. Now the model needs to checkUpdate again.
[undefined, undefined] → [value1, undefined] → change detected → 2nd formatter call
[value1, undefined] → [value1, value2] → change detected → 3rd formatter call
Now, given that you are trying to load a static JSON file into your project, it's better to maximize the usage of the manifest.json.
That way, you are sure that the data is already loaded and available in the model prior to any binding.
You achieve this by adding the JSON file as a data source under sap.app
manifest.json
"sap.app": {
"id": "com.sample.app",
"type": "application",
"dataSources": {
"data": {
"type": "JSON",
"uri": "model/data.json"
}
}
}
Now, simply add this dataSource called data as one of the models under sap.ui5.
"sap.ui5": {
"rootView": {
"viewName": "com.sample.app.view.App",
"type": "XML"
},
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "com.app.sample.i18n.i18n"
}
},
"data": {
"type": "sap.ui.model.json.JSONModel",
"dataSource": "data"
}
}
}
With this, you DON'T need to call this anymore:
var oModel = new JSONModel();
var oView = this.getView();
oModel.loadData("model/data.json");
var oPanel = oView.byId("user-panel-id");
oPanel.setModel(oModel,"data");
..as the data model we added in the manifest.json, is already visible to oView and oPanel right from the get go.
This way, it doesn't matter if the formatter gets called multiple times, as it would already have the data available to it right from the beginning.
I am trying to implement the List template feature of Alexa skill kit. However, I am unable to return the response in an appropriate format.
I have implemented the feature using the official documentation. However, I am not getting how to return the response of list template to my custom intent
'ListTemplate':function(){
var title = "This is a sample list";
var speechOutput = "Showing the sample list";
var template = {
"type":"Display.RenderTemplate",
"template":{
"type":"ListTemplate1",
"token":"ListTemplate",
"title":title,
"backButton":"VISIBLE",
"backgroundImage":{
"contentDescription":"backgroundImage",
"sources":[
{
"url":"https://democard.s3.amazonaws.com/hostel-720.jpg"
}]
},
"listItems":[{
"token":"item1",
"image":{
"sources":[{
"url":"https://democard.s3.amazonaws.com/c-v-raman-college-of-engineering-squarelogo-1534916004379+(3).jpg"
}],
"contentDescription":"first item of list"
},
"textContent":{
"primaryText":{
"type":"PlainText",
"text":"primary Text is here"
},
"secondaryText":{
"type":"PlainText",
"text":"Secondary text is here"
}
},
},
{
"token":"item2",
"image":{
"sources":[{
"url":"https://democard.s3.amazonaws.com/c-v-raman-college-of-engineering-squarelogo-1534916004379+(3).jpg"
}],
"contentDescription":"second item"
},
"textContent":{
"primaryText":{
"type":"PlainText",
"text":"primary text is here"
},
"secondaryText":{
"type":"PlainText",
"text":"secondary text"
}
}
}
]
}};
var directives =[ template ];
//return build_speechlet_response(title,speechOutput,directives, SESSION_LIST);
// function
build_speechlet_response(title,speechOutput,directives,phase){
const response = {
"version": "1.0",
"response": {
"outputSpeech":{
"type":"PlainText",
"text":"what else would you like to see"
},
"card":{
'type':'Simple',
'title':title,
'content':speechOutput
},
"directives":directives,
"shouldEndSession":'False'
},
"sessionAttributes":{
"template":"list_"
}
};
// return response;
this.emit(':tell',response);
},
The response I should get must be a custom list. But I am not getting it
It looks like this issues is that response is an object. It should be something like this.emit(':tell', speechOutput) (where speechOutput is a string).
If you want to also send a card it's this.emit(':tellWithCard', speechOutput, cardTitle, cardContent, imageObj).
But, since you're trying to use a render template, it would be something like:
this.response.speak(speechOutput)
.cardRenderer(cardTitle, cardContent, cardImage)
.renderTemplate(template);
this.emit(':responseReady');
You can find more info here - https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs/tree/1.x
I do notice you're using v1 of the SDK - I would really recommend using v2 as it's a lot more straight forward.
https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs
Hope that helps.
I tried the following code yet the response was not rendered.
const DisplayListIntentHandler = {
canHandle(handlerInput){
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'DisplayList';
},
handle(handlerInput){
var title = "This is a sample list";
var speechOutput = "Showing the sample list";
var template = {
type:'Display.RenderTemplate',
template:{
type:"ListTemplate1",
token:"ListTemplate",
title:'title',
backButton:"VISIBLE",
backgroundImage:{
contentDescription:"backgroundImage",
sources:[
{
url:"https://democard.s3.amazonaws.com/hostel-720.jpg"
}]
},
listItems:[{
token:"item1",
image:{
sources:[{
url:"https://democard.s3.amazonaws.com/c-v-raman-college-of-engineering-squarelogo-1534916004379+(3).jpg"
}],
contentDescription:"first item of list"
},
textContent:{
primaryText:{
type:"PlainText",
text:"primary Text is here"
},
secondaryText:{
type:"PlainText",
text:"Secondary text is here"
}
},
},
{
token:"item2",
image:{
sources:[{
url:"https://democard.s3.amazonaws.com/c-v-raman-college-of-engineering-squarelogo-1534916004379+(3).jpg"
}],
contentDescription:"second item"
},
textContent:{
primaryText:{
type:"PlainText",
text:"primary text is here"
},
secondaryText:{
type:"PlainText",
text:"secondary text"
}
}
}
]
}};
return handlerInput.responseBuilder
.addRenderTemplateDirective(template)
.getResponse();
}
};
I am trying to filter the data coming back from FindAll cause I only want data from a certain provider
// Data coming back from API
{
"-KDinaItb7lkHpai-DlG": {
"email": "johns#test.com",
"name": "John Smith",
"notes": "John is a great employee and is the best",
"phone": "215-543-9830",
"provider": "-KDhzbilOvv7Evuc5S_X"
},
"-KDjS0cCxFWQctcwXg0V": {
"email": "amanda#test.com",
"name": "Amanda Harrington",
"notes": "Amanda is a great employee",
"phone": "215-543-9830",
"provider": "-KDiokWebdhTNKTORWwn"
},
"-KDyf7pU_PyxRQSgFB59": {
"email": "lguy#test.com",
"name": "Larry Guy",
"notes": "He is a funny guy",
"phone": "702-454-2397",
"provider": "-KDhzbilOvv7Evuc5S_X"
}
}
// In the route
let providerId = model.get('provider').get('id');
this.store.findAll('employee').then(function(results) {
let prov = results.filterBy('provider', providerId);
console.log(prov);
});
When the console log happens and it returns an empty array. I think its because of the ID and its not looking at the nested object. Anyone got any thoughts?
Ok so your hash looks quite odd. Property name shouldn't be some generated hash.
code should be something like that.
I assume you have 1 wrapper object on index 0 within an array.
var filteredEmployees_promise = this.store.findAll('employee').then(function(results) {
var filteredResults = [];
Object.keys(Results[0]).forEach(key => {
var filteredObj = Results[0][key][providerId];
if(Ember.isPresent(filteredObj) {
filteredResults.pushObject(filteredObj)
}
});
return filteredResults;
});
And later
filterEmployees_promise.then(employees => { // Custom stuff })
I am using futures to make an async call to balanced payments. I want to catch errors and feed them back to the user so I can tell them why their payment failed.
Here is what I get on the server console. How can I parse out the errors[0].status, or errors[0].category_code from this error? I've tried to simply console.log(error[0].status);, but this does nothing.
I20140616-14:38:59.169(0)? "errors": [
I20140616-14:38:59.169(0)? {
I20140616-14:38:59.170(0)? "status": "Conflict",
I20140616-14:38:59.170(0)? "category_code": "card-not-validated",
I20140616-14:38:59.170(0)? "additional": null,
I20140616-14:38:59.170(0)? "status_code": 409,
I20140616-14:38:59.171(0)? "category_type": "logical",
I20140616-14:38:59.171(0)? "extras": {},
I20140616-14:38:59.171(0)? "request_id": "OHMf39d5030f56311e39cde02a
1fe53e539",
I20140616-14:38:59.171(0)? "description": "Card cannot be validated.
Your request id is OHMf39d5030f56311e39cde02a1fe53e539."
I20140616-14:38:59.172(0)? }
I20140616-14:38:59.172(0)? ]
Here is the future function I'm using.
var Future = Npm.require("fibers/future");
function extractFromPromise(promise) {
var fut = new Future();
promise.then(function (result) {
fut["return"](result);
}, function (error) {
fut["throw"](error);
fut.return(error.message);
});
return fut.wait();
}
I call this function from my code using something like this.
var customerData = extractFromPromise(balanced.marketplace.customers.create({
'name': customerInfo.fname + " " + customerInfo.lname,
"address": {
"city": customerInfo.city,
"state": customerInfo.region,
"line1": customerInfo.address_line1,
"line2": customerInfo.address_line2,
"postal_code": customerInfo.postal_code,
},
'email': customerInfo.email_address,
'phone': customerInfo.phone_number
}));
I had some help and got the answer. Hope this helps others. Here is the rewrite of the code I initially wrote.
The future code was both returning and throwing the error, so that was removed, the code was cleaned up a bit as well.
var Future = Npm.require("fibers/future");
function extractFromPromise(promise) {
var fut = new Future();
promise.then(function (result) {
fut.return(result);
}, function (error) {
console.log(error);
fut.throw(error);
});
return fut.wait();
}
And then the whole thing is wrapped in a try catch. I console log out the different parts of the message. Turned out that the JSON was stringified, so that had to be parsed first, then I could access the error like normal JSON data. And I've just learned that no errors will get back to the client unless you use the Meteor.error syntax first.
var customerData;
try {
customerData = extractFromPromise(balanced.marketplace.customers.create({
'name': customerInfo.fname + " " + customerInfo.lname,
"address": {
"city": customerInfo.city,
"state": customerInfo.region,
"line1": customerInfo.address_line1,
"line2": customerInfo.address_line2,
"postal_code": customerInfo.postal_code,
},
'email': customerInfo.email_address,
'phone': customerInfo.phone_number
}));
} catch (e) {
console.log(JSON.parse(e.message).errors[0].extras);
console.log(JSON.parse(e.message).errors[0].category_code);
var error = JSON.parse(e.message).errors[0];
throw new Meteor.Error(error.category_code, error.status_code, error.description, error.extras);
}