I am using Polymer 1.0, and I'm having a problem retrieving and inserting into the dom and showing. I can get the HTML "li" to print to the console, but I am unable to get polymer to render the HTML.
<example-app appjs="appJsFile.js">
<ul id="content">
//I want Content Here from Polymer
</ul>
</example-app>
appJsFile.js gets imported into "example-app". I have a function for this:
for(var i in this.listData){
var _ele = parent.document.createElement('li');
_ele.innerHTML = this.listData[i].listDataName;
return _ele;
}
Any ideas?
Your code doesn't show where you add the element to the DOM.
You create the element and then return it. In your case this would leave the function after the first li element was created.
Where do you return it to?
parent.document.createElement('li') just creates an element. For it to be shown you need to add it to the DOM (like someElement.append(_ele)).
Direct DOM manipulation should be avoided.In your case you could use <template is="dom-repeat">. If you want to support browsers that don't yet allow <template> inside <ul> you can of course do direct DOM manipulation.
For performance reasons, Polymer 1 doesn't do full DOM API polyfill. You have to use Polymer DOM API to access DOM elements to ensure the polyfills are used.
Form the code in your question it's not clear what the context is.
Do you want to add the elements into the template of a Polymer element or add it as child elements or even outside Polymer elements.
In your case this could look like
var content = this.$.content;
for(var i in this.listData) {
var ele = parent.document.createElement('li');
ele.innerHTML = this.listData[i].listDataName;
content.append(_ele);
}
Related
I have a custom-element with shadow DOM, which listens to attribute target change.
target is supposed to be the ID of the element which my component is supposed to be attached to.
I've tried using querySelector and getElementById to get the element of the outer DOM, but it always returns null.
console.log(document.getElementById(target));
console.log(document.querySelector('#' + target));
Both of the above return null.
Is there a way to get a reference to the element in the parent document from within shadow DOM?
You just have to call ShadowRoot.
this.shadowRoot.getElementById('target') should work.
Here's an example, the get syntax will bind an object property to a function.
get target() {
return this.shadowRoot.getElementById('target');
}
There are two use cases of shadow DOM as far as I can see:
You control the the shadow DOM solely through your hosting custom element (like in the answer of #Penny Liu). If you want make sure no other script should call and alter the nodes than this is your choice. Pretty sure some banking websites use this method. You give up on flexibility though.
You just want to scope some parts of your code for styling reasons but you like to control it via document.getElementById than you can use <slot>. After all, many libraries rely on the document object and will not work in shadow DOM.
Back to the problem, what you probably did was something like this:
shadowRoot.innerHTML = `...<script>document.getElementById('target')</script>`
// or shadowRoot.appendChild
This is NOT working! And this is not how shadow DOM was anticipated to work either.
Recalling method 2, you SHOULD fill your shadow DOM solely by <slot> tags. Most minimal example:
<!-- Custom Element -->
<scoped-playground>
<style>some scoped styling</style>
<div id="target"></div>
<script>const ☝☝☝☝ = document.getElementById('target')</script>
</scoped-playground>
<!-- Scoped playground has a shadowRoot with a default <slot> -->
...
this.shadowRoot.innerHTML = "<slot>Everything is rendered here</slot>";
...
More advanced <slot> examples can be found at:
https://developers.google.com/web/fundamentals/web-components/shadowdom#composition_slot
I'm using the webcomponentsjs polyfill. No x-tag, polymer, etc. preferably vanilla JS.
After cloning a template and appending it to the document I'm not able to remove it again since it's missing a parentNode.
var tmpl = document.getElementById('tmpl');
var clone = document.importNode(tmpl.content, true);
document.body.appendChild(clone);
console.log(clone.parentNode); // parentNode is null (not undefined!)
clone.parentNode.removeChild(clone); // fails!
You may see yourself in this jsbin
My Question is: How do I remove the element again. Am I missing something?
You are mixing up DocumentFragment vs. Node. The content of template is apparently an instance of DocumentFragment:
<template>
<div>node 1</div>
<p>node 2</p>
etc
</template>
According to the documentation, Node#appendChild accepts document fragments:
Various other methods can take a document fragment as an argument (e.g., any Node interface methods such as Node.appendChild and Node.insertBefore), in which case the children of the fragment are appended or inserted, not the fragment itself.
So, what do you expect to be a parent of document fragment? It’s obviously null, since the entity “document fragment” is virtual in this context. So, to achieve what you want you are to create a container at first, and then append nodes to it / clean it’s content up.
<body>
...
<div id='container'></div>
...
</body>
There is common approach to add template content involving ShadowDOM:
var shadow = document.querySelector('#container').createShadowRoot();
shadow.appendChild(document.querySelector('#tmpl').content, true);
or not using ShadowDOM, inserting as is:
document.querySelector('#container').appendChild(
document.querySelector('#tmpl').content, true
);
Hope it helps.
Right now, I'm binding events to the parent element of my custom tag's rendered content, then using classes to target the event onto the element which my custom tag actually renders. I feel this is likely to cause strange bugs. For instance, if anyone on my team places two custom tags using the same targeting-classes under the same immediate parent element, it would cause multiple events to fire, associated with the wrong elements.
Here's a sample of the code I'm using now:
$.views.tags({
toggleProp: {
template: '<span class="toggle">{{include tmpl=#content/}}</span>',
onAfterLink: function () {
var prop = this.tagCtx.view.data;
$(this.parentElem).on('click', '.toggle', function () {
prop.value(!prop.value());
});
},
onDispose: function () {
$(this.parentElem).off('click', '.toggle');
}
}
// ... other custom tags simply follow the same pattern ...
});
By the time we hit onAfterLink, is there any reliable way to access the rendered DOM Element (or DOM Elements) corresponding to the custom tag itself? With no risk of hitting the wrong element by mistake? I understand that the custom tag may be text without an HTML Element, but it would still be a text node, right? (Could I even bind events to text nodes?)
In other places, and using (far) older versions of JsViews, I've bound events after the render using (sometimes a lot of) targeting logic built into the rendered elements as data- attributes. Not only is this a far more fragile method than I like for accessing the rendered data, it would be incredibly risky and convoluted to try to apply this approach to some of our deeply-nested-and-collection-ridden templates.
I also don't like needing to insert a span with my custom tag, just so I can apply classes to it, but if it's still necessary for the event, I'll cope.
I ask, then, what is a safe, modular way to bind events to the DOM so that I also have access to the data rendered directly against those elements?
Edit: As an additional concern, using onAfterLink won't let me bind events to non-data-linked rendered content. This may be part of the design intent of JsViews vs pure JsRender, but I don't yet understand why that would be the case.
Rather than using this.parentElem, you can use
this.contents()
which is a jQuery object containing all immediate content elements within the tag.
You can also provide a selector argument,
this.contents("someselector")
to "filter" , and include an optional boolean "deep" flag to both "filter" and "find" - i.e.
this.contents("someselector", true).
Using the above APIs ensures you are only taking elements that are actually within the tag content.
You may not need to remove the handlers in onDispose, if the tag is only deleted along with its content, you can rely on the fact that jQuery will dispose handlers when the elements are removed from the DOM.
You can only attach events to elements, not to text nodes. So if your content does not include elements, you would need to add your wrapper element, but not otherwise.
$.views.tags({
toggleProp: {
template: '{{include tmpl=#content/}}',
onAfterLink: function () {
var prop = this.tagCtx.view.data;
this.contents().on('click', function () {
prop.value(!prop.value());
});
},
onDispose: function () {
this.contents().off('click');
}
}
});
Also take a look at samples such as http://www.jsviews.com/#samples/tagcontrols/tabs which use the above approach.
I've always wondered how this jQuery feature works: $('<span>Hello world</span>')[0]
That is supposed to return a reference to the newly created span element. How can I achieve the same result using the native DOM methods? insertAdjacentHTML? innerHTML? documentFragment?
I need to insert a HTML fragment and hold a reference to the outer element without the need of using createElement/appendChild.
Thanks.
It's possible to create an element, set its innerHTML, and return the first child. The container element is never added to the DOM:
var el = document.createElement('div');
el.innerHTML = '<span>Hello world</span>';
console.log(el.firstChild);
If that's wrapped in a function, I believe the original container will be eligible for garbage collection as soon as the child is appended somewhere else.
jQuery seems to be doing something more sophisticated, checking if the string contains a single tag or not, and creating a fragment for more complicated strings. See the parseHTML method on jQuery's source code.
I want to know the most effctive way to dynamically update, insert or remove elements from an html page.
The outcome of this is that, I can change an input element into a div element and vice versa based on a user action.
eg
<form><input type="text" value="Value to save"/></form>
and based on some event, i will change that to
<form><div>Value to Save</div></form>
Tx
I think you could do this task this way (pure JS, without using external frameworks):
//retrieve the <form>
var form = document.getElementsByTagName('form')[0];
//retrieve the <input> inside the form
var input = form.getElementsByTagName('input')[0];
//create a new <div> DOM element
var newElement = document.createElement('div');
//The containing div text is equal to the input value (your case)
newElement.innerHTML = input.value;
//simple empty the form by set innerHTML = ""
form.innerHTML = "";
//append the <div> inside the form
form.appendChild(newElement);
By the way, I sugges you, if you want to manipulate DOM and do stuff like these in an easier way, learn how to do it by using frameworks like jQuery or mootools ;)
This is a general description:
Creating: You can create elements with document.createElement and then use one the various insertion methods to the insert the element at a certain position (e.g. Node.appendChild). You need to get references to related nodes first.
Most browser also support the innerHTML attribute for elements. You can set that attribute to an HTML(or text) string and the content of the element will be updated accordingly.
Updating: It depends on which data you want to update. E.g. an input element has an attribute value. In order to change the value of a text input you need to get a reference to that element first, then you can do element.value = 'new value'. For content, you can use the already mentioned innerHTML attribute.
Have a look at an HTML element reference to see what attributes they have.
Deleting: You want Node.removeChild.
I suggest to also have a look at DOM traversal methods and be aware of browser differences.
Using:
element.removeChild
element.appendChild
By using these methods, you can retain references to the elements in case you want to swap them back over again. Any event handlers in place will remain attached to the elements, too.
This depends on your definition of most effective.
If you mean the simplest way, then you can use a library like jQuery and do it like this:
$('form').html('<dynamic markup>');
If you mean the most performant way you can do the following:
document.getElementByTagName('form').innerHTML = '<dynamic markup>';