I'm searching for a way to access an attribute on a Polymer custom element from the DOM
or to send data from Polymer.register to the DOM.
This really simple element below takes two values and multiplies them, placing the result in its result attribute.
How can I access this result from the outside?
<element attributes='value times result' name='value-box'>
<template>
<p>{{result}}</p>
</template>
<script>
Polymer.register(this, {
ready: function() {
if (this.value != null && this.times != null) {
this.result = this.value * this.times;
}
}
});
</script>
</element>
result is a property on your element just like times and value. You can access it from outside JS, as you would any property on a normal HTML element. For example:
<value-box value="2" times="10"></value-box>
<script>
document.querySelector('value-box').result;
</script>
Internal to your element, what you want is to keep the result computed property up to date as times/value change. There are a couple of ways to do that. One is to use <property>Changed watchers [1]:
<element name="value-box" attributes="value times result">
<template>
<p>result: {{result}}</p>
</template>
<script>
Polymer.register(this, {
valueChanged: function() {
this.result = this.value * this.times;
},
timesChanged: function() {
this.result = this.value * this.times;
}
});
</script>
</element>
Demo: http://jsbin.com/idecun/2/edit
Alternatively, you can use a getter for result:
Polymer.register(this, {
get result() {
return this.value * this.times;
}
});
Demo: http://jsbin.com/oquvap/2/edit
Note For this second case, if the browser doesn't support Object.observe, Polymer will setup a timer to dirty check result. This is why you see "here" printed in the console for this second example. Run the same thing in Chrome Canary with "Experimental WebKit features" enabled in about:flags, and you won't see the timer. Yet another reason why I can't wait for Object.observe to be everywhere! :)
Hope this helps.
Just wanted to add a useful follow up to this (Even though the question has been answered).
My follow up is in response to the following comment on the actual answer:
I'm curious as to why selection with jQuery didn't work. Does it not recognize Custom Elements? – CletusW Jul 8 '13 at 19:57
The most likely reason jQuery didn't see your element is because it was not fully formed by the browsers run time at that point.
I ran into this problem while developing my ASP.NET MVC + polymer js sample app on my github page, and essentially what I was trying to do was call methods and access properties on my polymer object before polymer had made everything usable.
Once I moved the code I was using into a button click (So I could trigger it manually after I visually could see my component was ready) everything worked fine.
For now, if you try to access anything too soon, EG: in your jQ doc.ready handler, there's a good chance you'll run into all sorts of daft problems like this.
If you can find a way of delaying your action, or even better using polymer signals to signal from the components ready handler to an outside agent, that sets a flag telling you the component is ready, then you can sort this easily.
Related
I've tried to google my question but it makes me even more confused. My question is:
Here's the jQuery code:
$(document).ready(function() {
$(window).resize(function() {
if ($(this).width() < 200) {
$("p").css("color", "red");
} else {
$("p").css("color", "green");
}
});
}
Why do we write (this) and not ("this") ?
How do I know if (document) and (window) should be written with " " - and why's that?
Maybe you could link me somewhere that explains my issue. My code apparently works either way, I'm just curious about the why.
In JavaScript namespace, this is reserved [source].
The JavaScript object literal this refers to the inherited object from the present state in the current execution.
Another example of this we can see is when we are looping through an array and the object this would symbolize the current array object. You may, for example, see this.title, or this.description if we were iterating through a database array of blog posts.
this in jQuery refers to the inherited object. When we add the quotation marks, and it becomes a string, such as "this". This makes jQuery parse it as a DOM selector.
Then we are now looking for the HTML DOM selector <this>, which to my knowledge, does not actually exist in the accepted HTML syntax standards.
As otherwise stated, the concept of this will become tricky when you are working in other JavaScript environments, such as React or Angular. Within the context of a functional component, this becomes the state, such as handling user sessions.
With Polymer 1.* and WCT, when testing my element <sp-veteran></sp-veteran> I am not able to stub out the methods ._getSpComboBox() and ._getItems() in the ready function. I get Error thrown outside of test function: this._getSpComboBox(...)._getItems is not a function.
Since it is in the ready function, I need to use the WCT api stub instead of sinon.stub since the later requires me to grab the element which I can not do before fixture().
Any suggestions?
original code:
_getSpComboBox: function() {
return Polymer.dom(this.$.veteran.root).querySelector('sp-combo-box');
},
ready: function() {
if (this.editMode) {
this._getSpComboBox()._getItems();
}
this.$.veteranNoAjax.read();
this._setStyle();
}
test:
<test-fixture id="sp-veteran">
<template>
<h2>edit veteran in edit mode</h2>
<sp-app>
<sp-toast></sp-toast>
<sp-veteran edit-mode></sp-veteran>
</sp-app>
</template>
</test-fixture>
before(() => {
replace('sp-app').with('fake-sp-app');
stub('sp-ajax', {read: ()=> entitiesMock});
const _getItems = ()=> entitiesMock;
stub('sp-veteran', {_getSpComboBox: ()=> _getItems});
Unfortunately testing ready in Polymer1 is kind of a pain, or at least I haven't found an easy way that doesn't have odd side-effects. Calling the ready method after you've attached your stubs/spies is always an option but as I mentioned it can cause some odd issues. This was alleviated in Polymer2 as ready is called by the first call of connectedCallback for your element, so you can create the element then bind your spies and manually add to trigger it, just don't forget to remove after.
In the case of DOM manipulation in a Polymer element, you should be using the attached lifecycle instead, this will solve your issue as I mentioned above for testing, but it also saves you a weird potential usage case in the future. Since ready only runs once for an instance of an element, any logic in your ready statement won't get re-run if that element is re-used later, instead if you put the logic in your attached lifecycle if that element is removed from the DOM then added again later in another location it will rerun it's logic to fetch it's new children.
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'm starting to learn angularJS better, and I've noticed that AngularJS tries to make strong emphasis on separating the view from the controller and encapsulation. One example of this is people telling me DOM manipulation should go in directives. I kinda got the hang of it now, and how using link functions that inject the current element allow for great behavior functionality, but this doesn't explain a problem I always encounter.
Example:
I have a sidebar I want to open by clicking a button. There is no way to do this in button's directive link function without using a hard-coded javascript/jquery selector to grab the sidebar, something I've seen very frowned upon in angularJS (hard-coding dom selectors) since it breaks separation of concerns. I guess one way of getting around this is making each element I wish to manipulate an attribute directive and on it's link function, saving a reference it's element property into a dom-factory so that whenever a directive needs to access an element other than itself, it can call the dom-factory which returns the element, even if it knows nothing where it came from. But is this the "Angular way"?
I say this because in my current project I'm using hard-coded selectors which are already a pain to mantain because I'm constantly changing my css. There must be a better way to access multiple DOM elements. Any ideas?
There are a number of ways to approach this.
One approach, is to create a create a sidebar directive that responds to "well-defined" broadcasted messages to open/close the sidebar.
.directive("sidebar", function(){
return {
templateUrl: "sidebar.template.html",
link: function(scope, element){
scope.$root.$on("openSidebar", function(){
// whatever you do to actually show the sidebar DOM content
// e.x. element.show();
});
}
}
});
Then, a button could invoke a function in some controller to open a sidebar:
$scope.openSidebar = function(){
$scope.$root.$emit("openSidebar");
}
Another approach is to use a $sidebar service - this is somewhat similar to how $modal works in angularui-bootstrap, but could be more simplified.
Well, if you have a directive on a button and the element you need is outside the directive, you could pass the class of the element you need to toggle as an attribute
<button my-directive data-toggle-class="sidebar">open</button>
Then in your directive
App.directive('myDirective', function() {
return {
restrict: 'A',
link: function(scope, element, attrs) {
angular.element('.' + attrs.toggleClass).toggleClass('active');
}
};
}
You won't always have the link element argument match up with what you need to manipulate unfortunately. There are many "angular ways" to solve this though.
You could even do something like:
<div ng-init="isOpen = false" class="sidebar" ng-class="{'active': isOpen}" ng-click="isOpen = !isOpen">
...
</div>
The best way for directive to communicate with each other is through events. It also keeps with the separation of concerns. Your button could $broadcast on the $rootScope so that all scopes hear it. You would emit and event such as sidebar.open. Then the sidebar directive would listen for that event and act upon it.
In Polymer (0.5.2) it appears that global HTML attributes, like lang, cannot be cleanly observed? For instance:
<polymer-element name="x-foo" attributes="lang bar">
..
<script>
Polymer({
langChanged: function () .. // never fires by itself
barChanged: function () .. // fires just fine
});
</script>
</polymer-element>
The interesting part is that the callback does fire if the element is dirtied in other ways. For example:
..
ready: function () {
this.lang = 'en';
}
..
This does not trigger any callback. However:
..
ready: function () {
this.lang = 'en';
this.bar = 'baz';
}
..
This fires both callbacks. So it appears that Polymer isn't correctly being notified about changes to "native" attributes? Is this a known issue? Can this be worked around?
The team advises folks to not define properties/attributes that have the same name as native DOM properties/attributes. link
Avoid defining a property or method with the same name as a native DOM property or method, such as id, children, focus, title and hidden; the results are unpredictable.
This is because Object.observe cannot actually observe those properties. They're coming from the C++ black box inside the browser.
My advice would be to use language instead of lang.
I cant seem to recreate this. What browser are you using?
http://jsfiddle.net/6sqo159z/16/
I had a few similar issues when I used Polymer extensively. Namely on IE.
Are you using the latest version of polymer and platform?
Try giving your attributes default values, even if those values are undefined.
Try including a publish object for your attributes.
publish:{
bar:"",
lang:""
}