I’m trying to display the store URLs from an API.
In the API are available different URLs and in the output, they are displayed as one single link. How can I display URLs in different lines?
My vue code:
<a v-bind:href=“storeUrl”>
{{storeUrl}}
</a>
My script:
....
computed:{
storeUrl() {
return this.games.store.map(({{url}}) => url).join(‘ ’)
},
}
I’m using https://api.rawg.io/api/games API
This is the current output:
This answer solves some problems after #chillin's answer.
As I mentioned in the comments, the reason you're not seeing any store urls is that you are iterating over an object that doesn't exist:
The problem, (as #chillin saw) is that you are iterating through
game.store when you should be iterating through game.stores If you
inspect the game object, you'll notice that there is a stores
array within, but not a store one.
You should also note that having the urls in anchors on their own will cause them to be squashed into one line. Wrapping the anchors in <p> elements solves that problem:
Before:
<a v-for="store in game.store" :href="store.url" :key="store.ID">{{store.url}}</a>
After:
<p v-for="store in game.stores" :key="store.ID">
<a :href="store.url">{{store.url}}</a>
</p>
Also, I'm not sure if the stores array could ever have duplicate IDs, (maybe if multiple versions of the same game are in the same store), but if that ever does happen your code could crash. So it might be a better option to simply use the index of the object as it's key, like so,
<p v-for="(store, index) in game.stores" :key="index">
<a :href="store.url">{{store.url}}</a>
</p>
...so as to avoid this potential problem.
Here's a demo and here's the modified codepen (I also removed the computed property storeUrl, as it was unused.
Updated with actual example
First, don't join them in the computed, and then implement using a v-for, something like this should work.
Basically this is my own take of course, but based on the actual API data, something like this should work, with a loop in a loop, I map out the data just for ease of use, you will end up with something like:
[
{
key: 'gta-v',
storeUrls: [
{
key: 'steam',
url: 'http:// ...'
},
{
key: 'riot',
url: 'http:// ...'
}
]
},
{
key: 'fortnite',
storeUrls: [
{
key: 'steam',
url: 'http:// ...'
},
{
key: 'riot',
url: 'http:// ...'
}
]
}
]
Using this we can also double down on a v-for in the template, and sort your data by game, and for each game loop through it's storeUrl's for a nice clean list, this also utilises the use of actual keys, rather than index.
<template>
<div class="root">
<div class="game" v-for="game in games" :key="game.key">
<h1>{{ game.key }}</h1>
<a v-for="store in game.storeUrls" :href=“store.url” :key="store.key">
{{store.url}}
</a>
</div>
</div>
</template>
export default {
data() {
return {
myData: null
}
},
computed: {
games() {
if (!this.myData) return [];
return this.myData.results.map(game => {
key: game.slug,
storeUrls: game.stores.map(store => {
return {
key: store.store.slug,
url: store.url_en
}
});
});
}
},
methods: {
getData() {
// However you do it, but the base data, with no mapping.
this.myData = axios.get('...');
}
}
}
I'm new to Vue, so maybe someone else can provide a better answer. But seems right to use v-for to loop over your store URLs.
Assuming your gameId watcher is running and completing successfully, I don't think you need to change anything since this.game.stores should already contain an array of objects.
So you should be able to do something like:
<a v-for="store in game.stores" :href="store.url" :key="store.store.id">{{ store.url }}</a>
I don't know the difference between store.id and store.store.id, but I've assumed that store.store.id uniquely identifies a store and is okay to be used as the key. (You'd have to check the API documentation to see what the IDs represent.)
Related
I hope I can explain the question clearly. I need to create a few categories in category with <input type="checkbox"/>. Can you help how do it
{
"properties":{
"category":[
"0" : "category-1",
"1" : "category-2"
],
"image": "https://link...",
...
}
}
now I can only add one value at a time, but I need a lot
{
"properties":{
"category": "category-1",
"image": "https://link...",
...
}
}
const handleSubmit = (e) =>{
e.preventDefault();
const movie = {id, category, title, poster, rating, year, video, trailer, description};
fetch('http://localhost:8000/movies', {
method: 'POST',
headers: {"Content-Type" : "application/json" },
body: JSON.stringify(movie)
}).then(() => {
alert('Успешно добавился фильм!');
})
}
So if im understanding you correctly, you want to add multiple categories to the body of a POST endpoint? As long as your api handles an array for the “category”, then its pretty easy! But first, there are some things I see in your code:
In javascript, you don’t explicitly write the index of an array. You just write the item, for
example
const listOfStrings = [“string 1”, “string 2”]
const listOfObj = [{name: ‘Aj”}, {name: ‘bj’}]
It seems like you are implicitly passing in the properties for your “movie” object, just make sure that each property is defined somewhere above.
Now on to your question! What you want to do is the following:
Create a list of categories, make sure they match whatever the backend is expecting
create a state to track the selected categories
map through the array and render an input for each
assign each input props based on its indexed category
create a function that updates the selected state with a new category or a category removed
The key principles you'll need to research if youre unfamiliar with this are:
Javascript: map, filter
React: JSX,
Rendering lists
hooks (useState)
Code Example: https://codesandbox.io/s/jolly-moon-fbhs7c?file=/src/App.js
Edit to clarify:I have an object from an API response that I get using the mounted function, data is saved not displayed yet. I need to be able to filter that data by allowing a user to input text in an input box before showing it on the page, then find where that keyword was used in a specific key value(name). Then show the results on a page but include other key/value pairs from the api array. This is what the api response looks like:
class: (...)
facets: (...)
numberFound: (...)
results: Array(202)
[0 … 99]
0:
class: "SearchResult"
contentGuid: "7f19462f-6c25-43a9-bdb5-479f5f42fbde"
dateUpdated: "2018-03-27T16:46:31Z"
description: "Converting a Word Document to Adobe Acrobat PDF Learning Services Converting a Word Document to Adobe Acrobat PDF Enterprise Converting a Word Document to Adobe Acrobat PDF / Reference ..."
document: Object
documentGuid: "035f5c69-d406-4c16-86ca-de12773a0963"
documentId: 154424
documentVersionId: 44043
fileId: 74213
format: "PDF"
id: "Document#1#44043"
isFavorite: false
languages: "English"
name: "Converting a Word Document to Adobe Acrobat PDF"
numberOfIndexedCoobs: 0
numberOfSharedLinks: 1
packageType: "PDF"
previewId: 74213
publicLinkTokens: Array(1)
resourceType: "Other"
score: 0.0054571675
snippets: Object
updatedById: 994
updatedByName: "Michael"
versionName: "3"
For example if someone enters "Adobe" in the search box, I would need to search for the word "adobe" in the name value for the entire object, and only show the ones that have "abobe" somewhere in the name value.
My thought was to get the document name split it, then do an includes() to check for the search term. This works but I can't seem to figure out how to get it all to work together and get the results on the screen, plus get additional information, such as document Id from the original results. this is what I have so far:
async getResults() {
return axios
.get(this.url, {
headers: {
"Content-Type": "application/json",
"Bravais-prod-us-Context": this.getCookie(),
},
})
.then((res) => {
this.search = res.data;
this.search.results.forEach((doc) => {
this.results = doc.document.name
.toLowerCase()
.split(" ")
.includes(this.termSearch.toLowerCase());
console.log(doc.document.name.split(" "));
console.log(this.results);
});
})
.catch((error) => console.log(error));
},
I need to show the original title(some words and acronyms are capitalized) plus the doc id(for url links) and a description, all of this info is in the initial api response.
<div v-for="" v-bind:key="">
{{ ???? }}
</div>
That works in the console but how do I get this back together and on the screen?? Any help is appreciated, not looking for someone else to do my coding, just need some advice.
I would start by diving your logic. At the moment you have a single function that makes an api call and then searches through the results. It would be better suited to have the api call in a separate method so that if the user searches multiple times it doesn't call the api each time. We can easily solve this by adding an extra method that checks if the results object is populated and decides which methods to call.
Casting all strings to lowercase is a good idea to normalize the data. There might be other ways but this works for it's intended purpose. However, splitting a string is unecessary as the includes() method searches through the whole string. See the MDN docs for String.prototype.includes()
To search within an array you can use the filter() method, which will create a new array with all elements that pass the implemented test. See the MDN docs for Array.prototype.filter().
With this in hand, we can write our logic as:
async handleSearch(searchString) {
if (!this.results.length) {
this.getResults()
}
this.searchResults(searchString)
},
async getResults() {
return axios.get(this.url, {
headers: {
"Content-Type": "application/json",
"Bravais-prod-us-Context": this.getCookie(),
},
}).then((res) => {
this.results = res.data.results
}).catch((error) => console.error(error));
},
searchResults(searchString) {
this.filteredResults = this.results.filter(item => {
let name = item.name.toLowerCase();
let searchTerm = searchString.toLowerCase();
return this.name.includes(searchTerm)
})
}
Your input field will call the handleSearch() method, and then you can write you html as such:
<div v-for="result in filteredResults" :key="result.id">
<p>Name: {{result.name}}</p>
<p>Description: {{result.description}}</p>
</div>
I am trying to pass a variable as the param to vue-router so it can be set dynamically. Pretty much as the example below:
<router-link :to="{
name: notification.name,
(notification.param_name): notification.param_value
}">Blabla</router-link>
I am not looking at setting the key like this: var[notification.param_name].
I suppose this question could be expanded to a more general problem, but I am having a hard time explaining it in another way.
It'd be simplest to make it a computed which returns the definition of the route based on the notification object (note that you need to specify the params inside the params property of the object):
computed: {
notificationRoute() {
let { name, param_name, param_value } = this.notification;
return { name, params: { [param_name]: param_value } };
}
}
And bind that to the to:
<router-link :to="notificationRoute">Blabla</router-link>
I have an element in my View in Aurelia that is not getting updated when an object from its Viewmodel is getting updated. I've seen the documentation about Pub/Sub and Event Aggregators, however this seems a little heavy-handed for what I want to do, since I am not trying to communicate between two different resources, but rather just within a View and its Viewmodel.
When a change occurs to the object in the Viewmodel, I don't know how to correctly update (or trigger an update to) the string interpolation in the View.
My code is as follows
myview.html
<h1>My List</h1>
<ul>
<li repeat.for="group of modelObject.groups">
<span>${group.id}</span>
<span repeat.for="state of group.region">${state}</span>
</li>
<ul>
<button click.delegate(editModelObject())>Edit</button>
myviewmodel.js
constructor()
{
this.modelObject = {
<other stuff>,
"groups": [
{
"id": "default",
"regions" : ["NY", "CT", "NJ"]
},
{
"id": "west",
"regions" : ["CA", "OR"]
}
],
<more stuff>
}
}
editModelObject() {
<something that updates both the id and regions of a group in this.modelObject>
}
For some reason, the states are correctly changing in the view, but the id's are not. Do I need to use something like Pub/Sub to get the two-way binding to work correctly? Or is there a simple thing that I am missing or doing wrong?
This works if you change a property of one of the array's objects. But this doesn't work if you assign one of the array's index because this would require dirty-checking. See https://github.com/aurelia/binding/issues/64
To solve your problem you should use splice() instead of indexed assignment. For instance:
const newItem = { id: 77, name: 'Test 77', obj: { name: 'Sub Name 77' } };
//instead of this.model.items[0] = newItem; use below
this.model.items.splice(0, 1, newItem);
Running example https://gist.run/?id=087bc928de6532784eaf834eb918cffa
I've got this in my client.js file
Template.data.champ = Meteor.call("checkLeague", function(error, results) {
console.log(results.data.data);
return results.data.data;
});
So it shows fine in the console.log but it doesn't actually show on the webpage.
This is my html file with handlebars template
<body>
{{> hello}}
{{> data}}
</body>
<template name="hello">
<h1>Hello World!</h1>
{{greeting}}
<input type="button" value="Click" />
</template>
<template name="data">
{{#each champ}}
{{name}}
{{/each}}
</template>
From my understanding (which is very limited in terms of Handlebars) but the {{#each champ}} iterates over objects? But for some reason, nothing is being displayed on the page.
This is the object structure (displayed in the console).
Object {Aatrox: Object, Ahri: Object, Akali: Object, Alistar: Object, Amumu: Object…}
Aatrox: Object
id: "Aatrox"
image: Object
key: "266"
name: "Aatrox"
title: "the Darkin Blade"
__proto__: Object
Ahri: Object
Akali: Object
Alistar: Object
Amumu: Object
Anivia: Object
Annie: Object
Ashe: Object
So basically I am passing an object that has properties which have values of objects. I assume the {{#each} iterates over the properties and gives access to the values (which is an object) and then I try to access the name property of that variable in the handlebars template but it doesn't work.
From the docs regarding Meteor.call:
If you include a callback function as the last argument (which can't
be an argument to the method, since functions aren't serializable),
the method will run asynchronously: it will return nothing in
particular and will not throw an exception.
So whatever value Template.data.champ is assigned, it's "nothing in particular" (note that what you return from the callback you have will never be used anywhere).
You could store it in a Session like this:
Session.setDefault('theData', [])
Meteor.call("checkLeague", function(error, results) {
Session.set('theData', results.data.data)
});
Template.data.champ = function(){
return Session.get('theData')
}
But I would try to go with a collection instead.
Peppe's answer is correct - here is an option for how to deal with this situation:
Template.data.created = function() {
Meteor.call('checkLeague', function(error, results) {
Session.set('champ', results.data.data);
});
};
Template.data.champ = function() {
return Session.get('champ');
};
The data is loaded when the template is created, and asynchronously stored into a session variable. Keep in mind that this isn't reactive, but that's hard to overcome since your data is coming from a method call.
I think the problem might just be that #each is expecting an array and you are passing an object. In your helper, try return _.toArray( result.data.data );.
I couldn't find any mention of this in the meteor docs but handlebars.js docs mentions Array. Also, I have passed Array before and it works.
In addition to what was said about using a session variable. I think you will also need to transform your list into a list of just objects instead of a list of key, value you pairs.
So instead you want to make your data just like this
var champ = [{ id: "Aatrox",
image: Object,
key: "266",
name: "Aatrox",
title: "the Darkin Blade"},
{ id: "Ahri",
image: Object,
key: "267",
name: "Ahri",
title: "Hitchhikers Guide"}, ... ];
return champ;
To get this structure from your current structure you'll need to do something like
var champ = [];
for (var a in results.data.data) {
for (var key in results.data.data[a]) {
champ.push(results.data.data[a][key]);
}
}