Rendering Dynamic Components in vue.js? - javascript

I'm trying to render a dynamic component in vue.js by faking an ajax call by using set timeout. It should register a new component then set it in the view! I'm using vue.js 1. Please show me a solution as to how I can get this working please.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>test dynamic components</title>
</head>
<body>
<div v-component="{{currentView}}"></div>
<script src="vue-v1.js"></script>
<script>
//Create the 'default' component
Vue.component("default", {
template: '<div>This should be replaced (and will be in 2 seconds)</div>'
});
//Create the parent ViewModel
var vm = new Vue({
el: "body",
data: {
currentView: "default"
}
});
//Pretend to load the data from the server
//This would actually be $.get("/url", data, function(){...});
window.setTimeout(function () {
//Create the new component using the template we received
Vue.component("BoardFeed", {
template: '<div>Template returned from server, what I really want</div><br>And directives work too:<div v-repeat="items">{{$value}}</div>',
data: function () {
return {
items: [1, 2, 3]
};
}
});
//And then change the page to that component
vm.currentView = "BoardFeed";
}, 2000);
</script>
</body>
</html>
ISSUE:
I'm using Vue.js 1 and I can't interpolate the view of my component! I don't think using the v-component even is used anymore

Your code works fine with Vue.js 1 when you make two small changes:
Use <component :is="currentView"></component> to include the components. v-component has been removed quite some time ago in 0.12.0.
Use <div v-for="value in items">{{value}}</div> to iterate over an array. v-repeat has been deprecated in 1.0.
Here's a working JSFiddle.

Related

after changing vuejs's html heading by calling this.$refs, vuejs not updating that html element afterwards

i have simple html code :
<script src="vue.js"></script>
<div id="app1">
<h1 ref="heading">{{ title }}</h1>
<button v-on:click="change" ref="myButton">Change Title</button>
</div>
<script src="app.js"></script>
and this is app.js
let v1=new Vue({
el: '#app1',
data: {
title: 'The VueJS Instance'
},
methods: {
change: function() {
this.title ='The VueJS Instance (Updated)';
}
},
watch: {
title: function(value) {
alert('Title changed, new value: ' + value);
}
}
});
v1.$refs.heading.innerText="BEFORE Change";
As you can see i am setting innertext for "h1 heading" element. After that i am clicking on button which will call change method, after that call it opens a new dialog window saying "Title changed, new value: The VueJS Instance (Updated)" but the page (h1 heading) not updated it still remains same "BEFORE Change", What is wrong in my code, i think it should update the heading (i am using Vue.js v2.6.11 version). Thanks
You should never update the DOM of a Vue template directly - otherwise the synchronization between the virtual DOM and the real DOM will be lost and you will get all kinds of strange errors or awkward behavior.
Noone can save you from shooting yourself in the foot. Remove the last statement in your code and you will see that the title is properly updated.

Can't write HTML in React Components

I am using React without Node, or any other thing (I cannot use it, they asked me to do use react in this way in my job... nothing I can do about it), I am using a HTML file with references to different scripts.
So, I managed to make this code work:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Add React in One Minute</title>
<script src="https://unpkg.com/react#16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js" crossorigin></script>
</head>
<body>
<h1>HTML with multiple scripts using React.js</h1>
<p>React.js without Node.js or any other thing</p>
<p>React.js is loaded as script tags.</p>
<div id="like_button_container"></div>
<div id="like_button_container2"></div>
<script src="like_button.js"></script>
<script src="like_button2.js"></script>
</body>
</html>
like_button.js :
'use strict';
const e = React.createElement;
class LikeButton extends React.Component {
constructor(props) {
super(props);
this.state = { liked: false };
}
render() {
if (this.state.liked) {
return 'You liked this.';
}
return e(
'button',
{ onClick: () => this.setState({ liked: true }) },
'Like'
);
}
}
const domContainer = document.querySelector('#like_button_container');
ReactDOM.render(e(LikeButton), domContainer);
like_button2.js (yes is an H1, not a button, just testing):
'use strict';
const f = React.createElement;
class LikeButton2 extends React.Component {
constructor(props) {
super(props);
this.state = { liked: false };
}
render() {
if (this.state.liked) {
return 'You liked this.';
}
return f(
'h1',
{ onClick: () => this.setState({ liked: true }) },
'Like'
);
}
}
const domContainer2 = document.querySelector('#like_button_container2');
ReactDOM.render(f(LikeButton2), domContainer2);
However, I cannot write "normal" HTML in the return of the component, cause it won't render and will give me an error:
return f(
<div>LOL</div>
);
Pic of the error:
https://i.imgur.com/VqxaQof.png
How can I solve this? is really limitating and awful to write HTMl within quotation marks... remember, cannot use Node or any other thing.
And also, ¿how can I render everything within the same div? If I try to render both components on the same div, it only renders the first script but the weird part is that one script is "aware" of the existence of the other (can't use same name for variables, for example).
Thank you in advance, I'm really suffering with this.
The biggest problem is that support for import statements is limited to more modern browsers. So if you want to keep the elements within the same div id="root", then you'll either have to define them within the same file or use a third-party library. That said, JSX isn't valid HTML, so you're either stuck forcing users to transpile the JSX into valid JS for every visit (bad -- slow performance, unoptimized code, and waste of bandwidth), or you're going to want to compile your JSX into a minified/optimized js file:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Add React in One Minute</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="application/javascript">
"use strict";function _instanceof(e,t){return null!=t&&"undefined"!=typeof Symbol&&t[Symbol.hasInstance]?!!t[Symbol.hasInstance](e):e instanceof t}function _typeof(e){return(_typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function _classCallCheck(e,t){if(!_instanceof(e,t))throw new TypeError("Cannot call a class as a function")}function _defineProperties(e,t){for(var n=0;n<t.length;n++){var i=t[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}}function _createClass(e,t,n){return t&&_defineProperties(e.prototype,t),n&&_defineProperties(e,n),e}function _possibleConstructorReturn(e,t){return!t||"object"!==_typeof(t)&&"function"!=typeof t?_assertThisInitialized(e):t}function _getPrototypeOf(e){return(_getPrototypeOf=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function _assertThisInitialized(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function _inherits(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&_setPrototypeOf(e,t)}function _setPrototypeOf(e,t){return(_setPrototypeOf=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}var H1LikeButton=function(e){function t(e){var n;return _classCallCheck(this,t),(n=_possibleConstructorReturn(this,_getPrototypeOf(t).call(this,e))).state={isLiked:!1},n.handleClick=n.handleClick.bind(_assertThisInitialized(n)),n}return _inherits(t,React.Component),_createClass(t,[{key:"handleClick",value:function(){this.setState(function(e){return{isLiked:!e.isLiked}})}},{key:"render",value:function(){return React.createElement(React.Fragment,null,this.state.isLiked&&React.createElement("p",null,"This is liked."),React.createElement("h1",{onClick:this.handleClick},!this.state.isLiked?"Like":"Dislike"))}}]),t}(),LikeButton=function(e){function t(e){var n;return _classCallCheck(this,t),(n=_possibleConstructorReturn(this,_getPrototypeOf(t).call(this,e))).state={isLiked:!1},n.handleClick=n.handleClick.bind(_assertThisInitialized(n)),n}return _inherits(t,React.Component),_createClass(t,[{key:"handleClick",value:function(){this.setState(function(e){return{isLiked:!e.isLiked}})}},{key:"render",value:function(){return React.createElement("div",null,this.state.isLiked&&React.createElement("p",null,"This is liked."),React.createElement("button",{onClick:this.handleClick},!this.state.isLiked?"Like":"Dislike"),React.createElement("br",null),React.createElement(H1LikeButton,null))}}]),t}();ReactDOM.render(React.createElement(LikeButton,null),document.getElementById("root"));
</script>
</body>
</html>
So... compile everything into a single file, or... you'll have to use a third party library that allows importing/exporting js files.
Working repo example (using requirejs): https://github.com/mattcarlotta/standalone-requirejs-example
Some tools to help you:
Compile Babel ES6+ to ES5 JS
Javascript Minifier
In short, it's uncommon to not use some sort of bundler (webpack, rollup, gulp, parcel, browserify, ...etc), so while it's possible to work with JSX in development, you'll still want to compile it for production. By not being able to use node and a bundler, expect to be handicapped throughout development/production.

Insert script tag into Angular 4 HTML Component

I am trying to implement the HTML5 version of the PhotoeditorSDK from this website - https://docs.photoeditorsdk.com/guides/html5/v4/introduction/getting_started
I have tried to implement using the Angular Github repo but it would seem that package.json illustrate this only works for Angular 5 & 6 and our app currently is a little outdated being Angular 4.x (and we cannot upgrade to 5 at this time)
Using the HTML5 method is fairly easy in a simple application but within an Angular 4 this seems to be tricky as due to Angular restrictions I am unable to use <script> tags within the component.
In the index <head> I have added the following:
<head>
<!-- React Dependencies for the SDK UI -->
<script src="js/vendor/react.production.min.js"></script>
<script src="js/vendor/react-dom.production.min.js"></script>
<!-- PhotoEditor SDK-->
<script src="js/PhotoEditorSDK.min.js"></script>
<!-- PhotoEditor SDK UI -->
<script src="js/PhotoEditorSDK.UI.DesktopUI.min.js"></script>
<link rel="stylesheet" href="css/PhotoEditorSDK.UI.DesktopUI.min.css"/>
</head>
In the template itself there is a simple <div> as follows :
<div id="editor" style="width: 100vw; height: 100vh;"></div>
The script tag itself looks as following - and would basically attach an image that would show within the editor to edit etc..
<script>
window.onload = function () {
var image = new Image()
image.onload = function () {
var container = document.getElementById('editor')
var editor = new PhotoEditorSDK.UI.DesktopUI({
container: container,
// Please replace this with your license: https://www.photoeditorsdk.com/dashboard/subscriptions
license: '{"owner":"Imgly Inc.","version":"2.1", ...}',
editor: {
image: image
},
assets: {
// This should be the absolute path to your `assets` directory
baseUrl: '/assets'
}
})
}
image.src = './example.jpg'
}
</script>
I am trying to figure out the best way to use <script> above directly within an Angular 4 Component - I know this is best practice but what is the best way to do this?
Your component has an element with id editor. This will only be available in the ngAfterViewInit hook of the component. Once this is called, the window.onload has been called ages ago. Also when the onload is called, the element doesn't exist at all yet, so it's also a bad idea to put it there.
Best would be to call the method from inside the ngAfterViewInit of your component, and use the #ViewChild annotation:
declare const PhotoEditorSDK: any;
#Component({
template: `<div style="width: 100vw; height: 100vh;" #editor></div>`
})
export class EditorComponent implements AfterViewInit {
#ViewChild('editor') editor: ElementRef<HTMLElement>;
ngAfterViewInit(): void {
const image = new Image();
image.addEventListener('load', () => {
const editor = new PhotoEditorSDK.UI.DesktopUI({
container: this.editor.nativeElement,
license: '{"owner":"Imgly Inc.","version":"2.1", ...}',
editor: {
image
},
assets: {
baseUrl: '/assets'
}
});
});
image.src = './example.jpg'
}
}

Vue.js mount component after DOM tree mutation to add a vue component

I have a use case (below) where I need to mount (if thats the correct term) a Vue.js component template that was inserted into the DOM via jQuery, I can setup a Mutation Observer or react to certain events that are triggered when the mutation happens.
I am using Vue.js v2
Here is a simple example I put together to illustrate the point:
live jsFiddle https://jsfiddle.net/w7q7b1bh/2/
The HTML below contains inlined-templates for two components
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<div id="app">
<!-- The use of inline-template is required for my solution to work -->
<simple-counter inline-template>
<button v-bind:style="style" v-on:click="add">clicks: {{ counter }}</button>
</simple-counter>
<simple-counter inline-template>
<button v-on:click="counter += 1">{{ counter }}</button>
</simple-counter>
</div>
<button id="mutate">Mutate</button>
The js:
// simple counter component
Vue.component('simple-counter', {
data: function() {
return {
counter: 0,
style: {
color: 'red',
width: '200px'
}
}
},
methods: {
add: function() {
this.counter = this.counter + 1;
this.style.color = this.style.color == 'red' ? 'green' : 'red';
}
}
})
// create the Vue instance
var initV = () => new Vue({
el: '#app'
});
// expose the instance for later use
window.v = initV();
// click handler that will add a new `simple-counter` template to the Vue.el scope
$('#mutate').click(function(){
$('#app').append(` <div is="simple-counter" inline-template>
<button v-bind:style="style" v-on:click="add">click to add: <span class="inactive" v-bind:class="{ active: true }">{{ counter }}</span></button></div>`)
// do something after the template is incerted
window.v.$destroy()
window.v = initV(); // does not work
})
As mentioned in the code, destroying the re-instantiating the Vue instance does not work, I understand why, the templates for the components are changed on first Vue instantiation to their final HTML, when you try and instantiate a second time, templates are not there, components are not mounted
I'd like to be able to find the newly added components after mutation and mount only those, is that possible? and how?
UPDATE:
I was able to find a way to do it via instantiating a new Vue instance with el set to the specific mutated part of the DOM as opposed to the whole #app tree:
$('#mutate').click(function(){
var appended =
$(`
<div is="simple-counter" inline-template>
<button v-bind:style="style" v-on:click="add">
click to add: {{ counter }}
</button>
</div>`
).appendTo($('#app'));
var newV = new Vue({el: appended[0]});
});
Seems to work, but also looks ugly and I am not sure what other implications this might have..
Use Case:
I am working on a way to write Vue.js components for a CMS called Adobe Experience Manager (AEM).
I write my components using inlined-template which gives me the advantage of SEO as well as server-side rendering using another templating language called HTL.
The way AEM authoring works is that, when a component is edited (via a dialog), that specific component is re-rendered on the server-side then injected back to the DOM to replace the old component, all done via Ajax and jQuery (no browser refresh).
Here is an example
AEM component template:
<button>${properties.buttonTitle}</button>
Here is what an author might do:
author visits the authoring page
opens the button component dialog to edit
changes the buttonTitle to "new button title"
Saves
upon saving, an ajax is sent, the component HTML is re-rendered on the server and returned is the new HTML. That HTML now replaces the old HTML via jQuery (mutates the DOM)
This is fine for static components, but if this was a Vue.js component, how do I dynamically mount it while keeping other components mounted.
An easy solution to this is to refresh the page... but that is just bad experience... There has to be a better way.
Thanks to #liam I was able to find an appropriate solution to my problem
After mutating the DOM with the HTML template, keep a reference to that template's parent element
for example:
var $template = $('<div is="simple-counter" inline-template> ..rest of template here.. <div>').appendTo('#app') // app is the Vue instance el or a child of it
Now you can create a new instance of your component and add $template to it as the el property
if my component was:
var simpleCounterComponent = Vue.component('simple-counter', {
data: function() {
return {
counter: 0,
style: {
color: 'red',
width: '200px'
}
}
},
methods: {
add: function() {
this.counter = this.counter + 1;
this.style.color = this.style.color == 'red' ? 'green' : 'red';
}
}
})
I can do:
var instance = new simpleCounterComponent({
el: $template.get(0) // getting an HTML element not a jQuery object
});
And this way, that newly added template has become a Vue component
Take a look at this fiddle for working example based on the question:
https://jsfiddle.net/947ojvnw/11/
One way to instantiate Vue components in runtime-generated HTML is:
var ComponentClass = Vue.extend({
template: '...',
});
var instance = new ComponentClass({
propsData: { name: value },
});
instance.$mount('#uid'); // HTML contains <... id="uid">
...
instance.$destroy(); // if HTML containing id="uid" is dropped
More here (I am not affiliated with this site)
https://css-tricks.com/creating-vue-js-component-instances-programmatically/

Custom HTML element attribute not showing - Web-Components

I'm exploring web-components custom HTML elements, and I am running into a problem adding custom attributes to my custom elements: any value I set in the markup is never honored.
For a simple element like this, which should show the text supplied in the "flagtext" attribute, it does always show the default value.
<test-flag flagtext="my text"></test-flag>
Full JSBin sample is here.
The JSBin uses the Polymer library (as this is the only thing I can pull in there). I am using webcomponents.js generally, same result. Both in Chrome 49 and in Firefox 45 gives same result. There is no error showing in the console or debugger.
Every sample I find on the web has similar code but I tried various versions and it always refuses to update. I even copied a few samples into JSBin and they do not work either.
What could be wrong? I understand it is experimental technology but the consistency with which this isn't working is still surprising. Has this standard been abandoned? (I see that the latest April 2016 W3C draft of custom elements has entirely changed its approach.)
When I define "attributeChangedCallback" function, it does not fire.
I can easily modify the property via Javascript, this is not a problem.
But why can I not specify the property in markup, as I am supposed to?
Edit - full code
Note that you'll need to put these into separate files for the HTML import, and that you need the "webcomponents-lite.js" library.
Main HTML file
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
test-flag
{
border: 1px solid blue;
}
</style>
</head>
<body>
<script src="lib/webcomponents-lite.min.js"></script>
<link rel="import" href="test-flag.html">
Here is the custom test-flag component:
<p>
<test-flag flagtext="my text"></test-flag>
</body>
</html>
File: test-flag.html
<template>
<style>
</style>
Content:
<div id="testflagID">
</div>
</template>
<script>
(function() {
var _currentScript = (document._currentScript || document.currentScript);
var importDoc = _currentScript.ownerDocument;
var customPrototype = Object.create(HTMLElement.prototype);
Object.defineProperty(customPrototype, 'flagtext', {value: '(default)', writable: true});
document.registerElement('test-flag', {prototype: customPrototype});
customPrototype.createdCallback = function()
{
var template = importDoc.querySelector('template');
var clone = document.importNode(template.content, true);
var idx = clone.querySelector("#testflagID");
idx.textContent = this.flagtext;
var root = this;
var createdElement = root.appendChild(clone);
};
})();
</script>
There are 2 concepts that are not automatically linked together.
In the HTML code:
<test-flag flagtext="my text"></test-flag>
...term flagtext is an HTML attribute (not a property).
In the JavaScript code:
Object.defineProperty(customPrototype, 'flagtext', {value: '(default)', writable: true})
...term flagtext is a JavaScript property (not an attribute).
For standard elements, the browser automatically binds the property value to the attribute value (and vice versa). For Custom Elements, too (with standard attributes). But if you want to add a custom attribute, you'll have to bind it manually.
For example, in the createdCallback() method, add:
this.flagtext = this.getAttribute( 'flagtext' ) || '(default)'
Live sample:
document.registerElement( 'test-flag' , {
prototype: Object.create( HTMLElement.prototype, {
flagtext: {
value: '(default)',
writable: true
},
createdCallback: {
value: function()
{
this.flagtext = this.getAttribute( 'flagtext' ) || this.flagtext
this.innerHTML = 'flagtext=' + this.flagtext
}
},
} )
} )
<test-flag flagtext='new content'>Hello</test-flag>
NB: the attributeChangedCallback() method is fired only when an attribute is changed after element creation (which is not the case here).

Categories