Tools:
Vue.js
Bootstrap-Vue (https://bootstrap-vue.js.org/docs/components/modal)
Problem:
I am trying to display a popup each time a row is clicked within a bootstrap grid. Then once the modal is hidden it disappears and the selected item goes away
I created a custom component for the Modal. And I am now trying to programmatically get rid of the selected certificate.
I have it working, but barely and it's very clunky. I want a more seamless approach on how someone would tackle this problem who has more Vue experience than I do
/// Modal component
<b-modal
size="lg"
id="certificate-details-modal"
hide-footer
header-bg-variant="dark"
header-text-variant="light"
#hidden="modalDismissed"
v-model="expiringCertificate"
>
<template slot="modal-title">
Certificate Details for <span class="certificateTitleHighlight">{{ expiringCertificate.commonName }}</span>
</template>
<b-container fluid>
<b-row>
<b-col>
<b-badge pill variant="dark">Identified</b-badge>
</b-col>
<b-col class="text-center">
<b-badge pill variant="info">Ready</b-badge>
</b-col>
<b-col class="text-right">
<b-badge pill variant="success">Resolved</b-badge>
</b-col>
</b-row>
...
/// Main Component
<ExpiringCertificateDetail
v-if="selectedCertificate"
v-on:modalDismissed="resetSelectedCertificate"
:expiringCertificate="selectedCertificate">
</ExpiringCertificateDetail>
...
data () {
...
selectedCertificate = undefined
},
methods: {
resetSelectedCertificate() {
this.selectedCertificate = undefined;
},
rowSelected(certificate) {
this.selectedCertificate = certificate[0];
this.$bvmodal.show('certificate-details-modal')
},
My goal would be to display a modal each time a row is clicked and have the selectedCertificate reset back to undefined once the modal is hidden (either closed or unfocused and closed (which should be the hidden event)
I have been thinking of two possible approaches. Each of them use a separate component for the modal.
1. Use v-model for the current selected item
Use the modal component as any other input: declare a v-model on the component. When the modal is hidden, reset the current item to null from inside the modal component. The v-model magic will do the rest.
Full example here:
https://codesandbox.io/s/bootstrap-vue-sandbox-w8j09
2. Reset the current selected item from outside the modal component
This is pretty much the approach you have shown in your code. The modal component is only responsible for displaying the details. When to show the modal, or when to set the current selected item is the parent's responsibility.
In this example, I used a similar implementation as yours. I just use v-model on the modal component to avoid the this.$bvmodal.show and make the code more 'declarative'.
https://codesandbox.io/s/bootstrap-vue-sandbox-rwll4
Both approaches make them possible to change the details component into something other than a modal.
Although the first approach is less verbose, I would go for the second approach because is leaves the responsibility of showing/hiding the details from outside.
Related
Playing aroud with vue with a todo list.
At this stage, I am able to show the list within and with an edit button, the text will be an input so I can edit the todo items. But for example if I have 10 items, and I clicked edit for item 2 and item 4 and item 5, then all these 3 items will change into input.
What I am thinking is if I click on item 4, item 2 will change back to text, so only one item will show input if edit clicked.
I have my template code as below
<td class="col-8">
<!-- if editable is false, show the checkbox and todo item -->
<div class="custom-control custom-checkbox" v-if="!editable">
{{ todo.item }}
</div>
<!-- if editable is true, turn item into an input so user can enter new value -->
<div v-else>
<input ref="editItem" type="text" class="form-control" :value="todo.item" #input="onInput">
</div>
</td>
<td class="col-2">
<button
class="btn btn-sm"
:class="editable ? 'btn-outline-success' : 'btn-outline-info'"
#click.prevent="editable ? onSave() : onEdit()">{{ editable ? 'save' : 'edit' }}
</button>
</td>
As I open up the vue inspect in chrome, I can see all items have their different data value.
How can I change others siblings' value?
Thanks in advance for any sugguestins and advices.
I wouldn't recommend you to change the value of another sibling, since you cannot be sure of the side-effects it can cause, obviously when you look at a todo list, it would feel like there are no side-effects but in general practice, a node should change elements that are below it in the parentage, neither above nor the siblings.
If you want to change elements above, its always drive via events. You let the parent know that something needs to be changed rather than changing it yourself.
This is what you can do here:
Rather using editable as a data property, use it as a prop to the TodoListItem component.
props: {
editable: {
type: Boolean,
default: false
},
}
When the Save/Edit button is clicked in the child component, rather than changing the value there itself, use events like so.
/** You should keep a deepCopy of the props if you are changing the state
of that object and then send the updated local data prop to the parent
as well for updation rather than changing the prop object.
NEVER CHANGE THE PROP OBJECT DIRECTLY. -> this.todoItem =
this.deepCopy(this.todo);
Do this in before mount hook of the child component.
**/
this.$emit('todo-item-action', action, todoItem);
Once this event is emitted, in the parent TodoList component, catch this event and change the state of the items like so:
<todo-list-item
v-for="todo in todos"
:key="todo['id']"
:todo="todo"
:editable="todo['editable']"
#todo-item-action="handleItemAction"
>
or if you don't want to mutate the todo object. here you can make sure editableItem is null if none is selected or only the active item ID is referred so that the prop will change and automatically other list item will become un-editable.
<todo-list-item
v-for="todo in todos"
:key="todo['id']"
:todo="todo"
:editable="editableItem === todo['id']"
#todo-item-action="handleItemAction"
>
The more you drive a component via events and props, the more re-usable it becomes. Always have an orchestrator component which manipulates these stateless components by handling events propogated by them and sending state via props.
This is the approach I have always followed and it always allow me to refactor cleanly and quickly.
Regards.
I've a component with a simple v-dialog to show a message to user and a v-btn to close it. The scenario is:
User click on the button that show v-dialog's component.
User click on the v-btn to close the component
A error is triggered on console: Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "show"
If click again on the button to open dialog, the dialog is not re-open, because the data() show not change the value from the component's v-btn.
The dialog component BasicMessageDialog.vue
<template>
<div class="text-center">
<v-dialog v-if="showDialog" width="500">
<v-card>
<v-card-title primary-title class="title">Ops...</v-card-title>
<v-card-text class="body-1">{{message}}</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text color="primary" #click="show = false" class="body-1">Beleza!</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
export default {
name: "BasicMessageDialog",
computed: {
showDialog() {
return this.show;
}
},
props: {
show: Boolean,
message: String
}
};
</script>
<style>
</style>
The main component Login.vue
<template>
...
<BasicMessageDialog :message="messageBasicDialog" :show="showBasicMessageDialog">
...
</BasicMessageDialog>
</template>
<script>
import BasicMessageDialog from "#/components/BasicMessageDialog";
export default {
name: "Login",
components: {
BasicMessageDialog
},
data: () => ({
showBasicMessageDialog: false,
messageBasicDialog: "",
)},
methods: {
forgetPassword() {
console.log("forgetPassword");
if (this.email == "") {
this.messageBasicDialog = "Digite o e-mail no campo!";
this.showBasicMessageDialog = true;
}
}
}
</script>
It's because your updating the show prop in your dialog component wherein your show data was came from your parent. That's why it is returning a warning of Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders.
To solve your issue, here are some ways to prevent that warning.
First when you clicked the dialog button or clicked on the outside of your dialog, you must emit an event in your dialog component. like this one.
in your V-dialog component. when user clicked on a button
<v-btn text color="primary" #click="this.$emit('hideModal')" class="body-1">Beleza!</v-btn>
Now in your parent component should received this event. Parent Component like this
<BasicMessageDialog :message="messageBasicDialog" :show="showBasicMessageDialog" #hideModal='showBasicMessageDialog = false'>
...
</BasicMessageDialog>
Now, the issue again is what if the user clicked the outside part of dialog not the button ? To solve this you must watch the value of show prop. like this one. In your v-dialog component put this.
watch: {
show(val) {
if(!val) {
this.$emit('hideModal')
}
}
}
And everything will work fine now.
Second is to use vue .sync modifier
For convenience, Vue js offer a shorthand for this pattern with the
.sync modifier. Please read the docs here sync modifier. This approach will let you avoid emitting event. Unfortunately, true two-way binding can create maintenance issues.
Last is to use state management, vuex
It serves as a centralized store for all the components in an
application, with rules ensuring that the state can only be mutated in. docs here vuex
a predictable fashion.
<v-btn text color="primary" #click="show = false" class="body-1">Beleza!</v-btn>
You can't change prop as said in error instead of you have to add function, send it as prop, and call when you need change your prop value , parent have to handle the function and change data.
<template>
...
<BasicMessageDialog :message="messageBasicDialog" :show="showBasicMessageDialog" :hide="showBasicMessageDialog=!showBasicMessageDialog">
...
</BasicMessageDialog>
</template>
and
<v-btn text color="primary" #click="hide" class="body-1">Beleza!</v-btn>
https://v2.vuejs.org/v2/guide/components-props.html#One-Way-Data-Flow
I'm trying to update the value in my inline edit data table from a method but I've been running into issues where the item I'm passing isn't getting updated.
I pass the props.item to my defined cancel method to be updated. The props.item.iron prop is synced so that should still be updated via edit dialog.
<template v-slot:item.iron="props">
<v-edit-dialog
:return-value.sync="props.item.iron"
large
persistent
#save="save"
#cancel="cancel(props.item)"
#open="open"
#close="close"
>
<div>{{ props.item.iron }}</div>
<template v-slot:input>
<div class="mt-4 title">Update Iron</div>
</template>
<template v-slot:input>
<v-text-field
v-model="props.item.iron"
:rules="[max25chars]"
label="Edit"
single-line
counter
autofocus
></v-text-field>
</template>
</v-edit-dialog>
</template>
I try to update the passed in obj but the change isn't reflected or passed back up to model.
cancel (item) {
console.log(item)
item.iron = 'clear'
}
Is there a way around this where I can update the prop.item externally from outside the edit dialog? My main use case is I have a request being made when a new value is saved, but I want to clear the value from the table if the request failed.
Codepen: https://codepen.io/dicquack/pen/zYxGOQx?editors=1011
Specifically line 116
EDIT:
So by taking out the .sync from the v-edit-dialog return value and changing the inner textbox from v-model to :value then I'm able to modify the value outside the edit dialog. I'm running into another issue where I now need to pass the new textbox :value to the edit dialog and pass it to save handler.
CodePen has been updated accordingly
You have to pass the updated iron value to save() along with reference to the updated object in this case let's use name (in real example use some unique id)
<v-edit-dialog
:return-value.sync="props.item.iron"
large
persistent
#save="save({name : props.item.name, props.item.iron})"
#cancel="cancel(props.item)"
#open="open"
#close="close"
>
in save() implementation , update iron field inside Data object based on name field,
save({name, iron}){
// update `iron` in Data object based on `name`
}
This is my first foray into using VueJS so any pointers or better ways to tackle the problem are much appreciated. Here is where I am at http://codepen.io/anon/pen/ezBwJw
I'm building a pricing plan table where users can browse 4 different payment plans. The table is interactive, users have access to radio buttons which toggle between viewing prices in GBP & USD, as well as viewing the cost if they pay per year or per month. All of this is working, but the issue I now have is that I want to pass some data to a 'summary' section which will be presented to the user before they choose to sign up. The one piece of data I am struggling to pass to the summary table is the price.
When a user selects a plan I want the price that is currently showing in that plan to show in the 'Total to pay now' field. In jQuery I'd do something like this (simplified version of Codepen)...
<div>
<h1>Basic</h1>
<div class="price">7</div>
Select this plan
</div>
<h2 class="total"></h2>
$('.select-plan').on('click', function() {
var planName = $(this).parent().find('.price').text();
$('.total').text(planName);
});
I'm currently using v-if to show the different prices for the respective plans, so I'm lost as to how I would get the item that is currently in view and pass that to the summary field.
JQuery Way
One option is to create watchers that call an updatePrice method whenever a variable that effects the current price changes. For example:
watch: {
'activePlan': 'updatePrice',
'currency': 'updatePrice',
'frequency': 'updatePrice'
},
... and then in methods:
updatePrice: function() {
this.price = $('.price.' + this.activePlan).text();
}
Here is a fork of your CodePen with that change. Notice that I've added the plan name as a class so that the JQuery selector can find the correct element.
Component Way (do this!)
Personally, I think it you'd be better off taking a totally different approach. I would make a custom component for plans. This will let you encapsulates all the functionality you require in a reusable and manageable way.
For example, you could make a component like this
Vue.component('plan-component', {
template: '#plan-component',
props: ['frequency', 'name', 'priceYearly', 'priceMonthly'],
computed: {
'price': function() {
// Move the logic for determining the price into the component
if (this.frequency === 'year') {
return this.priceYearly;
} else {
return this.priceMonthly;
}
}
},
methods: {
makeActivePlan() {
// We dispatch an event setting this to become the active plan
// A parent component or the Vue instance would need to listen
// to this event and act accordingly when it occurs
this.$dispatch('set-active-plan', this);
}
}
});
Components are related to an HTML template. So in your HTML you would need a template tag with id plan-component.
<template id="plan-component">
<h1>{{ name }}</h1>
<div>
<span>{{ price }}</span>
</div>
<a class="select-plan" v-on:click="makeActivePlan($event)" href="#">Select this plan</a>
</template>
Thus each plan gets its own component which handles the data related to that plan. And instead of repeating the same HTML for each plan in a table, you can just use your new custom <plan-component>, and bind the appropriate values to each plan (these are the props).
I've implemented this more fully as a JSFiddle here. I got rid of USB vs GBP currency because I wanted to keep things simple. I hope this gives you some idea about how to tackle your problem!
very new to react. you can say I have not yet started to think like React.
here is the problem:
<div>
<DropDown> </DropDown>
<Panel> </Panel>
</div>
In the dropdown, I select a value. Store it in state, as something as , currentLocation.
Then I go to Panel, hit a button, and I want to open a modal. When i open a modal, I need to pass the currentLocation to that model.
I can pass in arbitrary value to modal, but I cannot figure out a way to get the currently selected item from DropDown.
How do I get the value of the currently selected item to the Panel?
Am I even making sense?
When you call the setState in the dropdown that will force an update of the page.
Then if you call this.state in your component you should have the value you need there.
You should go over the basic tutorials to grasp the react basics.
But it goes like this:
getInitialState: function() {
return {
myVar: ''
//set your variables here
//getInitialState will get called before the component gets mounted
};
},
myCustomFunction: function(newVariable) {
this.setState({
myVar: newVariable
});
},
render: function() {
return (
<div>
<input
onChange={this.myCustomFunction}
/>
<MyCustomComponent myProp={this.state.myVar}/>
//everytime the state changes MyCustomComponent will have that new value as a prop
</div>
);
}
There is a lot of ambiguity in your question but I'll try for the simplest case.
You have a Panel component and a Dropdown component.
You want to the Panel to have access to a value that was set when the Dropdown was used.
Solution: When the Dropdown is actuated, it creates an Action that Stores the selected value.
When the modal button in the Panel is actuated, it creates an Action that requires the DropDownStore. Then it decides what to do based on that value.
The pattern I am loosely describing is known Facebook's Flux architecture which is basically just a more specific application architecture for React applications similar to pub/sub or an event bus.