Vue JS update computed property by user input - javascript

My problem is to replace the value of a computed property by the input made by a user. My setup is like this:
html
<div class="col-md-3">
<ul style="margin-top: 50px">
<ol v-for="note in notes">
<h3 #click="setActive($index)">{{note.name}}</h3>
</ol>
</ul>
</div>
<div class="col-md-9" v-show="activeNote">
<h2 v-show="nameIsText" #click="switchNameTag()">{{activeNote.name}}</h2>
<input class="form-control" v-show="!nameIsText" #keyup.enter="switchNameTag()" value="{{activeNote.name}}">
<textarea name="note-text" class="form-control" rows=10>{{activeNote.text}}</textarea>
</div>
js
<script>
var vm = new Vue({
el: 'body',
data: {
active: {},
nameIsText: true,
notes: [{
id: 1,
name: 'Note 1',
text: 'Text of note 1'
}, {
id: 2,
name: 'Note 2',
text: 'Text of note 2'
}, {
id: 3,
name: 'Note 3',
text: 'Text of note 3'
}, {
id: 4,
name: 'Note 4',
text: 'Text of note 4'
}, {
id: 5,
name: 'Note 5',
text: 'Text of note 5'
}]
},
methods: {
setActive: function(index) {
this.active = index;
},
switchNameTag: function() {
this.nameIsText = !this.nameIsText;
},
},
computed: {
activeNote: function() {
return this.notes[this.active];
},
},
});
</script>
I've made a simple note-app, if you click one note, a textarea with the text and a heading 2 with the name is displayed.
Now if you click the name within the <h2></h2>-Tags, the heading 2 is replaced by an input field - so the user can edit ne name of the current note.
Everything works, except the fact, that when I edit the name in the input field (the name is a computed property) the name isn't updating. A second problem is, if I click another note after editing the name of one note, the name of the old note remains in the input field instead of showing the name of the newly clicked note.
I've added two pictures for better understanding:
name as h2
name as input
So my (probably related) questions are, how can I edit the computed property in the input field, and display the name of a newly clicked note even if I haven't hit enter after editing the name in the input field?

You want to use the v-model binding for the items you're editing. These give you the two-way binding that will actively update the underlying data items.
Also needed to use v-if instead of v-show because activeNote can be undefined, in which case accessing its members is an error.
<div class="col-md-9" v-if="activeNote">
<h2 v-show="nameIsText" #click="switchNameTag()">{{activeNote.name}}</h2>
<input class="form-control" v-show="!nameIsText" #keyup.enter="switchNameTag()" v-model="activeNote.name">
<textarea name="note-text" class="form-control" rows=10 v-model="activeNote.text"></textarea>
</div>
Fiddle

Related

change event on updated model

I have a list of fields
fields: [
{
id:0,
label: 'dropdown 1',
value: 0,
options: [{ id: 0, value: 0, label: 'Option 1' }]
},
{
id: 1,
label: 'dropdown 2',
value: 0,
options: [
{ id: 0, value: 0, label: 'Hide dropdown 1' },
{ id: 1, value: 1, label: 'Show dropdown 1' }
]
}
]
that are filtered before being shown like so:
<div v-for="field in fields.filter(ffield => showField(ffield))" :key="field.id">
<b-form-select v-model="field.value" #change="changed(field)">
<option v-for="option in field.options" :key="option.id" :value="option.value">
{{ option.label }}
</option>
</b-form-select>
</div>
I can hide 'dropdown 1' based on the selected option in 'dropdown 2'. This is managed by the 'showField' function:
function showField(field) {
return field.value !== 'dropdown 1' || fields[1].value === 1
}
Lets say dropdown 1 is hidden and I select the second option Show dropdown 1. Vue's reactivity runs the showField function for all items in the list before running the changed event on the affected field. So now dropdown 1 is visible and then the eventhandler is called.
But what's really weird is that the parameter field in the changed function corresponds to the value of fields[0] even though I selected an option in 'dropdown 2' or fields[1].
What is going on?
I understand that this doesn't happen when dropdown 1 is after dropdown 2 in the list.
I tried modifying node_modules/bootstrap-vue/esm/components/form-select/form-select.js rendering function but this must be some internal thing that's causing it and I'm not familiar enough to keep digging.
Maybe I disregarded some pattern for this sort of thing?
The filter in the v-for came in because we used v-if="showField(field)" together with the v-for and that was incorrect.
How can I avoid this?

v-if and splice on users pick from v-model

I have the following code (from a tutorial, but I want to expand it a bit):
<div style="margin-top: 10px;">
v-for="task in taskItems"
:key="task.id"
<q-icon :name="task.icon"/>
<div
{{ task.text }}
</div>
</div>
my taskItems array looks like this:
taskItems: [
{
id: 1,
icon: 'settings',
text: 'Dolor, sit amet consectetm tot',
name: 'style'
},
{
id: 2,
icon: 'exit',
text: 'Lossssr dolor, sit amet consectetm tot',
name: 'getaway'
},
{
id: 3,
icon: 'lego',
text: 'Lomet consectetm tot',
name: 'buildingblocks'
},
{
id: 4,
icon: 'lego',
text: 'Lomet consectetm tot',
name: 'buildingblocks'
}
]
and I have the following v-model:
numberOfTasks: [
{ value: '1', label: '1' },
{ value: '2', label: '2' },
{ value: '3', label: '3' },
{ value: '4', label: '4' },
}
where a user can pick an object by clicking on a button (left out since that isn't the primary focus) and if they choose the first, then it is equal to value '1', if they choose the second, then it's equal to '2', etc.
The thing is, that I want my v-for at the top, which contains 4 tasks, to show the number of elements from its array equal to the numberOfTasks, that the user chooses.
So if the user chooses value: '3', then the v-for will only show 3 tasks from the tasks array.
I am new to Vue and have tried several things out, but none works.
How do I bind these together and do I need to use task.splice in my ?
Anyone have an idea, what to do?
I'll assume taskItems is a property of data as well as numberOfTask which store the value choosen form numberOfTasks.
You need to change v-for="task in taskItems" in v-for="task in actualTaskItems" where actualTaskItems is a computed property like so
// …
computed: {
actualTaskItems() {
return this.taskItems.splice(0, this.numberOfTask);
}
}
This is freehand so it might not be 100% correct, but you can simulate an index for loop in Vue. I've added a variable selectedNumberOfTasks which would just be a number (a data variable or something) that represents what the user has selected.
<div v-for="index in selectedNumberOfTasks" :key="index">
{{ taskItems[index].text }}
</div>
This will count up in the variable index to a max of selectedNumberOfTasks.
The JS for loop equivalent would be
for (let i = 0; i < this.selectedNumberOfTasks; i++)

Angular scope not affecting ng-show as expected

I have a question about Angular.js scope.
Firstly I am very new to Angular and I have read the scope documentation and tried to understand it the best I can. I have a feeling my question is similar to this:
ng-show not binding as expected
However my example is simpler in nature and I still don't understand what I am missing.
My html is very simple, I have a controller that wraps everything:
<div ng-controller="ApplicationFormController"></div>
Inside this I have sections:
<div ng-controller="ApplicationFormController">
<button ng-click="previous()">previous</button>
<button ng-click="next()">previous</button>
<p> You are on section #1 </p>
<div class="section1" ng-show="{{ section.id == 1 }}"> Section 1 </div>
<div class="section2" ng-show="{{ section.id == 2 }}"> Section 2 </div>
<div class="section3" ng-show="{{ section.id == 3 }}"> Section 3 </div>
</div>
As you can see I intend to show the section when it is applied to the controller.
My application logic is as follows:
app.controller('ApplicationFormController', ['$scope', function($scope) {
$scope.sections = sections;
$scope.section = $scope.sections[0];
$scope.next = function() {
var index = $scope.sections.map(function(x){
return x.id;
}).indexOf($scope.section.id);
$scope.section = $scope.sections[index+1];
};
$scope.previous = function() {
var index = $scope.sections.map(function(x){
return x.id;
}).indexOf($scope.section.id);
$scope.section = $scope.sections[index-1];
};
}]);
The sections array is as follows:
var sections = [
{
id: 1,
name: 'Section 1',
steps: [
{
id: 1,
name: 'Step 1',
},
{
id: 2,
name: 'Step 2',
},
{
id: 3,
name: 'Step 3',
},
]
},
{
id: 2,
name: 'Section 2',
steps: [
{
id: 1,
name: 'Step 1',
},
{
id: 2,
name: 'Step 2',
},
{
id: 3,
name: 'Step 3',
},
]
}
];
Very simple stuff.
So the issue lies in the showing and hiding.
When I trigger the next or previous event it runs, I can see this because the <p> tag updates with the appropriate id eg: if I press next the p tag will update to reflect:
<p>You are on section #2</p> as expected.
The odd thing is the section that is currently showing doesn't update. Section one in this case will stay visible while section two will stay hidden.
What is preventing the DOM from updating to reflect the current state of the controller.
That is because ng-show takes an expression upon which a watch is set internally. But you are providing value of expression (boolean string) by using interpolation ({{). So watch never executes afterwards since scope.$watch(attr.ngShow,...) will be evaluating scope['true/false'] instead of actual expression you intended to.
Change:
<div class="section1" ng-show="{{ section.id == 1 }}"> Section 1 </div>
to
<div class="section1" ng-show="section.id == 1"> Section 1 </div>
and so on for others too.

How to hide a tag with ng-hide when the tag is empty (contains no text)

I want to hide every tag which has no content (simple text) using ng-hide directive.
Here's what I am trying to accomplish:
<div class="menu-head" ng-hide="c1.section == ''">{{c1.section}}</div>
But this doesn't work. However, the following two evaluate to true (for testing purposes I set the c1.section field to the value of 'Section 1') and the respective div becomes hidden:
<div class="menu-head" ng-hide="c1.section == c1.section">{{c1.section}}</div>
<div class="menu-head" ng-hide="c1.section == 'Section 1'">{{c1.section}}</div>
The c1.section is accessed via
<div ng-repeat="c1 in col1">
from this controller:
function MenuCtrl($scope) {
"use strict";
$scope.col1 = MenuData.col1;
$scope.col2 = MenuData.col2;
$scope.col3 = MenuData.col3;
}
Where the object col1 may, or may not contain the field 'section'. So obviously whenever a field (any field) is missing from the object, I want its div to be missing/not showing in the DOM. Here's the MenuData object:
var MenuData = {
col1: [
{section: 'Section 1'}, // <-here the fields id, name, price and descr are missing so their divs must not show up in the DOM.
{
id: '1',
// section: 'Section 2', <- here the field section is missing (commented-out).
name: 'Position 1',
price: '2.50',
descr: 'some description'
},
{section: 'Section 3'},
{
id: '2',
section: 'Section 4',
name: 'Position 2',
price: '4.75',
descr: ''
}
]
};
How do I make the expression of ng-hide to evaluate to 'true' when there is no content in 'c1.section' data binding?
You should be able to use the following code:
<div ng-hide="!c1.section">
This will hide the div when c1.section equals '' or when the c1 object doesn't have a section property.
I have created a working Plnkr for your convenience at http://plnkr.co/edit/aOe7Vc8lmYf43ODkCymx?p=preview
Hope that helps!

How do I set the value for a select statement?

http://jsfiddle.net/WcJbu/
When I select a person, I want the favoriteThing selector to display their current selection.
<div ng-controller='MyController'>
<select ng-model='data.selectedPerson' ng-options='person.name for person in data.people'></select>
<span ...> likes </span>
<select ... ng-model='data.favoriteThing' ng-options='thing.name for thing in data.things'></select>
</div>
$scope.data.people = [{
name: 'Tom',
id: 1,
favorite_thing_id: 1
}, {
name: 'Jill',
id: 2,
favorite_thing_id: 3
}];
$scope.data.things = [{
name: 'Snails',
id: 1
}, {
name: 'Puppies',
id: 2
}, {
name: 'Flowers',
id: 3
}];
Do I need to set up a service and add watches, or is there a [good] way to use the favorite_thing_id directly in the select?
Change the second select to this:
<select ng-show='data.selectedPerson' ng-model='data.selectedPerson.favorite_thing_id'
ng-options='thing.id as thing.name for thing in data.things'></select>
Adding the thing.id as to the ng-options will allow you to select the data.things entries based on their id's instead of their references. Changing the ng-model to data.selectedPerson.favorite_thing_id will make angular automatically change to the correct option based on selectedPerson.favorite_thing_id.
jsfiddle: http://jsfiddle.net/bmleite/4Qf63/
http://jsfiddle.net/4Qf63/2/ does what I want - but it's pretty unsatisfying.
$scope.$watch(function() {
return $scope.data.selectedPerson;
}, function(newValue) {
if (newValue) {
$scope.data.thing = $filter('filter')($scope.data.things, {id: newValue.favorite_thing_id})[0];
}
})
I'd like to see all of that be possible from within the select statement.
Maybe I'll try to write a directive.
association = {key: matchValue}
So that I can do
<select ... ng-model='data.thing' ng-options='t.name for t in data.things' association='{id: "data.selectedPerson.favorite_thing_id"}'></select>

Categories