How to get composed DOM of Polymer Web Component? - javascript

I'm using Polymer 0.8-preview.
For example, I have the following component:
<dom-module name="greeting-tag">
<template>
<ul>
<template repeat="{{s in salutations}}">
<li>{{s.what}}: <input type="text" value="{{s.who}}"></li>
</template>
</ul>
</template>
</dom-module>
<script>
Polymer({
is: 'greeting-tag',
ready: function () {
this.salutations = [
{what: 'Hello', who: 'World'},
{what: 'GoodBye', who: 'DOM APIs'}
];
}
});
</script>
Is it possible to get the composed DOM (as JS object or text) at some point of the component life cycle?
For the example above I would like to get
<ul>
<li>Hello: <input type="text" value="World"></li>
<li>GoodBye: <input type="text" value="DOM APIs"></li>
</ul>
In other words, I need the result of processed template.

Polymer 0.8 is not yet released. Furthermore, there is clearly stated in the documentation draft, that array notification is in high flux.
The code you provided has a bundle of glitches:
dom-module should be identified by id, not by name.
template repeat is gone. One should use <template is='x-repeat'
items="{{salutations}}"> instead. Since array reflection is not yet released/frozen, I eliminated it from the example code below.
Putting everything together:
<dom-module id="greeting-tag">
<template>
<p>{{caption}}</p>
</template>
</dom-module>
<script>
Polymer({
is: 'greeting-tag',
properties: {
caption: {
type: String,
reflect: true
}
},
ready: function () {
this.caption = 'Welcome here',
console.log(this.innerHTML);
}
});
</script>
The latter will print:
//⇒ <p style-scope="greeting-tag">Welcome here</p>
AFAIK, there is no ready-to-use method to do the same with arrays yet.

Related

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:)

getElementsByClassName in context of vue

In the context of vue.js, how does getElementsByClassName work?
I have the following snippet of code below - the goal is to click a button and change the color of the h1 tag to green using vue.js methods:
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h1 class="main-header">{{ message }}</h1>
<button v-on:click="colorChange">Click me</button>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
methods: {
colorChange: function() {
// what do I do here...?
}
}
})
</script>
</body>
</html>
It's to my understanding that Vue is like React with a virtual DOM and you don't modify it directly.
Obviously in vanilla JS you just do document.getElementsByClassName('main-header')[0].style.backgroundColor="green"; but that seems counterintuitive in Vue.
Am I overcomplicating this and is this in fact how it works? Or does Vue have a specific way of handling this? I've been looking at https://v2.vuejs.org/v2/guide/class-and-style.html but it just explains how to bind classes. I'm also reading through https://v2.vuejs.org/v2/guide/events.html but I'm having a hard time finding what I need in regards to targeting an element/modifying it in some way...
How do you select and/or modify an element in the context of Vue?
You are correct. In Vue, interacting directly with the DOM is counter-intuitive.
Fortunately, there is a directive that allows you to apply style changes directly by binding to a data property. (Keep in mind, you could do something similar with a class as well).
<h1 class="main-header" v-bind:style="{ color: activeColor}">{{ message }}</h1>
<button v-on:click="colorChange">Click me</button>
In your component, create a data property and update it on button click:
data: {
message: 'Hello Vue!',
activeColor: 'red'
},
methods: {
colorChange: function() {
this.activeColor = 'green'
}
}

Using datalist from local DOM in polymer

My custom component 'autocomplete-input' should provide an autocomplete functionality. I am using the 'list' attribute of 'paper-input'. The value of the 'list' attribute has to be the 'id' of a 'datalist'.
This works fine, as long as I instantiate only one 'autocomplete-input' element. If there are multiple instances of the 'autocomplete-input' element, they all show the same autocomplete proposals. I guess this is because the 'list' attribute does not use the local DOM and therefore uses the first 'datalist' with that particular 'id'. How can I avoid this?
The 'autocomplete-input' element:
<dom-module id="autocomplete-input">
<template>
<datalist id="autocomplete">
<template is="dom-repeat" items="{{proposals}}">
<option data-value$="{{item.value}}">{{item.content}}</option>
</template>
</datalist>
<paper-input id="input" list="autocomplete" label="{{header}}" value="{{content::input}}"></paper-input>
</template>
<script>
Polymer({
is: 'autocomplete-input',
properties: {
header: String,
content: String,
proposals: Array
}
});
</script>
</dom-module>
Use a global counter and add it to your id
<dom-module id="autocomplete-input">
<template>
<datalist id$="[[autocompleteId]]">
<template is="dom-repeat" items="{{proposals}}">
<option data-value$="{{item.value}}">{{item.content}}</option>
</template>
</datalist>
<paper-input id="input" list$="[[autocompleteId]]" label="{{header}}" value="{{content::input}}"></paper-input>
</template>
<script>
Polymer({
is: 'autocomplete-input',
properties: {
header: String,
content: String,
proposals: Array,
}
}),
autoCompleteId: function() {
return this.autocompleteId = this.autocompleteId || ++globalId;
// see linke below to figure out how to use globals
}
</script>
</dom-module>
See Polymer 1.0 Global Variables for more details about globals in Polymer (My JS isn't good enough because I use Polymer only from Dart)

Event Handling between parent and child page in polymer 1.0

I have a parent polymer element called parent-page and a child element called child-page.
parent-page calls child page and passes an array with it. for e.g, in parent page:
<child-page items={{itemsArray}}></child-page>
Now, on the basis of certain activity child page fires an event with a new array.
eg, in child page:
this.fire('eventPerformed', newArray);
This array is being listened by the parent page and received with expected values.
Now, I want to pass that new array to the child page such that the child-page is rendered according to the new array.
How to achieve it?
Edit: my child page looks like this.
<dom-module id="child-page">
<style>
</style>
<template>
<template is="dom-repeat" items="{{itemArray}}" as="fooditem">
<div class="horizontal layout">
<div>{{fooditem.quantity}}</div>
<div>{{fooditem.name}}</div>
</div>
</template>
<paper-button on-click"changeArray"> ChangeArray</paper-button>
</template>
<script type="text/javascript">
Polymer({
is:'child-page',
properties:{
itemArray:Array
},
changeArray:function(){
this.itemArray=<<Some new Array>>
this.fire('eventPerformed',newArray);
}
});
</script>
</dom-module>
Is there any way I can call the template repeat with the new array in the same child page? Or do I have to fire an event to the parent-page and call the child page again? How to achieve it either way?
The child-page re-renders its template is="dom-repeat" items="[[itemArray]]" automatically whenever its itemArray property was updated.
Just add notify: true to the itemArray property in child-page to enable two-way-binding with the parent-page element. Then the parent-page is also notified whenever the item-array of child-page has changed (see the Polymer documentation on this topic).
Here is a small complete example:
<dom-module id="child-page">
<template>
<template is="dom-repeat" items="[[itemArray]]">
<div>[[item]]</div>
</template>
<div on-click="_changeArray">Change</div>
</template>
<script>
Polymer({
is: 'child-page',
properties: {
itemArray: {type: Array, notify: true}
},
_changeArray: function() {
this.itemArray = [4,5,6,7,8,9];
}
})
</script>
</dom-module>
<dom-module id="parent-page">
<template>
<span>Item count: </span><span>[[itemArray.length]]</span>
<child-page item-array="{{itemArray}}"></child-page>
</template>
<script>
Polymer({
is: 'parent-page',
properties: {
itemArray: {type: Array, value: [1,2,3]}
}
})
</script>
</dom-module>
<parent-page></parent-page>
Btw. note my usage of {{...}} and [[...]]. Use curly braces for two-way bindings and square brackets for one-way bindings.

Bind a value between a parent and a child element where child element is created using javascript

Using Polymer, does anyone know how to bind a value between a parent and a child element?
Below is my attempt however it doesn't work.
Note: child-component needs to be created using JavaScript.
<dom-module id="host-component">
<template>
<div>{{sharedValue}}</div>
<div id="childComponentContainer">
<!-- CHILD-COMPONENT GETS CREATED HERE -->
</div>
</template>
<script>
Polymer({is:'host-component',
properties:{
sharedValue:{
type: String,
notify:true,
observer: 'sharedValueChanged'
}
},
attached: function(){
var newElement = document.createElement('child-component');
Polymer.dom(this.$.childComponentContainer).appendChild(newElement);
},
sharedValueChanged:function(d){
console.log(d, ", said the child");
}
});
</script>
</dom-module>
<dom-module id="child-component">
<template>
<input is="iron-input" bind-value="{{sharedValue}}" />
</template>
<script>
Polymer({is:'child-component',
properties:{
sharedValue:{
type: String,
notify:true,
reflectToAttribute:true,
}
},
});
</script>
</dom-module>
Thanks :)
After re-reading Polymer's documentation (many times) I found a section about how Two-way data-binding events work where by on each change Polymer fires a DOM event. From this I came up with the work around below.
You'll notice this version also has two way binding so the host can change the child's value and the child can change the host's value!
<dom-module id="host-component">
<template>
<div>Host: <span>{{sharedValue}}</span></div>
<div>Host: <span><input is="iron-input" bind-value="{{sharedValue}}" /></span></div>
<div id="childComponentContainer">
<!-- CHILD-COMPONENT GETS CREATED HERE -->
</div>
</template>
<script>
Polymer({is:'host-component',
properties:{
sharedValue:{
type: String,
notify:true,
value: "Unchanged",
observer: 'sharedValueChanged'
}
},
attached: function(){
this.$.childComponent = document.createElement('child-component');
var host = this;
//Listens to the child's sharedValue. When changed it will update host's sharedValue.
this.$.childComponent.addEventListener("shared-value-changed", function(e){
host.sharedValue = this.sharedValue;
});
Polymer.dom(this.$.childComponentContainer).appendChild(this.$.childComponent);
},
//Observes the hosts's sharedValue. When changed it will update child's sharedValue.
sharedValueChanged: function(value){
if (this.$.childComponent) {
this.$.childComponent.sharedValue = value;
}
}
});
</script>
</dom-module>
<dom-module id="child-component">
<template>
<div>Child: <span>{{sharedValue}}</span></div>
<div>Child: <span><input is="iron-input" bind-value="{{sharedValue}}" /></span></div>
</template>
<script>
Polymer({is:'child-component',
properties:{
sharedValue:{
type: String,
notify:true,
value:"Unchanged",
reflectToAttribute:true,
}
}
});
</script>
</dom-module>

Categories

Resources