Feed Outside JS object into Vue.js component - javascript

In my JS I have list of products.
var productData = [{
title: 'Bike',
price: 300
},{
title: 'Guitar',
price: 199
}]
This data is gathered from remote Json in a Async call. So I have to do this in JS.
Eventually, I come to a situation, that I need to display this product. But here are 2 things.
1) In HTML I am outside of my main Vue instance scope. It is so because there is 3rd party API that interacts with those products, apart other things... So This array is not bound to Vue instance in any way.
2) This product list is dynamically created, on the fly. In order to display it, I have to do a good old html string concatenation and then apply it.
Main problem:
Now whenever I need to display a product - I have a Vue.js Component for it. That has a template, and all data goes really nicely. I obviously want to reuse this same template.
So right now I have something like this:
//Vue.js Component, that I want to use, and feed my product to:
Vue.component('product', {
props: ['product'],
template: `
<div class="prod">
<h3>{{product.title}}</h3>
<p>{{product.price}} €</p>
</div>
`, data() {
return {
}
}
});
Next in JS, I have this code:
var prodList = getProductsAsync("3rd party service URL and parameters");
var prodHtml = '';
for(var i = 0; i < prodList.length; i++){
prodHtml += `
<div class="prod">
<h3>${prodList[i].title}</h3>
<p>${prodList[i].price} €</p>
</div>
`;
}
Here I have 2 templates (one as JS html, and another in Vue.js component), that is redundant.
I need to have 1 way to maintain this template. At the moment template is simple, but more functionality will shortly come.
Idealy I would like my JS object to use my Vue.js template. As a result it would also behave as Vue.js component too.
So in my JS I would like to have it like this (But this obviously does not work):
var prodList = getProductsAsync("3rd party service URL and parameters");
var prodHtml = '';
for(var i = 0; i < prodList.length; i++){
prodHtml += ` <product :product='prodList[i]'></product>`; // attempt to use Vue.js component 'on the fly' but this does not work!
}
Any suggestions?

It doesn't work, because there is some order in which you have to create component, html, and Vue instance.
// just use all your code together
// first, prepare the component
Vue.component('product', {
props: ['product'],
template: `
<div class="prod">
<h3>{{product.title}}</h3>
<p>{{product.price}} €</p>
</div>
`
})
// and get products
var prodList = getProductsAsync('/someMountpoint')
// then generate html with unknown tags "product"
var prodHtml = '<div id="productList">'
for(let i = 0; i < prodList.length; i++) {
prodHtml += `<product :product='prodList[i]'></product>`
}
prodHtml += '</div>'
// append this html to DOM
$('#someWhere').html(prodHtml)
// and now create new Vue instance to replace
// these unknown product tags with real tags
new Vue({
el: '#productList'
})

Related

How can i hard code my data from realtime database?

So i have some data stored in a realtime datastore and only the admin can access the page where all the data is displayed, but how do i hard code the data to the website this is what i have and tried:
<!-- my html -->
<div v-for="(option, i) in reserveringen" :key="i">
{{ reserveringen[i] }}
</div>
data: function(){
return {
lidnummer: '',
reserveringen: []
}
},
mounted() {
self = this
const reserveringsref = firebase.database().ref('Reserveringen/')
reserveringsref.once('value', function(snapshot){
const lidnummer = ''
const reserveringen = []
snapshot.forEach(function(childSnapshot){
const data = childSnapshot.val()
self.lidnummer = data.Lidnummer
reserveringen.push(
"<tr><td>" + data.Lidnummer + "</td></tr>"
)
});
self.reserveringen = reserveringen;
});
},
This will only display the array i made as text. Can anyone help me with this issue or am i completely doing this wrong any advice will be thanked!
Your isue is not with RT-db but with Vue i guess;
If you want to build HTML in the JS section (as you do by pushing html tags inside an array), you should use the v-html directive instead of the interpolation.
Something like that
<div v-for="(option, i) in reserveringen" :key="i" v-html="reserveringen[i]">
</div>

Fail to access DOM element in Vue component when using generic javascript

I am trying to dynamic to update data from Vue.compnent by using jQuery way on Webpack project. following is code snippet.
<template>
<div id="container">
<slot>show string!!</slot>
<div id="s_container"></div>
</div>
</template>
<script>
export default {
}
const items = [
'thingie',
'anotehr thingie',
'lots of stuff',
'yadda yadda'
]
function listOfStuff () {
let full_list = ''
for (let i = 0; i < items.length; i++) {
full_list = full_list + '<li>{{items[i]}}</li>'
}
// const contain = document.querySelector('#s_container')
const contain = this.$el.querySelector('#s_container')
if (contain != null) {
contain.innerHTML = '<ul ${full_list} </ul>'
} else {
console.log('contain is null');
}
}
listOfStuff()
</script>
but this contain variable is always fail to get #s_container element. How can I get it correctly from outside java script?
Thanks.
First you need to bind your items array into a data return function like this:
export default {
data()
return {
items: ['thingie', 'yadda yadda']
}
}
Then you can use a v-for loop the display this array into a HTML li.
Like this into your template:
<template>
<div>
<ul>
<li v-for="(item, index) in items" :key=index>
{{ item }}
</li>
</ul>
</div>
</template>
In this way you can render a simple list using vue.js, if you need more informations about this feature read this part of the documentation: https://v2.vuejs.org/v2/guide/list.html
Have a good day !

Vue.js - Trouble displaying results from API call in component

Experimenting with Vue.js, trying to display results from a Wikipedia API call in a component using the v-for directive, but something is not working on the back end and I don't know what it is.
Link to the jsFiddle
HTML
<div id="app">
<input type="text" v-model="searchTerm" v-on:keyup="getResults">
<searchResult
v-for="item in results"
v-bind:result="item"
v-bind:key="item.key"
></searchResult>
</div>
Javascript
new Vue({
el: '#app',
data: {
api: "https://en.wikipedia.org/w/api.php?",
searchTerm: 'Ron',
searchDataString: "action=opensearch&format=json&origin=*&uselang=user&errorformat=html&search="+this.searchTerm+"&namespace=0%7C4&limit=20&profile=fuzzy",
searchCall: this.api+""+this.searchDataString,
results: []
},
methods: {
getResults() {
this.searchCall = this.api+"action=opensearch&format=json&origin=*&uselang=user&errorformat=html&search="+this.searchTerm+"&namespace=0%7C4&limit=20&profile=fuzzy";
//console.log( this.searchCall );
axios.post( this.searchCall )
.then(response => { this.processResults(response.data) });
},
processResults(data) {
//console.log( data );
for(var i = 0; i < data[1].length; i++) {
var resultItem = { key:i, link:data[3][i], name:data[1], description:data[2][i] };
this.results.push(resultItem);
console.log(resultItem);
}
}
}
});
Vue.component( "searchResult", {
props:['result'],
template: "<a target='_blank' href='{{ result.link }}'><div class='search-result'><h3>{{ result.name }}</h3><p>{{ result.description }}</p><div></a>"
});
The two issues on my mind are
the error message that shows in the console when typing input, and
that the array of results is creating empty objects instead of passing the data
When I look at the array in the console, all it shows are getters and setters. I'm new to this, so maybe that's what it's supposed to be doing.
I'm so close to getting this working, but I'm at my wits end, help is much appreciated.
The problem with your code is that html tags aren't case sensitive so naming a component searchResult causes issues. If you need to use searchResult, you'll have to use <search-result> in your template. I find it better just to avoid the problem altogether and give components lower-case names. Here are docs about the issue: https://v2.vuejs.org/v2/guide/components.html#Component-Naming-Conventions
You mentioned "the error message that shows in the console when typing input". I didn't get any errors copying and pasting your code (other than forgetting to include axios). What error are you getting?

Break some html code into Array of objects

I am trying to analyse some html code and break it into an array of objects.
Here is some example html code:
<slide data-time=5>
<div class="cds-block-title">Master Calendar</div>
<div class="cds-block-content">iframe to master calendar</div>
</slide>
<slide data-time=5>
<div class="cds-block-title">Weather</div>
<div class="cds-block-content">iframe to master Weather App</div>
</slide>
My goal is to break it down into an object similar to this:
[
{
"html":"<slide.....</slide>",
"time":"5",
"title":"Master Calendar",
"content":"iframe...."
},
{
"html":"<slide.....</slide>",
"time":"5",
"title":"Master Calendar",
"content":"iframe...."
}
]
I have tried a few different approaches.
Using Regex (This worked in my test, but not when I put it in production, the .match stopped working as expected, I also read a few posts stating that using regex to parse html code is not the best approach):
function splitSlidesHtml(html){
var html = '<slide data-time="5"><div class="cds-block-title">Activities & Sports</div><div class="cds-block-content">content</div></slide><slide data-time="5"><div class="cds-block-title">weather</div><div class="cds-block-content">content</div></slide>"';
var slides = html.match(/<slide.*?>(.*?)<\/slide>/g);
var slidesA = [];
if (!slides) {
slidesA.push({"html":html});
} else {
for (i in slides){
var c = {};
c.html = slides[i];
c.time = slides[i].match(/(data-time=)(.*?)>/)[2].replace(/['"]+/g, ''); // extract the time, and replace any quotes that might be around it
c.title = slides[i].match(/<div class="cds-block-title">(.*?)<\/div>/)[1];
c.content = slides[i].match(/<div class="cds-block-content">(.*?)<\/div>/)[1];
slidesA.push(c);
}
}
return slidesA;
} // end splitSlidesHtml
I have also tried using jQuery, which kind-of works, but I don't know enough about parseHTML to know how to make sure it breaks at the different slides.
var slides = $.parseHTML(html);
console.log(slides);
console.log(slides[0].innerHTML);
console.log(slides[0].outerHTML);
You can use $.parseHTML() to convert your HTML string into an array of DOM nodes and then loop over the nodes to grab the information you need. .map() is a good use in this case as you are mapping each node to something else.
var html = '<slide data-time=5>\
<div class="cds-block-title">Master Calendar</div>\
<div class="cds-block-content">iframe to master calendar</div>\
</slide>\
<slide data-time=5>\
<div class="cds-block-title">Weather</div>\
<div class="cds-block-content">iframe to master Weather App</div>\
</slide>';
var slides = $($.parseHTML(html)).map(function () {
return {
// store the HTML
html: this.outerHTML,
// store the data-time attribute
time: this.dataset.time,
// store the title
title: $('.cds-block-title', this).text(),
// store the content
content: $('.cds-block-content', this).text(),
};
}).get();
console.log(slides);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
This is what I finally came up with. I had trouble with map working to get the time correctly.
var html = sheetData['values'][prop]['html'];
var parsed = $.parseHTML(html);
var isSlide = true;
for (n in parsed){
var cur = parsed[n];
if (cur.nodeName == "SLIDE"){
var curSlide = {
html: cur.outerHTML, // store the HTML
time: cur.dataset.time, // store the data-time attribute
title: $('.cds-block-title', cur).html(), // store the title
content: $('.cds-block-content', cur).html(), // store the content
};
} else {
isSlide = false;
}
}

Dynamically creating kendo-grid columns in angular controller

I am trying to dynamically build the structure of a kendo-angular grid. My problem is that the grid options are not known when the k-options attribute is evaluated, so the grid is binding to ALL of the columns on the datasource.
Here is the HTML:
<div kendo-grid k-options="{{gridModel.options}}"
k-data-source="gridModel.myDataSource">
</div>
And here is the javascript in the controller:
// this is called after the api call has successfully returned with data
function getSucceeded(){
...
$scope.gridModel.options = function(){
// function that properly builds options object with columns, etc.
}
// this is just shown for example... the data is properly loading
$scope.gridModel.myDataSource.data(ds.data());
}
The data is properly loading, but because gridModel.options was evaluated in the HTML prior to being set by the success method, it is essentially ignored and all of the columns from the datasource are being rendered.
This works like a champ when gridModel.options is static.
How can I defer the evaluation of k-options and/or force a reevaluation after they've been set by the controller?
I was able to figure it out. I had to do four things:
Update my version of angularjs (I was on 1.08 which does not have the ng-if directive). I updated to 1.2.0rc3.
Wrap my kendo-grid div in an ng-if div
Invoke my function! I was just setting $scope.gridModel.options to a function - I needed to actually invoke the function so I'd be setting the variable to the value returned from the function.
I had to update my angular.module declaration to include ngRoute (based on it being separated into it's own module in 1.2.x).
Here's the updated HTML:
<div data-ng-if="contentAvailable">
<div kendo-grid k-options="{{gridModel.options}}"
k-data-source="gridModel.myDataSource">
</div>
</div>
And here's the updated controller (not shown: I set $scope.contentAvailable=false; at the beginning of the controller):
// this is called after the api call has successfully returned with data
function getSucceeded(){
...
$scope.gridModel.options = function(){
// function that dynamically builds options object with columns, etc.
}(); // <----- NEED to invoke function!!
// this is just shown for example... the data is properly loading
$scope.gridModel.myDataSource.data(ds.data());
$scope.contentAvailable=true; // trigger the ng-if
}
I actually moved the function into a config file so I'm not polluting the controller with too much configuration code. Very happy to have figured this out.
Here is a sample using 'Controller As' syntax, dynamic columns and paging.
var app = angular.module("app", ["kendo.directives"]);
function MyCtrl() {
var colsList = [{
name: "col1"
}, {
name: "col2"
}, {
name: "col3"
}, {
name: "col4"
}];
var gridCols = [];
var iteration = 1;
var vm = this;
vm.gridOptions = {
columns: gridCols,
dataSource: new kendo.data.DataSource({
pageSize: 10
}),
pageable: true
};
vm.buildGrid = function() {
var data = {};
vm.gridOptions.columns = [];
for (var x = 0; x < colsList.length; x++) {
if (iteration % 2 === 0 && x === colsList.length - 1) continue;
var col = {};
col.field = colsList[x].name;
col.title = colsList[x].name;
data[col.field] = "it " + iteration + " " + (1111 * (x + 1));
vm.gridOptions.columns.push(col);
}
// add one row to the table
vm.gridOptions.dataSource.add(data);
iteration++;
};
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<link rel="stylesheet" href="http://cdn.kendostatic.com/2015.1.318/styles/kendo.common.min.css" />
<link rel="stylesheet" href="http://cdn.kendostatic.com/2015.1.318/styles/kendo.default.min.css" />
<script src="http://cdn.kendostatic.com/2015.1.318/js/kendo.all.min.js"></script>
<body ng-app="app">
<div ng-controller="MyCtrl as vm">
<button ng-click="vm.buildGrid()">Build Grid</button>
<div kendo-grid="grid" k-options="vm.gridOptions" k-rebind="vm.gridOptions"></div>
</div>
</body>
We can use the k-rebind directive for this. From the docs:
Widget Update upon Option Changes
You can update a widget from controller. Use the special k-rebind attribute to create a widget which automatically updates when some scope variable changes. This option will destroy the original widget and will recreate it using the changed options.
Apart from setting the array of columns in the GridOptions as we normally do, we have to hold a reference to it:
vm.gridOptions = { ... };
vm.gridColumns = [{...}, ... ,{...}];
vm.gridOptions.columns = vm.gridColumns;
and then pass that variable to the k-rebind directive:
<div kendo-grid="vm.grid" options="vm.gridOptions" k-rebind="vm.gridColumns">
</div>
And that's it when you are binding the grid to remote data (OData in my case). Now you can add or remove elements to/from the array of columns. The grid is going to query for the data again after it is recreated.
When binding the Grid to local data (local array of objects), we have to somehow postpone the binding of the data until the widget is recreated. What worked for me (maybe there is a cleaner solution to this) is to use the $timeout service:
vm.gridColumns.push({ ... });
vm.$timeout(function () {
vm.gridOptions.dataSource.data(vm.myArrayOfObjects);
}, 0);
This has been tested using AngularJS v1.5.0 and Kendo UI v2016.1.226.

Categories

Resources