Flux architecture with polymer elements - polymer

A flux architecture is trending in web applications and so is polymer elements.
Is there any example how to make a polymer application, which use flux architecture?

I've been thinking about using the Flux pattern with (Polymer) Web Components. Up to date I have come up with three possible solutions, all different from your way, so here they are:
DISCLAIMER I use Reflux library and not the Facebook's one.
Actions and Stores as elements
My first attempt was to make Flux pattern into elements so that any view, which need access to a store and invokes actions simply imports them.
<dom-module id="a-view">
<template>
<my-actions id="actions"></my-actions>
<my-store model="{{model}}"></my-store>
<span>{{model.property}}</span>
<button on-click="executeAction"></button>
</template>
</dom-module>
<script>
Polymer({
is: 'a-view',
executeAction: function() {
this.$.actions.doSomething({});
}
});
</script>
<my-actions> and <my-store> simply encapsulate actions and stores. There are some downsides to this method. First, potentially numerous non-visual elements are created, which can have detrimental effect on performance. Also creating those elements can be tricky if they should be Polymer elements, because they need static state. For a complete example see this repo
Flux without Flux
Recently I realized, again, what Web Components really are. With WC, your main API is the browser, namely elements, attributes and events. And Flux essentially is an event-driven data flow. So why not use Custom Events to communicate between custom elements? Here's an excerpt from my yesterday's plunk
<template is="dom-bind">
<a-view clicks="[[clicks]]" id="one"></a-view>
<a-view clicks="[[clicks]]" id="two"></a-view>
<a-view clicks="[[clicks]]" id="three"></a-view>
<a-store click-history="{{clicks}}"></a-store>
</template>
<script>
Polymer({
is: 'a-view',
properties: { clicks: Object },
fireClick: function() {
// invoking action is simply firing an event
this.fire('a-view-click', {});
}
});
Polymer({
is: 'a-store',
attached: function(){
document.addEventListener('a-view-click', function(ev) {
// do work and update store here
}.bind(this));
}
});
</script>
This is nice, because is not limited in any way to Polymer. Custom elements can be created with native API or other library and simply communicate with browser acting as your dispatcher. Of course this doesn't give you ways of synchronization out of the box, but is a simple and clean way without any clutter.
As you will see on Plunker, store updates by data-bindings. Another possibility is to fire off another event, though I'm not sure which would be better or when
Use Polymer's behaviors
Finally I've just had an idea, which improves upon the first, by replacing action/store custom elements by behaviors. There's no code yet, but here's a sketch:
var MyActionsBehaviour = PolymerFlux.createActions({ /*...*/ });
var MyStore = PolymerFlux.createStore({ /*...*/ });
Polymer({
is: 'a-view',
behaviours: [ MyActionsBehaviour, MyStore ],
onClick: function() {
this.behaviourAction.invoke({});
}
}});
Polymer({
is: 'a-store',
behaviours: [ MyActionsBehaviour, MyStore ],
attached: function() {
this.behaviourAction.listen(function() {
// 1. do work
// 2. update views
});
}
}});
I left the view updating part blank. It would likely take place by signalling an event but another possibility would be firing another action (Reflux has a nice concept of nested actions). Also I'm currently leaving the PolymerFlux.createActions and PolymerFlux.createStore for your imagination ;). The exact internals would ofc depend on the Flux implementation you choose.

I have made an attempt to use flux-type architecture in a polymer application.
Here is the main-app.html:
<link rel="import" href="./bower_components/polymer/polymer.html">
<link rel="import" href="store-cart.html">
<link rel="import" href="store-cart2.html">
<link rel="import" href="view-cart.html">
<link rel="import" href="view-additems.html">
<dom-module id="main-app">
<style>
</style>
<template>
<!-- Stores-->
<store-cart id="cart1" action=[[action]]></store-cart>
<store-cart2 id="cart2" action=[[action]]></store-cart2>
<!--Views and other stuff-->
<view-additems cart="cart1"></view-additems>
<view-additems cart="cart2" add="3"></view-additems>
<view-cart update="[[updateView]]"></view-cart>
</template>
</dom-module>
<script>
Polymer({
is: 'main-app',
properties: {
action: {
type: Object,
value: {}
},
updateView: {
value: ""
}
},
listeners: { //dispatcher event -> action
'viewAction': 'viewAction', // Action from view to be dispatched to the store/stores
'storeUpdated': 'storeUpdated' // storeUpdated-event from store to views
},
viewAction: function(e) {
action = e.detail;
switch (action.type) {
// "CombineCarts" is needed because both of the stores needs to be updated in order
case 'combineCarts':
this.$.cart1.addItems(this.$.cart2.nbItems);
this.$.cart1.updateViews();
this.$.cart2.emptyCart();
this.$.cart2.updateViews();
break;
// default action when store/stores can be updated independently
default:
this.action = action;
}
},
storeUpdated: function(e) {
this.updateView = e.detail;
}
});
</script>
The whole example: https://github.com/grohjy/polymer_flux_example
The main idea is that a "dispatcher" is located at the top most level of the polymer application and it's role is to redirect messages from stores to views and viceversa. Each store and view defines to which messages they reacts and how. At the dispatcher there is also an example how to update multiple stores in needed order.
The stores and some of the views are also located at the top most level of the application. A view can also have child views. A store shouldn't have any visual dom elements.
Please feel free to comment and share ideas.

Related

Pass data to another Polymer component

I am working on a dashboard, in which I have a search panel at the top (let's call it component A), where users can enter a query. The value of this input will change a lot of other components in the dashboard (not only components that are its direct descendants or siblings). I want to send the search value from component A to component B, which should then respond by performing some action with the input value.
I have tried a few things:
Directly calling the function in component B. Haven't been able to get that to work at all.
Manually setting B's local property value and using an observer to trigger a function call. I manager to set the value, but the observer does not trigger.
Using a global variable, which I can easily access across components, but I still can't trigger functions in specific components.
How can I best do this?
I'm relatively new to Polymer, so forgive me if my ideas aren't completely 'Polymerised' :)
Approach 1
<dom-module id="component-B">
<template>
</template>
<script>
Polymer({
is: 'component-B',
properties: {
id: '',
observer: '_idUpdate'
},
_idUpdate: function(){
console.log("HELLO");
}
});
</script>
</dom-module>
<dom-module id="component-A">
<template>
</template>
<script>
Polymer({
is: 'component-A',
idSearch: function() {
var id = this.$.search.value;
document.querySelector('component-B').properties.id = id;
},
});
</script>
</dom-module>
As you want to send data to multiple elements (which might not be siblings of the firing element) you can use any of these two methods
Use iron-signal to fire the signal and then in all the elements where you want the data use iron-signal tag to listen to the signal
<iron-signals on-iron-signal-<signal-name>="<function>"></iron-signals>
You can also use standard HTML method dispatchEvent to fire a signal and then add eventListeners in all the element where you want data.

Polymer 1.0: <iron-meta> usage

Proper usage of the Polymer 1.0 element <iron-meta> is confusing. Here is the link on Github. And here is the link to the Polymer demo site.
Can someone please provide a proper code example of how to make it work?
This is the code I have so far.
<dom-module id="generic-element">
<style>...</style>
<template>
<iron-meta id="meta" key="info" value="foo/bar"></iron-meta>
The <code>value</code> stored at <code>key="info"</code> is <code><span>{{test}}</span></code>.
</template>
</dom-module>
<script>
(function() {
Polymer({
is: 'generic-element',
properties: {
test: {
value: function(){
return "Hello world"; // This is the only thing I can get to work so far.
// return (new Polymer.IronMetaQuery({key: 'info'}).value); // Doesn't totally break.
// All my other below attempts totally fail. Everything breaks.
// return this.$.meta.IronMetaQuery({key: 'info'}).value;
// return this.IronMetaQuery({key: 'info'}).value;
// return this.$.meta.byKey('info').getAttribute('value');
// return this.$.meta.byKey('info').value;
}
}
}
});
})();
</script>
Here is the Github link to the issue. And here is a Github repository that contains the complete problem code in context of the complete web app.
The issue with your code is that you are trying to set your element property's default value to something that's declared inside that same element's template itself. Two of the things that happen between the time when the element is created and when that element is attached include a) properties' default values are set; and b) the template undergoes preparations to be stamped into DOM. These tasks happen asynchronously so in essence you are generating a race condition.
Try setting your test default value inside the ready() callback - the ready() callback guarantees that DOM is ready to be accessed, which in your case is exactly where you declared your <iron-meta> key.
<dom-module id="generic-element">
<style>...</style>
<template>
<iron-meta id="meta" key="info" value="foo/bar"></iron-meta>
The <code>value</code> stored at <code>key="info"</code> is <code><span>{{test}}</span></code>.
</template>
</dom-module>
<script>
(function() {
Polymer({
is: 'generic-element',
properties: {
test: String
},
ready: function () {
// this will work
this.test = this.$.meta.byKey("info");
}
});
})();
</script>
jsbin: http://jsbin.com/vosekiwehu/edit?html,output

Parent element is ready before its child - Polymer 1.0

This question had been modified to match the actual problem.
The original question mistakingly focused on iron-ajax, please see the original problem below. The question should have been:
Please advice why child iron-ajax element is not ready during the 'ready' callback of my-component defined as follows:
<dom-module id="my-component">
<template>
<link rel="import" href="../../../bower_components/iron-ajax/iron-ajax.html">
<iron-ajax
id="selectionLoader"
url=""
method="GET"
handle-as="json"
debounce-duration="300"
last-response="{{ suggestedOptions }}"
last-error="{{ lastError }}"
verbose=true
>
</iron-ajax>
</template>
</dom-module>
<script>
(function () {
Polymer({
is : 'paper-select',
ready : function() {
console.log(this.$.selectionLoader.generateRequest); // undefined
}
})
})()
</script>
Original question
Original title: 'WebComponentsReady' fires before iron-ajax ready - Polymer 1.0
I need to assign some values to an observed property of a custom component that internally uses iron-ajax with disabled auto - so I need to call .generateRequest on the iron-ajax element. This should happen when host page/component is ready, in order to fetch from the server some defaults based on data in the host component code.
selected is an array property on the component observed like this:
observers: [
'_selectedChanged(selected.splices)' // _selectedChanged calls .generateRequest
]
The observer is triggered by:
window.addEventListener('WebComponentsReady', function() {
document.querySelector('paper-select').selected = [{id : 11855},{id : 11856}];
});
The problem is that WebComponentsReady fires before .generateRequest is available on the iron-ajax. So my component is initialized, _selectedChanged is called, but iron-ajax inside it is missing the method and in fact other properties/methods as well.
I've implemented a "deferred" workaround using setTimeout inside the component and it works like charm but it's obviously not the way. Also everything works if the observer is triggered some time later after the page load, e.g. by user's typing. This shows that the logic works, it's just the timing that is wrong.
What am I missing?
The real issue was having the html imports inside my component's <template>.
The 'wrong' order of events makes sense as iron-ajax is not even registered at the time when its host calls the 'ready' callback.
I've moved the imports outside <dom-module> and now everything works as expected.

Polymer custom element with template-as-content

In the code below, the content "Foo" of template#bar are always empty when I try to access it programatically or when inspecting the DOM in Chrome. Can someone explain why?
In general, how does one provide a template defined in an outer element to an inner element so the inner element can access the content and conditionally clone or import that content?
I am using polymer 0.4.2.
<polymer-element name="x-inner" noscript>
<!--
How can I access the content "Foo" of <template>Foo</template>,
So that I can import/clone it here?
Using <content> moves the template from x-outer to x-inner,
but the template's .content property is empty, instead of 'Foo' as I expected.
-->
<content></content>
</polymer-element>
<polymer-element name="x-outer" noscript>
<template>
<x-inner>
<!--
How can I pass a template to a custom element?
I don't want the contents of this template to be rendered
here in x-outer, but instead conditionally rendered by x-inner
-->
<template id="bar">Foo</template>
</x-inner>
</template>
</polymer-element>
<x-outer></x-outer>
This topic is potentially complicated, below is something to get you started.
(This is the third update to this answer, confirming the bit above about 'complicated' =P).
Polymer includes the TemplateBinding.js library.
The TemplateBinding.jslibrary imbues <template> with numerous features, including data-binding to models, conditional stamping, and replication/iteration via arrays. It also adds a feature whereby cloned nested templates do not replicate their own contents, preventing a possible explosion of useless nodes when iterating. Instead, TemplateBinding.js creates references in cloned-nested-templates to original content-ful templates. The upshot is that if you are using TemplateBinding.js you should use template.createInstance() API for best results.
Now, when using raw templates without TemplateBinding.js, you can stamp a template simply using var nodes = document.importNode(template.content, true). Of course, in this case you do not get the nested template replication optimization (which may or may not matter).
Note:
I removed the <content> node from the <x-inner>
template because it serves no purpose. The code below plucks the
template directly out of light-dom, and stamps the instance into the
shadow-root.
Declare x-inner before x-outer because the latter depends on the former.
Example code:
<x-outer></x-outer>
<polymer-element name="x-inner">
<template>
</template>
<script>
Polymer({
domReady: function() {
this.renderTemplate();
},
renderTemplate: function() {
// note: this only works if `template` is a true child of `this`
// (as opposed to projected)
var template = this.querySelector('template');
// use createInstance from TemplateBinding.js library API
this.shadowRoot.appendChild(template.createInstance());
/*
// this would work for raw templates, but Polymer includes TemplateBinding.js
this.shadowRoot.appendChild(stampTemplate(template));
*/
/*
// if you don't know whether TemplateBinding.js exists or not,
// you could do something like this (see stampTemplate definition below)
this.shadowRoot.appendChild(stampTemplate(template));
*/
/*
// this way uses the binding feature of TemplateBinding.js
template.setAttribute('bind', '');
template.model = { /* some data */ };
*/
}
});
// use best available API
function stampTemplate(template) {
if (template.createInstance) {
return template.createInstance();
} else {
return document.importNode(template.content, true);
}
}
</script>
</polymer-element>
<polymer-element name="x-outer" noscript>
<template>
<x-inner>
<template id="bar">Foo</template>
</x-inner>
</template>
</polymer-element>
http://jsbin.com/nemaha/14/edit

Is it possible to share mixins across web components (and imports) in Polymer?

As a follow up to How to extend multiple elements with Polymer and Polymer multiple inheritence/composition, based on their answers, I wonder if it's possible to share mixins across multiple web components (and multiple imports) to reuse functionality.
Mixins seem to be the only way to share functionality across multiple custom elements. However, it seems like you can only use a mixin within one import. Which means, if you have a mixin, that gives a web component a specific functionality (let's say draggable), it's not possible to mix it into the construction of your Polymer element if it's not in the same import.
Maybe I got something wrong there but if not, it feels like that the use of mixins isn't very flexible either, because I'm still not able to share functionality across web components.
UPDATE:
As Scott Miles pointed in his comments out, it is possible to use mixins in more than one import. I just wasn't sure how to do that and it turns out, that it's very straight forward.
Let's say we have a mixin that should be shared across multiple components, but components are distributed over many imports. All one has to do is to define that mixin in its own import on the window object. So for example:
shared.html
<script>
window.sharedMixin = {
// shared functionality goes here
};
</script>
And then, reusing that mixin in another component in another import, is as simple as importing shared.html.
my-component.html
<link rel="import" href="path/to/shared.html">
From that point on, sharedMixin is available as global object within that import:
Polymer('my-component', Platform.mixin({
// my-component logic
}, sharedMixin);
I hope that helps others. I'll write a blog post about that and will link it here.
UPDATE 2
I've written a blog post here: http://pascalprecht.github.io/2014/07/14/inheritance-and-composition-with-polymer/
To use a global-like component is the recommended way to go.
make a <name-you-like> and use get/set to change it (also you can use attributes although they are only sad strings).
from Polymer API guide you'll see working (good looking) examples like this:
<polymer-element name="app-globals">
<script>
(function() {
// these variables are shared by all instances of app-globals
var firstName = 'John';
var lastName = 'Smith';
Polymer({
ready: function() {
// copy global values into instance properties
this.firstName = firstName;
this.lastName = lastName;
}
});
})();
</script>
</polymer-element>
And play with them using javascript es5 getters/setters such as in the above mentioned case would look like
<polymer-element name="app-globals">
<script>
(function() {
// these variables are shared by all instances of app-globals
var firstName = 'Arnold';
var lastName = 'Schwarzenegger';
Polymer({
ready: function() {
// copy global values into instance properties
this.firstName = firstName;
this.lastName = lastName;
},
get fullTerminatorName() {
return this.firstName + ' ' + this.lastName;
}
});
})();
</script>
</polymer-element>
I'll be back.
This is now addressed by the Behaviors feature.
Example:
my-behavior.html:
<script>
MyBehavior = {
properties: {},
listeners: [],
_myPrivateMethod: function(){}
// etc.
};
</script>
my-element.html:
<link rel="import" href="my-behavior.html">
<script>
Polymer({
is: 'my-element',
behaviors: [MyBehavior]
});
</script>
my-other-element.html:
<link rel="import" href="my-behavior.html">
<script>
Polymer({
is: 'my-other-element',
behaviors: [MyBehavior]
});
</script>