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.
Related
The component I have has a list of names with an add button beside each
<h1>Select one of me!</h1>
<div>
<button>contrived</button>[I am already selected]
<button>example</button>
<button>beep</button> [I am already selected]
<button>boop</button>
</div>
<h1>Selected</h1>
<button>contrived</button>
<button>beep</button>
If I wanted to select beep or contrived in the bottom list, how should the buttons be configured so that I can easily find them without test-ids or using my knowledge of the current structure (i.e. I want to avoid getting the parent element and using the within helper if there's a better way).
Without using explicit test IDs or other attributes, you could use the getAllByText query to retrieve both and use the one you want.
const [firstBeep, secondBeep] = screen.getAllByText('beep')
This does assume you have knowledge about their ordering in the DOM, but doesn't take into account the structure itself.
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
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.
Knockout templating system is great, however in a web app where there are several separated contexts ("views") that are loaded by ajax, one issue appears:
Templates rely on ID
This means that if my chance you have one template with the same name on a view that on another view loaded previously and still existing in the webapp context, knockout (because the browser does this) will take the first matching #templateId element.
On our webapp, we eliminated the ID of all our elements, and when it really needs to be used, it's an ID that is javascript determined to not have duplicates.
Some views can be loaded multiple times in the lifetime of the app, so
no, we can't say "simply check if the id is already used before making your html code" to our team members.
The only other thing we could do would be to check if a specific template is loaded, and if not load it in async, then apply bindings. But for simplicity purpose and the way our project is set up right now, we can't apply an js AMD-like dependency manager.
Questions
Is that possible to specify directly the DOM reference to the template directly?
data-bind="template:function(){ return $('yourSelectorToTheTemplate')[0]; }"
I've looked knockout code and it's weird because we have this:
templateDocument = templateDocument || document;
var elem = templateDocument.getElementById(template);
if (!elem)
throw new Error("Cannot find template with ID " + template);
return new ko.templateSources.domElement(elem);
This means that it really use the DOM element, so why being forced to give an ID for it if we already have the ID?
How do we retrieve dynamically applied IDtemplate, that is also calling another dynamically applied ID (template recursively calling itself for example)?
Setting ID from a binding handler may be wrong: it may set the ID after other data bound elements referred to it, but it would be simpler to have to.
The best solution found for the moment:
Place templates (script element) at the top of the html view.
Use a bindingHandler that does initialization (I called mine "init" as you can see in the example below) to set the ID of the script element
Store that ID inside the $root context so it can be reused by other elements
The result looks like this:
<script type="text/html" data-bind="init: function(){ $rawData.folderItemTemplate = functionThatSetsAndReturnsUniqueId($element, 'folderItemTemplate'); }">
<li>
Some item
<ul data-bind="template: { name: $rawData.folderItemTemplate, foreach: children }"></ul>
</li>
</script>
As you can see we can use this template binding with template: { name: $rawData.yourPropertyName, foreach:... }
I have a mysterious issue with Ember/Handlebars views. I am attempting to implement very simple data binding in a Handlebars view. This works correctly when render my view by doing:
Ember.Views.NavView.create().append()
But the bound attribute isn't shown when I attempt to render another instance of the same view using the {{view}} helper, like so:
<script type="text/x-handlebars">
{{view App.Views.NavView}}
</script>
In the first case the attributes (hardcoded on the View for this test-case) are shown correctly. In the second case I get "metamorph-0-start" and "metamorph-0-end" tags, but the value itself is not rendered.
I have set up a JSFiddle (http://jsfiddle.net/XUyht/2/) that illustrates the problem clearly.
You'll see that I have rendered the view twice: the first using the {{view}} helper and the second using append() - but the attribute "working" only shows in the latter case.
What's going on here?
I don't know why you need this kind of implementation of the template, but anyway, since the 1.0-pre, the default context of a view is either its controller, either its parent view. So in your case, if you replace tmp.foo with view.tmp.foo, this is working.
see http://jsfiddle.net/Sly7/amLfk/