Limitations on polymer conditional templates? - polymer

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>

Related

For loop and Popup with liquid and jekyll

I want to create a list of modal popups with a for-loop, each of them displaying different text.
The site is created with Jekyll with the Liquid templating engine.
In particular, I want to create the list of my scientific publications, for each of them with 2 icons: one for the bibtex entry and one for the abstract. This information is stored in a yaml file.
I m following this simple tutorial for modal popups.
The popups work, but the text is the same for all the entries. How is possible to generate independent modal popups?
This is the html
{% for papers in papers %}
{% for content in paper.papers %}
<a title="{{content.name}}"><i class='{{content.icon}}' data-modal-target="#modal"></i></a>
<div class="modal" id="modal">
<div class="modal-header">
<div class="title">{{content.name}}</div>
<button data-close-button class="close-button">×</button>
</div>
<!-- text to display -->
<div class="modal-body">{{content.text}}</div>
</div>
{% endfor %}
{% endfor %}
and this is the Javascript code:
const openModalIcons = document.querySelectorAll('[data-modal-target]')
const closeModalButtons = document.querySelectorAll('[data-close-button]')
const overlay = document.getElementById('overlay')
openModalIcons.forEach(icon => {
icon.addEventListener('click', () => {
const modal = document.querySelector(icon.dataset.modalTarget)
openModal(modal)
})
})
function openModal(modal) {
if (modal == null) return
modal.classList.add('active')
overlay.classList.add('active')
}
closeModalButtons.forEach(button => {
button.addEventListener('click', () => {
const modal = button.closest('.modal')
closeModal(modal)
})
})
function closeModal(modal) {
if (modal == null) return
modal.classList.remove('active')
overlay.classList.remove('active')
}
overlay.addEventListener('click', () => {
const modals = document.querySelectorAll('.modal.active')
modals.forEach(modal => {
closeModal(modal)
})
})
The problem is that all your modals have id="modal", so the selector #modal is probably always selecting the first one. The id attribute should be unique in the entire document.
Something like this should work:
{% for papers in papers %}
{% for content in paper.papers %}
<a title="{{content.name}}"><i class='{{content.icon}}' data-modal-target="#paperModal{{forloop.parentloop.counter}}_{{forloop.counter}}"></i></a>
<div class="modal" id="paperModal{{forloop.parentloop.counter}}_{{forloop.counter}}">
<div class="modal-header">
<div class="title">{{content.name}}</div>
<button data-close-button class="close-button">×</button>
</div>
<!-- text to display -->
<div class="modal-body">{{content.text}}</div>
</div>
{% endfor %}
{% endfor %}
Instead of the for loop counter, you could also use the paper name as the ID (as long as it's unique), e.g. id="paperModal_{{content.name | slugify}}".
Edit: Edited the snippet to use forloop.counter and account for the nested for loop.

How to print something only on he first item when parsing using ng-repeat?

So I have a ng-repeat which I am parsing through an array propRentDetail. It prints out some divs with the property name and its rent.
I have also used orderBy : '-trent', so that it shows the property first which has the highest rent and goes down. Now, I have an up arrow icon from fontAwesome and I was trying to show that only with the first property div (i.e. the one with the highest rent). So how do I show something only for the first item?
<div ng-repeat = "summ in propRentDetail | orderBy: '-trent'">
<p>{{ summ.propname }}</p>
<p>{{ summ.proprent }}</p>
</div>
Use $first like this:
<div ng-repeat = "summ in propRentDetail | orderBy: '-trent'">
<div ng-if="$first">icon</div>
<p>{{ summ.propname }}</p>
<p>{{ summ.proprent }}</p>
</div>
inside ng-repeat, AngularJS expose a variable named $first, its value is type boolean which refers if the item is first on the list.
<div ng-repeat = "summ in propRentDetail | orderBy: '-trent'">
<i class="fa fa-arrow" ng-if="$first"></i>
<p>{{ summ.propname }}</p>
<p>{{ summ.proprent }}</p>
</div>
You can also use $index if you want
<div ng-repeat = "summ in propRentDetail | orderBy: '-trent'">
<i class="fa fa-arrow" ng-if="$index === 0"></i>
<p>{{ summ.propname }}</p>
<p>{{ summ.proprent }}</p>
</div>
Other variables available inside ng-repeat:
$last
$even
$odd

raw html in a vue js component (with nuxt)

I receive inside the object passed in my component a body (actu.body) with html tags inside (mostly p tags) and im wondering how to interpret them for the client side, my code is like that for now :
<template>
<div>
<!-- {{ actu }} -->
<v-parallax
:src="actu.images[0].url"
dark
>
<v-layout
align-center
column
justify-center
>
<h1 class="display-2 font-weight-thin mb-3">{{ actu.headline }}</h1>
<h4 class="subheading">{{ actu.summarry }}</h4>
</v-layout>
</v-parallax>
<v-card>
<v-card-text>
{{ actu.body }}
</v-card-text>
</v-card>
</div>
</template>
<script>
export default {
props: {
actu: {
type: Object,
required: true
}
}
};
is there a proper way to do that with vue js ?
have a look at the official guide: https://v2.vuejs.org/v2/guide/syntax.html#Raw-HTML
the trick is the v-html directive
<span v-html="rawHtml"></span>
Yes, use v-html.
<v-card-text v-html="actu.body"></v-card-text>

How can I return a message when I get no results from my filter?

I have set up page that shows different products. I've made a search box with a ng-model="query" and whenever I type my query the page gets automatically updated to the products that I have.
The problem is that whenever the query doesn't match something the page stays blank and this doesn't look good. Instead of a blank page I want the site to display a message saying along the lines of "No products found". How can I achieve this?
I've looked at other questions and their solutions but they didn't work.
Here is my code for the way the products are displayed. As you can see I've added a ng-show="products.length === 0 as some solutions suggested but the message never appears when I don't have a match.
<ul class="list-group">
<li ng-repeat="product in products | filter:query" class="list-group-item">
<h3><a id="productName" href="#/products/{{product.name}}">{{ product.name }}</a></h3>
<img id="mainImage" ng-src="{{ product.imgUrl }}" alt="{{ product.name}}">
<p id="description"> {{ product.description }}</p>
<p id="price"> Price: {{ product.price }} £</p>
<p ng-show="products.length === 0">Nothing found</p>
</li>
</ul>
Here is my controller code for the products page:
//Controller that fetches the available products to display them in the view
app.controller('productListController', function($scope, $http){
$http.get('products/products.json').success(function(data){
$scope.products = data.products;
});
});
Here is the code for the search box:
<div id="searchbox">
<div class="input-group">
<input type="text" class="form-control" ng-model="query" placeholder="Search for...">
<span style="width=1%;" class="input-group-btn">
<button class="btn btn-default" type="button"><span class="glyphicon glyphicon-search"></span></button>
</span>
</div><!-- /input-group -->
</div><!-- searchbox -->
Take the <p ng-show="products.length === 0">Nothing found</p> outside the ng-repeat.
<li ng-repeat="product in filteredProducts = (products | filter:query)" class="list-group-item">
<h3><a id="productName" href="#/products/{{product.name}}">{{ product.name }}</a></h3>
<img id="mainImage" ng-src="{{ product.imgUrl }}" alt="{{ product.name}}">
<p id="description"> {{ product.description }}</p>
<p id="price"> Price: {{ product.price }} £</p>
</li>
<p ng-show="filteredProducts.length === 0">Nothing found</p>
It doesn't show because the ng-repeat is empty.
EDIT:
In order to be sure that the msg is displayed after products data is loaded:
$http.get('products/products.json').success(function(data){
$scope.products = (data.products && data.products.length > 0) ? data.products : [];
});
});
The answer #Daniel is not entirely correct.
As you ng-repeat to use a filter, this filter must also be applicable to the condition of ng-show.
Like this:
<ul>
<li ng-repeat="product in products | filter:query" class="list-group-item">
<h3><a id="productName" href="#/products/{{product.name}}">{{ product.name }}</a></h3>
<img id="mainImage" ng-src="{{ product.imgUrl }}" alt="{{ product.name}}">
<p id="description"> {{ product.description }}</p>
<p id="price"> Price: {{ product.price }} £</p>
</li>
</ul>
<p ng-show="products && (products| filter:query).length === 0">Nothing found</p>
Live example on jsfiddle.
angular.module('ExampleApp', [])
.controller('ExampleController', function($scope) {
$scope.search = {};
$scope.arr = [];
$scope.isLoaded = false;
$scope.load = function() {
$scope.isLoaded = true;
$scope.arr = [{
x: 1
}, {
x: 2
}, {
x: 3
}, {
x: 11
}, {
x: 12
}];
};
$scope.clear = function() {
$scope.isLoaded = false;
$scope.arr = [];
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="ExampleApp">
<div ng-controller="ExampleController">
<input ng-model="search.x">
<button ng-click="load()">
Load array
</button>
<button ng-click="clear()">
Clear array
</button>
<div ng-repeat="a in arrFiltered = (arr|filter:search)">
{{a.x}}
</div>
<div ng-show="isLoaded && arrFiltered.length==0">
not found
</div>
</div>
</div>

Polymer bind value through core element

I have a problem with passing the category variable inside the <template> tag wrapped by the core-list.
I tried different binding approaches, but no luck. {{category}} corretcly appears outside the 2nd template tag.
<polymer-element name="library-list" attributes="category">
<template>
<style>
...
</style>
<service-library id="library" items="{{items}}"></service-library>
<core-list id="list" data="{{items}}" on-core-select="{{onClick}}">
<template>
<div class="item {{ {selected: selected} | tokenList }}" hidden?="{{category == type}}">
<div class="message">
<span class="title">{{title}}</span>
</div>
</div>
</template>
</core-list>
</template>
Maybe you want to try the injection approach.
<core-list data="{{data}}">
<template>
<div class="item {{ {selected: selected} | tokenList }}">
<span>{{foo}}-<b>{{category}}</b></span>
</div>
</template>
</core-list>
...
data.push({
foo: 999,
category:this.category,
...});
jsbin demo http://jsbin.com/mokok
I couldn't find a good solution, so I filtered the data instead that the core-list displays.