querying for elements that are inside binding helpers - javascript

In order to to get Polymer's data-binding without creating a custom element, I am using the "dom-bind" template helper. Later on, I am going to need to access the nodes inside the template so I can use masonry.js
to create a grid out of the data.
Here is the my template that is inside the main document:
<!-- Skills -->
<template is="dom-bind" class="careerSkills_consumer projects_consumer" id="resume-container">
<page-section id="resume">
<section-title>Skills and Projects</section-title>
<section-content>
<template is="dom-repeat" items="{{careerSkills}}">
<skill-category class="grid-item" title="{{item.header}}" skills="{{item.skills}}"></skill-category>
</template>
<project-showcase class="grid-item" projects="{{projects}}"></project-showcase>
</section-content>
</page-section>
</template>
The data itself is provided elsewhere and is irrelevant. The issue I am running into is that both dom-bind and dom-repeat seem to create local dom and put the result inside of it.
To create my grid, I need to access both the container for the grid, which will be the section-content element and the grid items, which are the skill-category elements inside the dom-repeat template.
If they all resided in the same document, I think could do (I am new to masonry, so this might not actually work):
document.addEventListener('WebComponentsReady', function () {
$('#resume section-content').masonry({
columnWidth: $('#resume skill-category')[0],
itemSelector: 'skill-category',
isFitWidth: true
});
});
But the queries don't seem to work because presumably the elements I need are hidden away from the main document in the shadow dom.
I was able to get access to the content inside #resume-container via:
Polymer.dom(document.querySelector('#resume-container')).node.content
However, I still can't get to the skill-category elements in the dom-repeat. This is getting kind of pedantic and I'm not even sure if it will work when masonry tries to do the positioning.
Is there a better way to go about this?
To be clear, this question is about how to properly gain reference to the content distributed inside of template helpers, but I would also appreciate any general advice to using polymer to do this sort of thing, where a custom element isn't exactly what I'm looking for since I'm only going to use the template in one spot and shadow dom is more hassle than help, but I need the data-binding.

Related

Benefit of using ng-container vs. template?

In angular we could do:
template: `<ng-container #c></ng-container>`
or:
template: `<template #c></template>`
To create a view container that is hidden when the template is rendered.
Is there a difference between using ng-container over the html template alternative? I'm guessing Angular has to have it's own template containers like ng-template or ng-container since use of the raw html template element could break non browser based runtimes, like mobile clients, etc.
The <ng-container> is always rendered, but does not represent a DOM element. It is still attached to the component's view.
The <ng-template> will only be rendered if it is explicitly requested.
Here's a good reference on the subject:
http://nataliesmith.ca/blog/2018-05-01-ngtemplate-ngcontainer-ngcontent/
To create a view container that is hidden when the template is rendered.
Always use <ng-template> when possible. The <ng-container> is for grouping DOM elements together. For example; when you need to apply a *ngIf to many <td> elements, because you can not use a <span> around <td>.

Use both Vue.js and jQuery autocomplete

I want to use both Vue.js and jQuery to make an autocomplete field displayed dynamically.
new Vue({
el: "#el",
data: {
toggle: false
}
});
var tags = [
"ActionScript","AppleScript","Asp","BASIC","C","C++","Clojure","COBOL","ColdFusion",
"Erlang","Fortran","Groovy","Haskell","Java","JavaScript","Lisp","Perl","PHP","Python",
"Ruby","Scala","Scheme"
];
$("#autocompleteSearch").autocomplete({
source: tags
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script src="https://unpkg.com/vue"></script>
<div id="el">
<input v-if="toggle" id="autocompleteSearch" type="search">
<button #click="toggle=!toggle">Toggle</button>
</div>
As you can see, it cannot work because of the v-if on the input. If I remove the v-if it works, but it is not displayed dynamically. How should I do?
There are a couple things to note here.
$(selector) looks up existing DOM elements at that point in time
For jQuery to find an element, it has to exist in the DOM when you perform the lookup. There are caveats to this regarding DOM Fragments, but that's not relevant for this particular question. So keep in mind that for jQuery to find something, it has to exist in the DOM.
Many jQuery UI methods initialize and transform elements
When you execute a jQuery UI method, like autocomplete, jQuery changes and creates markup on the page, related to the elements you are targeting. These methods can also keep internal variables related to the element as part of its initialization.
v-if creates and destroys elements
The purpose of the v-if is to say that, an element should only exist if some conditional is true. So if the conditional is false, it will not exist in the DOM. Also, if this conditional is subject to change, the element can potentially be created and destroyed many times.
So taking into account how v-if works, we can reflect on the two previous points.
If a v-if makes it so that an element does not exist when the jQuery selector runs, it will not find the element to initialize
If a v-if destroys an element, the jQuery UI widget may not function properly because it was related to the element that it initialized, and not a future element that is created to replace the previously created element.
With that in mind, when working with Vue and jQuery you have to keep in mind the needs of both, and how they may conflict with each other, such as in this case. The use of v-if is not necessarily the best directive to use when the element it controls is also used as part of a state in another library, such as jQuery.
In order to fix that, you can choose to use another directive, such as v-show or v-hide.
v-show and v-hide are two sides to the same coin. They determine if an element should be visible or not to the user. The distinct difference between these two directives and v-if is that v-show and v-hide do not remove the element from the DOM. They simply change the display state of the element.
So in relation to another library that relies on an element existing and using that element as part of some state management, this is great! You can control when your users see an element, and also potentially avoid conflicting with the secondary library, such as jQuery UI.
Having said that, this does not mean you should not use v-if. v-if is still useful for elements that should not exist at certain points in time. All this means is that you should be aware of the situation you are making, and consider any other logic in your application that may rely on those elements in order to minimize the chances of creating a bug.
TL;DR;
Here is the solution to your problem:
new Vue({
el: "#el",
data: {
toggle: false
}
});
var tags = [
"ActionScript","AppleScript","Asp","BASIC","C","C++","Clojure","COBOL","ColdFusion",
"Erlang","Fortran","Groovy","Haskell","Java","JavaScript","Lisp","Perl","PHP","Python",
"Ruby","Scala","Scheme"
];
$("#autocompleteSearch").autocomplete({
source: tags
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script src="https://unpkg.com/vue"></script>
<div id="el">
<!-- Just replace v-if by v-show -->
<input v-show="toggle" id="autocompleteSearch" type="search">
<button #click="toggle=!toggle">Toggle</button>
</div>

Automatic click element after all data loaded in Angular 2

I am trying to figure out how to automatic trigger a click event on certain element after all data are loaded.
the code is like this in html file:
<div *ngFor="let location of locations; let i = index;" class="location-wrapper" (click)="onSelect($event, i); $event.stopPropagation();">
<div class="location">{{ location }}</div>
</div>
the onSelect() method is doing some expansion of something that related to current location.
What I am trying to achieve is that I want the very first element of the *ngFor can be automatically clicked to show the things that related to it every time I get to this page.
Or maybe we can achieve it using other similar approach?
I have tried several ways to do this,
like putting some code in window.on('load', function() { // blablabla });
or using ngAfterViewInit() and ngAfterViewChecked(), both not work well.
You can do this in at least 2 ways. The first one would be old-fashioned javascript click(). The second would be just using component logic, just create an variable like selectedLocation which would hold current index or Object that is currently expanded. Don't forget to add initial value to it to make it after load page visible.
Javascript dispatchEvent (not Angular friendly solution)
Simply we just need to grab our item and use click() function. That's it. To grab an element we can use basic javascript method document.getElementById(elementId)" or with template variable.
<div
[id]="'location_' + i" <!-- For biding with document.getElementById -->
#locationPanel <!-- For biding with template variable -->
*ngFor="let location of locations; let i = index;" class="location-wrapper" (click)="onSelect($event, i); $event.stopPropagation();">
<div class="location">{{ location }}</div>
</div>
With Id it would look like document.getElementById("location_0").click() this gonna dispatch click event on element.
For template variable in your component you need to add
#ViewChildren locationPanel: QueryList<any>;
openFirstLocation() {
if (this.locationPanel && this.locationPanel.first)
this.locationPanel.first.nativeElement.click();
}
And in afterViewInit just call this.openFirstLocation();
Please note that it's not Angular friendly because Angular does not like when you interfere directly with DOM. However as long we don't change anything in structures then everything should be fine, but we should avoid manipulating dom with plain javascript.
Please note that too about using #ViewChild and document.* methods.
Use this API as the last resort when direct access to DOM is needed. Use templating and data-binding provided by Angular instead. Alternatively you can take a look at Renderer2 which provides API that can safely be used even when direct access to native elements is not supported.
Relying on direct DOM access creates tight coupling between your application and rendering layers which will make it impossible to separate the two and deploy your application into a web worker.
From Angular docs link

Bind data to content element in Polymer

I wanted to ask if it is possible to bind data to the element and access it later inside the actual content. Here is an example: I want to create a list component, however let the user define how to render every entry. Here is my current code:
List Element:
<template repeat="{{item in items}}">
<content></content>
</template>
User using it:
<ak-list items="{{items}}">
{{item.name}}
</ak-list>
However, this does not work
I suppose - you won't get access to data model from inside HTML portion in the web components.
You need to be defining the data in the template. I guess you might be already aware of that.
http://jsbin.com/yadazo/1/edit?html,output
A bin with how it could work.
Also, you can control the presentation by passing in an additional data which you can then use in your template -
An example of the same is below. -
http://jsbin.com/yateka/1/edit?html,output
both the list and how you want it can be supplied and then template created with that stuff.

Template rendered order: parent triggers before child template(s)

I'm quite new to Meteor and I'm having trouble trying to understand the "rendered" event on templates.
Assuming I have this two templates:
<template name="parent">
<div id="list">
{{#each childs}}
{{> child}}
{{/each}}
</div>
</template>
<template name="child">
<div class="item">
<!-- content -->
</div>
</template>
and these two events:
Template.parent.rendered = function () {
console.log('parent');
};
Template.child.rendered = function () {
console.log('child');
};
I always get this from console:
> parent
> child
> child
> child
So basically the parent template triggers "rendered" before the inner templates have finished rendering.
Because of that I'm unable to do execute any post operations to the DOM like jquery plugins.
e.g:
Template.parent.rendered = function () {
$('#list').myplugin();
};
Since this gets executed before inner templates are rendered it breaks the plugin.
Is there a workaround or a meteor event that I can use to safely now when a template is fully rendered, including it's inner templates?
My general advice for problems like this is you should look for a way to activate your plugin after rendering each child, rather than after rendering the parent - even if it means making extra calls to the plugin. If you can do that then it also solves the related problem of what happens when more children are added sometime later (of course this may not apply in your case).
It's hard to give a precise answer without knowing more details about what your plugin does but I can give an example from one of my applications:
I had to make sure all of the children were the same height as the tallest child. My initial reaction was that I had to somehow wait for all of the children to finish rendering and then adjust their heights once. The easier solution was just to resize all of them every time any of them were rendered. Sure it's O(N^2) comparisons, but it's still fast for a small list and works even when new children are added.
Note that if you absolutely had to call the plugin once and no new children could be added later, you could try an ugly hack to initialize the plugin only after all of them were rendered. For example:
Template.child.rendered = function () {
if ($('.child').length === Children.find().count()) {
$('#list').myplugin();
}
};

Categories

Resources