I have a list of elements, rendered server-side.
Each item has a button that changes its status.
<div class="list">
<div class="list-item" data-is-published="1" data-item-id="11">
<div class="item-link">Item 1</div>
<form method="post" class="list-item-form">
<div class="vue-mount">
<input type="submit" name="changeStatus" class="button-unpublish" value="Unpublish">
<!-- some hidden fields -->
</div>
</form>
</div>
<div class="list-item" data-is-published="1" data-item-id="12">
<div class="item-link">Item 2</div>
<form method="post" class="list-item-form">
<div class="vue-mount">
<input type="submit" name="changeStatus" class="button-unpublish" value="Unpublish">
<!-- some hidden fields -->
</div>
</form>
</div>
</div>
I'm using the following code to replace each item with a vue component
var ms = document.querySelectorAll('.vue-mount')
for (var i = 0; i < ms.length; i++) {
new Vue({
render: h => h(SubscriptionButton)
}).$mount(ms[i])
}
And on the component i get the values of data-is-published and data-item-id and set them on the component
<template>
<input
type="submit"
#click.prevent="changeStatus()"
:value="isPublished === 1 ? 'Unpublish' : 'Publish'">
</template>
<script>
export default {
....
created: function () {
const el = this.$root.$el.parentNode.parentNode
const status = el.dataset.isPublished
this.isPublished = parseInt(status)
this.itemID = el.dataset.itemID
}
}
I'm doing it this way to ensure that it works even if javascript is disabled, but the part this.$root.$el.parentNode.parentNode doesn't feel right.
Is the way I'm doing it ok? Are there better ways to achieve the same?
EDIT
I can put the data attributes on the element that vue will mount to and access them with this.$root.$el.dataset.
What I'm not sure about is how compliant to the best practices the use of this.$root.$el is.
You could use Element.closest (with a polyfill if IE support is required)
const el = this.$root.$el.closest("*[data-is-published]");
That avoids having to know the hierarchy depth of the DOM.
Info on MDN
Edited to add:
The appropriateness of using this.$root.$el depends on how you plan to structure the code. If the element you're trying to find is guaranteed to be a parent of the Vue application, then starting the search from $root is fine. If that bothers you, though, starting the search from this.$el instead would work.
The less conventional part of your approach is that it effectively creates a separate Vue application for each .vue-mount
Related
I'm tinkering with DaisyUI within Vue.js 3 (porting an existing Vue+Bootstrap application to Tailwind CSS). I liked the idea that DaisyUI doesn't have JS wizardry going on behind the scenes, yet there seems to be some CSS voodoo magic that is doing things more complicated than they need to be (or at least this is my impression).
From the DaisyUI examples, here's the modal I'm trying to integrate:
<input type="checkbox" id="my-modal" class="modal-toggle"/>
<div class="modal">
<div class="modal-box">
<h3 class="font-bold text-lg">Congratulations random Internet user!</h3>
<p class="py-4">You've been selected for a chance to get one year of subscription to use Wikipedia for free!</p>
<div class="modal-action">
<label for="my-modal" class="btn">Yay!</label>
</div>
</div>
</div>
no javascript, yet the problem is that the modal will come and go according to some obscure logic under the hood that tests the value of the my-modal input checkbox at the top. That's not what I want. I want my modal to come and go based on my v-show="showModal" vue3 reactive logic!
Daisy doesn't seem to make that possible. Or at least not easily. What am I missing?
The modal is controller by the input field, so to open or close the modal just toggle the value of that input:
<template>
<!-- The button to open payment modal -->
<label class="btn" #click="openPaymentModal">open modal</label>
<!-- Put this part before </body> tag -->
<input type="checkbox" v-model="paymentModalInput" id="payment-modal" class="modal-toggle" />
<div class="modal modal-bottom sm:modal-middle">
<div class="modal-box">
<h3 class="font-bold text-lg">Congratulations random Internet user!</h3>
<p class="py-4">You've been selected for a chance to get one year of subscription to use Wikipedia for free!</p>
<div class="modal-action">
<label class="btn" #click="closePaymentModal">Yay!</label>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
const paymentModalInput = ref()
const openPaymentModal = () => {
paymentModalInput.value = true
}
const closePaymentModal = () => {
paymentModalInput.value = false
}
</script>
Trying to implement the bootstrap-datepicker in conjunction with Vue, and not quite sure why my solution won't work.
I am using the code example at https://eonasdan.github.io/bootstrap-datetimepicker/
I added a v-on:click on the INPUT that then ties to a method.
TEMPLATE CODE:
<div class="container">
<div class="row">
<div class='col-sm-6'>
<div class="form-group">
<div class='input-group date' id='datetimepicker1'>
<input v-on:click="displayCal" type='text' class="form-control" />
<span class="input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
</div>
</div>
</div>
</div>
VUE
import jquery from 'jquery';
....
methods: {
displayCal: function () {
jquery('#datetimepicker1').datetimepicker();
}
}
Will that help with what I'm trying to do?
Not exactly, I misunderstood and thought that the datepicker is a bootstrap component. Unfortunately it is not, and so while vue implementation of bootstrap could be helpful, it would leave you still with quite a bit to implement yourself.
Option 1
Use a Vue component that has the functionality out of the box
const app = new Vue({
el: '#app',
components: {
vuejsDatepicker
}
})
<div id="app">
<vuejs-datepicker></vuejs-datepicker>
</div>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuejs-datepicker"></script>
Demo: https://codesandbox.io/s/mpklq49wp
If that does what you need then vuejs-datepicker component may be better for you to implement.
Option 2
Get jquery and Vue to talk
This is not ideal, because the two libraries will compete for DOM control. Vue mounts and unmounts of DOM elements are data driven. That means if your data changes, your DOM may replace an element that jquery may be relying on. SO you may be able to get it to work, but based on all the other stuff going on in the app, you may find some odd things happening, that said, here is a way you can do it...
<div id="app">
<div class="container">
<p>
TIMEDATE: {{timedate}}
</p>
<div class="row">
<div class='col-sm-6'>
<input type='text' class="form-control" id='datetimepicker4' />
</div>
</div>
</div>
</div>
const app = new Vue({
el: '#app',
data(){
return{
timedate: new Date(),
}
},
methods:{
onUpdate(e){
this.timedate=e.date;
}
},
mounted() {
const dp = $('#datetimepicker4')
dp.datetimepicker({
defaultDate: this.timedate,
});
dp.on('dp.change', this.onUpdate )
}
})
demo: https://jsfiddle.net/fjk6cLo8/7/
This code will allow you to add the datetime picker with a preset date. Upon change of the date, using the datetime picker, the data will be updated in the vue component (using dp.change listener)
The datetime picker is mounted once right after the component mounts, and the <input type='text' class="form-control" id='datetimepicker4' /> element is available.
i am a absolute beginner in vuejs,i have a feature of adding dynamic input fields on click of a button it will keep on adding rows and keeping in mind the counter should be incrementing also so that i can validate on backend, this is my code so far
<div id="settlement_container" class="container-fluid mt-4">
<div class="card rounded-0 shadow-lg">
<div class="card-body p-0">
<div class="card-header px-2">
<div class="row wow fadeIn">
<div class="col-5">
<h3>Add Store Status</h3>
</div>
</div>
</div>
<form class="custom-form-group" action="{{url('stores/addStoreStatusDB')}}" method="POST">
<div class="form-group col-6">
<label for="exampleInputEmail1">Tax</label>
<input type="text" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" name="tax" placeholder="Tax" required>
</div>
<div class="display-inline">
<div class="form-group col-md-6">
<button #click="addstatus" class="btn btn-primary">Add Rows</button>
</div>
</div>
<div class="display-inline">
<div class="form-group col-md-6">
<button type="submit" class="btn btn-primary">Update Tax</button>
</div>
</div>
<dynamic-rows/>
</form>
</div>
</div>
</div>
{{-- Main layout --}}
#push('script')
<script src="{{ asset('js/app_vue.js') }}" ></script>
<script>
Vue.component('dynamic-rows',{
//accept data inside template
props:['counter'],
//accept data inside template
template:"<label for='exampleInputEmail1'>counter</label>"
});
const app = new Vue({
el: '#settlement_container',
data: {
counter:0
},
component:['dynamic-rows'],
methods:{
addstatus:function(e){
appendDiv=""
e.preventDefault();
alert("inside");
}
}
});
</script>
now i can do this in jquery in 5 minutes , but as i am beginner in vuejs i cant developer the sense of it of how to do it, i have a component and i want to repeat the component every time the button is clicked,
here is the fiddle! fiddle
OK, so a lot going on here and I think it may be easier to break down some of the points in isolation for you to play with and learn.
To add inputs, I think it makes more sense to have the values being in an array. Using Vue, you can iterate through that array to let each array element have its own <input/> while also simply adding another array element to add a new input:
<template>
<div>
<div v-for="(tax, index) in taxes" :key="index">
<input v-model="taxes[index]" />
</div>
<button type="number" #click="add">Add</button>
<p>Count: {{taxes.length}}</p>
</div>
</template>
<script>
export default {
data(): {
return {
taxes: [0]
}
},
methods: {
add() {
this.taxes.push(0);
}
}
});
</script>
Now with regards to the counter, I don't know what you mean validate on the backend. You could add a watcher on the taxes array and process changes there? Watchers are used sparingly, with computed properties being much preferred, but they may make sense if you need to be sending data to the backend instead of into the DOM.
The counter prop you registered in your code is not really going to work for the pattern I showed. Generally, props are for parent components to pass data to child components. The preferred pattern when sending data from child to parent is to use $emit. Read more here.
I have problem with displaying component with v-if.
In one component i have <div v-if="seen">...</div>.
In another component I have <button v-on:click="seen = !seen">...</button>.
In "var vue = nev Vue({...})" file, in data: I have seen: true and this is not working.
I found "solution" which works: example
and I tried this "function version" of data in my code, but it doesn't work too :/
Here is my code:
Main File
var vue = new Vue({
el: '#app',
components: {
Navigation,
Home,
Footer,
Login
},
data: function () {
return {
seen: true
}
},
template: "<div><navigation></navigation><login></login><home></home><Footer></Footer></div>"
})
template that I can't see
<div v-if="seen" id="loginbox">
<form>
<input type="text" placeholder="login" class="input is-rounded"/>
<input type="password" placeholder="password" class="input is-rounded"/>
</form>
</div>
button template
<div class="navbar-menu">
<div class="navbar-start"></div>
<div class="navbar-end">
<p class="nav-item">
<a class="nav-link" v-on:click="seen = !seen">Login</a>
</p>
<p class="nav-item">
<a class="nav-link">Register</a>
</p>
</div>
</div>
I expect that when I click on button, "loginbox" template will be shown.
EDIT:
I did it in half way. I used props (used export default...) in in template that I cannot seen. It not work properly, becouse now I can change value of "seen" only with button which is in this template. I'd like change value of it by button which is in another template.
You should somehow share data between components.
You can do it many ways, but for this case i suggest to use event handling https://v2.vuejs.org/v2/guide/events.html#ad
Edited your sandbox with example - https://codesandbox.io/s/mzq0r2w88j
I am new to Vuejs. This is what I need to do.
<div v-for="r in records">
<div v-if="r.something">
<div id="x">
{{ r. something}}
more of r here.
</div>
</div>
<div v-else id="x">
same div as in the block above.
</div>
</div>
What I want do is not define div with id x two times as it is huge.
Make your 'div' a component and refer to it in both places.
There are many ways to define your component. This is example shows just one. If you are using WebPack, use a single file component. You can then have your script, html, and css all in one file that gets precompiled. That's the best way to manage your 'huge' div. Then you can continue to refactor and break it up into more components.
const myComponent = {
template: "<div :id='id'>HELLO, my id is {{id}}. r.foo is {{r.foo}} </div>",
props: {
id: String
},
data() {
return {
r: {
foo: 'bar'
}
}
}
}
<div v-for="r in records">
<div v-if="r.something">
<my-component id='x' />
</div>
<div v-else id="x">
<my-component id='x' />
</div>
</div>