How do I access properties of parent or other elements in Polymer?
For example my top-most element is "my-app".
Now I am in an element called "my-element-1", how would I access/reference any properties from "my-app" using Javascript?
Further, if I am in "my-element-2", would I be able to access/reference properties of "my-element-1"?
EDIT: There are many reasons why I want to do this, and I certainly believe there are better ways than having to do this. But, I do not know the ways.
1: The first use case that comes to mind is that I use "iron-pages", which is located in a parent element. Each "page" is therefore a child of that parent.
The parent definitely knows which "page" is "selected" because the "iron-pages" is a property of the parent.
However, each "page" does not know if it is selected or no longer selected because the "selected" attribute is only known to the parent.
In this use case, I just want to be able to know from within the "page" whether it is still selected or not.
2: The second use case is probably a general design pattern. I want to maintain a set of GLOBAL properties, which can then be accessed anywhere from within the Polymer app. My assumption is that it should be store within "my-app" as the root element.
1 In a closed parent-child pair like with the iron-pages you can take advantage of the selectedAttribute and selectedClass properties. By setting one of them you can let the child "know" that it's selected.
2 The other example isn't so simple. For keeping shared state you can use the oxygen-state element which was mentioned on Polymer's Slack recently. Similar solution is possible with iron-meta which lets you access global variables.
The more robust solution to connecting elements app-wide is the Flux pattern. It appeared as a solution to a similar problem - communication between elements on a page. If elements are to communicate freely, it quickly can become unwieldy. You can read a number of related questions on SO: Flux architecture with polymer elements, Binding to global variables in Polymer, Polymer 1.0: How to pass an event to a child-node element without using <iron-signals>?
A simplification of this is to use events to notify any interested element of changes elsewhere in your application. A naive way is to listen for events on the document. Then there is also the iron-signals element.
Related
I’m trying to fire a callback passed down from a parent component. Our old pattern for handling this was to call the function in didInsertElement. In Octane, I see that we can use the did-insert modifier but that seems weird for this use case since we’re not updating the DOM element that we’d use to call did-insert. I've also seen onRender being used in a few cases but I don’t see documentation on that and it’s not firing for me. Any suggestions?
For this specific use case, we have a parent component that can have one of many child components. And for each child component we have specific text that gets displayed in the parent component and we want the child component to be the owner of that text.
Your instinct that did-insert isn't the right solution here is, I think, correct. In general, modifiers should only be used when the element they're going to be attached to is used in some way—that is, for managing interactions with the DOM. In general, we prefer one-way data flow otherwise. However, the scenario you've outlined looks similar to a "registration" pattern, where when a child is instantiated, it tells its parent "Hey, I'm here, here's the info you need about me."
There are a couple reasonable options in this case:
Rethink whether the child component should in fact own that data. I don't doubt you have a good reason for the child component owning the data, but the fact that the parent is already responsible for deciding which child to render may suggest that a mapping which includes both the component to render and the associated text could be a good solution in this space. That would cleanly solve this issue.
Given that per your description you currently do want to avoid having the parent own that data, you could also consider having the child yield the data. However, this usually only works if the DOM relationship for it makes sense. If it does, you could choose to do something like this:
{{yield (hash block='data' text=this.theText)}}
<div class='the-component-body'>
{{yield}}
</div>
<ChildComponent as |child|>
{{#if (eq child.block 'data'}}
<h2>{{child.text}}</h2>
{{/if}}
{{child}}
</Child>
(You can see this strategy working here—in particular, see the resulting DOM!)
While that's very powerful, again, it only works if your DOM layout supports it.
Finally, and perhaps most simply for the use case you have, you can (though not necessarily best, as I think the other options above are usually better when available), you can 'register' the value for the component by calling an action passed into your component during the constructor. Now, using a component's constructor this way for component behavior can be a problem in that it only runs once, when the component is first instantiated, and Glimmer and Ember keep the component instance stable and just change the values passed to them over time as much as possible, so if the value you're passing back up depends on the argument you pass to it, the constructor won't work. If it's always stable and does not depend on the arguments to a component, this does work, and it's often appropriate for a 'registration' pattern, where the child component simply needs to pass a single piece of data to the parent when instantiated.
I'm new to web components, and I've noticed some examples set the dom in the custom-element's constructor, while others do it in the connectedCallback.
As both seem to work fine(although I tried only Chrome), I assume the main difference is the case in which a user creates the element in js and not attaching it to the page?
I guess the main question here is whether I'm missing some other reason to prefer one method over the other.
Thanks.
Best practices and rules for custom element constructors
What's safe to do in the constructor
In the constructor, it's safe to
create the shadow root;
create and append elements *;
attach event listeners to those elements (scoped to your own ShadowDOM);
create attributes * (which might still be a bad idea because in the dynamic creation case this might come unexpected).
What you cannot do in the constructor
In the constructor, you are not allowed (amongst other things)
to read any attributes you haven't created beforehand...
to access child elements...
...because those might not be present in the non-upgrade case, and definitely won't be present when you dynamically create your custom element using either document.createElement('my-custom-element') or new MyCustomElement.
What's unwise to do in the constructor
In the constructor, you probably don't want to
attach event listeners to elements outside of the component's shadow DOM (like e.g. document, window), because these are the kind of listeners you should clean up in your component's disconnectedCallback (which will be called when e.g. your component is moved in the DOM).
Attaching these listeners in the constructor and properly cleaning them up in the disconnectedCallback results in missing listeners once your component gets removed from (and later re-added) or moved in the DOM.
*Pitfalls and things to be aware of
You need to be aware of the custom element lifecycle to not fall into otherwise obvious pitfalls, which include:
If you add attributes in the constructor and have included those in your component's observedAttributes, remember this will immediately trigger the attributeChangedCallback for those attributes, even if you element is not yet connected (a.k.a. in the DOM).
If you create and append other custom elements into your component's shadow DOM, remember this will trigger those components' connectedCallback.
In part, these best practices and rules follow https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-conformance, in other parts they deviate from recommendations done there in the spec.
Specifically I disagree on the following (given the scope for the listeners is outside the component), for the reasons I gave above.
In general, the constructor should be used to set up initial state and default values, and to set up event listeners and possibly a shadow root.
Let's say I register some widgets with some ids(let's say id1, id2) in someNode. Now if I do, domConstruct.empty(someNode), this will remove the widgets from someNode by doing someNode.innerHTML= "". But are the widgets still in memory?
What I mean to say is, after domConstruct.empty(someNode), will I be able to register a widget with id1 or id2?
If not, then how can I achieve the same? I do not want to check for widget with same id during its creation and destroy it if it exists.
P.S: I am assuming that dojo.empty() and domConstruct.empty() work in the same way.
dojo/dom-construct's APIs do not know anything about Dijit widgets, so when you run domConstruct.empty(node) or domConstruct.destroy(node), it does nothing to look for and destroy widgets. (I've explained how to do that in a largely-related question.)
When Dijit widgets are created, they are populated in a hash maintained by the dijit/registry module, which allows them to be accessed by ID (or node) in the future. dijit/registry.findWidgets is also used by dijit/_WidgetBase#getChildren (which is in turn used by container and layout widgets) to retrieve child widgets according to the DOM tree.
Since dojo/dom-construct knows nothing of Dijit, it simply destroys all DOM nodes underneath the given node, and doesn't update Dijit's registry. Thus, you end up with an inconsistent state, where Dijit thinks these widgets still exist, but their DOM has actually been destroyed. As answered in the other question, you should use findWidgets to properly destroy the widgets first, or consider using a layout widget or ContentPane to manage the child widgets for you.
**I'm building dojo tree using the following code:
The tree is displayed as expected. The problem that I have is that the onClick event is only fired on leaf nodes. When I click on the root level node(I have several root level) it is just open showing child nodes.
How can I add "extra" onClick functionality to the root nodes?
You've got openOnClick set to true for your tree. I think the API docs answer this as well as I possibly could.
http://dojotoolkit.org/api/dijit/Tree/openOnClick
That said, it looks like you'd be able to connect to _onClick instead and do whatever you want (that's the method responsible for calling onClick only when openOnClick is false, anyway). Or, if you wanted to feel a little less guilty about accessing private members, dojo.declare yourself a subclass of dijit.Tree, extending _onClick to also fire another function you define as public.
It seems like there should be, but I've looked over the API and nada. I don't see how it's done in 2.x. Currently, I store references with the elements to the tab and tabview objects via jQuery's data method.
The why: When I interact with the tabs via mouseovers and clicks, I need to be able to reference the YUI tab/tabview objects' properties & methods. Since I'm using event delegation b/c I have so many tabs (tabs within tabs), I'm not directly attaching (potentially hundreds of) event listeners, and thus I don't have access to the tabs and tabviews via the this symbol.
There's a corresponding method for buttons (YAHOO.widget.Button.getButton) but not one for tabs unless I'm missing something obvious.
Additionally, it seems that if I have a tab object, there's not a reference to the tabview. Once again, I create my own reference, but I assume there should be a reference or an accessor method already.
Can anyone shed any light on this? Anyone else approaching this differently?
The best place for YUI questions are the forums on yuilibrary.com.
The YUI TabView component has event delegation built-in. Every Tab event is actually handled by the TabView that it belongs to. Each events is routed to the appropriate Tab, and the context of the handler is set to the Tab.
This allows you to assign your listeners as you normally would:
tabview.getTab(1).on('mouseover', function(e) {
console.log(e.target.innerHTML); // e.target === Tab Label Element
console.log(this.get('label')); // this === Tab instance
});
This works for nested TabViews as well.
There is currently no binding between Tab and TabView except for the TabView's "tabs" attribute. You can iterate this collection and compare your element to each Tab's "element" attribute if there is a use-case for knowing which TabView it belongs to.
have you tried using Firebug, using the DOM tab/DOM subpanel, and actually looking through the attributes/properties/methods on the document and/or related elements? It's usually the fastest way to see what you can access.
also worthwhile to do a for..in loop to enumerate all of the properties/methods of a returned object if you are unsure what is available and unable to get that info via firebug.
e.g.
var properties = "";
for (prop in obj) {
properties += prop+"\n";
}
alert(properties);
This is true in most cases, not just your particular question.
EDIT
Having just checked the YUI examples for tabview, I see there is no property on the DOM elements for the tabs that refer to JS objects. I suppose this has been done to avoid DOM pollution, but it looks like you may have to make those reference yourself when creating the tabs/tabviews
e.g.
var tabView = new YAHOO.widget.TabView('demo');
document.getElementById("demo").tabView = tabView;