How to compile template loaded from external api in Vue - compilation

I have a non-SPA web app that has Vue components and that works very well. However, I am looking for a way to load HTML that contains Vue via an external API.
So, I simply make a call to /ajax/dialogbox/client/add which is returning HTML containing Vue components, like:
<h1>Add client</h1>
<div>My static content</div>
<my-component></my-component>
but obviously <my-component></my-component> does not do anything.
In Angular 1 I was using $compile service to compile the HTML before output.
Is there a way to do the same in Vue?

There is a compile function available in Vue that compiles templates to render functions. Using the compiled functions requires a little more detail than you have provided (if you needed to use the returned template with data, for example), but here is one example.
console.clear()
Vue.component("my-component",{
template: `<h1>My Component</h1>`
})
const template = `
<div>
<h1>Add client</h1>
<div>My static content</div>
<my-component></my-component>
</div>
`
new Vue({
el: "#app",
data:{
compiled: null
},
mounted(){
setTimeout(() => {
this.compiled = Vue.compile(template)
}, 500)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.js"></script>
<div id="app">
<component :is="compiled"></component>
</div>
Note that in the example, I wrapped your example template in a div tag. Vue requires that there is on a single root element for a Vue or component.

After many hours, I managed to pass some properties to the component to be compiled.
In the HTML body:
<!-- some where in the HTML body -->
<div id="vCard2">
<component :is="compiled"></component>
</div>
<script>
var vmCard2 = new Vue({
el: '#vCard2',
data: {
compiled: null,
status: ''
},
methods: {
show: function () {
// macro is some dynamic string content or html template that contains mustache
var macro = this.status == 'some_switch' ? '...{{payment.status}}...' : '...{{refund.status}}...';
Vue.component('cp-macro', {
data: function () {
return {
payment: vmCard1.payment,
refund: vmCard1.refund
}
},
template: '<span>'+macro+'</span>'
})
this.compiled = Vue.compile('<cp-macro></cp-macro>');
},
hide: function () {
this.compiled = null; // must remove for the next macro to show
}
}
})
</script>

I use this method in my project for rendering templates as reactive component. All I need is passing in props URL for download template or template for immediate render:
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://unpkg.com/vue#3/dist/vue.global.js"></script>
<script>
const { createApp, defineComponent, markRaw } = Vue;
createApp({
template: `
<component v-if="component" :is="component"/>
<span v-else>Component is loading...</span>
`,
data: () => {
return {
component: undefined
}
},
provide: {
message: 'Hello Vue!'
},
mounted() {
setTimeout(async () => {
this.component = await this.defineRawComponent('<span>{{ message }}</span>')
}, 1000);
},
methods: {
async defineRawComponent(template, url) {
if (!template && !url) {
throw new Error('URL and template is not defined');
}
try {
let html = template;
if (!html) {
const { data } = await axios.get(url);
html = data;
}
return Promise.resolve(
markRaw(
defineComponent({
name: 'RawContent',
template: html,
inject: ['message']
})
)
);
} catch (err) {
return Promise.reject(err);
}
}
}
}).mount('#app')
</script>

Related

How to load Google API client library with SvelteKit

I'm new to SvelteKit and trying to find out how to load the Google client library for Javascript.
Google tells me to do it like this:
<head>
<script src="https://apis.google.com/js/api.js"></script>
<script>
function start() {
// Initializes the client with the API key and the Translate API.
gapi.client.init({
'apiKey': 'YOUR_API_KEY',
'discoveryDocs': ['https://www.googleapis.com/discovery/v1/apis/translate/v2/rest'],
}).then(function() {
// Executes an API request, and returns a Promise.
// The method name `language.translations.list` comes from the API discovery.
return gapi.client.language.translations.list({
q: 'hello world',
source: 'en',
target: 'de',
});
}).then(function(response) {
console.log(response.result.data.translations[0].translatedText);
}, function(reason) {
console.log('Error: ' + reason.result.error.message);
});
};
// Loads the JavaScript client library and invokes `start` afterwards.
gapi.load('client', start);
</script>
</head>
The problem is that SvelteKit doesn't allow 2 or more script tags on a page (I don't want it to be the layout page).
<script src="https://apis.google.com/js/api.js"></script>
<script>
import { onMount } from 'svelte';
gapi.client.init({...
</script>
This results in follwing error message:
A component can only have one instance-level <script> element
As my intention is to create a progressive web app (PWA) using Workbox I don't want to import the Google library as described here because the package containing this library would become too heavy.
Any ideas how to load the Google client library? Maybe there's a Workbox way to do it? Couldn't find a SvelteKit example on Google or YouTube.
Thanks in advance
The svelte:head tag allows you to add resources to the document head when a component is loaded. This example should work:
<script>
const start = async () => {
// Initializes the client with the API key and the Translate API.
// #ts-ignore
gapi.client.init({
'apiKey': 'YOUR_API_KEY',
'discoveryDocs': ['https://www.googleapis.com/discovery/v1/apis/translate/v2/rest'],
}).then(function() {
// Executes an API request, and returns a Promise.
// The method name `language.translations.list` comes from the API discovery.
return gapi.client.language.translations.list({
q: 'hello world',
source: 'en',
target: 'de',
});
}).then(function(response) {
console.log(response.result.data.translations[0].translatedText);
}, function(reason) {
console.log('Error: ' + reason.result.error.message);
});
};
const initializeGapi = async () => {
gapi.load('client', start);
}
</script>
<svelte:head>
<script src="https://apis.google.com/js/api.js" on:load={initializeGapi}></script>
</svelte:head>
I've made something like this.
Save it as GoogleMap.svelte to your lib folder. and use it like this;
<GoogleMap
{map}
globally
on:load={() => {
console.log('MAP SAYS IM LOADED');
}}
/>
Map is a reference object
globally defines it to window.map
<script>
import { onMount } from 'svelte';
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
//import mapStyles from './map-styles'; // optional
export let globally = false;
export let map;
let src = '';
const key = '';
// #ts-ignore
let container;
let zoom = 8;
let center = { lat: 37.5742776, lng: 43.7260158 };
onMount(() => {
Object.assign(window, {
mapLoaded: () => {
// #ts-ignore
map = new google.maps.Map(container, {
zoom,
center
// styles: mapStyles
});
dispatch('load', true);
if (globally) {
Object.assign(window, { map });
}
}
});
//Assign
src = `https://maps.googleapis.com/maps/api/js?key=${key}&callback=mapLoaded`;
});
</script>
<!-- This is tailwind css class change with whatever fits to your case. -->
<div class="w-full h-full" bind:this={container} />
<svelte:head>
{#if src}
<script {src}></script>
{/if}
</svelte:head>

wait for a job finished to render component in vuejs

I have a component here, and I need first to make a request using socket.io :
<template>
<h1>Don't show me before the socket's response</h1>
</template>
<script>
export default {
beforeCreate: function() {
let sessid = this.$cookie.get('sessid')
this.$options.sockets.logout = (data) => {
if (data.redirect) {
this.$router.push(data.redirect)
} else {
console.log('here, you can render the template')
}
}
this.$socket.emit('logout', { sessid })
}
}
</script>
This code works, but it shows the template in browser for a quick moment, before the redirection happens.
I would like to know if there's a tick to wait the socket response for rendering the template.
You can use v-if, when the socket response arrives, you can set a variable which can be used with v-if to not show the HTML, something like following:
<template>
<h1 v-if="sockResp">Don't show me before the socket's response</h1>
</template>
<script>
export default {
data: function() {
return {
sockResp: false
}
},
beforeCreate: function() {
let sessid = this.$cookie.get('sessid')
this.$options.sockets.logout = (data) => {
if (data.redirect) {
this.$router.push(data.redirect)
} else {
console.log('here, you can render the template')
this.sockResp = true
}
}
this.$socket.emit('logout', { sessid })
}
}
</script>

react prevent children render ( ajax loader waiting for response )

Im trying to write simple ajax loader and I wondering that i can prevent props.children render in parent container. The problem is that children want to render, no matter that Loader want to show it or not, and if render is based on ajax data that cousing errors.
Example: https://jsfiddle.net/j8dvsq39/
Example2:
This example will produce error couse this.state.data.user is undefined before ajax request.
Loader:
import React from 'react'
export default React.createClass({
getDefaultProps() {
return { text: "Loading", loaded: false };
},
render() {
if(this.props.loaded == false)
return <div>{this.props.text}</div>;
else
return <div>{this.props.children}</div>;
}
})
Class using Loader
import React from 'react'
import Loader from '../helpers/Loader';
import {comm} from '../Comm';
export default React.createClass({
getInitialState() {
return {loaded: false, data: null};
},
componentWillMount(){
comm.get("/xxx/xxx", {json: 1}, (back) => {
console.log(back);
this.setState({loaded: true, data: back});
});
},
render(){
return <Loader loaded={this.state.loaded}>{this.state.data.user.name}</Loader>
});
Reason is, initially you defined data=null and before the ajax call you are using this.state.data.user.name, it will throw the error:
Cannot read property 'name' of undefined
Simple solution is you need to put the check on data until you didn't get the ajax response, Check this:
var Loader = React.createClass({
getDefaultProps() {
return { text: "Loading", loaded: false };
},
render() {
if(this.props.loaded == false)
return <div>{this.props.text}</div>;
else
return <div>{this.props.children}</div>;
}
});
var Hello = React.createClass({
getInitialState() {
return {loaded: false, data: null};
},
componentWillMount(){
setTimeout(()=>{
this.setState({loaded: true, data: {user:{name: "login"}}});
}, 1000);
},
render: function() {
var user = null;
return <Loader loaded={this.state.loaded}>
<div>
Hello {this.state.data ? this.state.data.user.name : null}
</div>
</Loader>;
}
});
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='container'/>

React - how to update children on AJAX load

The example here - https://facebook.github.io/react/tips/initial-ajax.html - is modified below.
this.state.username is "placeholder" on load and is passed to <UserGist />
After AJAX loads this.state.username is changed to "octocat", but...
How come this is not passed down to the <UserGist /> component?
var UserGist = React.createClass({
getInitialState: function() {
return {
username: this.props.username
};
},
render: function() {
return (
<div>
{this.state.username}
</div>
);
}
});
var App = React.createClass({
getInitialState: function() {
return {
username: 'placeholder'
};
},
componentDidMount: function() {
this.serverRequest = $.get(this.props.source, function (result) {
var lastGist = result[0];
this.setState({
username: lastGist.owner.login
});
}.bind(this));
},
componentWillUnmount: function() {
this.serverRequest.abort();
},
render: function() {
return (
<div>
<UserGist username={this.state.username} />
</div>
);
}
});
ReactDOM.render(
<App source="https://api.github.com/users/octocat/gists" />,
document.getElementById('main')
);
You are passing the new username to your child component, but you are not updating your child state.
If you want to update your child state when the component receives new props, you should implement it.
UserGist component.
componentWillReceiveProps: function(nextProps){
this.setState({username: nextProps.username});
}
You should access the value via props in your UserGist component as it is getting passed in as a prop:
var UserGist = React.createClass({
render: function() {
return (
<div>
{this.props.username}
</div>
);
}
});
Which can then be represented as a stateless functional component (if you are using React > 0.14):
function UserGist(props) {
return <div>{props.username}</div>
}
It's not necessary to convert it into state unless you are going to be doing some sort of mutation to it within the component itself. It's best to try and use props as far as you can and minimise state.
Some further reading:
state vs props: What is the difference between state and props in React?
Stateless Functional Components

angular-slick carousel not working when using promise

This is driving my crazy, the first angular-slick is not working but the second is just fine, any idea what is going on?
I created a plunkr (in case someone is looking for an example in the future), but my problem is very odd because in my code/realproject is not working so I don't know what the hell is going on, anyway! here is the plunkr: http://plnkr.co/edit/URIbhoVpm1OcLSQqISPs?p=preview
I think the problem is related to the DOM because maybe angular needs to create the html before the carousel is render, I don't know... :(
This is the outcome:
https://db.tt/noc0VgGU
Router:
(function() {
'use strict';
angular
.module('mgxApp.landing')
.config(configFunction);
configFunction.$inject = ['$routeProvider'];
function configFunction($routeProvider) {
$routeProvider.when('/', {
templateUrl: 'app/landing/landing.html',
controller: 'homeCtrl',
controllerAs: 'hC'
});
}
})();
Controller:
(function() {
'use strict';
angular
.module('mgxApp.landing')
.controller('homeCtrl', homeCtrl);
homeCtrl.$inject = ['modalFactory', 'channelFactory'];
function homeCtrl(modalFactory, channelFactory) {
var hC = this;
hC.openAuthModal = modalFactory.openAuthModal;
hC.activeChannels;
channelFactory.allActiveChannels().then(function(activechannels){
console.log(activechannels);
hC.activeChannels = activechannels;
});
hC.w = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
hC.breakpoints = [
{
breakpoint: 768,
settings: {
slidesToShow: 2,
slidesToScroll: 2
}
}, {
breakpoint: 480,
settings: {
slidesToShow: 1,
slidesToScroll: 1
}
}
];
}
})();
HTML VIEW:
// NOT WORKING
<slick class="slider single-item" responsive="hC.breakpoints" slides-to-show=3 slides-to-scroll=3>
<div ng-repeat="channel in hC.activeChannels">
{{channel.get("username")}}
</div>
</slick>
// Working fine
<slick class="slider single-item" current-index="index" responsive="hC.breakpoints" slides-to-show=3 slides-to-scroll=3>
<div ng-repeat="i in hC.w">
<h3>{{ i }}</h3>
</div>
</slick>
Factory and Promise:
(function () {
'use strict';
angular
.module('mgxApp.channel')
.factory('channelFactory', channelFactory);
channelFactory.$inject = ['$rootScope', '$q'];
function channelFactory($rootScope, $q) {
var service = {
allActiveChannels : allActiveChannels
};
return service;
function allActiveChannels() {
var deferral = $q.defer();
var User = Parse.Object.extend("_User");
var query = new Parse.Query(User).limit(10);
query.find({
success: function(users) {
console.log(users);
/*for (var i = 0; i < users.length; i++) {
console.log(users[i].get("username"));
}*/
deferral.resolve(users);
},
error: function(error) {
console.warn(error);
deferral.reject();
}
});
return deferral.promise;
}
}
})();
My working code
<div tmob-slick-slider sliderData="" dynamicDataChange="true" class="utilHeightImg marqueeContainer">
<slick id="productCarousel" class="slider" settings="vm.slickAccessoriesConfig" data-slick='{"autoplay ": true, "autoplaySpeed": 4000}'>
<!-- repeat='image' -->
<div ng-repeat="slideContent in vm.slides track by $index" >
<div bind-unsafe-html="slideContent" ></div>
</div>
<!-- end repeat -->
</slick>
</div>
you have to write a directive to reinitialize the slider
angular.module('tmobileApp')
.directive('tmobSlickSlider',['$compile',function ($compile) {
return {
restrict: 'EA',
scope: true,
link: function (scope, element, attrs) {
scope.$on('MarqueesliderDataChangeEvent', function (event, data) {
$compile(element.contents())(scope);
});
}
};
}]);
Write this in your controller
hc.selectView=false; // make this hc.selectView=true when your promise get resolve
$scope.$watch('hc.selectView', function(newValue, oldValue) {
$scope.$broadcast('MarqueesliderDataChangeEvent');
});
I ended up using this solution:
Angular-slick ng-repeat $http get
I'd suggest you to use ng-if on slick element. That will only load slick directive only when data is present just by checking length of data.
Markup
<slick ng-if="ctrl.products.length">
<div ng-repeat="product in ctrl.products">
<img ng-src="{{product.image}}" alt="{{product.title}}"/>
</div>
</slick>

Resources