Accessing object values programmatically in Polymer 2 nested templates - javascript

With the conventional use of a dom-repeat loop and a template, the field
reference is hard-coded against the source object. In the example below we're
pulling the name and description fields from a JSON object. Works fine.
<template is="dom-repeat" items="[[subjects]]">
{{item.name}}, {item.description}}
</template>
In my application I'd like to pull values programmatically by using a
nested template that loops through a supplied list of fields. However I'm not
able to make it work, the results come out as literal text rather than performing as I'd like:
<template is="dom-repeat" items="[[subjects]]">
<template is="dom-repeat" items="[[fields]]" as="field">
{{item.{{field}}}},
</template>
</template>
These are the variations I've tried and the results using 'name' and 'description'
as the fields:
{{item.{{field}}}}, -> "{{item.name}} {{item.description}}"
{{item[ {{field}} ]}}, -> "{{item[ name ]}} {{item[ description ]}}"
Ideally, I would like it to work like this:
someFunction( {{item}}, {{field}} )
Where someFunction would take in the object & field specifier and return a string.
Just not sure how to make it happen. Any ideas?
Addendum showing missing parts called out:
<iron-ajax>
auto
url="https://api.github.com/users/burczu/repos"
params='{"type":"all"}'
handle-as="json"
on-response="handleResponse">
</iron-ajax>
and
<script>
class MyElement extends Polymer.Element {
static get is() { return 'my-element'; }
static get properties() {
return {
subjects: { type: Array },
fields: { type: Object }
};
}
ready() {
super.ready();
this.fields = JSON.parse('{"show": ["name", "description"] }').show;
}
handleResponse(data) {
this.subjects = data.detail.response;
}
}
window.customElements.define(MyElement.is, MyElement);
</script>

Ok, the solution isn't far off from what I wanted. It was a matter of applying the right syntax:
<template is="dom-repeat" items="[[subjects]]">
<template is="dom-repeat" items="[[fields]]" as="field">
[[ _formatText(item, field) ]],
</template>
</template>
<script>
class MyElement extends Polymer.Element {
. . .
_formatText(obj, field) {
return obj[field];
}
. . .
}
</script>
While it works as I wanted, all text returned by the _formatText function will be rendered as an HTML-safe string outside of the square brackets. No chance of emitting tags recognized by the browser. :(
If anyone knows how to get over that hurdle, please let me know.

Related

How can I pass an object key/value pair to a template in Vue?

This is what I have so far -- everything is ok except that the links go to http://localhost:5173/[object%20Object] and I think I've hit a mental wall.
My component:
<template lang="pug">
.nav-container
.nav-controller
ul.navbar-nav
li.btn(v-for="(value, key) in myObject" :key="value")
a.nav-link.nav-item(type='button' :href='{value}') {{key}}
</template>
<script>
const sections = {
Home: "/",
Programming: "/programming",
Finance: "/finance",
};
export default {
name: "NavBar",
data() {
return {
myObject: sections,
};
},
};
I realized it can be shrunk down and simplified but thats the current state of my project based on trial and error to understand how things work, not final.
I don't even know what I did -- but basically messing around with it in normal HTML and then converting it into pug through a website generator;
<template lang="pug">
link(rel='stylesheet' href='https://cdn.jsdelivr.net/npm/bootstrap#5.2.3/dist/css/bootstrap.min.css' integrity='sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65' crossorigin='anonymous')
.nav-container
.nav-controller
ul.navbar.nav
li.btn(v-for='(value, key) in myObject')
a.nav-link.nav-item(type='button' :href='value') {{ key }}
</template>
it works...

Vue.js warns You may have an infinite update loop in a component render function

I'm quite new to Vue.js & Buefy and I believe I understand why this happens but have no idea how to fix it.
I have a list of project partners sorted by country and I need to output a list with checkboxes (I'm using Buefy on the page anyway) and country names as title (only when there's a "new" country). This ends with browser doing infinite loop (verified with console.log) and Vue Devtools gives warning "You may have an infinite update loop in a component render function".
I believe this happens because changing prevTitle triggers re-rendering. I know it's not possible to pass parameters to computed properties and I haven't been able to use any of the tricks I've found to make partner.country available there.
var app = new Vue({
el: "#app",
data: {
prevTitle: ""
...
methods: {
changeCountryTitle(country) {
if (country != this.prevTitle) {
this.prevTitle = country;
return true;
}
return false;
}
<template v-for="partner in results">
<div v-if="changeCountryTitle(partner.country)">
{{ partner.country }}
</div>
<b-checkbox ... >{{ partner.name }}, ...</b-checkbox>
<br />
</template>
Then I tried to make a computed property where I do all processing instead of for loop in template and return a string that contains everything, including Buefy tags, which would be called
<span v-html="printPartnerList"></span>
But those Buefy tags don't get rendered properly, only HTML tags work, browser ignores Buefy tags showing the normal text only.
Any ideas how get this working? For time being I print country names for each partner after name etc. but it's not how this is supposed to work.
v-html doesn't evaluate Vue (or in this case Buefy) components, only regular HTML tags.
Your first approach wasn't that bad, but instead of calling a method inside the v-for you can add a computed property in the items that tells if the country should be rendered:
<template v-for="partner in computedResults">
<div v-if="partner.showCountry">
{{ partner.country }}
</div>
<b-checkbox ... >{{ partner.name }}, ...</b-checkbox>
<br />
</template>
New app:
var app = new Vue({
el: "#app",
data: {
// prevTitle: "" <- not needed
...
computed: {
computedResults() {
let prevCountry = ''
let newResults = []
this.results.forEach(partner => {
let showCountry = false
if (prevCountry != partner.country) {
showCountry = true
}
newResults.push({
...partner,
showCountry
})
prevCountry = partner.country
})
return newResults
}

Linking a text field in a child component to a parent component's props in VueJS

I have a child component sending data via an event to a parent component in VueJS. From the parent component, I am routing the data (or trying to route the data...) to a sibling of the child and create new components with the data sent from the child.
I use a dictionary to group the data for various reasons, then push the dictionary into an array. A v-for loop loops thru the array and populates the previously mentioned new components with data found in that array. I probably don't need to do it this way, but that's how I'm doing it. I am open to alternatives.
Anyway, it doesn't work great. So far I'm only able to get one of the three strings I need to show up where I want it to. I'll explain more after I post the code.
Already tried:
A dozen different versions of the code, including creating a simple v-for in a list to do the job, and various versions with/without a dictionary or array.
In my research for the problem I've gone through the VueJS docs, Googled a few things, and found nothing.
In App.vue (I tried to remove all the irrelevant stuff):
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<TweetDeck v-on:messageFromTweetDeck="msgReceived($event)"/>
<!-- <ul>
<li v-for="(tweet, index) in tweets" :key="index">{{ tweet }}</li>
</ul>-->
<TwitterMsg v-for="(tweet, index) in tweets" :key="index"
:name="tweet.name" :handle="tweet.handle" tsp=3 :msg="tweet.tweet" />
<TwitterMsg name="aaa" handle='aaa'
tsp=50 msg="hey this is a message on twitter"/>
<input type="text" v-model="placeholderText"/>
</div>
</template>
<script>
import TwitterMsg from './components/TwitterMsg.vue'
import TweetDeck from './components/TweetDeck.vue'
export default {
name: 'app',
components: {
TwitterMsg,
TweetDeck
},
data: function() {
return {
tweets: [],
message: "",
placeholderText: ""
}
},
methods: {
msgReceived(theTweet, name, handle) {
this.tweets.push({tweet: theTweet, name: name, handle: handle})
}
}
}
</script>
And in TweetDeck.vue:
<template>
<div>
<input type='text' v-model="yourName">
<input type='text' v-model="yourHandle">
<input type='text' v-model="yourTweet"/>
<button type='button' #click="sendTweet()">Tweet</button>
</div>
</template>
<script>
export default {
name: "TweetDeck",
data: function() {
return {
yourName: "Your name here",
yourHandle: "Your twitter handle",
yourTweet: "What's going on?"
}
},
methods: {
sendTweet() {
this.$emit('messageFromTweetDeck', this.yourTweet, this.yourName, this.yourHandle);
}
}
}
</script>
You can also see the mostly unimportant TwitterMsg.vue here (I am trying to copy Twitter for learning purposes:
<template>
<div>
<h4>{{ name }}</h4>
<span>#{{ handle }}</span>
<span> {{ tsp }}</span> <!-- Time Since Posting = tsp -->
<span>{{ msg }}</span>
<img src='../assets/twit_reply.png'/><span>1</span>
<img src="../assets/twit_retweet.png"/><span>2</span>
<img src="../assets/twit_fave.png"/><span>3</span>
</div>
</template>
<script>
export default {
name: "TwitterMsg",
props: {
name: String,
handle: String,
tsp: String,
msg: String
}
}
</script>
<style>
img {
width: 30px;
height: 30px;
}
</style>
Expected result:
The code populates a new TwitterMsg component with appropriate name, handle and message data each time I click the "Tweet" button.
Actual results:
My code fails to help the name and handle strings make it from the input text box in TweetDeck.vue all the way to their home in TwitterMsg.vue.
I will say that this.yourTweet in TweetDeck.vue DOES manage to make it all the way to its destination, which is good -- though it makes me wonder why the other two pieces of data didn't follow suite.
Totally lost. Also just in my first month of VueJS so it's pretty good that I can even make one string appear where I want it to. \o/
First, you need to remove the $event parameter
<TweetDeck v-on:messageFromTweetDeck="msgReceived"/>
Second, you can optimize the data format passed to the parent component:
sendTweet() {
this.$emit("messageFromTweetDeck",
{ tweet: this.yourTweet, name: this.yourName, handle: this.yourHandle }
);
}
And then modify your msgReceived method:
msgReceived(childData) {
this.tweets.push(childData);
}
Link: codesandbox
Hope to help you:)

vue.js and slot in attribute

I'm trying to build a vue.js template that implements following:
<MyComponent></MyComponent> generates <div class="a"></div>
<MyComponent>b</MyComponent> generates <div class="a" data-text="b"></div>.
Is such a thing possible?
EDIT
Here is the best I can reach:
props: {
text: {
type: [Boolean, String],
default: false
}
},
and template
<template>
<div :class="classes()" :data-text="text">
<slot v-bind:text="text"></slot>
</div>
</template>
but the binding does not work, text always contains false.
You can use the mounted() method to get text using $slot.default property of the component to get the enclosing text. Create a text field in data and update inside mounted() method like this :
Vue.component('mycomponent', {
data: () => ({
text: ""
}),
template: '<div class="a" :data-text=text></div>',
mounted(){
let slot = this.$slots.default[0];
this.text=slot.text;
}
});
Note: It will only work for text, not for Html tags or components.
You're mixing slots and properties here. You'll have to pass whatever you want to end up as your data-text attribute as a prop to your component.
<MyComponent text="'b'"></MyComponent>
And in your template you can remove the slot
<template>
<div :class="classes()" :data-text="text"></div>
</template>
Another thing: it looks like your binding your classes via a method. This could be done via computed properties, take a look if you're not familiar.
You can try this.
<template>
<div :class="classes()">
<slot name="body" v-bind:text="text" v-if="hasDefaultSlot">
</slot>
</div>
</template>
computed: {
hasDefaultSlot() {
console.log(this)
return this.$scopedSlots.hasOwnProperty("body");
},
}
Calling
<MyComponent>
<template v-slot:body="props">
b
</template>
</MyComponent>

Get data from an Array in Vue

Right now, displayed data in my page is an array as per snippet below:
But I want only to get or display the project names.
This is what I have only right now:
fetch(context,id){
if(context.getters['isEmpty']){
Vue.api.mediasetting.index()
.then(response=>{
var medias = response.data[key];
context.commit('setMedias',medias);
// console.log("init medias", medias[0].project_name)
},response=>{
});
}
},
Can I apply filter here and how? Thanks.
Depends on what html element you want to use for rendering the values, you may use a v-for directive to rendering the project_name values:
<template>
<ul id="example">
<li v-for="(media) in medias">
{{ media.project_name }}
</li>
</ul>
</template>
You are going to need to get the medias data from your store, you may get it using a computed property:
<script>
export default {
computed: {
medias: function () {
return this.$store.medias
}
}
}
</script>
Example above is assuming you are using single file component structure.

Categories

Resources