Should attributes be passed objects? - polymer

When I am passing in values to a polymer element attribute, I can pass in an object via Polymer's data binding. However, if I decide to leave "polymer-land" and want to set the attribute to an object, I cannot. Should I be passing in objects into attributes?
If the answer is "no, you should use methods for that"
then a follow-up question would be, "can I call an element's method without being in the Polymer framework?" For example, if I have a Polymer element:
<hello></hello>
and I want to access a method it has called "world". In Polymer it would be:
this.$.hello.world();
Is it possible to call this method without being in a Polymer element definition?

From inside Polymer
As you pointed out, inside of Polymer, one can use data binding to bind an object to a published property:
<polymer-element name="other-el">
<template>
<hello-world persons="{{persons}}"></hello-world>
</template>
<script>
Polymer('other-el', {
created: function() {
this.persons = [{'name': 'Eric'}, {'name': 'Bob'}];
}
});
</script>
</polymer-element>
From outside Polymer
From outside Polymer, Polymer elements also support serialized arrays/objects for attributes if their property type is hinted:
<hello-world persons="[{'name': 'Eric'}, {'name': 'Bob'}]"></hello-world>
Because .persons is a property on the element, you can also set it in JS:
document.querySelector('hello-world').persons = [{'name': 'Eric'}, {'name': 'Bob'}];

So to answer my own question.
I believe attributes should not be passed objects. If you need to pass an object into an element, use element methods.
To call a method, you can use simple element selection and method call.
document.querySelector('hello').world();
I thought I had tried this and it didn't work.
-Jay

Related

How to access Polymer functions from JS

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.

How can I add an additional binding to an element inside a dom-repeat?

I have a template dom-repeat element. I know that I can define the items attribute from an array, so that item can be accessed inside the template. However, I don't know how to access other objects inside the template if I bind them to customer polymer elements.
In this example, items is being defined by someItems, and item is passed into the element <my-el>. I also have a string mine that I want to pass into <my-el> and use there. I currently have the idea to do this in app-el.html, which contains the dom-repeat template:
app-el.html
<template is="dom-repeat" items="[[someItems]]">
<my-el item=[[item]] mine="[[mine]]"></my-el><br>
</template>
In theory, I would be able to access both in my-el.html like this:
my-el.html
[[item]] [[mine]]
However, when I try to access mine from inside <my-el> it is undefined. How can I correctly pass in this string so I can access it?
An MCVE can be found on this Plunker. Note how the item string is defined inside <my-el> but the mine string is not.
Your data bindings look correct, but there is no mine property for my-app. Did you mean to define <my-app>.myProperty as <my-app>.mine? Changing that property name to mine fixes your problem.
plunker

Polymer attributes and variable mapping

I have a polymer element looking like this:
<polymer-element name="foo" attributes="bar">
<template>
<core-ajax
url="url.php"
params="{{ {last: bar} }}"></core-ajax>
{{bar}}
</template>
</polymer-element>
Though, when I create element, the attribute shows up on the page, but the ajax request is like url.php?last so it seems that variable is undefined.
Also, this.bar returns undefined.
How can I solve that?
Keep in mind core-ajax has been deprecated in polymer 1.0 to iron-ajax.
Anyway, is params bind being resolved properly?
Based on documentation for new iron-ajax element the sintax should be:
params='{"alt":"json", "q":"chrome"}'

How to reference "this" in polymer expression

I want to pass the current polymer element as an attribute to another element. Using {{this}} returns null. Is there a supported way to access the value of "this" other than creating an attribute that returns the value
Example
<polymer-element name='my-el'>
<template>
<sub-element target={{this}}>
I think no. When a new instance of a polymer element is created, scope for template is the element instance and expressions are evaluated using this scope. So, {{this}} is evaluated using the element instance and expected to be a property on model.
"{{}}" appears to be the solution
I can't find documentation on it, but it works in v0.5 and is used in existing polymer components (core-a11y-keys)
According to the expression scopes section of the Polymer 0.5 documentation, you can use an empty binding expression: "{{}}". Note that this hasn't been documented in Polymer 1.0, so no guarantees, but it appears to still be working.
<polymer-element name="my-el">
<template>
<sub-element target="{{}}"></sub-element>
</template>
</polymer-element>
Alternatively, you can use the parentElement attribute which all polymer elements have. Here's an example from the documentation for using core-a11y-keys for keyboard navigation. Using `parentElement' would look like this:
<polymer-element name="my-el">
<template>
<sub-element target="{{parentElement}}"></sub-element>
</template>
</polymer-element>
Be careful with parentElement though. If you move the element, its parent could change.

Confused about the difference between Polymer attribute reflection and two way databinding

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.