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.
Related
The custom component
I've created a custom component for the navigation of my app. It consists of an ul element and all its li elements are dynamically created based on the items in the router's navigation list. This is happening in the attached event of the component. Nothing special is going on here.
The custom attribute
But because I want to have a good looking fancy menu I also created a custom attribute and implemented it into the root ul element of the custom component. In the attached event of the custom attribute I'd like to do some dom manipulations so that my menu looks like a menu created by those cool kids.
The problem
Although the attached event of the custom attribute is fired AFTER the attached event of the custom component, the dynamically created li items are not part of the dom in the attached event of the custom attribute yet.
The question
My assumption was that on attached event the view of the actual component is attached to dom and that all components before are also attached to the dom. And when I am done with the attached event the html that has been dynamically created here is also attached. Am I mistaken?
On a side note
I am aware of using TaskQueue could solve my problem. However, I would like to know if there's a different approach/solution first because I believe that moving things in time could cause a chain of paradoxes leaving you in a maintenance nightmare.
Allow me take away your misconception about the TaskQueue.
Your idea of moving things in time causing maintenance issues would certainly apply to using setTimeout() because that incurs an actual delay, and forces execution onto the next event loop. queueTask() is more or less the same thing (it uses setTimeout() internally).
However, the TaskQueue's queueMicroTask() method works very differently.
When you call queueMicroTask(), unlike queueTask() and setTimeout(), the task is scheduled for immediate execution on the same event loop. This is a very robust mechanism with a guaranteed execution order, and it's generally considered good practice to use it within attached() before doing any DOM manipulation.
In fact, queueMicroTask() is used internally by Aurelia in various places (mostly in binding and templating-resources). Two notable places are:
Property- and collection observers use it to "delay" notifying subscribers until all other bindings have completed their internal updating work
The repeat attribute uses it to set an ignoreMutations flag (and unset it after the queue is flushed) to prevent infinite recursion while updating its inner collection
You can generally consider there to be two "phases" to the bind() and attached() hooks: a non-queued, and a queued phase. The queued phase is when components do work that relies on the whole component graph to first be done with some other (usually recursive) process.
queueMicroTask() does not delay execution, just pushes it to the end of the call stack
It's the functional equivalent of passing the function as a callback to the end of the call stack, but saves you the trouble of writing the spaghetti code required to locate that last call and wire it all up. It's super clean.
all its li elements are dynamically created based on the items in the
router's navigation list. This is happening in the attached event of
the component
See, whenever you create anything during attached(), you can't rely on that thing being there during another component's attached() as this depends on the order of compilation/composition. That's an internal matter. This is especially true for custom attributes. Custom attributes (particularly those in style libraries) use the TaskQueue all over the place because it's the only way they can rely on the DOM being done.
Using queueMicroTask() here will guarantee two things:
It's executed when aurelia is completely done with the "first pass" of attacheds and rendering
It's executed immediately when aurelia is done with that - not even a microsecond delayed.
The best (and perhaps only correct) way to address this is indeed by using the TaskQueue - I promise :)
Whether via JavaScript or jQuery, are there any detriments to setting values to elements that don't exist?
In some of my generic functions that addresses dynamically-built DOMS, some class elements are assigned values and attributes but they might not always exist.
You can create elements that aren't connected to the DOM and do any operations to them that you'd do to normal DOM element. This is often a better approach because all your changes to the disconnected DOM element won't cause the browser to redraw. Then, after you've applied all your changes, you can attach that element to the DOM and only cause the browser to redraw once.
Likewise, you can assign as many classes as you wish, whether there are styles associated with them or not doesn't matter.
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.
I'm inexperienced with javascript/jquery. I was reading the docs for http://api.jquery.com/mouseover/ where it states:
The mouseover event is sent to an element when the mouse pointer enters the element. Any HTML element can receive this event.
1) What sends the event to the element ?
2) Is the event 'stored' in the element? I'm imagining that each HTML element in the DOM is treated as an object and the presence or absence of the event is a property of the object.
The browser itself generates the event, which is a special object implementing the Event interface and it is passed to the handler function as an argument. The event is not stored in the element, it is created by the engine.
You are on the right rack, this link should help you better understand Javascript and how browsers operate. It is the documentation for the DOM Level 2. This is when functionality to allow scripts and other programs to dynamically access and update various sections of content and structure of the DOM was added. It includes a list of the various interfaces that were introduced.
HTML elements on their own implement an interface called the HTMLElement, which is the base interface for HTML elements, either directly or indirectly through an interface that inherits it.
Elements themselves are interfaces that represent an object within the document. The Element interface lays out various properties that are common for numerous kinds of elements.
There are a couple of different interfaces within the web platform.
It's always a plus to look up these various interfaces and learn how they interact. The Document is a great starting place.
Assuming I have this code, button with inline onclick event:
<button onclick='..js stuff..'>mybutton</button>
I have this button created multiple times because of server-side loop.
Or I would be better giving a class to these buttons, and just do (using jQuery):
$(".button-class").on('click',function(){..});
What is better in terms of performance?
My questions are-
In the inline onclick, does it creates a handler for each button?
In jQuery event binding, does the handler is created only once, and is binded for each button, or, here as well, the handler is created multiple times?
I guess that these are the factors which affect any performance difference. Perhaps the only downside for .on(..) is that I have to do DOM search by class name. (?)
The answer is: it doesn't matter.
Use the latter (jQuery binding) because it moves the code away from the DOM and makes it easier to work with.
With the inline attribute a different handler is theoretically added for each event; each attribute implicitly creates a new callback/function1 that wraps the supplied code. This handler will be replaced if the attribute (or corresponding DOM property) is assigned a different value later. In the case when all the event handlers have been created this is the "worst" approach in terms of book-keeping.
With the jQuery (addEventListener) version the same function callback is added for all the matching elements. Multiple event handlers for the same element/event may be added; care may be required to avoid unintentional repeated-binding.
Furthermore, with delegated events jQuery could avoid binding to each element separately (ie. it only binds one event handler further up the propagation chain). Depending on how many elements are to have events "attached", this could result in a significant decrease of actual events listened to while still only using a single event handler function.
The chance of their being an actual real-world performance difference between the approaches is slim-to-none, degenerate cases aside. Use the form that is most clear/extensible/maintainable which, IMOHO, is rarely the event properties; especially when embedded directly into HTML attributes. (One issue with the inline attribute form is that it cannot bind to an appropriate closure context and so it must use - ick! - global context in many cases.)
1 Browsers first only had inline events (almost exclusively specified in HTML attributes) and are well-optimized for this case. The actual event handler function is only created on demand. Consider the case of <button onclick="alert(">Hi!</button>, where the "onclick" contains a syntax error in the inline JavaScript. A modern browser will only parse the JS (and thus only create the actual handler function) when the the button is clicked or the .onclick property is read.
Using onclick is frowned upon and considered bad form, you should instead be using element.on('click') with jQuery or ng-click in Angular.js.
They both result in the same number of listeners, and basically the same performance.
The counter part using on.event is: if you reload Or rerender the objects using akax you need to instanciate the on.event again, it creates more code and thats more complicated.