I have this structure, and I want to bind , say custom attributes or non-standard attributes like for or some-new-attribute to child doms created by a dom-repeat template. How do i achieve this?
<dom-module id="card-list">
<template>
<li>Some Text</li>
<template is="dom-repeat" items="{{items}}" >
<li id="{{ item.text }}" for="{{ item.text }}">{{ item.text }}</li>
</template>
</template>
<script>
Polymer({
is: "card-list",
extends:"ul",
ready: function() {
this.items = magically_get_an_array_of_objects();
},
});
</script>
</dom-module>
If magically_get_an_array_of_texts function returns this array of objects
[
{text:"foo"},
{text:"bar"}
]
The resulting template would be this
<ul is="card-list">
<li>Some Text</li>
<li id="foo">foo</li>
<li id="bar">bar</li>
</ul>
Notice how the for attribute did not get binded.
Question
How do you bind to attributes to child doms, specifically when they are looped?
You could simply use binding to native element attribute.
for$="{{ item.text }}"
Your version will set a property. It will not show up in the HTML, as you noticed, but it will be available as property. document.querySelector("#foo").for will print foo.
If you want to use custom attributes, you will need to extend the <li> element, define the for attribute and set reflectToAttribute to true.
<script>
Polymer({
is: "card-li",
extends:"li",
properties: {
for: {
type: String,
reflectToAttribute: true
}
}
});
</script>
And then use it like this.
<li is="card-li" id="{{ item.text }}" for="{{ item.text }}">{{ item.text }}</li>
Related
I want to insert the text contained inside a tag into another spot in the code after clicking on it.
so in a list like this:
<ul>
<li>small</li>
<li>medium</li>
<li>large</li>
</ul>
When clicking small or any of the listed options I need that string to be placed where it says 'TEXT HERE' below.
<li class="that" data-option="{{ get_value:product_options size='TEXT HERE' }}{{ relate:stone_options }}{{ slug }} {{ /relate:stone_options }}{{ /get_value:product_options }}">{{ slug }}</li>
It would also have to switch out from the previous clicked option, if small is clicked you get {{ ... size='small' ... }} but when clicking on medium, the word small changes to medium.
I'm not great at javascript dunno if this is an ajax thing or what, help would be appreciated!
Here is working code to solve your problem. just add class to A tag
<ul>
<li>blue</li>
<li>green</li>
<li>red</li>
</ul>
<li class="that" data-option="{{ get_value:product_options
size='TEXT HERE' }}{{ slug }} {{ /get_value:product_options }}">{{
slug }}</li>
<script type="text/javascript" charset="utf-8">
let a = document.querySelectorAll('.colors');
let that = document.querySelector('.that');
// get data-option attribute
let attr = that.getAttribute('data-option');
let atxt;
let aLength = a.lenght;
let i;
for(i = aLength; i >= 0; i--){
a[i].addEventListener('click',()=>{
atxt = a[i].textContent;
// find and replace TEXT HERE with options text
that.setAttribute('data-option',attr.replace("TEXT HERE",atxt));
});
}
</script>
I'm trying to pass slot into the slot but child component which requires passed down slot doesn't see it.
In the child (TheTable) I have table component from Core UI for Vue (CDataTable) which requires certain slots which I want to pass from a parent:
<CDataTable
class="overflow-auto"
:items="this.items"
:fields="fieldData"
:sorter="{ external: true, resetable: true }"
:sorter-value="this.sorterValue"
:table-filter="{ external: true, lazy: false, placeholder: 'Enter here', label: 'Search:'}"
:responsive="false"
:loading="this.loading"
:hover="true"
:items-per-page-select="true"
:items-per-page="this.perPage"
#pagination-change="paginationChanged"
#update:sorter-value="sorterUpdated"
#update:table-filter-value="filterUpdated"
>
<slot name="requiredTableFields"></slot>
</CDataTable>
In the parent component I have:
<TheTable
v-bind:fields="fields"
v-bind:endpoint-url="endpointUrl"
>
<template slot="requiredTableFields">
<td slot="name" slot-scope="{item}">
<div>{{ item['attributes.email'] }}</div>
<div class="small text-muted">
{{ item['attributes.firstname'] }} {{ item['attributes.lastname'] }}
</div>
</td>
<td slot="registered" slot-scope="{item}">
<template v-if="item['attributes.created_at']">{{ item['attributes.created_at'] }}</template>
<template v-else>Not setup yet</template>
</td>
</template>
</TheTable>
Is there any way to make it work?
Cheers,
Casper
Merging the underlying slots into a single requiredTableFields slot isn't going to work because there's no (easy) way to break the child slots back out once they've been merged.
Instead you can just keep the slots separate:
<TheTable ...>
<template v-slot:name="{ item }">
<td>
...
</td>
</template >
<template v-slot:registered="{ item }">
<td>
...
</td>
</template>
</TheTable>
This is passing two scoped slots, name and registered, into TheTable.
Assuming you don't want to hard-code the slot names into TheTable you'd then need to iterate over the $scopedSlots to include them dynamically.
<CDataTable ...>
<template v-for="(x, slotName) in $scopedSlots" v-slot:[slotName]="context">
<slot :name="slotName" v-bind="context" />
</template>
</CDataTable>
Some notes on this:
x is not used, we just need to loop over the slot names.
The 'data' associated with the scoped slot is referred to as context and is just passed on.
If the slots weren't scoped slots it'd be slightly different. We'd iterate over $slots instead and remove all the parts that refer to context.
There is a : at the start of the :name attribute as we want to pass a dynamic name. It is an unfortunate coincidence that one of the slots in the original question is also called name, potentially leading to some confusion here.
The v-bind="context" part is analogous to the JavaScript spread operator, if that makes it any clearer. Think of it as attributes = { name: slotName, ...context }.
Below is a complete example illustrating this technique outlined above. It doesn't use CDataTable but the core principle for passing on the slots is exactly the same.
const Comp2 = {
template: `
<div>
<h4>Left</h4>
<div><slot name="top" item="Red" /></div>
<h4>Right</h4>
<div><slot name="bottom" item="Green" /></div>
</div>
`
}
const Comp1 = {
template: `
<div>
<comp2>
<template v-for="(x, slotName) in $scopedSlots" v-slot:[slotName]="context">
<slot :name="slotName" v-bind="context" />
</template>
</comp2>
</div>
`,
components: {
Comp2
}
}
new Vue({
el: '#app',
components: {
Comp1
}
})
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<div id="app">
<comp1>
<template v-slot:top="{ item }">
<button>Slot 1 - {{ item }}</button>
</template>
<template v-slot:bottom="{ item }">
<button>Slot 2 - {{ item }}</button>
</template>
</comp1>
</div>
I want to repeat a piece of HTML, multiple times in my template.
But I want it to be repeated at different places on my page. This means that ngFor is not the solution as the pieces would be repeated directly one after the other.
A 'working solution' would be to define a specific #Component for my repeated HTML, and do something like that :
<p>Whatever html</p>
<my-repeated-html></my-repeated-html>
<h4>Whatever</h4>
<my-repeated-html></my-repeated-html>
But I find it overkill to create a dedicated component for doing something like that, it has no functional meaning and is only required by the HTML structure I want to set up.
Is there really nothing in ng2 template engine to allow me to define an "inner template" and use it wherever I need it in the current template?
update Angular 5
ngOutletContext was renamed to ngTemplateOutletContext
See also https://github.com/angular/angular/blob/master/CHANGELOG.md#500-beta5-2017-08-29
original
The recently added ngTemplateOutlet might be what you want
<template [ngTemplateOutlet]="templateRefExpression" [ngOutletContext]="objectExpression"></template>
It can currently be used like
<template #templateRef>
<pre>{{self | json }}</pre>
</template>
<template [ngTemplateOutlet]="templateRef"></template>
A template can also be passed to a child component to be rendered there
#Component({
selector: 'some-child',
providers: [],
template: `
<div>
<h2>Child</h2>
<template [ngTemplateOutlet]="template" ></template>
<template [ngTemplateOutlet]="template" ></template>
</div>
`,
directives: []
})
export class Child {
#ContentChild(TemplateRef) template:TemplateRef;
}
to be used like
<some-child>
<template>
<pre>{{self | json }}</pre>
</template>
</some-child>
stackblitz example
Another Plunker example
that uses data passed as
<template [ngTemplateOutlet]="..." [ngOutletContext]="templateData"
This way ngOutletContext can be used in the template like
<template let-image="image">
{{image}}
where image is a property of templateData
If $implicit is used
<template [ngTemplateOutlet]="..." [ngOutletContext]="{$implicit: templateData}"
the ngOutletContext can be used in the template like
<template let-item>
{{item}}
<campaign-channels-list (onItemSelected)="_onItemSelected($event)" [customTemplate]="customTemplate" (onDragComplete)="_onDragComplete($event)" [items]="m_blockList"></campaign-channels-list>
<template #customTemplate let-item>
<a href="#" [attr.data-block_id]="item.blockID">
<i class="fa {{item.blockFontAwesome}}"></i>
<span>{{item.blockName}}</span>
<i class="dragch fa fa-arrows-v"></i>
<span class="lengthTimer hidden-xs">
{{item.length | FormatSecondsPipe}}
</span>
</a>
</template>
and in rx component:
<div class="sortableList">
<li (click)="_onItemSelected(item, $event, i)" *ngFor="let item of m_items; let i = index" class="listItems list-group-item" [ngClass]="{'selectedItem': m_selectedIdx == i}">
<template [ngTemplateOutlet]="customTemplate" [ngOutletContext]="{$implicit: item}">
</template>
</li>
</div>
pay attention to:
[ngOutletContext]="{$implicit: item}"
as well as
<template #customTemplate let-item>
I'm working on a set of conditional views based on the data available in a JSON object - effectively, show a media view if we have media to show, show a document view if we have merely text information to show, etc. The approach I've been using to date uses hasOwnProperty() to check the JSON object to determine the available data and work out the view template based on what's there.
I've implemented something as a barebones version of this, but now I get nothing at all. The if seems to just kill the nested templates. Here's what I'm trying:
<template bind if="{{ posts[postid].hasOwnProperty('video') }}">
<div class="tileHeader">Posted by #{{ posts[postid].creator_id }} <time-ago datetime="{{ posts[postid].creation_date }}"></time-ago></div>
<div class="tile">
<div class="heroTop" style="background-image: url({{ posts[postid].body }}) no-repeat"></div>
<div class="heroBottom">
<div class="headline">{{ posts[postid].url_title }}</div>
<div class="postDesc">{{ posts[postid].url_description }}</div>
</div>
<div class="attribution">
{{ posts[postid].url }}
</div>
</div>
</template>
<template bind if="{{ posts[postid].hasOwnProperty('image') }}">
<div class="tileHeader">Posted by #{{ posts[postid].creator_id }} <time-ago datetime="{{ posts[postid].creation_date }}"></time-ago></div>
<div class="tile solo-view">
<div class="heroSolo">
{{ posts[postid].body }}
</div>
<div class="attribution">
{{ posts[postid].url }}
</div>
</div>
</template>
Two questions:
1. Can this if statement work in this context, or does this need to be re-built as a filter?
2. What happens in the case where both ifs are true for a given render?
Ok, this seems to be working. Is it messy? Yes, definitely.
Effectively, from my API I get a slew of post_ids that I need to format differently depending on what I'm finding. Trying to use something like JSON.hasOwnProperty didn't work (don't know why) so I'm resorting to assigning a variable based on a separate discovery function.
Is there a better way to do this? Of this, I'm certain. If you've got a better approach, please do let me know. But here's what I've come to:
<template repeat="{{ postid in postids }}">
<core-ajax id="postdetail" url="api/1/posts/{{ postid }}" data-postid="{{ postid }}" postid="{{ postid }}" handleAs="json" method="GET" auto on-core-response="{{ updatePostDetail }}"></core-ajax>
<template if="{{ posts[postid].displaytype == 'articleImage' }}">
<div class="tileHeader"><user-print creatorid="{{ posts[postid].creator_id }}" prepend="Posted by" size="small"></user-print> <span hidden?="{{ showchannel }}">In channel {{ posts[postid].channel_id }}</span> <time-ago prepend="Last update " isostring="{{ posts[postid].creation_date }}"></time-ago></div>
<div class="tile media-view" style="background: url({{ posts[postid].banner }}) no-repeat; background-size: cover;" title="{{ posts[postid] | descText }}">
<div class="heroBottom">
<div class="type">{{ posts[postid].displaytype }}</div>
<div class="headline">{{ posts[postid].url_title }}</div>
<div class="postDesc">{{ posts[postid].body | stripTags | shorten }}</div>
<div class="attribution"> {{ posts[postid].url }} </div>
</div>
</div>
</template>
<template if="{{ posts[postid].displaytype == 'video' }}">
... (etc)
</template>
</template>
<script>
Polymer('post-list', {
postids: [],
posts: {},
created: function(){
},
ready: function(){
},
updatePostList: function(e){
this.postids = e.detail.response.post_ids;
},
updatePostDetail: function(e){
json = e.detail.response.post;
postid = json.id;
this.posts[postid] = json;
this.posts[postid].displaytype = 'barePost'; // default value so I don't have to worry about a bunch of similar 'else' statements
this.posts[postid].hasVideo = 'noVideo'; // ditto
if(json.hasOwnProperty('url_meta_tags')){
if(json.url_meta_tags.hasOwnProperty('og:video') || json.url_meta_tags.hasOwnProperty('twitter:player:stream')){
this.posts[postid].hasVideo = 'video';
this.posts[postid].displaytype = 'video';
}
else if(json.url_meta_tags.hasOwnProperty('og:image') || json.url_meta_tags.hasOwnProperty('image') || json.hasOwnProperty('banner')){
if(json.body.length > 350){
this.posts[postid].displaytype = 'longArticle';
}
else if(json.body.length > 0){
this.posts[postid].displaytype = 'articleImage';
}
else{
this.posts[postid].displaytype = 'bareImage';
}
}
}
else if(json.hasOwnProperty('files')){
this.posts[postid].displaytype = 'embeddedMedia';
}
}
</script>
From the documentation: https://www.polymer-project.org/docs/polymer/binding-types.html#importing-templates-by-reference
You could use the ref attribute combined with bind to make this less of a mess.
<!-- Create Your displayType templates -->
<template id="longArticle">
<!-- template for displayType === longArticle -->
</template>
<template id="bareImage">
<!-- template for displayType === bareImage -->
</template>
<!-- then construct your loop -->
<template for={{postid in postids}}>
<template bind ref="{{posts[postid].displaytype}}"></template>
</template>
I have a json that is converted from XML and kept "#attributes" :
{"stop":"1021","route":"0057","direction":"1","departures":{"departure":[
{"#attributes":{"accurate":"1","headsign":"Rennes R"},"content":"2013-03-25T12:00:23+01:00"},
{"#attributes":{"accurate":"0","headsign":"Rennes R"},"content":"2013-03-25T12:20:00+01:00"},
{"#attributes":{"accurate":"0","headsign":"Rennes R"},"content":"2013-03-25T12:40:00+01:00"}]}},...
you can here access to the properties in javascript [1] with :
departure[0]["#attributes"].accurate
but if you want to grab it in a template with Meteor, how do you do this?
[1] JSON #attributes
I would suggest accessing the field in a function such as
Templates.your_template.attributes = function()
{
return this["#attributes"];
}
in your template
{{attributes}}
the template in the html side will look like this :
<template name="status">
<div class="line {{status}}_line">
{{stop}}
{{#each departures.departure}}
{{> attributes}}
{{/each}}
</div>
</template>
<template name="attributes">
<div class="attributes">
{{content}}
accurate : {{{access_attributes this}}}
</div>
</template>
and on the js side :
Template.attributes.access_attributes = function(context) {
return context["#attributes"].accurate
}