Vue.js binding is hiding parent divs and elements (not intended) - javascript

I'm building a simple Vue.js page and my HTML snippet is as follows:
<div id="vueApp">
<a href="#" class="button is-outlined" #click="showNextDiv" >Show Div</a>
<div class="modal animated fadeIn" v-bind:class="{ is-active: isActive }">
...
</div>
</div>
I'm using the Bulma CSS Framework and the "is-active" modifier makes the div active (as the name suggests).
My problem is that whenever I load the page the <div id="vueApp"> does not show up neither does the button that makes isActive turn true.
This is my vue instance declaration:
new Vue({
el: '#vueApp',
data: {
isActive: false
},
methods: {
showNextDiv: function(){
this.isActive = true;
}
}
});

The dash character in your CSS binding makes the name an invalid javascript property name so you need to enclose it in quotes:
v-bind:class="{ 'is-active': isActive }"
I suspect there might be something in your console highlighting this error.
UPDATE
Should have included my jsFiddle showing that your JS and HTML code works if the is-active is wrapped in quotes:
https://jsfiddle.net/psteele/mzjb8wvb/

Related

Vue rendering elements before hiding them

I have a very simple site where I'm using the most basic Vue.
When I load the site, it renders divs which are not supposed to show, for a fraction of a second, before hiding them. It makes the site look very unprofessional.
Here is the site if you want to experience the glory of flashing divs: http://sqlforever.com/
Looking around on the web, I've tried to use v-show, v-if and even v-cloak. None of them work.
Here is an example where I'm using v-show, along with v-cloak:
<div class="row v-cloak" v-show="display_schema">
...
</div>
The definition of v-cloak:
[v-cloak] { display:none; }
Vue definition:
const app = new Vue({
el: '#app',
data: {
...
errorMsg: "",
warnMsg: "",
...
display_schema: false,
...
Else where I'm using v-if:
<div class="row">
<div id = "errorPanel" v-if="errorMsg != ''" class="alert alert-danger alert-dismissible fade show v-cloak" role="alert">
<strong>ERROR: </strong> {{errorMsg}}
<!--button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button-->
</div>
<div id = "warnPanel" v-if="warnMsg != ''" class="alert alert-warning alert-dismissible fade show v-cloak" role="alert">
<strong>WARNING: </strong> {{warnMsg}}
<!--button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button-->
</div>
</div>
Currently I'm also loading css and js directly off the web, I'll load them locally when this goes 'production' but simple experiments didn't seem to make a difference.
I'm not normally a web dev but I've done a site or two using knockoutjs and don't remember having this issue.
What am I doing wrong?
You're using v-cloak but incorrectly. From the docs:
This directive will remain on the element until the associated Vue instance finishes compilation. Combined with CSS rules such as [v-cloak] { display: none }, this directive can be used to hide un-compiled mustache bindings until the Vue instance is ready.
It's a directive, not a class name. Your CSS is correctly styling it as an attribute:
[v-cloak] { display:none; }
But you need to apply it in your template as a directive, not a class name. For example, this will remove the flash of content:
<div class="container" id="app" v-cloak>

Vuejs typing into input with v-model directive removes a CSS class somehow

So I have a form with multiple input fields, whenever I type something into these fields, somehow it manipulates the DOM, and removes a certain class active from the form container. Please see this GIF to see it.
Steps to reproduce:
Click create your account
Type anything in any of the shown field
Observe the faulty behavior (which is the result of removing the active class)
Template code:
// active class is getting injected here
<div class="login-forms__container">
<section class="new-user__section">
<form>
<h1>{{ $t('login.create-form.title') }}</h1>
<p>{{ $t('login.create-form.subtitle') }}</p>
<b-field>
<b-input
v-model="registerForm.fullName"
:placeholder="$t('login.create-form.name')"
type="text"
icon-pack="fas"
icon="user"
maxlength="25"
/>
</b-field>
....
<div class="field">
<small>
<a target="_blank" #click.prevent="toggleCreateAccount">
{{ $t('login.create-form.registered') }}
</a>
</small>
</div>
</form>
</section>
</div>
JS code:
methods: {
toggleCreateAccount () {
document
.querySelector('.login-forms__container')
.classList
.toggle('active');
},
What I've tried:
Disable/Enable hot reloading
Tracing DOM event listeners (through breakpoints)
Changing the class name (suspected that "active" name is too common and might get removed by other libraries)
Using e.preventDefault() and click.prevent;
Or even removing the function responsible for adding the class and inject the class manually through inspect elements
Note:- removing v-model fixes the issue
You're modifying the DOM manually in a way that Vue can't trace. When it comes time to rerender, it sees the class you added, but since it doesn't match the VDOM in the template (you don't have "active" in your template), it thinks it must be removed (VDOM = source of truth).
You should conditionally include the active class in your template, Vue will automatically patch the DOM for you.
An abbreviated example:
template: `<div :class="{'.login-forms__container': true, active: createAccountActive }">`,
data() {
createAccountActive: false,
},
methods: {
toggleCreateAccount() {
this.createAccountActive = !this.createAccountActive;
}
},

Vue #click doesn't work on an anchor tag with href present

EDIT: I forgot to clarify, what I'm looking for is to know how to write an anchor tag with the href attribute present but that when the element is clicked, the href should be ignored and the click handler should be executed.
I'm currently using Vue 1 and my code looks like:
<div v-if="!hasPage(category.code)">
<div>
<template v-for="subcategoryList in subcategoryLists[$index]" >
<ul>
<li v-for="subcategory in subcategoryList"v-on:click.stop="test()">
<a :href="subcategory.url">{{subcategory.label}}</a>
</li>
</ul>
</template>
</div>
</div>
What i'm trying to do is present the user with some buttons that represent my site's categories, some of this buttons will lead the user to another page and other will toggle a dropdown with a list of subcategories, when clicking these subcategories you will be taken to the subcategory's page.
These would be pretty simple but these buttons need to be tracked by Google Tag Manager so that's why I used the #click attribute to call a function and ignore the href attribute. This doesn't work, I have tried #click.prevent, #click.stop, #click.stop.prevent and none of these work.
The thing is that if I remove the href attribute, the #click method works great but an SEO requirement is to have href attributes on every link.
Any help would be appreciated.
As #dan-oswalt specified in the comments, you have to add the click event on the same element as the href. The reason it did not work the time you tried is most likely because you tried with #click.stop which only stops propagation, not the default action. (Redirect to the link). What you want is to prevent the browser to redirect the user: #click.prevent
new Vue({
el: '#app',
data: {
subcategoryList: Array(10).fill({
url: 'http://google.com',
label: 'http://google.com'
})
},
methods: {
test(event) {
event.target.style.color = "salmon"
}
}
})
Vue.config.devtools = false
Vue.config.productionTip = false
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<main id="app">
<template>
<ul>
<li v-for="subcategory in subcategoryList">
<a :href="subcategory.url" v-on:click.prevent="test($event)">{{subcategory.label}}</a>
</li>
</ul>
</template>
</main>

Listen for Foundation event in Vue.js?

I'm building an app in Vue.js with the general structure of:
<app>
<filters-component></filters-component>
<div class="off-canvas-content">
<nav-component></nav-component>
<div class="row card-grid">
<card-component v-for="item in items">
<modal-component v-if="launchModal === true"></modal-component>
</card-component>
</div>
</div>
</app>
This allow me to render the modal on the DOM only if a data element of launchModal is set to true (after clicking the button to launch the modal). This works great, but I need to do the reverse when it's closed.
According to Foundation's documentation, the Reveal (modal) component should emit an event called closed.zf.reveal when it's closed.
How do I listen for this event on the parent element (card-component) and then change launchModal to false, when it's called?
Thanks!
Essentially this will likely boil down to, in your modal-component (add these to the script in Modal.vue)
methods:{
onModalClosed(){
this.$emit("modal-closed")
}
},
mounted(){
this.$el.addEventListener('closed.zf.reveal', this.onModalClosed)
},
beforeDestroy(){
this.$el.removeEventListener('closed.zf.reve‌​al', this.onModalClosed)
}
Or something to that effect, depending on what element emits the event. If some other element emits the closed.zf.reveal event, then you could add a ref="modal" to it and then use this.$refs.modal.addEventListener and this.$refs.modal.removeEventListener.
Then you could just
<modal-component v-if="launchModal === true"
#modal-closed="launchModal = false">
</modal-component>
Edit
So the issue with listening to the event is that Foundation is using jQuery to fire the event. That means that you cannot listen for it using native methods (addEventListener), you have to listen to it with jQuery. So the modified code from above would be this:
methods:{
onModalClosed(){
this.$emit("modal-closed")
}
},
mounted(){
$(this.$el).on('closed.zf.reveal', this.onModalClosed)
},
beforeDestroy(){
$(this.$el).off('closed.zf.reve‌​al', this.onModalClosed)
}
And this does, in fact, catch the event. The problem is that Foundation, for whatever reason, moves the modal outside of the Vue and appends it to the bottom of the document when the modal is initialized. That causes Vue to throw an error when launchModal is set to false because the modal is no longer inside the Vue, and Vue complains when it tries to remove it from the DOM.
That being the case, I suggest you use your v-if inside the modal for the things that are rendering very slowly. That will result in a component like this.
Vue.component("modal", {
props:["show"],
template: "#modal-template",
watch:{
show(newVal){
if (newVal)
$(this.$el).foundation("open")
}
},
methods:{
onModalClosed(){
this.$emit("modal-closed")
}
},
mounted() {
new Foundation.Reveal($(this.$el))
$(this.$el).on("closed.zf.reveal", this.onModalClosed);
},
beforeDestroy() {
$(this.$el).off("closed.zf.reveal", this.onModalClosed);
}
});
And the template is
<template id="modal-template">
<div class="reveal" data-reveal>
<div v-if="show">
Stuff that is expensive to render
</div>
<button class="close-button" data-close aria-label="Close modal" type="button">
<span aria-hidden="true">×</span>
</button>
</div>
</template>
And here is the working example.

Polymer 1.0 can't access elements within nested <template> element

I am using Polymer 1.0 and I am building a small accordion example. I have data binding to the accordion text fine, I just want to change the icon of the accordion when I click it.
Below is my code
<dom-module id="ab-accordion">
<template>
<iron-ajax
auto
handle-as="json"
on-response="handleResponse"
debounce-duration="300"
id="ajaxreq"></iron-ajax>
<template is="dom-repeat" id="accordion" items="{{items}}" as="item">
<div class="accordion" on-click="toggleParentAcc">
<div id="accordion_header" class="accordion__header is-collapsed">
<i class="icon icon--chevron-down"></i>
<span>{{item.name}}</span>
</div>
<div id="standard_accordion_body" class="accordion__body can-collapse">
<div class="accordion__content">
<content id="content"></content>
</div>
</div>
</div>
</template>
</template>
<script>
Polymer({
is: "ab-accordion",
//Properties for the Element
properties: {
accordian: Object,
childaccordions: Object,
// Param passed in from the element - Set if the accordion is open by default.
open: String,
data: String,
reqUrl: {
type: String,
value: "https://example.com/service.aspx"
},
},
ready: function () {
this.items = [];
},
attached: function () {
// Run once the element is attached to the DOM.
},
toggleParentAcc: function (event) { // Toggle the classes of the accordions
//This is where I want to toggle the class
this.$.accordion_header.classList.toggle('is-collapsed');
if (typeof event !== 'undefined') {
event.stopPropagation(); // Stop the click from going up to the parent.
}
},
handleResponse: function (e) {
this.items = e.detail.response.sports;
}
});
</script>
</dom-module>
Basically inside the toggleParentAcc function I want to toggle the class of the div with ID accordion_header. But I just get undefined or null.
I have tried the following two lines:
this.$.accordion_header // #1
this.$$('#accordion_header') // #2
How I access that element inside the dom-repeat?
UPDATE: I can't even access the elements within the when inside the attached function.
attached: function(){
this.$.accordion_header // This is null?!
this.$$('#accordion_header'); // this is also null!
}
https://www.polymer-project.org/1.0/docs/devguide/local-dom.html#node-finding
Note: Nodes created dynamically using data binding (including those in dom-repeat and dom-if templates) are not added to the this.$ hash. The hash includes only statically created local DOM nodes (that is, the nodes defined in the element’s outermost template).
I think it would be better if you'd use Polymer.dom(this.root) instead. Also I'd advice you to not use static IDs in dom-repeat as they are meant to be unique. Use classes instead.
Looks like you might be encountering Event Retargeting which happens when events "bubble" their way up the DOM tree. Read this documentation to learn more.
When I encountered this, I solved it by using something like:
var bar = Polymer.dom(event).path[2].getAttribute('data-foo');
inside my Polymer() function.
To figure it out in your case, you should go to the console and search the DOM tree / event log to locate your target. If you have trouble locating the correct area of the console, post a comment and I might be able to help further.
I eventually figured out a way of doing this without having to select elements in the nested template.
<template id="accord_template" is="dom-repeat" items="{{items}}" as="item">
<ab-accordion-row id="[[item.id]]" name="[[item.name]]" open="[[item.open]]">
</ab-accordion-row>
</template>
ab-accordion is another element, I just feed it the data and I can then change the classes based on the params.
<div id="accordion" class="accordion" on-click="toggleAccordion">
<div class$="{{getClassAccordionHeader(open)}}">
<i class="icon icon--chevron-down"></i>
<span>{{name}}</span>
</div>
<div id="standard_accordion_body" class$="{{getClassAccordionChild(open)}}">
<div class="accordion__content">
<content></content>
</div>
</div>
</div>
try with this.
toggleParentAcc: function (event) { // Toggle the classes of the accordions
//This is where I want to toggle the class
var header = event.target.parentElement;
Polymer.dom(header).classList.toggle('is-collapsed');
// rest of your code
}

Categories

Resources