I am trying to make a polymer element that authenticates users against an SMF forum. I want the element to expose a "user" property which will initially be an empty object {} but if/when the element has made an ajax request and is able to authenticate the user, this object will contain details about the user (and an authentication token) for use in the rest of the application. The rest of the application is in the content section of the element, and will only be displayed when authetication has happened.
So the application is structured like this
<smf-auth login="login/url", fail="fail/url", splash="initial/splash/img" user>
rest of application which will need access to user
</smf-auth>
I have published user with reflective properties, and set its initial value to {}. However when I run unit tests, I run javascript to get the element (as a javascript variable) and look at el.user and it has the value "" (ie blank string). I tried altering the code to initialise user as something more complex, but I still get a blank string.
Here is the element definition (with my more complex user)
<polymer-element name="smf-auth" attributes="login fail splash">
<template>
<core-ajax id="req" url="{{login}}" handleAs="json" on-core-response="{{handleResponse}}"></core-ajax>
<img id="splash" src="{{splash}}"/>
<template if="{{autheticated}}">
<content></content>
</template>
</template>
<script>
Polymer('smf-auth',{
publish:{
user:{
value:{token:{},uid:0,name:''},
reflect:true
}
},
created:function(){
this.authenticated = false;
},
attached:function(){
this.$.req.go();
},
handleResponse:function(response){
//TODO decide what the response is
}
});
</script>
</polymer-element>
So how should I declare and use the "user" property so that the content of the element (more polymer elements) has access to it
You need to specify a data binding to a user property when you instantiate the smf-auth element. Then you can access this property inside the element's body:
<smf-auth user="{{user}}" ...>
Hello {{user.name}}!
</smf-auth>
This assumes that your smf-auth element is itself inside a Polymer element. Otherwise you need an auto-binding template element.
If your element hierarchy is deep and you need the user object in an element at the bottom, it can be a problem to pass the user property down the hierarchy. In this case another option might be to use core-signals and send an event when the user has logged in. Inner elements can then listen for this event and fetch the user object from the event details.
Related
According to observer, it observes all changes to Polymer properties.
But, two-way binding (using {{}}) already does that, right? Why do we need observer to process the changes?
They are similar in that a value-change invokes the effects of both an observer and data binding, but they have different purposes.
Two-way data binding
A two-way data binding is an annotation
Sets a property of element A to the value of another property in element B
Any changes to B's property automatically update A's property, and vice versa
Observer
An observer is a function that is called whenever a value-change occurs to one or more properties
Example: To set an element's operational mode based on the value of its enabled property
An observer's purpose is not necessarily to set another property (unlike data bindings). It could call another function based on the new property value.
Example: To generate an AJAX request when a url property changes
The observed properties must be in a single element (e.g., a parent element cannot observe a child's property without binding it to its own copy of the property)
I think you got both of them mixed up.
<dom-module id="my-element">
<template>
<paper-input value="{{myValue}}">
</template>
<script>
Polymer({
is:"my-element",
properties:{
myValue:{
type:String
}
}
</script>
</dom-module>
value="{{myValue}}" can be read as whenever there is any change value, myValue will also get updated.
Now, consider a scenario where you want to be informed whenever myValue changes. Above written code is not enough for that (yes, i know you can listen on value-changed event to know about the change, we'll come back to that later). In order to do that you'll need to add observer on myValue only then you'll know when myValue has changed.
Above mentioned case had another solution ie listen to value-changed event fired by paper-input, but what about cases where your property is not binded to any element(its getting its value from db let's say) and you want to know when its value changes.
So to summarise it two-way binding is used when you want to know that value of some property which is not part of your own shadow-dom has changed and observer is used when you want to know about the changes in properties inside your own dom
EDIT - THIS IS A COMPLETE RED HERRING. One of the user properties down the hierarchy had the readOnly property set for user. This was preventing it propagating.
I am struggling to understand databinding and how values propagate when the property changes I have a tree structured set of elements (the structure is spread across separate element definitions - not with <content> tags as possibly implied by the structure show below)
<my-app user="{{user}}">
<my-session user="{{user}}">
<my-login user="{{user}}"></my-login>
</my-session>
<template is="dom-if" if="[[user.name]]">
<my-pages user="{{user}}">
<iron-pages>
<my-menu user="{{user}}"></my-menu>
<my-reports user="{{user}}"></my-reports>
</iron-pages>
</my-pages>
</template>
</my-app>
Each of these elements at their different definitions define a property
user : {
type: Object,
notify: true
}
And all the elements are linked with two way data binding
<my-pages> is lazy loaded using importHref after the user has logged on (and therefore user.name is defined)
I have a property of user called keys which is used for access control. In particular both <my-menu> and a sub element of <my-reports> uses this to determine which menu items to display.
This all works fine on initial log on. But if I change the logged on user, then this change to the user property is apparently not propagating properly
What I can see is that from the debugger triggered during a page change from iron pagess I can see that the <my-app> 's user has the new logged on user value BUT <my-pages>'s user has the old user. For some reason data binding of user is not working down the tree structure, even though it appears to have successfully propagated up from <my-login> to <my-app>. .
I am assuming that possibly the "object" of user is not changing only the paths. I am getting confused about what I should be doing here. Can someone help.
Really cannot tell what's wrong with your code with the information that you have provided except for the syntax error where instead of closing my-pages you have started a new one, but here's a plunker emulating your code. I was able to successfully change the user for all the elements.
Sorry if this comes out a bit garbled, I'm not sure how to ask this question.
What I am trying to do is keep the DOM synced with a localStorage value, and am updating the localStorage value with an interact.js mouse event.
Currently, I am able to properly set the localStorage value, but am having problems updating the DOM.
My current build is within the Polymer framework, so I am having trouble selecting shadow DOM content.
The DOM tree looks like
PARENT-ELEMENT
# SHADOW ROOT
EL
EL
DIV
CUSTOM ELEMENT
EL
EL
Here are some ways I have failed to solve the problem. The Custom Element is in pure JS, since I am not sure how to properly wrap interact.js function in Polymer:
I tried directly accessing the PARENT-ELEMENT's shadow DOM from the Custom Element in pure JS.
var shadowDOMNode = document.querySelector('PARENT-ELEMENT');
var dom_object_1 = shadowDOMNode.querySelector('#dom_object_1');
dom_object_1.innerHTML = localStorage.dom_object_1;
I tried selecting a helper updateDOM() function from the PARENT Polymer element and running it from the Custom Element's setter directly.
if (event.dy > 0) {
this.$$('PARENT-ELEMENT').updateDOM();
}
Maybe I am taking the wrong approach entirely, but I haven't been able to find analogues for interact.js in using native Polymer functions.
I hope this question was clear enough...
If we ignore the interact.js part of the problem and focus on Polymer, you could probably solve this without coupling the two.
To bind to a localStorage value with Polymer, use the <iron-localstorage> element. In the following example, the localStorage value named flavor_1_amount is loaded and stored into a property named _flavor1Amount. If the value doesn't exist in localStorage or is empty, the <iron-localstorage> element fires an event (iron-localstorage-load-empty), which allows you to bind to a callback (e.g., to initialize it).
<iron-localstorage name="flavor_1_amount"
value="{{_flavor1Amount}}"
use-raw
on-iron-localstorage-load-empty="_initFlavor1Amount">
</iron-localstorage>
In the same element, you could provide an input for the user to update the localStorage value.
<paper-input label="Flavor Amount (mL)" value="{{_flavor1Amount}}"></paper-input>
And you can use <iron-localstorage>.reload() to keep your data binding in sync, assuming it could be changed externally.
See this codepen for a full demo. Check your localStorage from Chrome DevTools:
Generally speaking you should use this.set() or any of the array mutation methods if it's an array in order for the ShadowDOM to be notified properly.
Since you want to perform this update from outside the element itself, imperatively, I'd suggest this:
Expose a couple of methods from your element that you can use to add/remove/change property values from outside your element.
These methods would internally use the proper channels to make the changes.
An example (you can call addItem() to add items from outside your element):
<base href="https://polygit.org/components/">
<script src="webcomponentsjs/webcomponents-lite.min.js"></script>
<link href="polymer/polymer.html" rel="import">
<dom-module id="x-example">
<template>
<template is="dom-repeat" items="[[data]]">
<div>{{item.name}}</div>
</template>
</template>
<script>
HTMLImports.whenReady(function() {
"use strict";
Polymer({
is: "x-example",
properties: {
data: {
type: Array,
value: [
{name: "One"},
{name: "Two"},
{name: "Three"}
]
}
},
// Exposed publicly, grab the element and use this method
// to add your item
addItem: function(item) {
this.push("data", item);
}
});
});
</script>
</dom-module>
<x-example id="x-example-elem"></x-example>
<script>
setTimeout(function() {
// simply 'grab' the element and use the
// `addItem()` method you exposed publicly
// to add items to it.
document.querySelector("#x-example-elem").addItem({name: "Four"});
}, 2500);
</script>
Important: That being said, this is not the "Polymeric" way of doing stuff as this programming-style is imperative, in constrast with Polymer's style which is more declarative. The most Polymeric solution is to wrap your interact.js functionality in an element itself and use data-binding between your 2 elements to perform the changes.
I have run into a problem where paper-dropdown element's on-core-select event is being fired by a core-selector belonging to a separate element in my polymer app. Here are excerpts from the polymer element that includes the paper-dropdown (along with the polymer event script):
<paper-dropdown id="widthUnits" class="unitSelection" selected="{{item.data.designWidth[1]}}" on-core-select="{{ conditionUnitSelectionChanged }}" valueattr="label">
<paper-item label="mm"></paper-item>
<paper-item label="cm"></paper-item>
<paper-item label="m"></paper-item>
</paper-dropdown>
conditionUnitSelectionChanged: function(e, detail, sender) {
// Ensure the selection has in fact changed
if (sender.selected != detail.item.label)
{
this.ajaxUpdateUnit(sender);
}
},
And here is the core-selector and related code that is part of an entirely different element within the application. FYI, SelectedItemKey is watched by polymer 'Changed' events in both of the elements involved...if that matters.
<core-selector id="itemSelector" target="{{$.itemList}}" multi="false" selected="{{selectedItemKey}}" selectedAttribute="active"></core-selector>
<div id="itemList" layout horizontal wrap>
<template repeat="{{item, i in items}}">
<div id="{{item.name}}">
<entriesHeading name="{{item.name}}" horizontal layout center>
<div flex>{{item.name}}</div>
<paper-menu-button icon="more-vert" halign="right">
<paper-item label="Edit" on-tap="{{ itemEdit }}"></paper-item>
<paper-item label="Copy" on-tap="{{ itemCopy }}"></paper-item>
<paper-item label="Delete" on-tap="{{ itemDelete }}"></paper-item>
</paper-menu-button>
</entriesHeading>
<entriesContainer vertical layout>
*** container contents ***
</entriesContainer>
</div>
</template>
</div>
Any suggestions on how I can avoid this unwanted interplay with core-select events? Perhaps a specific listener of some sort (limited to listening for paper-dropdown(s) core-select event)?
It's not possible for the paper-dropdown to receive an event from anywhere but inside it's own subtree. You have to present a jsbin or some kind of reproduction, otherwise I must suggest your diagnosis is incorrect.
You should try to figure out what is going on with the events, to make sure you have good understanding of the system.
Having said that, another way of approaching the problem is by being data-driven and not control-driven.
IOW, it's best to react to data-changes instead of events. It's hard to give really good suggestions because I can only see a tiny piece of your application, but here are some suggestions:
You have
<paper-dropdown id="widthUnits" class="unitSelection"
selected="{{item.data.designWidth[1]}}"
on-core-select="{{ conditionUnitSelectionChanged }}" valueattr="label">
It's a bit unfortunate that this important data is referenced as item.data.designWidth[1]. Generally one wants to factor the application data so that you aren't using deeply nested expressions like that. Just as an example, if you could build a UI like design-width-editor and bind it to <design-width-editor designWidth="{{item.data.designWidth[1]}}"> then you could put logic inside of design-width-editor that just deals with designWidth and doesn't need to know about item or data. This gives you a lot more flexibility with your data structures and makes it easier to think about.
In any case, given the construction you have, one thing you could do is observe the data directly:
observe: {
'item.data.designWidth[1]`: 'designWidth1Changed'
}
Now you can implement designWidth1Changed() to take the needed action. The key bit here is that you are no longer dependent on any particular UI for modifying the designWidth data. You can replace that UI at will; all that matters is that if the value changes, some action is taken.
Scott put me on the right track. After some refactoring as described in the previous comments, I used async to my advantage in order to avoid observers executing when I didn't want them to (such as when the elements model item object changed...and therefore all of its observed properties). Here is a sample of some of the script code from the host element mentioned above that was implemented to resolve the final issue:
ignoreChanges: null,
observe: {
'item.data.designWidth[0]': 'designWidthValueChanged',
'item.data.designWidth[1]': 'designWidthUnitChanged',
}
designWidthValueChanged: function(oldVal, newVal) {
if (!this.ignoreChanges) {
// send update of width value input via ajax
this.ajaxUpdateCondition("designWidth", newVal, this.item.data.designWidth[1]);
}
},
designWidthUnitChanged: function(oldVal, newVal) {
if (!this.ignoreChanges) {
// send update of width unit selection via ajax
this.ajaxUpdateCondition("designWidth", this.item.data.designWidth[0], newVal);
}
},
itemKeyChanged: function(oldVal, newVal) {
// itemKey is a published attribute that is 2 way bound to a parent element (where item selection occurs from a collection)
this.toggleIgnoreChanges(true); //set flag to ignore changes to observed values while item object switches
this.item = this.items[newVal]; //point to the correct/selected item in the collection (items is published attribute of this element)
this.async(this.toggleIgnoreChanges); //reset flag after observe functions have executed
},
toggleIgnoreChanges: function(flagstatus) {
this.ignoreChanges = flagstatus || !this.ignoreChanges;
}
Let's say I have a Polymer element x-foo which uses templates for data-binding.
<template>
<!--shadow DOM-->
<template repeat='{{item in items}}'>
<div class='content'>{{item}}</div>
</template>
</template>
items is a property of x-foo which decides what is present in the view.
Now, on the fly in one of the methods of x-foo I do:
this.items = getNewItemList();
and then try to access shadow DOM content,
this.shadowRoot.querySelectorAll('.content') // was supposed to return 5 elements
I find that Polymer still hasn't iterated through the template loop and generated my shadow DOM content. Is there a way to know when it has finished it?
By design, Polymer waits until your JavaScript has finished processing before it does expensive things like messing with DOM. That way you can do several operations at once and not worry about thrashing DOM and slowing down your application.
The short answer to your question is to do something like this:
this.items = getNewItemList();
this.async(
// `async` lets the main loop resume and perform tasks, like DOM updates,
// then it calls your callback
function() {
this.contents = this.shadowRoot.querySelectorAll('.content');
}
);
A better answer is to avoid needing to query for the elements. Instead, let the elements communicate with the container via events or even using the 'item' objects (the data model). If you can drive your UI from your data-model, and not the reverse, you will have a better time.