Why do Polymer's computed properties need explicit property arguments? - polymer

I was digging a bit into the Polymer 1.0 elements and I am a little curious about the computed properties.
For example, in paper-drawer-panel.html,
<dom-module id="paper-drawer-panel" …>
…
<div id="main" style$="[[_computeDrawerStyle(drawerWidth)]]">
…
</div>
…
</dom-module>
<script>
Polymer({
is: 'paper-drawer-panel',
…
_computeDrawerStyle: function(drawerWidth) {
return 'width:' + drawerWidth + ';';
},
…
</script>
drawerWidth is a property of paper-drawer-panel, so why is it so important to explicitly include it in the parameters of the computed property?
Is
[[_computeDrawerStyle()]]
…
_computeDrawerStyle: function () {
return 'width:' + this.drawerWidth + ';';
}
Is this bad practice?

Explicit arguments in computed bindings serve an important purpose: telling Polymer which properties the computed binding depends on. This allows Polymer to know when to recalculate and update the computed binding.
Take [[_computeDrawerStyle()]], for example. In this case, Polymer has no idea what other properties the computed binding depends on, and will only calculate it once on load.
As soon as you add drawerWidth explicitly ([[_computeDrawerStyle(drawerWidth)]]) Polymer now knows that it should run the computed binding again for a new value every time drawerWidth changes.

I think you are confused. What you are referring to in your code example here style$="[[_computeDrawerStyle(drawerWidth)]]" is a call to a private function called _computeDrawerStyle and of course you need to explicitly give it the right parameters. Check the documentation here to learn about computed properties.

Polymer has two separate concepts and you are confusing them.
Computed properties. These are properties that depend on other ones and are recalculated whenever their components changed. You can then databind the value of that computed property as a property value. <paper-draw-panel> does NOT have a computed property (I checked the code).
Function calls referenced in the databinding (which is what _computeDrawStyle) is. This causes Polymer to call that function (of the element) when ever any of its parameters changed. The parameters are all properties (or you can use subproperties of objects and indexes of arrays) This is what is happening here.

Related

Svelte if block's behavior is different form component's property when is set to undefined

I prefer to define a visible property for Svelte components rather than writing Svelte's if block in template. In my opinion, it is more clean to set a visible property instead of adding 2 lines of if block codes in the template.
I am trying to define a visible property for a Svelte component which must have the same behavior of if block.
Here is the repl.
El.svelte
<script>
export let visible = true // should be visible by default
</script>
{#if visible}
<svelte:element this="div" {...$$restProps}>
<slot />
</svelte:element>
{/if}
App.svelte
<script>
import El from './El.svelte';
let visible = undefined
</script>
<El {visible}>Component (undefined)</El>
{#if visible}
Svelte if block (undefined)
{/if}
The if block is working as expected, but the component is rendered which shouldn't be.
It seems that undefined property isn't passed to component during Svelte compilation.
What is the best way to make consistent behavior for visible property?
Update:
The reason I want to use as visible property is the final code is more readable. Here is a sample code using if block:
{#if invoice.payment}
{#if invoice.payment?.approved}
<Badge>Approved</Badge>
{#if !order.confirmed}
<Button>Void Payment</Button>
{/if}
{/if}
{#if invoice.payment?.status }
<b>{invoice.payment.tranasction.id}</b>
{/if}
{/if}
And using visible property:
<Div visible={invoice.payment?.approved}>
<Badge>Approved</Badge>
<Button visible={!order.confirmed}>Void Payment</Button>
</Div>
<B visible={invoice.payment?.status}>{invoice.payment.tranasction.id}</B>
The updated repl to support all scenarios.
But I'm not sure if it's correct implementation or future usage in nested components and event/property/action bindings.
Update 2:
I don't think this is a my-opinion-based usage: Vue.js has v-if and
Angular has ng-if that does the same thing as visible.
If you really want to stick with this approach (which I would not recommend - just use {#if} when you need it), then the best way to deal with this might be to not allow undefined to ever be passed as a value, because that resolves to the default set in the component (in this case visible = true).
So if you have a value that is potentially undefined, make sure it resolves to either true or false, e.g.
<El visible={visible ?? false}>
Edit in response to question update: Using property inspection is indeed a working approach, though I would not allow arbitrary values, only boolean, null or undefined. The simplest setup for this would be:
export let visible = undefined;
if ('visible' in $$props == false)
visible = true;
A reactive statement is not required in this case, because whether a property is defined at all does not change. (This requires setting visible={false} instead of visible="false".)
One reason to not do this at all is the use of $$restProps, as the docs state:
It shares the same optimisation problems as $$props, and is likewise not recommended.
Also, you will run into issues if you want to do event handling.

How do I bind an expression to <paper-button>.active?

I'd like to activate a <paper-button> using an expression bound to its active property, but this code doesn't work:
<paper-button id="btnPointDown" on-click="{{decrement}}" active="{{points == 0}}">\/</button>
How can I fix this?
In Polymer, you can't put an expression directly in the bindings. You have to use a function, which will return your condition result, and use it in the binding instead.
With your example, it would look something like this:
<paper-button id="btnPointDown" on-click="decrement" active="[[_checkIfPointsIsZero(points)]]">\/</paper-button>
The function _checkIfPointsIsZero will be evaluated each time points changes, assuming points is in the component's properties.
I also recommend using a simple data binding in that case.

Why do we need Polymer Observer if two-way bindng will work?

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

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 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.