Clean separation of view and code with Vue using templates - javascript

I hope this is a trivial problem for someone and I am just missing a tiny magic piece of code: I am migrating from knockout.js to vue.js and I am wondering if there is a simple way to use templates the same way in vue.js as in knockout.js. The following code snipped uses knockout.js and works as expected:
function viewmodel() {
this.person = ko.observable(new person());
}
function person() {
this.first = ko.observable('hello');
this.last = ko.observable('world');
}
ko.applyBindings(new viewmodel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/html" id="person-template">
<p data-bind="text: first"/>
<p data-bind="text: last"/>
</script>
<div>
<div data-bind="template: { name: 'person-template', data: person }"/>
</div>
However, I can't figure out how to achieve the same in vue.js. I am aware of components in vue.js and all that, but I want to keep the structure as it is and not put more view-specific code into my JavaScript files. (I am already not happy with the el: '#app', but that's my least concern at the moment) Of course this code doesn't work:
var app = new Vue({
el: '#app',
data: {
person: {
first: 'hello',
last: 'world'
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.js"></script>
<script type="text/x-template" id="person-template">
<p v-text="first"/>
<p v-text="last"/>
</script>
<div id="app">
<div><!-- how? is this even possible? --></div>
</div>
Is there a way to get this working by only changing the HTML part of code? Preferably only the <div>-Element.

So this is not quite only changing the template, but a single additional property can work here. Specify a template in the Vue.
var app = new Vue({
el: '#app',
template: "#person-template",
data: {
person: {
first: 'hello',
last: 'world'
}
}
});
I also modified your template slightly because a template for any Vue or component requires one root element. I recommend you do not use self closing tags in Vue as I've seen this cause issues. Vue requires valid HTML5 and self closing tags are not valid HTML5.
<script type="text/x-template" id="person-template">
<div>
<p v-text="person.first"></p>
<p v-text="person.last"></p>
</div>
</script>
Working example.
Additionally, a very common way of specifying templates in this fashion, without using the kind of hacky script tag is to use the HTML5 template element.
<template id="person-template">
<div>
<p v-text="person.first"></p>
<p v-text="person.last"></p>
</div>
</template>
Finally, knowing you are slightly unhappy with the el:"#app" You can leave the el property out of the Vue definition and mount the Vue on an element of your choice using the $mount method. So if you wanted to go completely hog wild on separation of concerns you could do something like this.
const renderer = Vue.compile(document.querySelector("#person-template").innerHTML)
const example = {
data: {
person: {
first: 'hello',
last: 'world'
}
}
}
const app = new Vue(Object.assign({}, example, renderer))
app.$mount("#app")
Example.
I should mention nobody really does that (compiling their templates manually, it's common to use $mount) that I'm aware of. It also depends on using the version of Vue that includes the compiler (which you might not have if you are using webpack or some other build tool for example). You could also do something like this:
const app = new Vue(Object.assign({}, example, {template: "#person-template"}))
app.$mount("#app")
which would basically allow you to specify a template, a component, and a place to mount it all separately.

Related

Vue template isn't rendering in for loop

So after following a beginner Vue tutorial to setup a Todo app, I decided to try to adapt some parts of it for a website I'm trying to make. What I'm stuck on is that despite everything saying my for-loop is supposed to work, it doesn't.
The project itself was created using the vue-cli, and most of the code copy-pasted from the tutorial. (which is working fine with its own for-loop)
It seems like the data might be not passed onto the template maybe?
I have tried:
having the info inside the props and data sections
passing whole object and only parameters to the template
tried with hard-coded values inside array which is iterated on
(After setting up a new vue-cli project:)
App.vue:
<template>
<div id="app">
<create-section v-on:create-section="addSection" />
<section v-for="section in sections" v-bind:key="section.title" :info="section"></section>
</div>
</template>
<script>
import CreateSection from "./components/CreateSection";
import Section from "./components/Section";
export default {
name: "App",
components: {
CreateSection,
Section
},
data() {
return {
sections: []
};
},
methods: {
addSection(section) {
this.sections.push({
title: section.title,
description: section.description
});
console.log(
"Added to sections! : " + section.title + " | " + section.description
);
console.log("Sections length: " + this.sections.length);
}
}
};
</script>
Section.vue
<template>
<div class="ui centered card">
<div class="content">
<div class="header">{{ info.title }}</div>
<div>{{ info.description }}</div>
</div>
</div>
</template>
<script type = "text/javascript" >
export default {
props: {info: Object},
data() {
return {};
}
};
</script>
Expected result:
Display Section template on the website (after creating it with addSection that another script calls. Not included for brevity)
Actual result:
Nothing is displayed, only a empty tag is added
I believe the problem is that you've called it Section. As <section> is a standard HTML element you can't use it as a component name.
There is a warning built into the library but it seems to be case sensitive, which isn't entirely helpful. Try changing your components section to this:
components: {
CreateSection,
section: Section
},
You should then see the warning.
The fix would just be to call it something else.
This is mentioned in the first entry in the style guide:
https://v2.vuejs.org/v2/style-guide/#Multi-word-component-names-essential
section is an existing HTML5 element, you should name your section component something different.
If you really want to name the component Section, register it as 'v-section'
The problem is that when you do the loop in the <section v-for="section in sections" v-bind:key="section.title" :info="section"></section> the Array sections is not ready, there is nothing there.. so when you add new things to this array you need to trigger (computed prop) to send again the data to the section component.
Aside from the issue with using an existing HTML5 command as a name for your Vue component (you should change that to another name by the way), you should also look into how you declared the props within Section.vue. The code below shows the correct way to do it:
<script type = "text/javascript" >
export default {
props: ['info'],
data() {
return {};
}
};
</script>
The props take in the name of the property being declared from the parent component and should be a string.
Hope this helps.

Can't create custom Vue component in JSFiddle

In my JSFiddle example, I'm trying to define a custom Vue component: https://jsfiddle.net/50wL7mdz/402951/ .
Unfortunately, nothing is rendered. Why?
The code:
HTML:
<script src="https://unpkg.com/vue"></script>
<blog-post title="hi again!"></blog-post>
JS:
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
You forgot to create a Vue. Wrap the <blogpost> component into a div and create a Vue using that div as the template.
Like so
HTML:
<script src="https://unpkg.com/vue"></script>
<div id="app">
<blog-post title="hi again!"></blog-post>
</div>
JS
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app'
});
Look at the documentation documentation
Here is the working fiddle
My friend I recommend you another option, that I preferred. Please use, if you want, sandbox where you can add or modify components much much easier.
Here is the sandbox link.

Vuejs and drupal

I'm working on a Drupal project where we compile the js and sass of the theme with webpack. As we are moving in a near future to other backend(Laravel), and the idea is to use vuejs on front-end. So it seems to us a good idea, in meanwhile, start using vuejs in some pages and components, so we could start learn it about it. I have experience with angular and react but none with vue. I add it vue, the vue-loader, etc, but seems dificult to make it work and I'm not sure which could be the best way to implement/add vuejs in this escenario. Any recomendation or link will we very helpful.
Introduction
Vue is good choice because of two reasons in your case:
It is simplest to learn than Angular and React
It is progressive - it means you can easy use it only in constrained part of your existing project.
If you look at dock of life cycle of Vue instance
https://v2.vuejs.org/v2/guide/instance.html
You will see there are some options of create template and connect it with instance of vue.
a) by "el" option - selecting existing element from dom
b) by template option
including template as a string
selecting template by id of script with type text/x-template
You can use Vue instantly after page load or mount it later so you have flexibility.
Examples
I understood your question is about simplest way to integrate vue with drupal. I think that these examples of use Vue on simple html page will help you.
Simplest way
Simplest way is use el option and load Vue from cdn. ( remember about change cdn to minified on production )
<style>
[v-cloak] {
display: none;
}
</style>
<div id="app" v-cloak>
<h1>{{ heading }}</h1>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<script>
new Vue({
el: "#app",
data: { heading: "Hello World" }
});
</script>
By using text/x-template
<div id="app"></div>
<script id="app-template" type="text/x-template">
<div>
<h1>{{heading}}</h1>
</div>
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<script>
let v = new Vue({
template: `#app-template`,
data: { heading: "Hello World" }
});
v.$mount();
document.querySelector("#app").appendChild(v.$el);
</script>

Using html as input for a knockout-component

I am in the process of implementing some components in my codebase. However, I have ran into an smaller issue with the template part. I would like to send in the template as an input to a knockout-component but I am not sure how to do it or if it even is possible.
Taking an example from http://knockoutjs.com/documentation/component-overview.html I hope that I can do something like this:
<like-or-dislike params="value: userRating">
<div class="like-or-dislike" data-bind="visible: !chosenValue()">
<button data-bind="click: like">Like it</button>
<button data-bind="click: dislike">Dislike it</button>
</div>
<div class="result" data-bind="visible: chosenValue">
You <strong data-bind="text: chosenValue"></strong> it.
And this was loaded from an external file.
</div>
</like-or-dislike>
But I cannot find any documentation if that works at all. The reason why I want to implement it that way is simply because I am having some server generated html that I want to still be a part of a component. Otherwise I will have to make it a json-object and render the html inside the component which seems like a unnecessary extra step. The good thing about using components is that the logic is seperated in it's own file and it is easier to seperate logic between different components. I understand that if I do it like this I have to copy the html if I want to reuse the component.
Am I thinking of this the wrong way or is this possible?
Thanks for your sage advice and better wisdom.
I can't say I fully understand your situation but I think I may have the answer. You can actually have the server generate <script type="text/html"> and use that (by id of course) with a component. The KO documentation is pretty poor on component templating, but here is an example using an element.
A couple of things I've learned with components. The viewmodel must be declared before declaration, and the <script> must be in the dom prior to binding.
function ComponentViewModel() {
var self = this;
self.Title = ko.observable("This is a Component VM");
}
function ViewModel() {
var self = this;
self.ExampleComponent = ko.observable({
name: 'Example'
});
}
ko.components.register('Example', {
template: {
element: 'ComponentTemplate'
},
viewModel: ComponentViewModel
})
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script id="ComponentTemplate" type="text/html">
<span data-bind="text: Title"></span>
</script>
<div data-bind="component: ExampleComponent"> </div>
I won't devalue components, but I also would point you to using templates with a data binding, it's essentially the same thing (please correct me if I'm wrong). and doesn't require the component be established. This is better for situations where the would-be component occurs less frequently.
function ComponentViewModel() {
var self = this;
self.Title = ko.observable("This is a Template with a VM");
}
function ViewModel() {
var self = this;
self.ComponentVM = ko.observable(new ComponentViewModel());
self.ExampleComponent = ko.observable({
name: 'ExampleTemplate', // This is the ID
data: self.ComponentVM
});
}
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script id="ExampleTemplate" type="text/html">
<span data-bind="text: Title"></span>
</script>
<div data-bind="template: ExampleComponent"> </div>
I hope these help!

Vue.js 2.0: Apply vue component rendered using v-html, compile the markup

I'm using VueJS 2.0
Is there any way to make the below render as a link?
Here is my vue component:
<template>
<div v-html="markup"></div>
</template>
<script>
new Vue({
data() {
return {
markup: '<router-link :to="{path: 'https://www.google.com'}"></router-link>',
});
},
});
</script>
In the above example, I want to dynamically export a piece of markup, it contains some dynamic contents, such as router-link like above.
But that content did not compile, and exports a <router-link> tag as a final result.
Any way to make it compile programmatically?
What I really want is to find a way to compile a piece of html manually. If v-html doesn`t work, Is there any other way?
v-html works only for pre-compiled html which is basically generated text.
If you want do dynamically change content, simply use if conditions to render your list view based on prop that will tell you the type of the list view.
I don't think it's a good idea to save the markup in your db. It's rather more convenient to save some settings in your db and based on those to render the necessary html. (the prop type in your case). Maybe if you provide a more concrete example, some suggestions will follow. As you can see, the answers were based on your router-link example which I think is not enough to answer your question
I don't think you can instantiate Vue instances via v-html directive. You must override the default to do that, which would take lots of efforts.
If you just want dynamic links, why not try this:
data: {
menu: []
}
and then :
<router-link v-for="item in menu" :to="item.src">{{item.name}}</router-link>
PS: Can you give an example that you must do such things? I am really interesting in what needs it would be.
Given that you want to render a list of links, one way to do this can be like this:
<template>
<router-link v-for="list in lists" :to="{path: list}"></router-link>
</template>
<script>
new Vue({
data() {
return {
lists: ['https://www.google.com', 'https://www.stackoverflow.com']
});
},
});
</script>
Edit:
You can use an approach like following as well using with the help of dynamic components.
Vue.use(VueRouter)
new Vue({
el: "#app",
data: {
dynamicComp: "router-link"
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/2.2.0/vue-router.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js"></script>
<div id="app">
somethind
<component :is="dynamicComp" :to="{path: 'https://www.google.com'}"></component>
</div>

Categories

Resources