I am new to polymer and I am trying to read JSON data in a custom-element and display it in other element.
This is my JSON data:
jsonData.json
[
{
"name":"Ladies+Chrome+T-Shirt",
"title":"Ladies Chrome T-Shirt"
},
{
"name":"Ladies+Google+New+York+T-Shirt",
"title":"Ladies Google New York T-Shirt"
}
]
This is my shop-app.html file where I try to read data from JSON file (I am not sure if this is correct or not as I am not able to test it):
<dom-module id="shop-category-data">
<script>
(function(){
class ShopCategoryData extends Polymer.Element {
static get is() { return 'shop-category-data'; }
static get properties() { return {
data: {
type: Object,
computed: '_computeData()',
notify: true
}
}}
_computeData() {
this._getResource( {
url: 'data/jsonData.json',
onLoad(e){
this.set('data.items', JSON.parse(e.target.responseText));
}
})
}
_getResource(rq) {
let xhr = new XMLHttpRequest();
xhr.addEventListener('load', rq.onLoad.bind(this));
xhr.open('GET', rq.url);xhr.send();
}
}
customElements.define(ShopCategoryData.is, ShopCategoryData);
})();
</script>
</dom-module>
This is the element where I want to display the data I read from the JSON file:
<dom-module id="shop-app">
<template>
<app-location route="{{route}}"></app-location>
<app-route
route="{{route}}"
pattern="/:page"
data="{{routeData}}"
tail="{{subroute}}">
</app-route>
<shop-category-data data="{{data}}"></shop-category-data>
<template>
<div> Employee list: </div>
<template is="dom-repeat" items="{{data}}">
<div>First name: <span>{{item.name}}</span></div>
<div>Last name: <span>{{item.title}}</span></div>
</template>
</template>
</template>
<script>
class ShopApp extends Polymer.Element {
static get is() { return 'shop-app'; }
}
customElements.define(ShopApp.is, ShopApp);
</script>
</dom-module>
The line <shop-category-data data="{{data}}"></shop-category-data> should give me the data, which I then try to display using dom-repeat. But nothing is being displayed. So, I think there is some mistake in my reading the JSON data.
Edit:
The JSON is read correctly, it is just not getting reflected back in my:
<shop-category-data data="{{data}}"></shop-category-data>
Computed properties is not returning a value. If you want to define data as a computed property you must return a value from the computed property function _computeData(). But in your case you are using asynchronous XMLHttpRequest. So, if you return a value after calling this._getResource... you need to make it synchronous (which no one recommends).
Plnkr for synchronous method: http://plnkr.co/edit/jdSRMR?p=preview
Another way is calling the method inside ready(). This is asynchronous.
Plnkr: http://plnkr.co/edit/pj4dgl?p=preview
It's not getting reflected back because the json is assigned to data.items, rather than to data itself.
this.set('data', JSON.parse(e.target.responseText));
It's recommended to use <iron-ajax>, and scrap <shop-category-data>. e.g. replace the following line
<shop-category-data data="{{data}}"></shop-category-data>
with
<iron-ajax auto url="data/jsonData.json" handle-as="json"
last-response="{{data}}"></iron-ajax>
Related
As the code show below, A.vue file has element data return some number values
<template></template>
<script>
export default {
data(){
return{
element: [
{
number:'11'
}
{
number:'22'
}
]
}
}
}
</script>
Now I want to get element.length from A.vue to B.vue. Is there a way to do that? I saw a solution with button click but i dont want to use button to pass data.
B.vue file
<template>
<div>I want to get element.length here</div>
</template>
You can simply achieve it by passing prop (which contains the length of the element array) from A.vue component to B.vue component. Here is the live demo :
Vue.component('bcomponent', {
// declare the props
props: ['length'],
// just like data, the prop can be used inside templates
// and is also made available in the vm as this.message
template: '<div>Element length: {{ length }}</div>',
});
var app = new Vue({
el: '#app',
data: {
element: [{
number: '11'
}, {
number: '22'
}]
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<!-- Component A -->
<div id="app">
<BComponent :length="element.length">
</BComponent>
</div>
If it's possible, just pass the data as a prop from B to A, this way you can implement any logic on the data.
If it's not, you should use vuex for data storage, so any component can access it.
just when I thought I figured out how app-route is working, I run into an issue that let me doubt that my understanding of this element is correct.
From what I understood, it's the responsibility of app-location to keep the browser URL and the value of route in sync. If one of them changes app-location takes care that the other one is changing as well.
And because the route attribute of app-route, is in sync with it's data attribute, changes of the data attribute by the paper-tabs in the code below causes a change in the route attribute of app-route causes the app-location to update the browser URL.
However since I didn't use the fallback-selection attribute in paper-tabs the surfing to http://localhost will set the path to '/' and therefore not showing the home-page.
So I thought I could redirect the URL with the code in the ready function. But unfortunately the route.path indeed changes but the URL doesn't.
Why is that? What do I have to do to redirect the route manually via a function?
Or in other words: Why does a change of routeData.subpage via the paper-tabs element causes a redirect, and a change of routeData.subpage from the function not?
<dom-module id="polymer-app">
<template>
<style>
:host {
display: block;
}
</style>
<app-location route="{{route}}"></app-location>
<app-route id="ar"
route="{{route}}"
pattern="/:subpage"
data="{{routeData}}"
tail="{{routeTail}}"
active="{{routeActive}}">
</app-route>
<header>
<paper-tabs attr-for-selected="name" selected="{{routeData.subpage}}">
<paper-tab name="home">Home</paper-tab>
<paper-tab name="settings">Settings</paper-tab>
</paper-tabs>
</header>
<section id="main">
<iron-pages attr-for-selected="name" selected="[[routeData.subpage]]">
<home-page name="home"></home-page>
<settings-page name="settings"></settings-pagina>
</iron-pages>
</scection>
</template>
<script>
Polymer({
is: 'polymer-app',
properties: {
route: Object,
routeData: Object,
routeTail: Object,
routeActive: Boolean,
},
ready: function() {
if (this.route.path == "/") {
this.set('routeData.subpage', 'home');
console.log(this.routeData);
console.log(this.route);
}
},
});
</script>
</dom-module>
Add a page property with an observer that will either assign it the current value (based on the URL) or a default value if the URL path is empty
static get properties() {
return {
page: {
type: String,
reflectToAttribute: true,
observer: '_pageChanged',
},
}
static get observers() {
return [
'_routePageChanged(routeData.page)',
];
}
_routePageChanged(page) {
// If no page was found in the route data, page will be an empty string.
// Default to 'home' in that case.
this.routeData.subpage = page || 'home';
}
This appears to be similar to a question I asked earlier: In Polymer 2.0 how to observe edits to properties of an object bound to an element inside dom-repeat?
But it's not. The code example is similar, but the question is focusing on a different aspect of that code.
Here is the example code: https://plnkr.co/edit/iTZqM4GwpASEqQgtRGEk
The list element:
<link rel="import" href="./editor-element.html">
<dom-module id="list-element">
<template>
<dom-repeat items="{{list}}" as="item">
<template>
<div class="item">
<editor-element description="{{item.description}}">
</editor-element>
</div>
</template>
</dom-repeat>
</template>
<script>
class ListElement extends Polymer.Element {
static get is() {
return "list-element";
}
static get properties() {
return {
list: {
type: Array,
value:function() {
return []
}
}
}
}
ready() {
super.ready();
this.push("list", {
description:"one"
})
this.push("list", {
description:"two"
})
setTimeout(function() {
this.set("list.0.description", "one edited");
}.bind(this), 500)
setTimeout(function() {
this.unshift("list", {
description:"three"
})
}.bind(this), 1000)
}
}
customElements.define(ListElement.is, ListElement);
</script>
</dom-module>
And the editor element:
<dom-module id="editor-element">
<template>
<div>Editor for [[description]]</div>
</template>
<script>
class EditorElement extends Polymer.Element {
static get is() {
return "editor-element";
}
static get properties() {
return {
description:String
}
}
static get observers() {
return [
"observe(description)"
]
}
observe(description) {
console.log("Observed change for TODO item: "+description);
}
}
customElements.define(EditorElement.is, EditorElement);
</script>
</dom-module>
In the list (parent) element, two todo items are pushed onto the array, the first one is modified, and then a third item is unshifted into the array.
The observer in the editor (child) element is triggered twice for the pushes, and once for the edit. So far so good. It's then triggered three times for the unshift.
I understand the logic behind this, the unshift scrambles the mapping between data and elements, which are re-used to render the newly assigned data.
My question is this:
How do I write an observer that can distinguish between observations triggered by a restructuring of the array, vs those triggered by an edit to a property of an item in that array?
Unshift is enough to illustrate this, but the same thing would happen if the dom-repeat has a sort or filter applied.
Just to clarify, I'm neither here nor there on the fact that the addition of one item to the array in the parent element results in an observable change in every dom-repeated child element. What is important however is that I can distinguish that observable change from the mutation of a property in one specific child element. In the approach shown in the sample code, I cannot, so it might mean I have taken the wrong approach to data binding here.
I am trying to get two way data-binding between a host element and a template in Polymer using templatizer. For example if I am trying to keep two input boxes in-sync:
<html>
<body>
<my-element>
<template >
<input type="text" value="{{test::change}}" />
<div>The value of 'test' is: <span>{{test}}</span></div>
</template>
</my-element>
<dom-module id="my-element">
<template>
<input type="text" value="{{test::change}}" />
value:
<p>{{test}}</p>
<div id="items"></div>
<content id="template"></content>
</template>
</dom-module>
<script>
Polymer({
is: 'my-element',
test: {
type: String,
value: "a"
},
behaviors: [ Polymer.Templatizer ],
_forwardParentProp: function(prop, value) {debugger},
_forwardParentPath: function(path, value) {debugger},
_forwardInstanceProp: function(inst, prop, value) {debugger},
_forwardInstancePath: function(inst, path, value) {debugger},
ready: function() {
this._instanceProps = {
test: true
};
var templates = Polymer.dom(this.$.template).getDistributedNodes();
template = templates[1];
this.templatize(template);
var itemNode = this.stamp({ test: this.test});
Polymer.dom(this.$.items).appendChild(itemNode.root);
}
});
</script>
</body>
</html>
In the above code I hit the debugger in the _forwardInstanceProp but not any of the others. Why is this? Inside _forwardInstanceProp I can access my-element and manually update the test property. Is there a better way to do this? I also could add an observer on my-element to the test property and then propagate any changes in my-element to the template. Is there a better way to do that? I am just trying to understand what all four of these methods do and when/why they should be used.
It beats my why I can never get neither _forwardParentPath nor _forwardParentProp to run. However, I know when the other two run :)
_forwardInstanceProp runs for direct properties of model passed to stamp and _instanceProps is initialized:
this._instanceProps = {
text: true
};
var clone = this.stamp({
text: this.text
});
_forwardInstancePath on the other hand runs when you pass nested objects to stamp:
var clone = this.stamp({
nested: {
text: this.text
}
});
See this bin for an example: http://jsbin.com/kipato/2/edit?html,js,console,output
In the stamped template there are two inputs bound to two variables which trigger instanceProp and instancePath. Unfortunately I've been unable to fix the error thrown when the latter happens.
I have a simple template that renders an array object. However, it fails with the following message:
[dom-repeat::dom-repeat]: expected array for `items`, found [{"code":1,"name":"Item #1"},{"code":2,"name":"Item #2"},{"code":3,"name":"Item #3"}]
The array is passed in the attribute of the custom element in the following format:
[{"code":1,"name":"Item #1"},{"code":2,"name":"Item #2"},{"code":3,"name":"Item #3"}]
I have read the docs on template repeaters several times and still unable to find what I am doing wrong.
Any help would be much appreciated!
Here is my custom element:
<dom-module id="x-myelement">
<template>
<div>
<h1>{{title}}</h1>
<ul>
<template is="dom-repeat" as="menuitem" items="{{items}}">
<li><span>{{menuitem.code}}</span></li>
</template>
</ul>
</div>
</template>
<script>
(function() {
Polymer({
is: 'x-myelement',
title: String,
items: {
type: Array,
notify: true,
value: function(){ return []; }
}
});
})();
</script>
</dom-module>
And here is now I use it:
<x-myelement title="Hello Polymer"
items='[{"code":1,"name":"Item #1"},{"code":2,"name":"Item #2"},{"code":3,"name":"Item #3"}]'>
</x-myelement>
You need to put your element properties into the properties object (see the Polymer documentation on properties):
Polymer({
is: 'x-myelement',
properties: {
title: String,
items: {
type: Array,
notify: true,
value: function() {return [];}
}
}
});
Otherwise Polymer has no information about your properties. It treated items as a string and didn't parse the attribute value as a JSON array. Eventually dom-repeat was passed a string for its items property as well, resulting in the error that you saw.