Is it possible to write JavaScript directly in vue js? - javascript

New to vuejs. I am trying to write javascript directly in the vue file. Below is the code. I keep getting the following errors...
compiled with problems
70:18 error 'openpopup' is defined but never used no-unused-vars
73:18 error 'closepopup' is defined but never used no-unused-vars
Html with script:
<template>
<div class="customers-page">
<h2>Customers</h2>
<button type="add" class="add-button" onclick="openpopup()">Add</button>
<div class="popup" id="popup">
<h3>Input the following information</h3>
<button type="add-customer" class="submit-customer-button" onclick="closepopup()">Submit</button>
</div>
</div>
</template>
<script type="application/javascript" >
let popup = document.getElementById("popup");
function openpopup(){
popup.classList.add("open-popup")
}
function closepopup(){
popup.classList.remove("open-popup")
}
</script>

The very purpose to use Vue is to leverage its features for handling this type of logic reactively, Following is the snippet which can be used(vue 3 options api)
<template>
<div class="customers-page">
<h2>Customers</h2>
<button type="add" class="add-button" #click="openpopup">Add</button>
<!-- onclick="openpopup()" -->
<div class="popup" :class="popupToggle ? 'open-popup' : ''">
<h3>Input the following information</h3>
<button type="add-customer" class="submit-customer-button" #click="closepopup">Submit</button>
<!-- onclick="closepopup()" -->
</div>
</div>
</template>
<script>
export default {
data() {
return {
popupToggle: false,
};
},
methods: {
openpopup() {
this.popupToggle = true;
},
closepopup() {
this.popupToggle = false;
},
},
};
</script>
Here the popup view is maintained by a state variable popupToggle, if you want to use something similar to id then you should go through refs here

When you use frameworks like Vue / React etc, using the native DOM is discourage (basically those .getElementById, .classlist.add or similar). One main reason is security, anyone can go to inspect and do DOM manipulations.
If you want to avoid the Options API boilerplate, you can use Composition API, which is similar to what you are doing.
Besides, if you are using conditional rendering, v-if is recommended instead of class binding, because the elements are not rendered if it is false.
<template>
<div class="customers-page">
<h2>Customers</h2>
<button type="add" class="add-button" #click="openPopup()">Add</button>
<div v-if="isShowPopup" class="popup">
<h3>Input the following information</h3>
<button type="add-customer" class="submit-customer-button" #click="closePopup()">Submit</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isShowPopup = ref(false)// acts similar to options api data block
// here I am using arrow functions
const openPopup = () => {
isShowPopup.value = true // in composition API, instead of using this., you use .value
}
const closePopup = () => {
isShowPopup.value = false
}
</script>

Related

Sharing reactive localStorage state between multiple vue apps on the same page

I'm creating a dashboard with Laravel and VueJS, I created a button that allows to enlarge or reduce my sidebar in a Sidebar.vue component here is my components:
<template>
<aside :class="`${is_expanded ? 'is-expanded' : ''}`">
<div class="head-aside">
<div class="app-logo">
<i class="bx bxl-trip-advisor"></i>
</div>
<span class="app-name">CONTROLPANEL</span>
</div>
<div class="menu-toggle-wrap">
<button class="menu-toggle" #click="ToggleMenu()">
<span class="boxicons"><i class="bx bx-chevrons-right"></i></span>
</button>
</div>
</aside>
</template>
<script>
export default {
data() {
return {
is_expanded: ref(localStorage.getItem('is_expanded') === 'true'),
}
},
methods: {
ToggleMenu() {
this.is_expanded = !this.is_expanded
localStorage.setItem('is_expanded', this.is_expanded)
},
},
}
</script>
The problem that arises is that in another component I created a navbar with a fixed width, what I would like to do is that when my sidebar changes size I would like my navbar to also change, in the other component I just have a template with a nav and the import of my sidebar.
You say you are using laravel with vue. It's not clear how the vue components are integrated, but I'm going to assume that laravel injects individual vue components that you would like to communicate. (as opposed to a vue based SPA that communicates with laravel using API only)
The two components are not aware of each other. Even though they both have access to the same localStorage, they don't know when the values there are updated.
There are several ways you can deal with this, here's one way
create a reactive object outside of the components to manage the shared state
import { ref, computed } from 'vue'
const isExpandedRef = ref(localStorage.getItem('is_expanded') === 'true');
window.IS_EXPANDED = computed({
get(){
return isExpandedRef.value
},
set(value){
isExpandedRef.value = !!value;
localStorage.setItem('is_expanded', isExpandedRef.value);
}
})
the ref is required so that when isExpandedRef.value changes, the computed getter triggers notifications to it's listeners.
If you load this before any of the components, you will create a computed(reactive) variable available to any script on the page (frames and shadow dom aside)
then you can use in your components like this.
<template>
<aside :class="`${is_expanded ? 'is-expanded' : ''}`">
<div class="head-aside">
<div class="app-logo">
<i class="bx bxl-trip-advisor"></i>
</div>
<span class="app-name">CONTROLPANEL</span>
</div>
<div class="menu-toggle-wrap">
<button class="menu-toggle" #click="is_expanded = !is_expanded">
<span class="boxicons"><i class="bx bx-chevrons-right"></i></span>
</button>
</div>
</aside>
</template>
<script>
export default {
data() {
return {
is_expanded: window.IS_EXPANDED,
}
},
}
</script>
because is_expanded is a computed with with getters and setters, you can set the value straight from the template, or use this.is_expanded = !this.is_expanded in methods.
As stated already, this relies on using the global window object. This solution is proposed for its simplicity. There are some drawbacks to using the window object, and a more robust solution would rely on injecting such shared state instead of relying on window, but it comes with more overhead.
This code works fine
<template>
<aside :class="`${is_expanded ? 'is-expanded' : ''}`">
<div class="head-aside">
<div class="app-logo">
<i class="bx bxl-trip-advisor"></i>
</div>
<span class="app-name">CONTROLPANEL</span>
</div>
<div class="menu-toggle-wrap">
<button class="menu-toggle" #click="ToggleMenu()">
<span class="boxicons">
<i class="bx bx-chevrons-right"></i>click me
</span>
</button>
</div>
</aside>
</template>
<script>
export default {
data() {
return {
is_expanded: localStorage.getItem("is_expanded") === "true",
};
},
methods: {
ToggleMenu() {
this.is_expanded = !this.is_expanded;
localStorage.setItem("is_expanded", this.is_expanded);
},
},
};
</script>
Here is a GIF showing the result in action: https://share.cleanshot.com/TtKsUj
Make local storage reactive by using watcher and setting its deep property to true
https://vuejs.org/guide/essentials/watchers.html#deep-watchers
or if you are using vue2 then use event bus

Created not being triggered in Vue js

I'm implementing an application with Vue Js and I've the following code:
<template>
<simple-page title="list-patient" folder="Patient" page="List Patient" :loading="loading">
<list-patients #patientsLoaded="onPatientsLoaded"/>
</simple-page>
</template>
Both simple-page and list-patients are custom components created by me. Inside ListPatients I've an HTTP request on Create callback, as follows:
created() {
axios.get("...").then(response => {
...
this.$emit('patientsLoaded');
})
},
Then, my objective is to handle the patientsLoaded event and uptade the loading prop on the top parent component, as follows:
data() {
return {
loading: true
}
},
methods: {
onPatientsLoaded(params) {
this.loading = false;
}
}
However, the created method is not being triggered inside the list-patients component. The only way I can make this work is by removing :loading.
Any one can help?
Edit 1
Code of simple page:
<template>
<section :id="id">
<!-- Breadcrumb-->
<breadcumb :page="page" :folder="folder"/>
<!-- Breadcrumb-->
<!-- Simple Card-->
<simple-card :title="page" :icon="icon" :loading="loading" v-slot:body>
<slot>
</slot>
</simple-card>
<!-- Simple Card-->
</section>
</template>
Code of simple card:
<b-card>
<!-- Page body-->
<slot name="body" v-if="!loading">
</slot>
<!--Is loading-->
<div class="loading-container text-center d-block">
<div v-if="loading" class="spinner sm spinner-primary"></div>
</div>
</b-card>
Your list-patients component goes in the slot with name "body". That slot has a v-if directive so basically it is not rendered and hooks are not reachable as well. Maybe changing v-if to v-show will somehow help you in that situation. Anyway, you have deeply nested slots and it is making things messy. I usually declare loading variable inside of the component, where fetching data will be rendered.
For example:
data () {
return {
loading: true;
};
},
mounted() {
axios.get('url')
.then(res => {
this.loading = false;
})
}
and in your template:
<div v-if="!loading">
<p>{{fetchedData}}</p>
</div>
<loading-spinner v-else></loading-spinner>
idk maybe that's not best practise solution
v-slot for named slots can be indicated in template tag only
I suppose you wished to place passed default slot as body slot to simple-card component? If so you should indicate v-slot not in simple-card itself but in a content you passed it it.
<simple-card :title="page" :icon="icon" :loading="loading">
<template v-slot:body>
<slot>
</slot>
</template>
</simple-card>

How can DOM manipulation like wrapping text in a paragraph element be achieved using only Vue.js?

I have a Vue component which has a contenteditable div that lets users type in a message. When the user first attempts to create a message, I am using jQuery to wrap the text in a <p> tag. I cannot understand how this could be achieved using Vue.js alone...
Vue.js component
<template>
<div id="Message" contenteditable="true" #focus="formatMessage" #keydown="formatMessage" #keyup="formatMessage" #keypress="formatMessage">
</div>
</template>
<script>
import $ from 'jquery'
formatMessage: function(event) {
if ($("#Message > p").length === 0) { // if no <p> element when user interacts with div
$("#Message").contents().eq(0).wrap("<p />"); // then wrap a <p> tag around the first child content
}
}
Is it possible to do this using just Vue.js so I don't have to load the jQuery library for simple DOM manipulation (which may cause an issue with Vue's virtual DOM being out-of-sync with jQuery's changes)?
Before formatMessage():
<div id="Message" contenteditable="true">
I started typing here
</div>
After formatMessage():
<div id="Message" contenteditable="true">
<p>I started typing here</p>
</div>
Is it possible/better to try to do it using Vue's virtual DOM? Could I somehow use createElement to create a new p tag and then update its contents with what the user is typing? Maybe thats not the way the Virtual DOM works I'm not sure.
You can use v-if and duplicate the code a little if you want to achieve something similar
<template>
<div v-if="shouldWrap === false" contenteditable="true" #focus="formatMessage" #keydown="formatMessage" #keyup="formatMessage" #keypress="formatMessage">
</div>
<p v-else>
<div contenteditable="true" #focus="formatMessage" #keydown="formatMessage" #keyup="formatMessage" #keypress="formatMessage">
</div>
</p>
</template>
<script>
export default {
data () {
return {
shouldWrap: false
}
},
methods: {
formatMessage() {
this.shouldWrap = true
}
}
}
</script>
But probably trying to match the styling of a p should also work.
Do not use JQuery-like DOM manipulation in VUE, VUE is data driven framework, you need to store some data in component to trigger layout, for example
<template>
<div contenteditable="true" #focus="formatMessage" #keydown="formatMessage" #keyup="formatMessage" #keypress="formatMessage">
<!-- wrap 'p' tag, if 'shouldWrap'-->
<p v-if="shouldWrap">{{content}}</p>
<!-- without wrap-->
<template v-else>{{content}}</template>
</div>
</template>
<script>
export default {
data () {
return {
shouldWrap: false,
content:'' // text, you want to display inside div
}
},
methods: {
formatMessage() {
this.shouldWrap = true
}
}
}
</script>

Execute a method when image is loaded

I've been trying to execute a method when a image has fully loaded, but it simply throws an error that my function doesn't exists:
Uncaught ReferenceError: imageLoaded is not defined
at HTMLImageElement.onload (projetos:1)
Here's my code:
<template>
<div class="card">
<h2>{{projeto.nome}}</h2>
<div class="row">
<div class="col-sm-10 col-sm-offset-1" id="img-container">
<img :src="projeto.foto" :alt="projeto.nome" onload="imageLoaded()">
<div class="row" id="info">
<div class="col-xs-6">Conclusão: {{projeto.duracao}}</div>
<div class="col-xs-6">Projeto realizado em {{projeto.ano}}</div>
<br><br>
<p>{{projeto.descricao}}</p>
</div>
</div>
</div>
</div>
</template>
<script>
import { eventBus } from "../../main";
export default {
props: ["projeto"],
methods: {
imageLoaded() { // the function i want to execute
eventBus.loading(false);
}
}
};
</script>
I've tried using :load="" (wich i discovered that doesn't exists) and placing a function outside my export default scope. Since my images are in a v-for i think onload is the best way of achieving this.
So is there a better way? How can i do this? Is there a directive i can use? Am i doing something wrong?
Use the v-on directive or the shorthand # for listening for DOM events like you would do for example with #click="" - so #load="imageLoaded()" should work.

Popup using knockout js

i'm migrating one of my older jquery plugins from DOM jungle to this fancy mvvm framework knockout.
Which technique would i use to properly display a popup container? I ahve to populate it 'by call' since i get a json feed every time.
I tried an approach using the with binding, but it still attempts to populate the partial at its first runtime.
<!-- ko with: daySubmitFormViewModel -->
<div class="ec-consulation-lightbox">
<form id="cForm" class="form-container">
// Some bindings here.
</form>
</div>
<!-- /ko with: -->
It can be done without custom binding as well. Example is below
<div class="modalWindowBackground" data-bind="visible: popupDialog" >
<div class="modalWindow" data-bind="with:popupDialog">
<div class="content">
<h2 data-bind="text: title"></h2>
<p>
<span data-bind="text: message"></span>
</p>
<div class="buttonSpace">
<input type="button" class="closeButton" data-bind="value: closeButtonText, click: $root.hidePopupDialog" />
</div>
</div>
</div>
</div>
Viewmodel code:
self.showAlert = function (title, message, closeButtonText) {
self.popupDialog({ title: title, message: message, closeButtonText: closeButtonText });
};
self.hidePopupDialog = function () {
self.popupDialog(null);
};
//Code which opens a popup
self.remove = function () {
.... some code ...
if (someCondition) {
self.showAlert('SomeTitle', 'Message', 'OK');
return;
}
.... some code ...
};
Create a custom binding, have its open / close function trigger on a observable.
I've done a custom binding for jQuery Dialog that uses this approuch in combination with KO
templates.
<div id="dialog" data-bind="dialog: { autoOpen: false, modal: true, title: dialogTitle }, template: { name: 'dialog-template', data: dialogItem, 'if': dialogItem }, openDialog: dialogItem"></div>
You can find my binding here along with some others
https://github.com/AndersMalmgren/Knockout.Bindings
Live demo http://jsfiddle.net/H8xWY/102/
https://github.com/One-com/knockout-popupTemplate
That pretty much does what you ask for. It's deeply configurable, and under steady development (we use it in our web applications ourselves).
Disclaimer: I'm a One.com developer. I am also the person who originated the above mentioned lib.

Categories