I am using Laravel + Vue.js to create a SPA. In the SPA, I am creating a form where user can write markdown content in it and click button to submit it. I want to show an error toaster at the bottom right corner of the screen if the user didn't input any content when they clicked the send button.
I am using boostrap vue toast to implement the error toaster.
However, when I clicked the send button, the error toaster will just blink for 1 second and dissapear immediately. Also, the error toaster blinks at top left corner which is different from what I wrote in the code below.
The mixin that contains the method to invoke the toast:
ErrorMessage.vue
# ErrorMessage.vue
<script>
export default {
methods: {
showErrorMessage (msg) {
this.$bvToast.toast(msg, {
title: ["Error!"],
variant: "danger",
toaster: "b-toaster-bottom-right"
});
}
}
};
</script>
I imported the above mixin in this vue component ArticleForm.vue.
ArticleForm.vue
<template>
<form #submit.prevent="passArticleData">
<div id="editor-markdown-editor">
<div id="editor-markdown">
<div
id="editor-markdown-tag-input"
#click="toggleTagModal"
>
<ul v-if="insertedTags.length">
<li v-for="tag in insertedTags"
:key="tag.id"
class="tag"
>
{{ tag.name }}
</li>
</ul>
<span v-else>タグを入力してください</span>
</div>
<div id="editor-markdown-textarea">
<textarea :value="input" #input="update" ref="textarea"></textarea>
<div id="drop-here"></div>
</div>
</div>
<div id="editor-preview">
<article v-html="compiledMarkdown(input)" class="markdown-render" ref="articleHtml"></article>
</div>
</div>
</form>
</template>
<script>
import _ from "lodash";
import ErrorMessage from "./mixins/ErrorMessage";
import { markdownTable } from "markdown-table";
export default {
props: {
article: Object,
tags: Array
},
mixins: [ErrorMessage],
data () {
return {
articleToPass: {
title: "",
body: "",
isDraft: true
},
input: "",
tagsToPass: [],
insertedTags: [],
tagModalOpen: false
};
},
methods: {
update: _.debounce(function (e) {
this.input = e.target.value;
}, 300),
passArticleData (e) {
// Get title from input
try {
this.articleToPass.title = this.$refs.articleHtml.getElementsByTagName("h1").item(0).innerHTML;
} catch (e) {
this.showErrorMessage(["Body md can't be blank"]);
}
// Remove first line(title) from users' input
this.articleToPass.body = this.input.substring(this.input.indexOf("\n") + 1);
// tag id of written article
const tagIds = this.insertedTags.map(obj => obj.id);
this.tagsToPass = tagIds;
this.$emit("handle-new-data", this.articleToPass, this.tagsToPass);
}
}
Parent component of the above vue component:
ArticleCreate.vue
<template>
<div id="content-area">
<header-component></header-component>
<main id="editor-area">
<article-form :article="article" :tags="tags" #handle-new-data="postArticle"></article-form>
</main>
</div>
</template>
<script>
import HeaderComponent from "./Header.vue";
import ArticleForm from "./ArticleForm.vue";
export default {
data () {
return {
article: {
title: "",
body: "",
is_draft: true
},
tags: []
};
},
components: {
HeaderComponent,
ArticleForm
},
methods: {
postArticle (articleObj, tagsData) {
const data = { article: articleObj, tags: tagsData };
axios.post("/api/articles", data)
.then((res) => {
this.$router.push({ name: "article.show", params: { article: res.data } });
});
}
}
};
</script>
I tried:
changed this.$bvToast to this.$root.$bvToast (based on this issue)
downgrade my bootstrap version from 4.6.0 to 4.5.3 (based on this question)
I have spent trying to solve this but failed. Does anyone know why is this happening and how can I make it work? Please let me know if you need any extra information. Thank you in advanced.
As stated in this comment on stackoverflow that is usually a sign of a bootstrap version mismatch.
I was actually able to reproduce that issue and also fix it with rolling back to bootstrap v4
Broken with bootstrap 5
https://codesandbox.io/s/bootstrap-vue-toasting-broken-with-bootstrap-5-bqe2c
Working with bootstrap 4
https://codesandbox.io/s/bootstrap-vue-toasting-working-with-bootstrap-4-jk2jl
I have figured out the problem.
The problem is that bootstrap-vue css is not loaded properly in my project.
Just add
import "bootstrap-vue/dist/bootstrap-vue.css";
to app.js and it works perfectly fine now.
Thank you
Related
I create a vue component to load scripts for ads dynamically, and when the route changes, the component should destroy herself and change back when the route enters.
So when the route leaves, there is no problem, but when I go to a page and then return to the same page, the ads do not appear anymore.
<template>
<div style="display: none;">
<slot />
</div>
</template>
<script>
export default {
props: {
async: { type: Boolean, default: true },
location: { type: String, default: '' }, // or elemnt id which will select the sapce
src: { type: String, required: false, default: '' }
},
data () {
return {
script: null
}
},
beforeDestroy () {
console.log('remove')
if (this.script) {
if (!this.location) {
this?.$el?.removeChild(this?.script)
}/** else {
const tag = document.querySelector(this.location)
tag?.parentNode?.removeChild(this.script)
} */
}
},
mounted () {
console.log('add loadjs')
this.loadJs()
},
methods: {
loadJs () {
const scriptTag = document.createElement('script')
console.log(this.$el)
scriptTag.async = this.async || true
// console.log(Object.keys(this.$slots.default[0]))
if (!this.src && this?.$slots?.default) { // when script is empty
scriptTag.text = this?.$slots?.default[0]?.text
} else { scriptTag.src = this.src }
if (!this.location) { // when location is not set load after element
this.$el.appendChild(scriptTag)
} else {
const location = document.querySelector(this.location)
location.appendChild(scriptTag)
}
this.script = scriptTag
}
}
}
</script>
the service for the ads is
<template>
<div>
ads
<div :id="id">
<script-component>
googletag.cmd.push(
function() {
googletag.display('{{ id }}');
}
);
</script-component>
</div>
</div>
</template>
<script>
const scriptLoadder = () => import('~/components/script/scriptLoadder')
export default {
components: {
'script-component': scriptLoadder
},
props: {
id: { type: String, required: true }
}
}
</script>
I have another similar component for another ads service that works on server load (when I enter the home page for the first time, this works fine). The issue is when the route changes, and then I go back to the same route. Both services of ads are just not appearing.
this is how I am using the component
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<template>
<div>
<google-ads id="ATF_LB_1" :key="$route.fullPath + Math.random().toString(16) " />
or
<google-ads id="ATF_LB_1" :key="$route.fullPath" />
<script-component>
{{ pageScript.HP }}
</script-component>
or
<script-component :key="$route.fullPath">
{{ pageScript.HP }}
</script-component>
or
<script-component :key="$route.fullPath + Math.random().toString(16) ">
window.alert('test on page load works when going back not')
</script-component>
</div>
</template>
So the answer is incredibly annoying. The problem was not with my code but with the provider's code. The code they gave me was intended to run on SSR only site. The only thing to pay attention to this code to "fix" the behavior is to unmount the component or key="$router.fullpath" but this will cause another issue depend on your component place. When you are inside the component and change the page, Nuxt will run the following lifecycle destroy the component and the mount on the new page (which is a problem) and then destroyed it again. This will cause latency unless you add async or defer.
So to summarize, the problem was with the provider code; this component will load the script tag inside the template where you need it. I will open the npm repo for this component that works with Nuxt and create a git issue of the memory leak in a component lifecycle.
I have a Vue page which loads an json array from an api and displays the content in a list of multiple s by using v-for.
If you focus on one of the textarea's or change the text a function automatically resize's the textarea to fit the content.
<div v-for="post in posts">
<textarea v-model="post.body" rows="1" #focus="resizeTextarea" #keyup="resizeTextarea"></textarea>
</div>
resizeTextarea(e) {
let area = e.target;
area.style.overflow = 'hidden';
area.style.height = area.scrollHeight + 'px';
}
With my limited Vue knowledge, I can't find a solution to automatically resize all textarea's after loading the data from the API. There is no #load on a textarea.
I was trying to reach the same goal with using a watcher on the data but it feels like a long workaround.
Anyone a descent solution? Thank you!
https://jsfiddle.net/oehoe83/c1b8frup/19/
One solution would be to create a component for your textarea element and then resize it in the mounted() hook. Here's an example using single-file components:
// CustomTextarea.vue
<template>
<textarea
v-model="value"
ref="textarea"
rows="1"
#focus="resize"
#keyup="resize"
>
</textarea>
</template>
<script>
export default {
props: {
value: {
type: String,
required: true,
}
},
mounted() {
this.resize();
},
methods: {
resize() {
const { textarea } = this.$refs;
textarea.style.height = textarea.scrollHeight - 4 + 'px';
}
}
}
</script>
Then in your parent:
<template>
<div v-for="post in posts">
<CustomTextarea v-model="post.body" />
</div>
</template>
<script>
import CustomTextarea from './CustomTextarea.vue';
export default {
components: {
CustomTextarea,
}
// etc.
}
</script>
Note: if you're using Vue 3, replace value with modelValue in the child component.
Alternatively you could use a watch like you suggested, there's nothing wrong with that. Something like this:
watch: {
posts() {
// Wait until the template has updated
this.$nextTick(() => {
[...document.querySelectorAll('textarea')].forEach(textarea => {
this.resizeTextarea({ target: textarea });
});
});
}
}
you can add the ref attribute :
<div id="app">
<div v-for="post in posts" ref="container">
<textarea v-model="post.body" rows="1"#focus="resizeTextarea" #keyup="resizeTextarea" ></textarea>
</div>
</div>
and add the following code at the end of mounted() :
this.$nextTick(()=>{
this.$refs.container.forEach( ta => {
ta.firstChild.dispatchEvent(new Event("keyup"));
});
});
I'm building an application to power the backend of a website for a restaurant chain. Users will need to edit page content and images. The site is fairly complex and there are lots of nested pages and sections within those pages. Rather than hardcode templates to edit each page and section, I'm trying to make a standard template that can edit all pages based on data from the route.
I'm getting stuck on the v-model for my text input.
Here's my router code:
{
path: '/dashboard/:id/sections/:section',
name: 'section',
component: () => import('../views/Dashboard/Restaurants/Restaurant/Sections/Section.vue'),
meta: {
requiresAuth: true
},
},
Then, in my Section.vue, here is my input with the v-model. In this case, I'm trying to edit the Welcome section of a restaurant. If I was building just a page to edit the Welcome text, it would work no problem.:
<vue-editor v-model="restInfo.welcome" placeholder="Update Text"></vue-editor>
This issue is that I need to reference the "welcome" part of the v-model dynamically, because I've got about 40 Sections to deal with.
I can reference the Section to edit with this.$route.params.section. It would be great if I could use v-model="restInfo. + section", but that doesn't work.
Is there a way to update v-model based on the route parameters?
Thanks!
Update...
Here is my entire Section.vue
<template>
<div>
<Breadcrumbs :items="crumbs" />
<div v-if="restInfo">
<h3>Update {{section}}</h3>
<div class="flex flex-wrap">
<div class="form__content">
<form #submit.prevent>
<vue-editor v-model="restInfo.welcome" placeholder="Update Text"></vue-editor>
<div class="flex">
<button class="btn btn__primary mb-3" #click="editText()">
Update
<transition name="fade">
<span class="ml-2" v-if="performingRequest">
<i class="fa fa-spinner fa-spin"></i>
</span>
</transition>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { VueEditor } from "vue2-editor"
import Loader from '#/components/Loader.vue'
import Breadcrumbs from '#/components/Breadcrumbs.vue'
export default {
data() {
return {
performingRequest: false,
}
},
created () {
this.$store.dispatch("getRestFromId", this.$route.params.id);
},
computed: {
...mapState(['currentUser', 'restInfo']),
section() {
return this.$route.params.section
},
identifier() {
return this.restInfo.id
},
model() {
return this.restInfo.id + `.` + this.section
},
crumbs () {
if (this.restInfo) {
let rest = this.restInfo
let crumbsArray = []
let step1 = { title: "Dashboard", to: { name: "dashboard"}}
let step2 = { title: rest.name, to: { name: "resthome"}}
let step3 = { title: 'Page Sections', to: { name: 'restsections'}}
let step4 = { title: this.$route.params.section, to: false}
crumbsArray.push(step1)
crumbsArray.push(step2)
crumbsArray.push(step3)
crumbsArray.push(step4)
return crumbsArray
} else {
return []
}
},
},
methods: {
editText() {
this.performingRequest = true
this.$store.dispatch("updateRest", {
id: this.rest.id,
content: this.rest
});
setTimeout(() => {
this.performingRequest = false
}, 2000)
}
},
components: {
Loader,
VueEditor,
Breadcrumbs
},
beforeDestroy(){
this.performingRequest = false
delete this.performingRequest
}
}
</script>
Try to use the brackets accessor [] instead of . :
<vue-editor v-model="restInfo[section]"
I am new to Vue.js and I think its amazing. I have been tasked to start implementing some vue components in our non-greenfield web application and I thought I would start by implementing some self-contained "widgets" that have to deal heavily with state in my work's rails app.
Its working great as a self-contained component but I want to load it with a data attribute so the component know what it needs to deal with. My Vue file looks like (I have redacted parts of this due to IP concerns):
<template>
<div class="card">
<div class="card-body">
${{ b.id }}
</div>
<div class="card-footer bg--blue-sky">
${{ b.amount }}
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
errors: [],
b: {
id: null,
amount: null
}
}
},
// Fetches posts when the component is created.
created: function () {
jQuery.ajax({
url: "/api/b/" + '2' + ".json",
method: 'GET',
dataType: "json"
})
.then(response => {
this.b = response.b
})
.catch(e => {
this.errors.push(e)
});
}
}
</script>
<style scoped>
</style>
The component is registered with:
import FiDis from '../components/fi_dis.vue'
Vue.component('fi_dis', FiDis);
document.addEventListener('turbolinks:load', () => {
const fi_dis = new Vue({
el: '#bs',
components: { FiDis }
})
});
And in my html.erb code I create the components with:
<div id="bs" policy="2">
<fi_dis data-b-id="1"></fi_dis>
<fi_dis data-b-id="2"></fi_dis>
</div>
This all works flawlessly, and does exactly what I want it to do except for one thing. I want to access the data-b-id attribute within the created function of the component (i.e. replace the number '2' in the url of the ajax call above with the value form the attribute). In this way, I hope for the component to handle ANY "fi_dis" I choose, merely by specifying the b-id in the data attribute I want it to handle.
How can I achieve this?
You communicate data values passing props from parent component to child components.
So for example you should define which props your component is allowed to receive:
import FiDis from '../components/fi_dis.vue'
Vue.component('fi_dis', FiDis);
document.addEventListener('turbolinks:load', () => {
const fi_dis = new Vue({
el: '#bs',
components: { FiDis },
props['bId'],
created() { // This is a lifecycle method
this.printPropertyValue();
},
methods: {
// Your custom methods goes here separeted by commas
printPropertyValue() {
console.log(this.bId);
}
}
})
});
And the sintax for passing the data from the component implementation is using v-bind:propertyName or :propertyName (short hand).
<div id="bs" policy="2">
<fi_dis :bId="1"></fi_dis>
<fi_dis :bId="2"></fi_dis>
</div>
I am working on a modal component using VueJS 2. Right now, it basically works -- I click on a button and the modal opens, etc.
What I want to do now is create a unique name for the modal and associate the button with that particular button.
This is what I have in mind. The modal has a unique name property:
<modal name='myName'>CONTENT</modal>
And this would be the associate button:
<button #click="showModal('myName')"></button>
What I need to figure out is how to pass the parameter of showModal to the modal component.
Here is the method that I'm using in the root vue instance (i.e, NOT inside my modal component):
methods: {
showModal(name) { this.bus.$emit('showModal'); },
}
What I want to do is to access the name property in the component -- something like this:
created() {
this.bus.$on('showModal', () => alert(this.name));
}
But this shows up as undefined.
So what am I doing wrong? How can I access the name property inside the modal component?
NOTE: If you are wondering what this.bus.$on is, please see the following answer to a previous question that I asked: https://stackoverflow.com/a/42983494/7477670
Pass it as a parameter to $emit.
methods: {
showModal(name) { this.bus.$emit('showModal', name); },
}
created() {
this.bus.$on('showModal', (name) => alert(name));
}
Also, if you want to give the modal a name, you need to accept it as a prop in the modal component.
Vue.component("modal",{
props:["name"],
...
})
Then I assume you will want to do something like,
if (name == this.name)
//show the modal
<!-- File name is dataTable.vue -->
<template>
<div>
<insertForm v-on:emitForm="close"></insertForm>
</div>
</template>
<script>
import InsertForm from "./insertForm";
import Axios from "axios";
export default {
components: {
InsertForm
},
data: () => ({
}),
methods: {
close(res) {
console.log('res = ', res);
}
}
};
</script>
<!-- File name is insertForm.vue -->
<template>
<div>
<v-btn #click.native="sendPrameter">
<v-icon>save</v-icon>
</v-btn>
</div>
</template>
<script>
export default {
data: () => ({
mesage:{
msg:"Saved successfully",
color:'red',
status:1
}
}),
methods: {
sendPrameter: function() {
this.$emit("emitForm", this.mesage);
}
}
};
</script>
https://vuejs.org/v2/guide/components-custom-events.html