Using Vue to enhance existing Multi page / server rendered / classic web app - javascript

I’v been searching for hours now and didn’t find anything close to my use case.
I have a classic / multi page / server rendered e-commerce webstie made with JAVA
I have a page where the server renders a list of products with a pagination
Today, i use jQuery to do the pagination to give a better loading experience to the user
On my server, if the request is AJAX a send a json response, else i render a normal html view
With jQuery and vanilla it’s really easy, with Vue it doesn’t seem to work because Vue’s v-for and other template binding replaces the server rendered template directly…
The server would render this :
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
article {
margin: 8px 0;
background: #eee;
padding: 20px;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<!-- server rendered -->
<div id="app">
<h2>Freelance list</h2>
<article>
<h3>louane</h3>
<p>
City : <strong>courbevoie</strong>
<br> Phone : <strong>05-36-23-51-89</strong>
</p>
</article>
<article>
<h3>indra</h3>
<p>
City : <strong>rheden</strong>
<br> Phone : <strong>(354)-415-2419</strong>
</p>
</article>
<article>
<h3>angelo</h3>
<p>
City : <strong>montpreveyres</strong>
<br> Phone : <strong>(883)-474-9314</strong>
</p>
</article>
prev
next
</div>
<!-- server rendered -->
I want to be able to do something like this but with Vue :
// fake url link, normally this would be taken from the href or something
var url = 'https://randomuser.me/api/?seed=abc&results=3&page=';
var page = 1;
var $articles = $('.articles');
var tpl = $articles.children().eq(0).clone();
$('.prev').click(function(e) {
e.preventDefault();
if (page <= 1) {
return
}
page--;
$.getJSON(url + page)
.done(onReqDone);
});
$('.next').click(function(e) {
e.preventDefault();
page++;
$.getJSON(url + page)
.done(onReqDone);
});
function onReqDone(res) {
$articles.html('');
res.results.forEach(function(user) {
var $node = tpl.clone();
$node.find('h3').text(user.name.first);
$node.find('strong:eq(0)').text(user.location.city);
$node.find('strong:eq(1)').text(user.phone);
$articles.append($node);
window.scroll(0, 0);
});
}
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
article {
margin: 8px 0;
background: #eee;
padding: 20px;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<!-- server rendered -->
<div id="app">
<h2>Freelance list</h2>
<div class="articles">
<article>
<h3>louane</h3>
<p>
City : <strong>courbevoie</strong>
<br> Phone : <strong>05-36-23-51-89</strong>
</p>
</article>
<article>
<h3>indra</h3>
<p>
City : <strong>rheden</strong>
<br> Phone : <strong>(354)-415-2419</strong>
</p>
</article>
<article>
<h3>angelo</h3>
<p>
City : <strong>montpreveyres</strong>
<br> Phone : <strong>(883)-474-9314</strong>
</p>
</article>
</div>
prev
next
</div>
<!-- server rendered -->
Any ideo on how to do it ? Here are my tries :
https://jsfiddle.net/7270zft3/2/ : problem, it doesn’t remove the old dom
PS :
Before anyone talks about SSR with Vue ou just doing an SPA, here’s why i cant :
This e-commerce website can’t be totally re made with a Single Page App, it will cost too much time and money for the benefit it would bring to us
This e-commerce needs SEO to continue to drive traffic, just like any e-commerce btw
If Vue can really be used like jQuery (this is why we bet on Vue), we should be able to do this without doing a full rewrite
Event if we had time to rewrite an SPA, we can’t use SSR because our backend is made with JAVA and SSR seems to only be available with node and PHP with v8js module

You can attach a DOM ref to the server rendered content and then just as in jQuery, just clear the contents to the DOM element.
You need to perform this action only once, so you can may be add checks to see if your DOM ref is empty if page === 1
new Vue({
el: "#app",
data: {
users: null,
page: 1
},
methods: {
loadData: function(prev) {
var page = this.page
if (prev) {
page--
} else {
page++
}
fetch('https://randomuser.me/api/?seed=abc&results=3&page=' + page)
.then(res => res.json())
.then(data => {
this.$refs.serverContent.innerHTML = '';
this.users = data.results
this.page = page
window.scroll(0, 0)
})
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
article {
margin: 8px 0;
background: #eee;
padding: 20px;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<h2>Freelance list</h2>
<div ref="serverContent">
<article>
<h3>louane</h3>
<p>
City : <strong>courbevoie</strong>
<br> Phone : <strong>05-36-23-51-89</strong>
</p>
</article>
<article>
<h3>indra</h3>
<p>
City : <strong>rheden</strong>
<br> Phone : <strong>(354)-415-2419</strong>
</p>
</article>
<article>
<h3>angelo</h3>
<p>
City : <strong>montpreveyres</strong>
<br> Phone : <strong>(883)-474-9314</strong>
</p>
</article>
</div>
<!-- Vue part -->
<!-- how to plug Vue to handle the pagination ? -->
<article v-for="user in users">
<h3>{{ user.name.first }}</h3>
<p>
City : <strong>{{ user.location.city }}</strong>
<br> Phone : <strong>{{ user.phone }}</strong>
</p>
</article>
<!-- Vue part -->
<button v-show="page > 1" #click="loadData(true)">prev</button>
<button #click="loadData()">next</button>
</div>
<!-- server rendered -->
But to do it Vue style, it is advised that you remove the rendering of first page from the server and let Vue handle data-fetching by itself so that Vue can use the data it stores as the source of truth instead of manipulating the DOM.

An example of hydration. I wasn't able to get Vue to stop warning me that my generated HTML didn't match the original; it's not critical. In development, Vue will "bail and do a full render", but in production it will leave the pre-rendered. You just want to be sure they match, so that when it does update, it's what you expect.
I left jQuery in for the getJSON. Other than that, it's jQuery free.
// fake url link, normally this would be taken from the href or something
var url = 'https://randomuser.me/api/?seed=abc&results=3&page=';
var page = 1;
$.getJSON(url + page).done((res) => {
const articles = res.results;
new Vue({
el: '#app',
template: `
<div id="app">
<h2>Freelance list</h2>
<div class="articles">
<article v-for="article in articles">
<h3>{{article.name.first}}</h3>
<p>
City : <strong>{{article.location.city}}</strong>
<br> Phone : <strong>{{article.phone}}</strong>
</p>
</article>
</div>
prev
next
</div>
`,
data: {
page,
url,
articles
},
methods: {
getPage() {
$.getJSON(this.url + this.page)
.done((res) => {
this.articles = res.results;
});
}
},
watch: {
page() {
this.getPage();
}
}
});
});
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
article {
margin: 8px 0;
background: #eee;
padding: 20px;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app" data-server-rendered="true">
<h2>Freelance list</h2>
<div class="articles">
<article>
<h3>louane</h3>
<p>
City : <strong>courbevoie</strong>
<br> Phone : <strong>05-36-23-51-89</strong>
</p>
</article>
<article>
<h3>indra</h3>
<p>
City : <strong>rheden</strong>
<br> Phone : <strong>(354)-415-2419</strong>
</p>
</article>
<article>
<h3>angelo</h3>
<p>
City : <strong>montpreveyres</strong>
<br> Phone : <strong>(883)-474-9314</strong>
</p>
</article>
</div>
prev
next
</div>
<!-- server rendered -->

Someone on the Vue forum found a good approach close to what was posted here but suited better my need : https://forum.vuejs.org/t/using-vue-to-enhance-existing-multi-page-server-rendered-classic-web-app/30934/20

Related

TypeError: this.$refs is not a function

So I have a problem with VueJs. I created a "Confirmation Dialogue" and added it to a On-Click-Event on buttons. It worked fine.
Now I tried to copy the implementation to add it to another button on a different parent. It says "TypeError: this.$refs.confirmDialogue.show is not a function" in the Console whenever I try to click the button. The other button still works completly normal.
Am I missing something? I already tried to remove the working button, so only one component uses the Confirm Dialogue but that also didn't work.
I'm new to VueJs. Hope someone can help me with this problem.
Child PopupModal:
<template>
<transition name="fade">
<div class="popup-modal" v-if="isVisible">
<div class="window">
<slot></slot>
</div>
</div>
</transition>
</template>
<script>
export default {
name: 'PopupModal',
data: () => ({
isVisible: false,
}),
methods: {
open() {
this.isVisible = true
},
close() {
this.isVisible = false
},
},
}
</script>
<style scoped>
/* css class for the transition */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.popup-modal {
background-color: rgba(0, 0, 0, 0.5);
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
padding: 0.5rem;
display: flex;
align-items: center;
z-index: 1;
}
.window {
background: #fff;
border-radius: 5px;
box-shadow: 2px 4px 8px rgba(0, 0, 0, 0.2);
max-width: 480px;
margin-left: auto;
margin-right: auto;
padding: 1rem;
}
</style>
Parent ConfirmDialogue:
<template>
<popup-modal ref="popup">
<h2 style="margin-top: 0">{{ title }}</h2>
<p>{{ message }}</p>
<div class="btns">
<button class="cancel-btn" #click="_cancel">{{ cancelButton }}</button>
<span class="ok-btn" #click="_confirm">{{ okButton }}</span>
</div>
</popup-modal>
</template>
<script>
import PopupModal from "../confirmDialogue/PopupModal.vue"
export default {
name: 'ConfirmDialogue',
components: { PopupModal },
data: () => ({
// Parameters that change depending on the type of dialogue
title: undefined,
message: undefined, // Main text content
okButton: undefined, // Text for confirm button; leave it empty because we don't know what we're using it for
cancelButton: 'Abbrechen', // text for cancel button
// Private variables
resolvePromise: undefined,
rejectPromise: undefined,
}),
methods: {
show(opts = {}) {
this.title = opts.title
this.message = opts.message
this.okButton = opts.okButton
if (opts.cancelButton) {
this.cancelButton = opts.cancelButton
}
// Once we set our config, we tell the popup modal to open
this.$refs.popup.open()
// Return promise so the caller can get results
return new Promise((resolve, reject) => {
this.resolvePromise = resolve
this.rejectPromise = reject
})
},
_confirm() {
this.$refs.popup.close()
this.resolvePromise(true)
},
_cancel() {
this.$refs.popup.close()
this.resolvePromise(false)
// Or you can throw an error
// this.rejectPromise(new Error('User cancelled the dialogue'))
},
},
}
</script>
<style scoped>
.btns {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.ok-btn {
padding: 0.5em 1em;
background-color: #1F51FF;
color: #fff;
border: 2px solid #0ec5a4;
border-radius: 5px;
font-size: 16px;
text-transform: uppercase;
cursor: pointer;
}
.cancel-btn {
padding: 0.5em 1em;
background-color: #d5eae7;
color: #000;
border: 2px solid #0ec5a4;
border-radius: 5px;
font-size: 16px;
text-transform: uppercase;
cursor: pointer;
}
</style>
Working button:
<td class="last-td last-row">
<div class="button-wrapper">
<div class="wrapper-edit">
<button class="button button-edit">Bearbeiten</button>
</div>
<div class="wrapper-cancel">
<button class="button button-cancel" #click="doDelete">Löschen</button> <!-- Here is the working button -->
<confirm-dialogue ref="confirmDialogue"></confirm-dialogue>
</div>
</div>
</td>
</tr>
</thead>
</template>
<script>
import ConfirmDialogue from '../confirmDialogue/ConfirmDialogue.vue'
export default {
name: "bookingElements",
components: { ConfirmDialogue },
methods: {
async doDelete() {
const ok = await this.$refs.confirmDialogue.show({
title: 'Buchung löschen',
message: 'Sind Sie sicher, dass Sie die Buchung löschen wollen?',
okButton: 'Buchung löschen',
})
if (ok) {
alert('Die Buchung wurde Erfolgreich gelöscht')
}
},
Button that isn't working:
<td id="buttonCell">
<button class="button" #click="doDelete">Buchen</button>
<confirm-dialogue ref="confirmDialogue"></confirm-dialogue>
</td>
</tr>
</tbody>
</template>
<script>
import ConfirmDialogue from "../confirmDialogue/ConfirmDialogue.vue"
export default {
name: "bookElements",
components: { ConfirmDialogue },
methods: {
async doDelete() {
const ok = await this.$refs.confirmDialogue.show({
title: 'Buchung löschen',
message: 'Sind Sie sicher, dass Sie die Buchung löschen wollen?',
okButton: 'Buchung löschen',
})
if (ok) {
alert('Die Buchung wurde Erfolgreich gelöscht')
}
},
I got found the mistake.
I created a for-each loop to create the Table.
At the working button there was no for-each loop though.
So VueJs tried to generate the Confirmation Dialogue 9 times and this resulted to an error.
So I just need to put the
<confirm-dialogue ref="confirmDialogue"></confirm-dialogue>
to the top of Template like this
<template>
<confirm-dialogue ref="confirmDialogue"></confirm-dialogue>
...
</template>
because it's vanished and only shows when the button is clicked.

How can I block some characters in a textarea with VueJs?

I have a textarea for sending messages and I wanna block emails and site links. So when I write an # or https:// there must be error messages shown, using v-if, but how can I do this? Which functions?
new Vue({
el: "#app",
data: {
message: {
content: ""
}
},
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<textarea v-bind="message" v-model="message.content" cols="30" rows="10">
</textarea>
<p v-if="message.content == '#'">
No special characters
</p>
</div>
</div>
You can check by regex type , for your condition var regex = /(#|https)/g; . Also set hasError data for message display control and you can use vue watch for your data changing (message.content)
new Vue({
el: "#app",
data: {
message: {
content: ""
},
hasError: false
},
watch: {
'message.content': function(newVal,oldVal) {
var regex = /(#|https)/g;
this.hasError = newVal.match(regex);
}
}
})
body {
background: #20262E;
padding: 10px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 10px;
transition: all 0.2s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<textarea v-bind="message" v-model="message.content" cols="30" rows="5">
</textarea>
<p v-if="hasError">
No special characters
</p>
</div>
</div>
i think you should add method #change or #input to textarea, and then test for everything using regex and than replace it or whatever you want to do

Save contentEditable into html file with javascript

How can I save contenteditable element with javascript(no PHP) into actual HTML code? So I can edit content whenever even in offline mode.
Like when you click "save button" it replace old file with new one(text with changes).
If there is a way to make this work in offline mode with any other programming lang please suggest.
I found a few examples but they were all made with PHP.
Also, I will post code. In this code, you are able to edit the file with javascript and save it. But problem is that it does not save into actual HTML code.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title of the document</title>
</head>
<style type="text/css">
body{
font-family: "Dosis";
font-size: 1.3em;
line-height: 1.6em;
}
.headline{
font-size: 2em;
text-align: center;
}
#wrapper {
width: 600px;
background: #FFF;
padding: 1em;
margin: 1em auto;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
border-radius: 3px;
}
button {
border: none;
padding: 0.8em;
background: #F96;
border-radius: 3px;
color: white;
font-weight: bold;
margin: 0 0 1em;
}
button:hover, button:focus {
cursor: pointer;
outline: none;
}
#editor {
padding: 1em;
background: #E6E6E6;
border-radius: 3px;
}
</style>
<body>
<div id="wrapper">
<section>
<h1 class="headline">contentEditable Demonstration</h1>
<button id="editBtn" type="button">Edit Document</button>
<div id="editDocument">
<h1 id="title">A Nice Heading.</h1>
<p>Last Edited by <span id="author">Monty Shokeen</span>
</p>
<p id="content">You can change the heading, author name and this content itself. Click on Edit Document to start editing. At this point, you can edit this document and the changes will be saved in localStorage. However, once you reload the page your changes will be gone. To fix it we will have to retrieve the contents from localSotrage when the page reloads.</p>
</div>
</section>
</div>
<script>
var editBtn = document.getElementById('editBtn');
var editables = document.querySelectorAll('#title, #author, #content');
if (typeof(Storage) !== "undefined") {
if (localStorage.getItem('title') !== null) {
editables[0].innerHTML = localStorage.getItem('title');
}
if (localStorage.getItem('author') !== null) {
editables[1].innerHTML = localStorage.getItem('author');
}
if (localStorage.getItem('content') !== null) {
editables[2].innerHTML = localStorage.getItem('content');
}
}
editBtn.addEventListener('click', function(e) {
if (!editables[0].isContentEditable) {
editables[0].contentEditable = 'true';
editables[1].contentEditable = 'true';
editables[2].contentEditable = 'true';
editBtn.innerHTML = 'Save Changes';
editBtn.style.backgroundColor = '#6F9';
} else {
// Disable Editing
editables[0].contentEditable = 'false';
editables[1].contentEditable = 'false';
editables[2].contentEditable = 'false';
// Change Button Text and Color
editBtn.innerHTML = 'Enable Editing';
editBtn.style.backgroundColor = '#F96';
// Save the data in localStorage
for (var i = 0; i < editables.length; i++) {
localStorage.setItem(editables[i].getAttribute('id'), editables[i].innerHTML);
}
}
});
</script>
</body>
</html>
You'll want to use something like the downloadInnerHtml function as described here. Ideally you'll probably also want to strip out the script tag and content editable attribute before exporting because you won't want the final html page to be editable

How to update data on a page without refreshing on the vue.js?

My view is like this :
<div class="favorite" style="margin-bottom:5px;">
#if (Auth::user())
<add-favorite-store :id-store="{{ $store->id }}"></add-favorite-store>
#else
<a href="javascript:" class="btn btn-block btn-success">
<span class="fa fa-heart"></span> Favorite
</a>
#endif
</div>
My component is like this :
<template>
<a href="javascript:" class="btn btn-block btn-success" #click="addFavoriteStore($event)">
<span class="fa fa-heart"></span> <label id="favoriteId">{{ store_id == 'responseFound' ? 'Un-Favorite' : 'Favorite' }}</label>
</a>
</template>
<script>
export default{
props:['idStore'],
mounted(){
this.checkFavoriteStore()
},
methods:{
addFavoriteStore(event){
var label = $('#favoriteId')
var text = label.text()
event.target.disabled = true
const payload= {id_store: this.idStore}
if(text == "Favorite") {
this.$store.dispatch('addFavoriteStore', payload)
}
else {
this.$store.dispatch('deleteFavoriteStore', payload)
}
setTimeout(function () {
location.reload(true)
}, 1500)
},
checkFavoriteStore(){
const payload= {id_store: this.idStore}
var data = this.$store.dispatch('checkFavoriteStore', payload)
data.then((res) => this.store_id = res)
}
},
data() {
return {
store_id: ''
}
}
}
</script>
On the my code above, I using
location.reload(true)
to update data on the page. But it's reload the page.
I want when update the page, it's not reload the page
How can I do it?
Ok Here is a simple use case but less complicated as yours and using vuejs as it should be used. (http://codepen.io/simondavies/pen/MJOQEW)
OK let laravel/php code get the store ID as well as if its already been favorited. This way your script is not first checking the store to then decide what to do.
What this does is sends the store-id and the is-favorited through the component like:
<favorite :store-id="{{$store->id}}" :is-favorited="{{$store->isFavorited}}"></favorite>
Then the Vue component will update the button to display if its already liked (red) or not (grey), and then also handle the click event and update accordingly as well.
As you are using Laravel to tell the component if it's already favorited you can get rid of your checking function and one less http request. Then you only need to then update the store when the user clicks the favourite button.
And as seen in the demo it updates, no need to refresh.
I hope this help you re-write yours so you get waht you want.
PS i have left out the Logged IN check #if (Auth::user()) you have so you can put that back in etc
Vue.component('favorite', {
template: '<button type="button" #click.prevent="updateFaviteOption" :class="{ \'is-favorited\': isFavorited }"><span class="fa fa-heart"></span></button>',
props: [
'storeId',
'isFavorited'
],
data: function() {
return {}
},
methods: {
updateFaviteOption: function() {
this.isFavorited = !this.isFavorited;
///-- DO YOU AJAX HERE i use axios
///-- so you can updte the store the the this.isFavorited
}
}
});
new Vue({
el: '#app',
});
.favorite-wrapper {
margin: 20px auto;
padding: 0;
text-align: center;
width: 500px;
height: 100px;
}
a:link,
a:active,
a:visited {
text-decoration: none;
text-transform: uppercase;
color: #444;
transition: color 800ms;
font-size: 18px;
}
a:hover,
a:hover:visited {
color: purple;
}
button {
margin: 10px auto;
padding: 8px 10px;
background: transparent;
width: auto;
color: white;
text-transform: uppercase;
transition: color 800ms;
border-radius: 4px;
border: none;
outline: none;
}
button:hover {
cursor: pointer;
color: #058A29;
}
.fa {
color: #d9d9d9;
font-size: 20px;
transition: color 400ms, transform 400ms;
opacity: 1;
}
.is-favorited .fa {
opacity: 1;
color: red;
transform: scale(1.4);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css" rel="stylesheet" />
<div id="app">
<div class="favorite-wrapper">
<favorite :store-id="3" :is-favorited="true">
</favorite>
</div>
</div>
Another solution explained in vue-router doc. Simply use the watch system applied to the $route native attribute. You'll be able to detect new route even if they use the same component and fetch the data in consequence.
What you need is vm-forceUpdate.
Force the Vue instance to re-render. Note it does not affect all child components, only the instance itself and child components with inserted slot content.
I haven't tried using this myself, just came across it while working on a project.
vue forceUpdate or you could just call the function in the setTimeout method.
setTimeout(function () {
this.checkFavoriteStore();
}, 1500)
You are using a lot of javascript here, you can use vue to do most of the work there.

createShadowRoot makes element not visible

I am following this tutorial on the shadow DOM:
http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom/
For some reason, when I call the createShadowRoot function on the element, that element becomes invisible.
Here is my code:
<div id="nameTag">Bob</div>
<template id="nameTagTemplate">
<style>
.outer {
border: 2px solid brown;
}
</style>
<div class="outer">
<div class="boilerplate">
Hi! My name is
</div>
<div class="name">
Bob
</div>
</div>
</template>
<script>
var shadow = document.querySelector('#nameTag').createShadowRoot();
// var template = document.querySelector('#nameTagTemplate');
// shadow.appendChild(template.content.cloneNode());
</script>
When I don't call this method then the code works fine.
Any ideas why it is making it invisible?
Thanks :)
The main objective of shadow DOM is separation of content from presentation. Read: The content is in the document; the presentation is in the Shadow DOM
In Shadow DOM <content> acts as the insertion point for the content (in this case,
the text 'Bob' in the element) we want to show . Without this, the content, though already available in the document, can't be presented.
So, You need to modify your code to this -
<template id="nameTagTemplate">
<style></style>
<div class="outer">
<div class="boilerplate">
Hi! My name is
</div>
<div class="name">
<content></content>
</div>
</div>
</template>
And, try using
var shadow = document.querySelector('#nameTag').createShadowRoot();
var template = document.querySelector('#nameTagTemplate');
//shadow.appendChild( template.content.cloneNode() ); // does not work
shadow.appendChild( template.content.cloneNode(true) );
// or
shadow.appendChild( template.content );
using pure javascript:
// shadow DOM example with <template> and template string
//
var nameTag = function(selector, newName)
{
var name = newName || document.querySelector(selector).innerHTML;
var shadow = document.querySelector(selector).createShadowRoot();
var templateNode = createNameTagTemplate(name);
var clone = document.importNode(templateNode, true);
shadow.appendChild(clone.content);
};
function createNameTagTemplate(name)
{
var templateNode = document.createElement("template");
templateNode.innerHTML = `
<style>
.outer {
border: 2px solid brown;
border-radius: 1em;
background: red;
font-size: 20pt;
width: 12em;
height: 7em;
text-align: center;
}
.boilerplate {
color: white;
font-family: sans-serif;
padding: 0.5em;
}
.name {
color: black;
background: white;
font-family: "Marker Felt", cursive;
font-size: 45pt;
padding-top: 0.2em;
}
</style>
<div class="outer">
<div class="boilerplate">
Hi! My name is
</div>
<div class="name">
${name}
</div>
</div>`;
return templateNode;
}
then if you have this:
<div id="foo">Bob</div>
nameTag("#foo") will make the div into a nametag using current name, or
nameTag("#foo", "Joe") will make it for name "Joe" instead.
NOTE: Supported in Firefox behind the dom.webcomponents.enabled flag (about:config).

Categories

Resources