In my ideal world, I'd love for Polymer components to be able to use Object.observe() transparently to listen for changes to the properties of models, without the model objects having to themselves be custom elements.
In the example below, I have an element that has a property model of type Object. The HTML template uses {{model.name}} and I would like the element to update automatically whenever that property changes.
In my demo/example, the hacky way I accomplish this is by using Object.observe() within custom code, and when any change happens, I set model to undefined and back again. This "refreshes" the UI and picks up the changes. Of course, in any nontrivial UI, this would be very janky, so this is a bit of a "hope is not a strategy" moment....
Is there any design pattern for tersely doing this sort of thing right now, and/or a roadmap for doing such things in the future?
This is my custom element:
<link rel="import" href="../bower_components/polymer/polymer.html">
<dom-module id="test-view">
<template>
<p>Hello, I am called <span>{{model.name}}</span>, how are you?</p>
</template>
</dom-module>
<script>
Polymer({
is: 'test-view',
properties: {
model: Object,
},
observers: [
'_modelChanged(model)'
],
ready: function() { },
attached: function() { },
detached: function() { },
_modelChanged: function(model) {
if (model) { Object.observe(model, this._observer.bind(this)); }
},
_observer: function() {
const oldModel = this.model;
this.model = undefined;
this.model = oldModel;
}
});
</script>
and this is an HTML page that drives it through its state transitions:
<html>
<head>
<script src="../bower_components/webcomponentsjs/webcomponents.js"></script>
<link rel="import" href="../test-view/test-view.html">
</head>
<body>
<test-view id="first" model='{ "name": "Wilma" }'></test-view>
<script>
const newModel = { name: 'Fred' };
const testView = document.querySelector('#first');
window.setTimeout(function() { testView.model = newModel; }, 2000);
window.setTimeout(function() { newModel.name = 'Barney'; }, 4000);
</script>
</body>
</html>
This is probably not a complete answer to your question, more of comment, but since code does not look well in comments I'm still posting it as an answer. I think that kind of thing may even have worked in 0.5 without the additional observer. However, to improve performance this kind of behaviour was removed in 1.0. I also hope we get it back sometime.
To avoid setting the model to undefined and back to trigger a refresh, you could also just notify Polymer of the changes. I assume that this would be more efficient, especially when you have complex objects and lots of data-binding.
_observer: function(changes) {
changes.forEach(function(change){
this.notifyPath("model." +change.name, change.object[change.name]);
}.bind(this));
}
Related
I'm trying a simple click event in Vue JS, but it doesn't seem to be working. I've checked the console for errors and there's nothing in there when I click the button or before. I'm following a tutorial so as far as I can see it should work? Is there something I've missed or I'm not understanding?
<body>
<div id="app">
<!--Listening for a click event-->
<button v-on:click="increase">Click Me To Increase Counter</button>
<p>{{ counter }}</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// Application instance (so here we can technically create two views dependant upon x, y or z)
new Vue({
// Element used to bind to
el: "#app",
// Data Properties (accessed from object using 'this.property')
data: {
counter: 0
},
// Element Properties
methods: {
increase: function() {
this.counter++;
}
}
});
</script>
</body>
I also tried the same code in stackoverflow snippet editor and it is working properly. Below is the working example. I hope this will be helpful.
// Application instance (so here we can technically create two views dependant upon x, y or z)
new Vue({
// Element used to bind to
el: "#app",
// Data Properties (accessed from object using 'this.property')
data: {
counter: 0
},
// Element Properties
methods: {
increase: function() {
this.counter++;
}
}
});
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<!--Listening for a click event-->
<button v-on:click="increase">Click Me To Increase Counter</button>
<p>{{ counter }}</p>
</div>
Thanks,
Jignesh Raval
Brother your Codebase is absolutely fine. It maybe your editor fault.
You just put your code in a html file and simply run with a browser. It will work perfectly.
Thanks.
I'm using Polymer 1.0 to create a web application.
I have various elements doing one or more iron-ajax calls and I have another element for showing the loading-overlay. But in my current sollution I have added the loading-overlay, with its logic to show or not, to every element doing ajax calls.
<dom-module id="backend-call-application">
<template>
<iron-ajax id='loadA' loading="{{_loadingA}}" ...></iron-ajax>
<iron-ajax id='loadB' loading="{{_loadingB}}" ...></iron-ajax>
<loading-overlay id="loadingOverlay" with-backdrop></loading-overlay>
</template>
<script>
Polymer({
is: 'backend-call-application',
observers:[
"_isXhrLoading(_loadingA,_loadingB,....)"
],
_isXhrLoading: function() {
for (var i = 0; i < arguments.length; i++) {
if (arguments[i]) {
this.$.loadingOverlay.open()
return;
}
}
this.$.loadingOverlay.close()
}
});
</script>
</dom-module>
Now my question is, what is the best way to show such a loading-overlay?
One idea of mine would be, to have something like an observer in the loading overlay. So every element doing requests will bind its properties to the observer. These properties could be stored in an array and everytime on change, the loading-overlay checks if at least one have loading properties set to true. When one or more properties are true the loading-overlay will be opened and when all requests finished loading it will be closed.
Another idea was to use events to tell the loading-overlay when a element starts/stops loading. But here will be the problem, that I have more than one request at the same time (The first request closes the overlay, but the page hasn't finished loading).
Edit:
The loading-overlay is an element containing the IronOverlayBehavior.
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../bower_components/iron-overlay-behavior/iron-overlay-behavior.html">
<link rel="import" href="../../../bower_components/paper-spinner/paper-spinner.html">
<dom-module id="loading-overlay">
<template>
<paper-spinner active="true"></paper-spinner>
</template>
<script>
Polymer({
is: 'loading-overlay',
behaviors: [
Polymer.IronOverlayBehavior
]
});
</script>
</dom-module>
use one property for loading instead of 2!!
<iron-ajax id='loadA' loading="{{_loading}}" ...></iron-ajax>
<iron-ajax id='loadB' loading="{{_loading}}" ...></iron-ajax>
bind loading value to loading-overlay with some attribute and when loading is true display loader
<loading-overlay id="loadingOverlay" is-loading="[[_loading]]" with-backdrop></loading-overlay>
so each time any ajax is made then _loading will become true so displays loader
I think the only thing you could shorten is the _isXhrLoading observer.
_isXhrLoading: function() {
if(Array.from(arguments).indexOf(true) >= 0) {
this.$.loadingOverlay.open()
} else {
this.$.loadingOverlay.close()
}
}
I am using Polymer and trying to get the app-router setup. I cannot get the app to direct the user to the landing page.
I have tested all of the other pieces individually, so I know that my pages will render.
<dom-module id="app-body">
<template>
<app-location route="{{route}}" use-hash-as-path></app-location>
<app-route route="{{route}}" pattern="/:page" data="{{data}}"></app-route>
<iron-pages id="content-pages" selected="{{data.page}}" attr-for-selected="name">
<landing-page name="landing"></landing-page>
<lobby-page name="lobby"></lobby-page>
</iron-pages>
</template>
<script>
window.addEventListener('WebComponentsReady', function() {
Polymer({
is: 'app-body',
properties: {
data: {
type: Object,
value: function() {
return {
page: '/landing'
};
notify: true
}
}
},
observers: [
'_onRoutePathChanged(route.path)'
],
_onRoutePathChanged: function(path) {
// If we do not have an initial URL, we redirect to /landing
if (!path) {
this.set('route.path', '/landing/');
}
}
});
});
</script>
</dom-module>
Firstly, you appear to have the same misconception I had, that to make properties transfer downwards you need notify true. That is not so, you only need notify true to export from your element to a parent element that uses it. In your case that is not needed.
What might be happening is that routeData gets set to {page: '/landing'} during your element initialization. At that point the route is not active so routeData is not mapped back to the route.path and fed back through app-location to the url. When it does eventually map back through, there is no change to routeData so the observer doesn't fire to tell you it has changed.
I'd like to ask you people about workflow with Polymer. I know that I should use my own elements or double check if element that I need isn't alredy published. It's really nice, I admit it. However the Polymer Starter Kit comes as single-page app. Is it the recommended approach for using Polymer? What about large pages that would need a lot of data to be loaded? Are there alternative approaches?
You don't need to have all your elements rendered at same time. They can be created on the fly only when needed, and can be destroyed as well.
To create your elements on the fly, you can use DOM Manipulation methods like:
var myElement = document.createElement("my-element");
this.$.container.appendChild(myElement);
myElement.myProperty = "anything";
To remove, just do this way:
var myElement = this.$.container.querySelector("my-element");
myElement.parentNode.removeChild(myElement);
If you need dynamic load a HTML Import, you can use this.importHref if your code is inside a polymer Element (and it should be).
this.importHref('myElement.html', function(e) {
// Create your element here
});
Putting things together...
Suppose you have a polymer element like that:
<dom-module id="my-app">
<template>
<div id="container"></div>
<paper-button on-click="_loadElement">Load Element</paper-button>
<paper-button on-click="_removeElement">Remove Element</paper-button>
</template>
</dom-module>
<script>
Polymer({
is: 'my-app',
_loadElement: function() {
this.importHref('myElement.html', function(e) {
var myElement = document.createElement("my-element");
this.$.container.appendChild(myElement);
myElement.myProperty = "anything";
});
},
_removeElement: function() {
var myElement = this.$.container.querySelector("my-element");
myElement.parentNode.removeChild(myElement);
}
});
</script>
I've been trying to use Polymer for a project I'm working on. And although I enjoyed it quite a lot so far, I ran into a problem I just can't solve.
I dumped it down to a simple example. Basically it's just a list element (item-list) that contains item elements (item-card) and i want to parse the items position to the element via the attribute pos. But for some reason the items attribute is allways undefined! Is this because the attribute is bound to the variable i, which dies after the template repeat? If so, how do I work around it? Is there a different approach I should be using here?
SOLUTION: You can find the solution by reading through all the comments, but to sum it up: apperantly there was a timing issue and the attribute wasn't ready at the ready callback. But I found out about the domReady callback (polymer lifecycle documentation). Using domReady it works just fine! Thanks to Günter Zöchbauer for the help!
This is the item-list.html:
<link rel="import" href="components/polymer/polymer.html">
<link rel="import" href="item-card.html">
<polymer-element name="item-list">
<template>
<style>
</style>
<template repeat="{{values, i in data.data}}">
<item-card pos="{{i}}"></item-card>
</template>
</template>
<script>
Polymer({
created: function()
{
this.num = 123456;
this.data = { "data":
[
{
"value":999
},
{
"value":666
},
{
"value":555
},
{
"value":222
}
]
};
}
});
</script>
</polymer-element>
This is the item-card.html
<link rel="import" href="components/polymer/polymer.html">
<polymer-element name="item-card" attributes="pos">
<template>
<style>
</style>
</template>
<script>
Polymer({
ready: function()
{
console.log("ready: " + this.pos);
}
});
</script>
</polymer-element>
I didn't bother putting the index.html in, since it just containts one item-list element.
Thanks alot!!
I think you need a pos field in <item-card> in addition to the attributes="pos" declaration.
The repeated element also references the bound model which can be accessed like querySelector('item-card').templateInstance.model property.
See https://github.com/PolymerLabs/polymer-selector/blob/master/polymer-selector.html#L286 for an example.
Info:
According to the comments it turned out to be a timing issue. The value wasn't yet assigned when the ready callback was called but using domReady worked.