i have created editable Grid in angular js.
issue that i m facing is if user click edit and change values and then cancel edit . it updates the scope that is default behavior as m using ng-model
but i wanted to preserver old state so in case if user tries to cancel i can set old values in Grid
View
<tr ng-repeat="course in courses">
<td>
<span>{{course.id}}</span>
</td>
<td>
<span ng-show="course.editMode == null || course.editMode == false">{{course.number}}</span>
<input class ="form-control" ng-model="course.number" ng-show="course.editMode == true" />
</td>
<td>
<span ng-show="course.editMode == null || course.editMode == false">{{course.name}}</span>
<input class="form-control" ng-model="course.name" ng-show="course.editMode == true" />
</td>
<div class="btn-group">
<i ng-click="updateCourse(course)"></i>
<i ng-click="cancelEditMode(course)"></i>
</div>
Controller
$scope.courses = [];
var oldvalue = '';
$scope.toggleEditMode = function (course) {
course.editMode = true;
oldvalue = course;
};
$scope.cancelEditMode = function (course) {
course.editMode = false;
var index= $scope.courses.indexOf(course);
$scope.courses[index] = oldvalue;
};
i have tried to have a variable oldvalue and when user clicks edit i save existing value in oldvalue and then on canceledit i tries to set oldvalue.
but it didnt work, i always get new value in oldvalue.
can some one help me out wat i m doing wrong ?
You should create a copy of the object as the old value. You can use angular.copy method for that. Otherwise you are operating on the same object and any updates to this object will change the old value too. Here's a working example in plunker.
You have a second error in cancelEditMode method because you are setting editMode property to false in the object you will replace. That's also fixed in the example.
That's because course is an object, and when you assign an object to some variable, that variable will hold only a reference to that object. So, when you change the original object (the course), the oldvalue reflects the change.
You need to make a copy of the original course object:
...
oldValue = angular.copy(course);
...
Related
First of all, I don't see how the modal could have anything to do with this issue since its actually in this component's code, not a child. Still, this is in a modal, just in case.
I'm opting to not use FormArray since I need to keep track of my selections that may be added in a separate object, so I'm just creating unique IDs for the controls and adding them to the FormGroup. I can access the controls in the ts code, set values, get values, etc, but the form isn't binding and I can't figure out why not.
I can have an unknown number of items in this modal form, which will each have a selector (dropdown to select a property) and then the input to be able to modify some data. The input could be of different types, so it needs to be added and binded upon the choice from the selector.
<form [formGroup]="multiEditFormGroup" novalidate>
<div *ngFor="let item of multiEditSelections; let i = index">
<div>
<mdb-select [options]="availablePropsSelect"
placeholder="Choose property to edit"
class="colorful-select dropdown-main"
(selected)="multiEditSelectProp(item.selectorId, $event)"></mdb-select>
<label>Property to edit</label>
</div>
<div>
<div>
<input mdbActive
type="text"
class="form-control"
[formControlName]="item.controlId" />
</div>
</div>
</div>
</form>
Excepts of ts code:
public multiEditFormGroup = new FormGroup({});
onModalOpen():void {
const selectorId = this.utils.uuid();
this.multiEditFormGroup.addControl(selectorId, this.propSelector);
this.multiEditSelections.push({
selectorId: selectorId,
prop: '',
label: '',
type: '',
controlId: '' // not yet known since type hasn't been chosen
});
}
onSelect(selectorId: string, selectEvent: any): void {
let selection = this.multiEditSelections.find(selection => {
return selection.selectorId === selectorId;
});
const controlId = this.utils.uuid();
const prop = selectEvent.value;
this.multiEditFormGroup.get(selection.selectorId).setValue(prop);
this.multiEditFormGroup.markAsDirty();
this.multiEditFormGroup.markAsTouched();
const model = this.multiEditModel.find(model => {
return model.prop === prop;
});
this.multiEditFormGroup.addControl(controlId, this.testCtrl);
selection.controlId = controlId;
selection.prop = prop;
selection.label = model.label;
selection.type = model.type;
}
Logging to console shows that items are being added to the FormGroup, but the binding isn't happening to the DOM. For example, I can add a (keyup) event handler to my input and set the value in the form control which has already been created, and the FormGroup is updated. However, any input added in the front-end doesn't update the FG since it obviously isn't binding. Is this a timing issue or because the controlId is being updated later? I'm creating the FormControl before updating my array that is being iterated.
Oh and I get no errors in console on this.
I think you need to make this change:
[formControlName]="item.controlId"
needs to be:
formControlName="{{item.controlId}}"
This question already has an answer here:
Knockout.js bound input value not updated when I use jquery .val('xyz')
(1 answer)
Closed 6 years ago.
self.totalHours = ko.pureComputed(function() {
var start=self.num1;
var end=self.num2;
return start+end;
});
<input type="text" data-bind="textInput: start">
<input type="text" data-bind="textInput: end">
<input type="text" data-bind='text: totalHours()'>
The above first is part of my viewmodel and the second is part of my model. num1,num2 are observables. Every time I change manually the value inside the above first two inputs the third input is updated immediately; however, when the values change by code, knockout does not listen to the changes and total is not updated. How may I oblige knockout to listen to the changes provoked by code?
Quite some stuff you can fix and improve here:
A computed value will re-evaluate when an observable it uses in its method changes: self.num1 and/or self.num2 need to be observable and evaluated using ()
If you want to bind an <input>'s value, you have to use either the value or textInput data-bind; the text bind will not work.
If you want to write to a computed, you'll have to specify a write method. You'll have to tell knockout how to update the computed's dependencies to make sure all values add up. (e.g.: setting totalHours could set num1 to totalHours and num2 to 0)
You've bound to start and end, while your viewmodel properties are named num1 and num2.
When using value or textInput, user input will be returned as a string. You'll need to parse the strings to numbers if you want to use them in any math.
Now that all code should be working correctly, you can update your viewmodel's values via the inputs, or via code:
var ViewModel = function() {
var self = this;
self.num1 = ko.observable(0);
self.num2 = ko.observable(0);
self.totalHours = ko.pureComputed(function() {
var start = parseFloat(self.num1());
var end = parseFloat(self.num2());
return start + end;
});
};
var vm = new ViewModel();
ko.applyBindings(vm);
// Updating your values from code:
vm.num1(1);
vm.num2(2);
// Whenever the values need to be updated via js,
// you should change the source values, _not_ the
// <input>'s values. Worst case scenario, you'll
// have to do something like this:
var updateNum1ViaDOM = function() {
ko.dataFor(document.querySelector("input")).num1(5);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input type="text" data-bind="textInput: num1">
<input type="text" data-bind="textInput: num2">
<span data-bind='text: totalHours'></span>
Note: it's probably better to use an extender to force num1 and num2 to be numeric: Live Example 1: Forcing input to be numeric
Not sure if it is a copy paste problem but the the code you posted will not work as intended. I've updated the example, when changing an observable value it must be passed as parameter so as not to overwrite the knockout observable
self.start = ko.observable();
self.end = ko.observable();
self.totalHours = ko.computed(function() {
return self.start() + self.end();
});
<input type="text" data-bind="textInput: start">
<input type="text" data-bind="textInput: end">
<input type="text" data-bind='text: totalHours()'>
//Then when changing the value by code
var newValue = 42;
model.start(newValue); //assuming you are making the change outside your viewmodel
*Just noticed this code will throw an exception when you edit the input bound to totalHours as it does not have a write handler defined. This is a separate issue though.
I have some problems with knockoutjs writables computes observables I think.
I created a fiddle.
What I need is actually not so hard:
I have the nullable WeightInGramms and VolumeInMilliliters values.
These values should be bound to two input fields (only one of them should be visible).
At the top, the user can choose which of these values he want to use with the radio buttons.
At initialisation, when both of them are null, the "g" radio button should be checked, also when WeightInGramms is not null. When VolumeInMilliliters have some value, the "ml" radio button should be checked.
I used a knockoutjs writable computes observable for this, please correct me if there is a better way to do this!
So, the read function seems to work, when I change the value in the input which are bind to WeightInGramms or VolumeInMilliliters. But when I change the radio buttons nothing happens...
var ViewModel = function (data) {
var self = this;
this.VolumeInMilliliters = ko.observable(data.VolumeInMilliliters);
this.WeightInGramms = ko.observable(data.WeightInGramms);
this.GrammIsSelected = ko.computed({
read: function() {
return (!self.WeightInGramms() && !self.VolumeInMilliliters()) || !self.VolumeInMilliliters();
},
write: function (newValue) {
console.log(newValue);
return newValue;
},
owner: this
});
};
When I change the radio buttons, the corresponding input field should be visible:
<div data-bind="visible: GrammIsSelected">g is active</div>
<div data-bind="visible: !GrammIsSelected()">ml is active</div>
Edit:
When the Form is loaded for the first Time both values will be null -> the "g" button should be checked.
The observables can be initialized with:
null, null
33, null
null, 33
Both can be null, but only one of them can have a value.
If the user types in a value, and then clicks the other radio the value can be applied to the other value.
I hop it is a bit clearer
Some tips:
Make your viewModel (JS) resemble the view (HTML) as much as possible. Additionally, this avoids having to repeat too much markup. In this case, radio buttons are always lists, and so it is most convenient to store the options in an array.
Instead of testing whether GrammIsselected, you should define a selected observable that holds the selected metric. This way if you ever add more options, the code will still work without refactoring.
When to use a computed property? A computed property adds readonly value by calculating a result based on multiple observables/ variables. A writeable computed property does the same, except you can write back changes. This makes it especially useful for 'Select all' style checkboxes (see example 2 in the docs), data validation & transformations.
The absolutely clearest setup for what you want to achieve would be the following:
var ViewModel = function (data) {
this.metrics = [
{ name: 'g', value: ko.observable(data.WeightInGramms) },
{ name: 'ml', value: ko.observable(data.VolumeInMilliliters) }
];
this.selectedMetric = ko.observable(this.metrics[0]);
};
By setting an object as observable (selectedMetric), you can furthermore simplify the markup for the volume/weight input:
<div class="control-group">
<label class="control-label">choose</label>
<div class="controls" data-bind="with: selectedMetric">
<input type="text" data-bind="value: value">
<span class="help-inline" data-bind="text: '(' + name + ')'"></span>
</div>
</div>
Getting the 'final value' of your app would be as easy as retrieving selectedMetric().value().
A computed property isn't super useful here, but for example, if you wanted to provide a way for the user to both set the g/ml with radio buttons and text, you could add the following method to your viewModel:
this.selectedMetricByText = ko.computed({
read: function() {
return this.selectedMetric().name;
},
write: function(value) {
var newMetric = ko.utils.arrayFirst(this.metrics, function(metric) {
return metric.name === value;
}) || false;
this.selectedMetric(newMetric || this.metrics[0]);
}
}, this);
Fiddle
Your write function doesn't write anything, it seems?
Contrary to this other answer, based on my experience I'll give you the advice not to avoid writeable computeds: used wisely they can be very effective!
Note: in my answer I try to remain close to the original design from the question, but if you're able (have resources available) I recommend redesigning things even more based on the answer by #Tyblitz.
Here's the way you could approach this utilizing a computed:
var ViewModel = function (data) {
var self = this;
self.VolumeInMilliliters = ko.observable(data.VolumeInMilliliters);
self.WeightInGramms = ko.observable(data.WeightInGramms);
var _measurementType = ko.observable("volume");
self.MeasurementType = ko.computed({
read: function() {
return _measurementType();
},
write: function (newValue) {
_measurementType(newValue);
self.VolumeInMilliliters(newValue === "volume" ? 0 : null);
self.WeightInGramms(newValue === "mass" ? 0 : null);
}
});
};
ko.applyBindings(new ViewModel({ VolumeInMilliliters: 12 }));
label { cursor: pointer; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<label>
<input type="radio" name="measurementType" value="volume" data-bind="checked: MeasurementType">
Volume
</label>
<input data-bind="value: VolumeInMilliliters, enable: MeasurementType() === 'volume'">
<label>
<input type="radio" name="measurementType" value="mass" data-bind="checked: MeasurementType">
Weight in gramms
</label>
<input data-bind="value: WeightInGramms, enable: MeasurementType() === 'mass'">
For radio buttons, you need to use the "checked" binding.
http://knockoutjs.com/documentation/checked-binding.html
And for my personal experience (as a KO nija) I have to give you the advice: avoid writeable ko computed.
<input type="radio" name="unitSelector" value="g" data-bind="checked: unit" /> Grams</br>
<input type="radio" name="unitSelector" value="ml" data-bind="checked: unit" /> Millis</br>
Now the view model
var ViewModel = function (data) {
var self = this;
self.unit = ko.observable('g');
self.userValue = ko.observable(data.WeightInGramms);
};
Now the binding should only care about the value entered by the user, you don't need computed here and you don't need two fields...
<input type="text" data-bind="textInput: userValue ">
<span data-bind="text: unit"> </span>
It looks really too simple but that's what you need, as #Jotabe mentioned, you should take measurement and the unit as two separate things... what you do with this thing later, could be done with computed observables.
If this thing doesn't solve your problem then you should tell what you really want...
index.html
<div class="modal-header" >
<button type="button" class="close" ng-click = "submit(information.add, information.subject, information.emailContent); close()">×</button>
<h3>Compose Email</h3>
</div>
<div class="modal-body">
<form name = "form.email">
<tags options="{addable: true}" typeahead-options="typeaheadOpts" data-model="information.add" data-src="toPerson as toPerson for toPerson in to"></tags>
<input type="text" placeholder="Subject" style="width:95%;" data-ng-model = "information.subject"><br />
<textarea style="width:95%;" rows="10" data-ng-model = "information.emailContent"></textarea>
</form>
</div>
emailViewController.js
$scope.information = {
add: [],
subject: [],
emailContent: []
};
$scope.clear = function() {
if ($scope.information.add !== "") {
$scope.information = null;
}
};
I am setting the value of $scope.information to null. After doing this, the input box value bound to information.subject and the textarea value bound to information.emailContent are reset. However, the tags input value bound to information.add does not reset. Does anyone know why this is being caused.
I think $scope.remove() in the angular-tags widget should be used to remove the tag. I am not sure how to implement it though. Angular-tags source code can be found here - https://github.com/boneskull/angular-tags/blob/master/src/tags.js
Here is a plunker - http://plnkr.co/edit/PaG1k5N37BTOflObnN7K?p=preview
Attempt 1
This is a plunker of what I have tried so far - http://plnkr.co/edit/jjE2bU8zkkyw36rtAymL?p=preview . I am redefining the value of $scope.info to null in a function wrapped inside $timeout. I thought maybe the changes I made to the $scope are not being applied to the view, so I tried to wrap it in a $timeout. Doing so did not fix the problem though.
This code $scope.information = null; is supposed to nuke all your information and clear the entire thing? This only sets the reference to the object containing the arrays to null. The arrays are still there and still referenced by your widget I expect - (I'm not sure how the library you are using for your tags is implemented)
The below code actually empties the arrays:
$scope.information.add.length = 0;
$scope.information.subject.length = 0;
$scope.information.emailContent.length = 0;
This is how I populate the Table and attach checkbox to controller
<tr ng-repeat="key in queryResults.userPropNames">
<td><input type="checkbox"
data-ng-checked="selectedKeys.indexOf(key) != -1"
data-ng-click="toggleSelect(key)">
</td>
<td>{{key}}</td>
<td ng-repeat="user in queryResults.users">
{{user.properties[key]}}
</td>
</tr>
This is how my HTML for button looks
<div>
<span ng-if="!validKeys" class="button-terminal primary save-user-keys"
data-disabled="false">Save Keys</span>
<span ng-if="validKeys" class="button-terminal primary save-user-keys"
data-ng-click="saveUserKeys()">Save Keys</span>
</div>
and my Controller looks like
$scope.toggleSelect = function (attribute) {
if ($scope.selectedKeys.indexOf(attribute) === -1) {
$scope.selectedKeys.push(attribute);
} else {
$scope.selectedKeys.splice($scope.selectedKeys.indexOf(attribute), 1);
}
};
$scope.saveUserKeys = function() {
$scope.customAttributes.mappingForUser = $scope.selectedKeys;
$scope.saveMappings();
};
$scope.validKeys = !!$scope.selectedKeys;
But my button is always enabled, even if I de-select all the checkboxes
What is wrong with this code?
Thank you
$scope.selectedKeys is an Array, even when no keys are selected. However empty Arrays are truthy (!![] // => true).
One fix would be to check the length of selectedKeys instead:
$scope.validKeys = $scope.selectedKeys && $scope.selectedKeys.length;
Alternatively, if assigning validKeys was just an attempt to get the view to render correctly, on the view you could just update the ngIf to ng-if="selectedKeys.lengh"
If you print validKeys (i.e. {{validKeys}}, do you see it changing between true/false? Also, if I understand this correctly, you should be testing for the length of validKeys - if higher than 0, enable the button, otherwise disable it.