How to render html entity symbols in a dom-repeat? - polymer-2.x

I ask this questions because all existing solutions which are existing like:
polymer issue 432
polymer issue 1331
entities-in-polymer-element-definition
for example are outdated and/or not working any longer.
I have a simple dom-repeat statement:
<template is="dom-repeat" items="[[items]]">
<a target="_blank" href="[[item.url]]">[[item.label]]</a>
</template>
As you can see here im showing a list of urls. When the datasource now contains html entities:
this.push('items', {
label: '© Google 2018',
url: 'http://www.google.de'
});
The entities wont render:
© Google 2018
My <a> tags wont have any id's and I also dont know which datasource item has an html entity and which not. So how am I supposed to render html entities with Polymer version 2.5.0?

A solution is to assign each item in the dom-repeat a unique id, then set the innerHTML on that item after the render.
In the following code (and this pen) I set an id with _assign(index). In the ready() method, I call Polymer.RenderStatus.afterNextRender to wait for all items to draw, then set the anchors' innerHTML's in the same manor using a querySelector on that id:
<dom-module id="show-entity">
<template>
<style>
:host {
display: block;
}
</style>
<template is="dom-repeat" items="[[items]]">
<a target="_blank" id="[[_assign(index)]]" href="[[item.url]]"></a><br>
</template>
</template>
<script>
/**
* `show-entity`
* https://stackoverflow.com/questions/21060890/
*
* #customElement
* #polymer
* #demo demo/index.html
*/
class ShowEntity extends Polymer.Element {
static get is() { return 'show-entity'; }
static get properties() {
return {
items: {
type: Array,
value: () => { return []; }
}
}
}
ready() {
super.ready();
this.items.push({
label: '© Google 2018',
url: 'http://www.google.de'
},{
label: '§ Google 2018',
url: 'http://www.google.de'
});
Polymer.RenderStatus.afterNextRender( this, () => {
this.items.forEach((el, idx) => {
let id = this._assign(idx);
this.shadowRoot.querySelector('#'+id).innerHTML = el.label;
});
})
}
_assign(index) {
return 'foo'+index;
}
}
window.customElements.define(ShowEntity.is, ShowEntity);
</script>
</dom-module>
Note that you must import polymer/lib/utils/render-status.html to use Polymer.RenderStatus.afterNextRender.

Related

vuejs - how to convert ajax loaded html into vuejs components

I have an #app container with some html, and I create Vue instance on the #app, all the content is compiled and converted to Vuejs components.
Then I ajax in another html string, and I need to somehow compile that into components, how do I do that?
That is the use case for the template option, which can either reference to a <template> with specific ID in your DOM (like you do with your app using the el option), or directly your HTML template:
Vue.component({
template: `<span class="myItem"><slot></slot></span>`
});
You can easily turn it into an Async Component by providing a function to your component list, which returns a Promise that resolves with the component constructor (e.g. using Vue.extend):
const MyLoadingComponent = Vue.extend({
template: '#my-loading',
});
new Vue({
el: '#app',
data() {
return {
items: [],
};
},
components: {
'my-component': loadComponentWithLoading,
},
methods: {
addItem() {
this.items.push(Math.random());
},
},
});
// https://v2.vuejs.org/v2/guide/components-dynamic-async.html#Handling-Loading-State
function loadComponentWithLoading() {
return {
component: loadComponent(),
loading: MyLoadingComponent,
};
}
// https://v2.vuejs.org/v2/guide/components-dynamic-async.html#Async-Components
function loadComponent() {
return new Promise(function(resolve, reject) {
loadComponentHtml().then((html) => {
resolve(Vue.extend({
// https://v2.vuejs.org/v2/api/#template
template: html,
}));
});
});
}
function loadComponentHtml() {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(`<span class="myItem"><slot></slot></span>`);
}, 2000);
});
}
.myItem {
color: green;
}
.myLoading {
font-style: italic;
color: red;
}
<script src="https://unpkg.com/vue#2"></script>
<div id="app">
<button #click.prevent="addItem">Add some list items</button>
<ul>
<li v-for="item of items">
<my-component>{{item}}</my-component>
</li>
</ul>
</div>
<template id="my-loading">
<span class="myLoading">Loading…</span>
</template>
Such thing is not considered a good practice in Vue, but you can actually create components dynamically by using the Vue with template compiler.
Usually, the stand-alone distribution of Vue where you just include a script tag into your page already includes a template compiler.

Using the same component template and updating the API data

I'm building a page that has a embeds a specific twitch stream video. I'm only displaying one video at the top of my page.
Twitch has an embed code that allows you to grab the channel you want to watch and it will display the embedded video and chat. It requires a div id to target the DOM to add the embedded video.
https://dev.twitch.tv/docs/embed/everything/
My problem is when I click on another page, that uses the same template, it doesn't replace the video. Rather, it adds another IFRAME embed video to the id. So every time I click on the page, it just adds another video to the div id.
I'm using the watch function to update other elements of the page. So when I click on another page, using the same template, the data updates correctly. Everything works and updates except for that embed video.
Is there a way to clear out that div id every time I click another another page? I apologize in advance. I've only been learning Vuejs for a couple of weeks now, and it's all rather new to me.
Here is why my template looks like:
<template>
<div class="video heading-title container">
<div class="streamWrapper">
<div class="row">
<div v-for="live in streams" class="col-12 stream-card">
<div class="twitch-vid-wrapper">
<div id="twitch-embed"></div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import appService from '../service.js'
import '../embedTwitch.min.js' // twitch video embed script
export default {
name: 'Video',
data: function() {
return {
id: this.$route.params.id,
streams: []
}
},
created() {
this.getFirstLiveStream()
this.getLiveStreams()
},
watch: {
'$route' (to, from) {
this.id = to.params.id
this.getLiveStreams()
this.getFirstLiveStream()
}
},
methods: {
getLiveStreams(game){
game = this.$route.params.id;
appService.getLiveStreams(game).then(data => {
this.live = data
});
},
getFirstLiveStream(game) {
game = this.$route.params.id;
appService.getFirstLiveStream(game).then(data => {
this.streams = data
let channelName = this.streams[0].channel.display_name
appService.getTwitchStream(channelName)
});
}
}
}
</script>
Here is the method I have in my service:
const appService = {
getFirstLiveStream(game) {
return new Promise((resolve) => {
axios.get('/kraken/streams/?sort=views&stream_type=live&game='+game)
.then((response) => {
// send variables to calc the offset
var total = response.data._total;
var query = this.calculateSingleOffset(game, total)
resolve(query)
})
})
},
getTwitchStream(channel) {
return setTimeout(function(){
new Twitch.Embed('twitch-embed', {
width: '100%',
height: 480,
channel: channel
});
}
, 500);
}
}
Thanks!
As I understood, what you need is how to assign different id for one twitch template inside each instance of the component.
The solution:
add one data property like twitchId
simply uses Date.now() to generate unique id (this method is just one demo, you can use own methods to get one ID).
then bind <div :id="twitchId"> which will embed into twitch video.
Vue.config.productionTip = false
function embedContent(elementId) {
let contents = ['I am a test', 'nothing', 'connecting', 'bla bla ...', 'Puss in boots', 'Snow White']
document.getElementById(elementId).innerHTML = '<p>' + contents[Math.floor(Math.random()*contents.length)] + '</p>'
}
Vue.component('twitch', {
template: `<div class="video">
<h2>{{name}}</h2>
<div :id="twitchId"></div>
</div>`,
props: ['name'],
data() {
return {
twitchId: ''
}
},
created: function () {
this.twitchId = Date.now().toString(16)
},
mounted: function () {
embedContent(this.twitchId)
}
})
app = new Vue({
el: "#app",
data: {
videos: [
'channel 1',
'World Cup for Football',
'StackOverFlow'
]
}
})
.video {
border: 1px solid red;
}
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<div>
<twitch v-for="(item, index) in videos" :name="item" :key="index"></twitch>
</div>
</div>

VueJS: #click.native.stop = "" possible?

I have several nested components on the page with parents component having #click.native implementation. Therefore when I click on the area occupied by a child component (living inside parent), both click actions executed (parent and all nested children) for example
<products>
<product-details>
<slide-show>
<media-manager>
<modal-dialog>
<product-details>
<slide-show>
<media-manager>
<modal-dialog>
</products>
So I have a list of multiple products, and when I click on "canvas" belonging to modal dialog - I also get #click.native fired on product-details to which modal-dialog belongs. Would be nice to have something like #click.native.stop="code", is this possible?
Right now I have to do this:
#click.native="clickHandler"
and then
methods: {
clickHandler(e) {
e.stopPropagation();
console.log(e);
}
code
<template>
<div class="media-manager">
<div v-if="!getMedia">
<h1>When you're ready please upload a new image</h1>
<a href="#"
class="btn btn--diagonal btn--orange"
#click="upload=true">Upload Here</a>
</div>
<img :src="getMedia.media_url"
#click="upload=true"
v-if="getMedia">
<br>
<a class="arrow-btn"
#click="upload=true"
v-if="getMedia">Add more images</a>
<!-- use the modal component, pass in the prop -->
<ModalDialog
v-if="upload"
#click.native="clickHandler"
#close="upload=false">
<h3 slot="header">Upload Images</h3>
<p slot="body">Hello World</p>
</ModalDialog>
</div>
</template>
<script>
import ModalDialog from '#/components/common/ModalDialog';
export default {
components: {
ModalDialog,
},
props: {
files: {
default: () => [],
type: Array,
},
},
data() {
return {
upload: false,
}
},
computed: {
/**
* Obtain single image from the media array
*/
getMedia() {
const [
media,
] = this.files;
return media;
},
},
methods: {
clickHandler(e) {
e.stopPropagation();
console.log(e);
}
}
};
</script>
<style lang="scss" scoped>
.media-manager img {
max-width: 100%;
height: auto;
}
a {
cursor: pointer;
}
</style>
Did you check the manual? https://v2.vuejs.org/v2/guide/events.html
There is #click.stop="" or #click.stop.prevent=""
So you don't need to use this
methods: {
clickHandler(e) {
e.stopPropagation();
console.log(e);
}
}
In the Vue, modifiers can be chained. So, you are free to use modifiers like this:
#click.native.prevent or #click.stop.prevent
<my-component #click.native.prevent="doSomething"></my-component>
Check events
I had the same problem. I fixed the issue by using following:
<MyComponent #click.native.prevent="myFunction(params)" />

Fetch data in component on initiation using parameters from Vuex store

I am new to Vue and am trying to build a simple movie app, fetching data from an API and rendering the results. I want to have an incremental search feature. I have an input field in my navbar and when the user types, I want to redirect from the dashboard view to the search results view. I am unsure of how to pass the query params from the navbar to the search results view.
Here is my App.vue component
<template>
<div id="app">
<Navbar></Navbar>
<router-view/>
</div>
</template>
<script>
import Navbar from './components/Navbar.vue'
export default {
name: 'App',
components: {
Navbar
},
}
</script>
And here is my navbar component where I have the input field
<template>
<nav class="navbar">
<h1 class="logo" v-on:click="goToHome">Movie App</h1>
<input class="search-input" v-on:keyup="showResults" v-model="query" type="text" placeholder="Search..."/>
</nav>
</template>
<script>
import router from '../router/index'
export default {
data: function () {
return {
query: this.query
}
},
methods: {
goToHome () {
router.push({name: 'Dashboard'})
},
showResults () {
//here on each key press I want to narrow my results in the SearchedMovies component
}
}
}
</script>
If I use router.push to the SearchedMovies component then I am only able to pass the query as a parameter once. I thought about using Vuex to store the query and then access it from the SearchedMovies component, but surely there is a better way of doing it?
I also read about using $emit but since my parent contains all the routes, I'm not sure how to go about this.
You don't need to redirect user anywhere. I've made a small demo to show how one might do it. I used this navbar component as you described and emit an event from it:
const movies = {
data: [
{
id: 0,
title: 'Eraserhead',
},
{
id: 1,
title: 'Erazerhead',
},
{
id: 2,
title: 'Videodrome',
},
{
id: 3,
title: 'Videobrome',
},
{
id: 4,
title: 'Cube',
},
]
};
Vue.component('navbar', {
template: '<input v-model="filter" #input="onInput" placeholder="search">',
data() {
return {
filter: '',
};
},
methods: {
onInput() {
this.$emit('filter', this.filter);
}
}
});
// this is just a request imitation.
// just waiting for a second until we get a response
// from the datasample
function request(title) {
return new Promise((fulfill) => {
toReturn = movies.data.filter(movie => movie.title.toLowerCase().indexOf(title.toLowerCase()) !== -1)
setTimeout(() => fulfill(toReturn), 1000);
});
}
new Vue({
el: '#app',
data: {
movies: undefined,
loading: false,
filter: '',
lastValue: '',
},
methods: {
filterList(payload) {
// a timeout to prevent
// instant request on every input interaction
this.lastValue = payload;
setTimeout(() => this.makeRequest(), 1000);
},
makeRequest() {
if (this.loading) {
return;
}
this.loading = true;
request(this.lastValue).then((response) => {
this.movies = response;
this.loading = false;
});
}
},
mounted() {
this.makeRequest('');
}
})
<script src="https://unpkg.com/vue"></script>
<div id="app">
<navbar v-on:filter="filterList"></navbar>
<ul v-if="!loading">
<li v-for="movie in movies" :key="movie.id">{{ movie.title }}</li>
</ul>
<p v-else>Loading...</p>
</div>
Also jsfiddle: https://jsfiddle.net/oniondomes/rsyys3rp/
If you have any problem to understand the code above let me know.
EDIT: Fixed some bugs and added a couple of comments
EDIT2(after the comment below):
Here's what you can do. Every time user inputs something inside a navbar you call a function:
// template
<navbar v-on:input-inside-nav-bar="atInputInsideNavBar"></navbar>
// script
methods: {
atInputInsideNavBar(userInput) {
this.$router.push({
path: '/filtred-items',
params: {
value: userInput
}
})
}
}
Then inside you 'searched movies' page component you can access this value so:
this.$route.params.value // returns userInput from root component

How to look up the element in the HTML in the .vue file that needs to be updated:

You can consider me a toddler in the vuejs worlđ
In my app i fetch some posts that are present in firebase database
Each post has a upvote and downvote button just like stack overflow where users can upvote or downvote( it's completely u to them)
Eeverything like votes getting updated to databae and rest all works great*
Here comes the problem
Firebase provides a event listener for listening whenever there is change in each child i.e post (in my case upvotes. Downvotes)
i add this listener to the created() lifecycle hook so that the votes update when there is change in them by other users
here is the simplified code below of my .vue file
<template>
<div>
<div v-for="post in posts" id="post.key" class="container">
<p id="upvotes">{{ post.up}}</p>
<p id="downvotes">{{ post.down }}</p>
</div>
</div>
</template>
<script>
export default{
created:{
const ref = this.$firebase.database().ref();
ref.child("posts").on('child_changed', function(post) {
var upvotes = post.val().up;
var downvotes = post.val().down;
//how to look up the element in the HTML above that needs to be updated:
//if it were plain javascript we would have done something like this
//var postElm = document.getElementById(post.key);
//postElm.getElementById("upvotes").innerHTML = upvotes;
//postElm.getElementById("downvotes").innerHTML = downvotes;
});
}
}
</script>
My issue:
how to look up the upvotes and downvotes element in the HTML above in the template that needs to be updated:
according to the docs we can register a reference ref to an element but there is this note saying:
because the refs themselves are created as a result of the render function, you cannot access them on the initial render - they don’t exist yet! $refs is also non-reactive, therefore you should not attempt to use it in templates for data-binding.
so how can I reference the elements to update them
Update: Here is an example where I have mocked the firebase behavior. posts should be a data item, because you control its contents. computed items are for derived values based on other data or props items. You wouldn't make a computed based on a value external to Vue, because such values are not reactive.
const payloads = [{
val() {
return {
key: 2,
up: 10,
down: 3
};
}
},
{
val() {
return {
key: 1,
up: 3,
down: 10
};
}
}
];
new Vue({
el: '#app',
components: {
postVoter: {
template: '#post-voter-template',
data() {
return {
posts: [{
key: 1,
up: 0,
down: 0
},
{
key: 2,
up: 1,
down: 0
},
{
key: 3,
up: 0,
down: 1
}
]
}
},
created() {
const i = setInterval(() => {
if (payloads.length) {
console.log("Doing");
const post = payloads.shift();
const item = this.posts.find((p) => p.key === post.val().key);
item.up = post.val().up;
item.down = post.val().down;
} else {
console.log("Done");
clearInterval(i);
}
}, 1000);
}
}
}
});
.container {
display: flex;
justify-content: space-between;
width: 10rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.6/vue.min.js"></script>
<template id="post-voter-template">
<div>
<div v-for="post in posts" class="container">
<p id="upvotes">{{ post.up}}</p>
<p id="downvotes">{{ post.down }}</p>
</div>
</div>
</template>
<post-voter id="app"></post-voter>
You should be finding which data item needs updated, rather than trying to find it in the DOM. You don't say how posts is created or populated, so I'm having to surmise what it looks like. The code should go something like this:
<template>
<div>
<div v-for="post in posts" class="container">
<p id="upvotes">{{ post.up}}</p>
<p id="downvotes">{{ post.down }}</p>
</div>
</div>
</template>
<script>
export default{
created() {
const ref = this.$firebase.database().ref();
ref.child("posts").on('child_changed', (post) => {
const item = this.posts.find((p) => p.key === post.key);
item.up = post.val().up;
item.down = post.val().down;
});
}
}
</script>