Using plain JS in Vue.js Component - vue.js

So, I want to create a navbar and rather than re-invent the wheel, I am using some public code to speed up my MVP dev.
Essentially, I am using this nav-bar code - https://codepen.io/PaulVanO/pen/GgGeyE.
But I am not sure of how I can implement jquery part within my Vue code (I have made a component, copied over html and css, now just need to integrate the jquery functionality within it.)
Here is the Jquery code I need to integrate.
$('#toggle').click(function() {
$(this).toggleClass('active');
$('#overlay').toggleClass('open');
});
It would be really thankful if anyone could help me accomplish with this.

Assuming you have your markup (html and css) as part of one component, getting the toggle to add/remove a class would be really simple, you just need to have a method toggle the active state and a data property to keep the data. An example would be better, so here it goes.
In your component object:
{
data() {
return {
isActive: false
}
},
methods: {
toggleMenu(){
this.isActive = !this.isActive
}
}
}
In your markup you need this
<div class="button_container" id="toggle" :class="{'active': isActive}" #click="toggleMenu">
<span class="top"></span>
<span class="middle"></span>
<span class="bottom"></span>
</div>
------------------------------------
<div class="overlay" id="overlay" :class="{'open': isActive}">
<nav class="overlay-menu">
<ul>
<li >Home</li>
<li>About</li>
<li>Work</li>
<li>Contact</li>
</ul>
</nav>
That should get you going, just note i used the shorthand form for v-on and for v-bind
EDIT:
Here's also a link to an updated pen with the whole example

Related

Conditional wrapper rendering in vue

I'm making a link/button component which either can have a button or an anchor wrapper, a text and an optional icon. My template code below is currently rendering either an anchor or a button (with the exact same content) based on an if statement on the wrapper element, resulting in duplicate code.
<template>
<a v-if="link" v-bind:href="url" class="btn" :class="modifier" :id="id" role="button" :disabled="disabled">
{{buttonText}}
<svg class="icon" v-if="icon" :class="iconModifier">
<use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
</svg>
</a>
<button v-else type="button" class="btn" :class="modifier" :id="id" :disabled="disabled">
{{buttonText}}
<svg class="icon" v-if="icon" :class="iconModifier">
<use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
</svg>
</button>
</template>
Is there a more clean way for wrapping my buttonText and icon inside either an anchor or button?
I've solved my issue by intensive Google-ing! Found this issue regarding Vue on Github which pointed me in the right direction.
Small piece of backstory
I'm using Vue in combination with Storybook to build a component library in which a button can either be a button or an anchor. All buttons look alike (apart from color) and can be used for submitting or linking. To keep my folder structure ordered, I would like a solution that generates a multiple buttons types (with or without link) from one single file.
Solution
Using computed properties I'm able to "calculate" the necessary tag, based on the url property of my component. When a url is passed, I know that my button has to link to another page. If there is no url property it should submit something or preform a custom click handler (not in the sample code below).
I've created the returnComponentTag computed property to avoid placing any complex or bulky logic (like my original solution) in my template. This returns either an a or a button tag based on the existence of the url property.
Next, as suggested by ajobi, using the :is attribute I'm able to define the component tag based on the result of my computed property. Below a stripped sample of my final (and working) solution:
<template>
<component :is="returnComponentTag" v-bind:href="url ? url : ''" class="btn" :class="modifier" :id="id">
{{buttonText}}
</component>
</template>
<script>
export default {
name: "Button",
props: {
id: {
type: Number
},
buttonText: {
type: String,
required: true,
default: "Button"
},
modifier: {
type: String,
default: "btn-cta-01"
},
url: {
type: String,
default: ""
}
},
computed: {
returnComponentTag() {
return this.url ? "a" : "button"
}
}
};
</script>
You could extract the wrapping element into a dedicated component.
<template>
<a v-if="link" v-bind:href="url" class="btn" :class="modifier" :id="id" role="button" :disabled="disabled">
<slot></slot>
</a>
<button v-else type="button" class="btn" :class="modifier" :id="id" :disabled="disabled">
<slot></slot>
</button>
</template>
// You would use it like this
<SomeComponent /* your props here */ >
{{buttonText}}
<svg class="icon" v-if="icon" :class="iconModifier">
<use v-bind="{ 'xlink:href':'#sprite-' + icon }"></use>
</svg>
</SomeComponent>
There are multiple ways of doing this. Two examples would be the following based on the point of view:
You are defining two different components (Button or Anchor) and want to use a wrapper to render either one of them.
You could seperate the Wrapper Content into two components so that the wrapper only decides on which of the components to render (either the Button or the Anchor).
The problem with this approach could be you will have doubled code for methods and styling for the button and anchor component.
You are defining the content as a component and use the wrapper to define what to wrap the content in.
See Answer of https://stackoverflow.com/a/60052780/11930769
It would be great to know, why you would want to achive this. Maybe there are better solutions for your usecase. Cheers!

Why isn't the value of the object properties inserted here?

I started learning vue yesterday and I'm now fiddling around on the CLI3.
Currently I'm trying out the different approaches to inserting data into my markup.
Here, I basically want to make a "list of Lists".
This here is list1:
<template>
<div>
<ul v-for="item in items">
<li :text="item"></li>
</ul>
</div>
</template>
<script>
export default{
name:"list1",
data() {
return {
items: {
item1 : "itemA",
item2 : "itemB",
item3 : "itemC"
}
}
}
}
</script>
This is the list of lists:
<template>
<div>
<h1>All my stuff in a biiig list!</h1>
<listOfLists />
</div>
</template>
<script>
import listOfLists from '#/components/listOfLists.vue'
export default {
name: 'myComplexView.vue',
components: {
listOfLists
}
}
And this is inserted into myComplexView.vue inside views (im working with routing as well, though it doesnt work perfectly yet as you will see on the screenshot), which you can see here:
<template>
<div>
<h1>All my stuff in a biiig list!</h1>
<listOfLists />
</div>
</template>
<script>
import listOfLists from '#/components/listOfLists.vue'
export default {
name: 'myComplexView.vue',
components: {
listOfLists
}
}
</script>
This is the result Im getting:
https://imgur.com/H8BaR2X
Since routing doesnt work correctly yet, I had to enter the url into the browser manually. Fortunately, the site at least loaded that way as well, so I can tackle these problems bit by bit ^^
As you can see, the data was iterated over correctly by the v-for.
However, the data wasn't inserted in the text attribute of the li elements.
I'm a bit clueless about the cause though.
Maybe I'm not binding to the correct attribute? Vue is using its own naming conventions, based off standard html and jquery as far as I understood.
You've got this in your template:
<li :text="item"></li>
This will bind the text attribute to the value, outputting, e.g.:
<li text="itemA"></li>
You should be able to see this in the developer tools. In the picture you posted you hadn't expanded the relevant elements so the attributes can't be seen.
I assume what you want is to set the content. For that you'd either use v-text:
<li v-text="item"></li>
or more likely:
<li>{{ item }}</li>
Either of these will output:
<li>itemA</li>
On an unrelated note, I would add that this line will create multiple lists:
<ul v-for="item in items">
It's unclear if that's what you want. You're going to create 3 <ul> elements, each with a single <li> child. If you want to create a single <ul> then move the v-for onto the <li>.

Vue.js. Best practices with initialization-only data binding?

I have this component which is managed by Bootstrap, specifically one of those nav-tabs widgets where, as you click Bootstrap shows and hides.
Bootstrap keeps track of which item was clicked on using the .active class. And, in Vue, I was to initialize a certain nav as being active on page load. But, once that's done, I want Vue leave the .active class management entirely up to Bootstrap.
<template>
<li class="nav-item" v-if="toshow">
<a class="nav-link" v-bind:id="'nav_' + link"
:class="{ active: isActive }" :aria-expanded="isActive"
v-bind:href="'#'+link" data-toggle="tab" #click="onclick">
{{label_}}
<span v-if="badge" class="badge" :class="badge_level">{{badge}}</span>
<span v-if="dynamic_badge" class="badge" :class="badge_level" >{{badge_value}}</span>
</a>
</li>
</template>
At page load time, each component checks against Vuex and figures out if its id is in this.$store.state.active_tab - that's what sets .active.
,isActive: function(){
//active_tab is where I specify which tab should be active
//at first
var res = this.link === this.$store.state.active_tab;
return res;
},
v-once is not a good fit, because the only thing I want to disable is the computation of .active (the badge children need to be updated live).
The component works, kinda. I think mostly because this.$store.state.active_tab's value does not mutate so Vue doesn't re-render. But it seems brittle at best.
What are best practices for using Vue to only set the initial values of certain variables, and then relinquishing control, without using v-once?
I would just access the a.nav-link element and add the .active class to its classList directly.
You can add a ref attribute to the a.nav-lank element link so:
<a class="nav-link" ref="link" ...>
...
</a>
And then add the .active class in the mounted hook:
mounted() {
if (this.link === this.$store.state.active_tab) {
this.$refs.link.classList.add('active');
}
}

Why does my Vue component require :key?

I have a small Vue.js component which displays a favorite star icon. Clicking on the icon favorites/unfavorites the element. So far I have only implemented the UI part, which looks like this:
<template>
<div :key="favorite">
<a v-on:click="toggleFavorite" style="cursor: pointer">
<i v-show="favorite" class="text-warning fas fa-star"></i>
<i v-show="!favorite" class="text-warning far fa-star"></i>
</a>
</div>
</template>
<script>
export default {
data() {
return {
favorite: true,
}
},
mounted() {
},
methods: {
toggleFavorite() {
this.favorite = !this.favorite
}
},
props: ['team-id'],
}
</script>
<style scoped>
</style>
As you can see, the logic is pretty simple.
This works well, but one thing that bothers me is that, if I remove the :key property from my template, the icon is not updated when I click on it (even though I have checked that the underlying property is indeed updated correctly). Adding :key makes it work, I imagine because it forces Vue.js to completely re-render the component when favorite is updated.
Why is this happening? I'm fairly new to the world of JS frameworks, so forgive any obvious stuff I might be missing. I did some research online but couldn't find an explanation. I just want to make sure I'm doing things the right way and not merely hacking around the issue here.
Vue patches with the virtual DOM whenever it is necessary. That is, whenever vue detects the changes on the DOM, it patches them for faster performance. And patching in the DOM will not change the icon or image. You need to replace the DOM instead.
Thus, vue provides the way for us whenever we need to change the DOM by replacing method, we can use :key binding.
So, :key binding can be used to force replacement of an element/component instead of reusing it.
The following whole html div will be replaced whenever there is change in favorite data as we're :key binding on it:
<div :key="favorite">
<a v-on:click="toggleFavorite" style="cursor: pointer">
<i v-show="favorite" class="text-warning fas fa-star"></i>
<i v-show="!favorite" class="text-warning far fa-star"></i>
</a>
</div>
This is why vue forcefully allows us to use :key binding inside a loop as there's need of replacing the elements inside the loop whenever it detects the changes in the data. This is made compulsory from 2.2.0+ and ESLint also have implemented this feature so that if you miss :key binding inside the loop, then you'll see the error on that line when you use editor that supports eslint, so that you can fix the error.
Just an opinion, the strict requirement of the :key binding should be removed from the vue as we might want a loop of predefined data and don't want to change the DOM but we still use the v-for loop for listing bigger data. But it might be rare case though.
Read carefully on the documentation for :key binding and then you'll have an idea.
The :key binding can be useful when you want to:
Properly trigger lifecycle hooks of a component
Trigger transitions
Use :key binding to replace the DOM. Remember it slower the performance as it replace the whole DOM that is bound to the element.
Don't use :key binding when you don't want to replace the DOM or
you think there's no data changes detection required. This will
allow vue to perform better without :key binding.
Its seems to be a general issue of FontAwesome CSS regardless the framework.
There is an issue on github and here the same issue with react https://github.com/FortAwesome/Font-Awesome/issues/11967
To prove that, here is a simplified version of the same example but using bootstrap icons
new Vue({
el: '#app',
data() {
return {
fav: true
}
}
});
<script
src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"
></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<div id="app">
<div>
<a v-on:click="fav = !fav" style="cursor: pointer">
<i v-show="fav" class="glyphicon glyphicon-star"></i>
<i v-show="!fav" class="glyphicon glyphicon-star-empty"></i>
</a>
</div>
</div>
You shouldn't need the :key, it's only necessary in v-for loops. I would suggest you remove it and replace your v-show with a v-if and v-else directive.
<i v-if="favorite" class="text-warning fas fa-star"></i>
<i v-else class="text-warning far fa-star"></i>
v-if removes and addes the section to the DOM whereas v-show just hides it so this way well resolve your issue
Ok I think the problem here is that you're changing your root data object. To preserve reactivity, you shouldn't change the root data object after you've instantiated Vue.
Here is your code in a simple Vue. I didn't need :key to make it work. I would keep :key for inside loops.
markup
<div id="vueRoot">
<a v-on:click="toggleFavorite" style="cursor: pointer">
<i v-show="store.favorite" class="text-warning fas fa-star">Fav</i>
<i v-show="!store.favorite" class="text-warning far fa-star">Not fav</i>
</a>
</div>
code
vm = new Vue({
el : "#vueRoot",
data() {
return { store :{
favorite: true
}}
},
mounted() {
},
methods: {
toggleFavorite() {
this.store.favorite = !this.store.favorite
}
}
}
);
This is a working example with minimal changes. From what you've showed us, you should just have <i> element, then do what you want with a dynamic class list, like...
<i :class="['text-warning','fa-star',store.favorite?'fas':'far']"></i>

Can't require('durandal/app') in shell.js

This is my shell.html defining a navbar, pretty much lifted straight from the tute.
<div>
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<ul class="nav" data-bind="foreach: router.navigationModel">
<li data-bind="css: { active: isActive }">
<a data-bind="attr: { href: hash }"><span><i data-bind="attr: { class: glyph }"></i> <span data-bind="text: title"></span></span></a>
</li>
</ul>
<span class="nav">
Welcome <span data-bind="text: app.user.name()"></span>
</span>
<div class="loader pull-right" data-bind="css: { active: router.isNavigating }">
<i class="icon-spinner icon-2x icon-spin"></i>
</div>
</div>
</div>
<div class="container-fluid page-host" data-bind="router: { transition:'entrance' }"></div>
</div>
This is the problem part:
<span data-bind="text: app.user.name()"></span>
user is a property I add to app right after it's created. It is updated by the view models for log in and log out.
To get that binding to work I need to bring the object into scope in the view model. Normally I would do that like this: var app = require('durandal/app');. In most view models this is no problem. But view management gets seriously messed up when I do it in shell.js.
All I'm trying to do is fish out some app state to indicate whether the user is currently logged in.
There are two possible solutions
Get app into scope somehow
Manage application state in some other way that is in scope in shell.js
I'm sure this is trivial to old hands; I await your wisdom. Searching google didn't work well because "app" is way too broad a search term.
I dodged the problem by tacking user onto document instead of app, but that's hideous. I'm all ears for the right way to do this.
Interestingly, HTML5 browsers define sessionStorage for exactly the purpose to which I polluted document. If you do this in your boot code
if (typeof(document.sessionStorage) == undefined) document.sessionStorage = {};
then you can assume it exists in the rest of your app.
We can't count on browser support, but it's not hard to check and if necessary create it. If you look up sessionStorage you'll see a warning that the contents will be lost at the next page load, but in a SPA that matters not at all.
One way to accomplish this (most probably the right one), would be using a shared AMD module as discussed in Session Data with Durandal.
What we did in our Durandal app is create a separate static module called config, which contains, among other properties, an observable property called userName.
At the top of your module (the shell.js file, in this case), put the following:
define([
'durandal/system',
'durandal/activator',
'plugins/router',
'durandal/app',
...
'config'
],
function(system, activator, router, app,..., config) {
}
);
The config module should be static, which means that it should return an object literal, not a constructor function.
Your config module might look something like this (bare bones):
define('config', ['knockout'],
function(ko) {
var userName = ko.observable('');
return {
userName: userName
};
}
);