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`
}
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.
So the image above is my code inside a comp.js (Vue component) when the user clicks 'Update' button, it will grab the updated input and push it to firestore.
I put v-on:emit on every single input and I tried to reference it on the 'Update' button.
This is my modal inside the HTML file, I tried to connect the function from comp.js to function.js.
However, the methods above didn't work and it gave me the error below.
I'm not really sure how to grab the value from each input and connect
UPDATED CODE BASED ON ITTUS'S SUGGESTION:
So based on the Ittuss suggestion below, I tried to pass over to items through the emit inside the Update button, I did this because I want to pass all the input values above the button to update the Firestore database.
I changed the cuisedit to this:
And I updated the function in the .js to this:
However, I received an error saying that I didn't reference correctly.
Do you know why it isn't grabbing the value emitted from the button?
In your cuiseedit component, $emit should contain only 1 additional parameter:
<button #click="$emit('some-change', obj2.id)"></button>
and in component:
<transition name="fade">
<cuisedit v-if="showModal" :obj2="cuisinemodal" #some-change="updateData">
</cuisedit>
</transition>
updateData: function (id) {
firestore.collection("koreanbap-cuisines").doc(id).update()....
}
Note
If you want to pass more data, you should wrap them to an object
<button #click="$emit('some-change', {id: obj2.id, event: $event})"></button>
I have a dynamic table generated on the server-side. Each row of the table is associated with an ID. Every row has a button. My goal is simple: when the user clicks a button in a row, that particular button should change to the 'is-loading' class and an AJAX call should fire. When the AJAX call resolves, the button should change to the 'is-success' class.
I could do this easily in jQuery, but I'm trying to learn Vue.js for this project. When I try to think about this from a Vue perspective, I'm effectively saying that the state of each row is changing first to a "loading" state and then to a "success" state. So if I have a rowState object, I can change the value of rowState[rowID] to change the state of the button, and bind my button's class to the value of rowState[rowID]. For example, for row #2 of the table, my button might look like:
<button :class="[{ 'is-loading': (rowState[2] == 1) }, {'is-success': (rowState[2] == 2) }]" #click="changeRowState(2)">
Then the changeRowState method would look like:
changeRowState(rowID) {
this.rowState[rowID] = 1;
}
This works fine (i.e. it adds the 'is-loading' class to the button in row #2 when it's clicked) if I define rowState in Vue like this:
data: {
rowState: {2: 0},
}
However, it fails if I don't pre-populate rowState with a list of all the possible row IDs. If there is no 2 property when rowState is created, adding that property later does not work.
data: {
rowState: {},
}
I can't see any practical way to add a property for every row in this dynamically generated table into the initial definition. Am I missing something obvious? I'm reasonably familiar with JS but very new to Vue.
Here's a fiddle demonstrating the behavior. If you change the rowState definition to {2: 0}, it works (the button turns red when clicked). If the 2 property is not defined in data, however, it does not (the button remains unchanged).
For an object, you have to use Vue.set (or this.$set). See Reactivity In Depth from the docs.
Vue js doesn't allow you to access arrays directly. See the documentation at https://vuejs.org/2016/02/06/common-gotchas/
Instead of using
this.rowState[rowID] = newValue;
You should use
rowState.$set(rowID, newValue)
or
rowState.splice(rowID, 1, newValue).
In an Angular 4 application, I have a template driven form with controls bound to values in my component. When the form input changes the object bound to the input changes immediately (two-way binding). When the cancel button is clicked, I want to undo the change to the bound object. This allows the user to change values then change their mind and cancel their changes.
https://plnkr.co/edit/RnnPwtHZY0qTN1H6er0z?p=preview
The plunker above has such a form with a bound field to read the hero.name
<h2>{{hero.name}} details!</h2>
An input bound to a hero object.
<form #myForm="ngForm" (ngSubmit)="save(myForm)">
<div class="form-group">
<label>name: </label>
<input name="heroName" [(ngModel)]="hero.name" placeholder="name" />
</div>
<button (click)="cancel(myForm)">Cancel</button>
<button type="submit">Save</button>
</form>
The cancel button calls the ngForm's resetForm() method.
cancel(myForm){
myForm.resetForm();
}
Repro steps
Change the hero name; Observe the h2 changes immediately proving that the bound object changed as well
Click cancel; Observe the name is cleared and the h2 changes because the hero.name is now null
I expected the cancel button to change the hero.name back to the original value. Is this how resetForm() is supposed to work? Is there a different way?
If you make a reset() method, where you set the default values, then you can call it whenever it's needed, as in ngOninit and reset button click:
ngOnInit(){
this.reset();
}
reset(){
this.hero = new Hero(1,'Plunker');
}
cancel(myForm){
this.reset();
}
DEMO
You can reunite cancel() and reset() by refactoring, but you may want keep it as is in case you add something else in cancel.
From the angular documentation, I guess the solution is to use a reactive form instead of a template driven form.
https://angular.io/guide/reactive-forms reads (emphasis :
In keeping with the reactive paradigm, the component preserves the
immutability of the data model, treating it as a pure source of
original values. Rather than update the data model directly, the
component extracts user changes and forwards them to an external
component or service, which does something with them (such as saving
them) and returns a new data model to the component that reflects the
updated model state.
So I have a modal box that allows the user to edit / save some data.
I just want to add that unlike other Meteor apps, I don't want to save the data straight away - I want the user to fill in all the fields before hitting save where it will save to the database and send to server etc. This is mainly because I want the user to be able to hit the "cancel" button to revert all changes.
I have a drop down box at the start of the form where depending on the value, fields will be shown or hidden
<select class="form-control" id="ddlNewInputType" placeholder="Enter your input type">
<option value="input">Input</option>
<option value="formula">Formula</option>
</select>
And I have a handlebar around a field like this to determine whether I want to show it
{{#if isFormula }}
<div class="row">
<input type="text"
id="txtNewInputFormula" placeholder="Enter formula">
</div>
{{/if}}
With a helper looking like this
isFormula: ->
$('#ddlNewInputType').val() == 'formula'
However, this doesn't work. Aside from when it first loads onto the page, it never hits isFormula, probably because Meteor doesn't consider any of the HTML elements as reactive so it never re-evaluates when the HTML element changes.
What is a suitable way to get around this? Is it possible to make something reactive explicitly in Meteor? I was also considering putting the dropdown list value into a session variable but that just seems messy because I'm going to need to manage this session variable (remember to clear it when the modal box closes etc.)
Your analysis is correct - a reactive variable needs to be involved in order for your helper to reevaluate after changing the select element. The basic strategy looks like:
Initialize a reactive variable when the template is created.
Whenever the select changes, update the reactive variable.
Read the reactive variable in your helper.
Rather than use a session variable, let's use a ReactiveVar scoped to your template. Here's an example set of modifications:
Template.myTemplate.helpers({
isFormula: function() {
return Template.instance().isFormula.get();
}
});
Template.myTemplate.events({
'change #ddlNewInputType': function (e, template) {
var isFormula = $(e.currentTarget).val() === 'formula';
template.isFormula.set(isFormula);
}
});
Template.myTemplate.created = function() {
// in your code, default this to the current value from
// your database rather than false
this.isFormula = new ReactiveVar(false);
};
Remember that you'll need to also do:
$ meteor add reactive-var
See my post on scoped reactivity for a full explanation of this technique.