I have a ViewModel which accepts JSON to build observableArray() and also have a selected observable for storing the object when editing.
var ViewModel = function (data) {
var self = this;
self.list = ko.observableArray(data);
self.selected = ko.observable();
}
I'm showing the list in a table with edit button. On edit, the selected object goes into selected
self.edit = function (o) {
self.selected = ko.observable(o);
}
Next, I have a form which binds with the selected and displays all the properties.
<form>
<input type="text" data-bind="value: selected().Name">
</form>
The problem is that I want this form to be shown for adding an item and not only when the user clicks edit. But initially, the selected observable is undefined and throws error. Also, I want to push the data in selected to my observableArray when the user clicks on Add button.
What will be the best approach? Where can I put a custom binding so that this scenario works?
Update
My problem is similar this question.
But I can't implement the given solution to an observable
Fiddle implementing a part of problem and a suggested solution
<form data-bind="with: selected">
<input type="text" data-bind="value: Name">
</form>
This will not render the content of the form until a item is selected
To reuse form use a template binding
<form data-bind="template: { if: selected, data: selected, name: 'my-template' }">
</form>
Use a "blank" item in the selected variable to act as your "new item". Then, filling in the form for adding an item will fill in the values in the "blank" item.
Here are some snippets (partially based on your code and on another question)
Form:
<form data-bind="with: selected">
<input type="text" data-bind="value: Name">
</form>
ViewModel
var ViewModel = function (data) {
var self = this;
self.list = ko.observableArray(data);
self.selected = ko.observable(
{ name: "" });
}
Then, the "add" button for putting a new item into the data array can contain this:
// Add this item
self.list.push(self.selected());
// Reset the form to a new blank item
self.selected({ name: "" });
So when the form is loaded, there is an item in the selected variable - a blank item.
To avoid having to create a new blank model with all of the fields, you can use a variation on Ryan Niemeyer's answer here, using a valueWithInit binding for nonexistent fields. See his example jsFiddle.
Then, you can do this:
Form:
<form data-bind="with: selected">
<input type="text" data-bind="valueWithInit: Name">
</form>
ViewModel
var ViewModel = function (data) {
var self = this;
self.list = ko.observableArray(data);
self.selected = ko.observable({});
}
Seems like just simple javascript was the answer to the question.
I added one Cancel button and on click of it:
document.getElementById("form").reset();
This also cleared the selected observable.
Also, for initial form display I used $data with all the properties:
<form data-bind="with: selected" id="form">
<input type="text" data-bind="value: $data.Name">
</form>
No custom binding handlers were required!!
Related
I have an ng-repeat which creates a form with some starting data. Then the user is free to modify said data and the changes should appear in the form. Before that, the user submitted data are sanitized by another function, which is called by an ng-click on a button.
Everything works well under the hood (I checked my $scope.some_array, from which ng-repeat takes the data and the new data is in the right place) but nothing happens on the page.
The element:
<li ng-repeat="field in some_array" id="field-{{$index}}">
<div class="{{field.field_color}}">
<button type="button" ng-click="save_field($index)">done</button>
{{field.nice_name}}
</div>
<div id="field-input-{{$index}}">
<input type="text" id="{{field.tag}}" value="{{field.content}}">
<label for="{{field.tag}}">{{field.nice_name}}</label>
</div>
</li>
save_field code:
$scope.save_field = function (index) {
console.log($scope.some_array[index]["content"])
var value = $("#field-" + index).children("div").children("input").val()
var name = $scope.some_array[index]["name"]
var clean_value = my_clean(value)
if (norm_value === "") {
return
}
$scope.some_array[index]["content"] = clean_value
console.log($scope.some_array[index]["content"])
}
On the console I see:
10.03.16
10/03/16
Which is right, but in the form I only see 10.03.16. I already tried putting $timeout(function(){$scope.$apply()}) as the last line of my function, but the output is still the same.
You shouldn't use input like this if you want to bind a variable to it. Digest loop will refresh the value but it will not be updated visibly because this is not html native behavior.
Use ng-model instead, it will update view value of the input as expected:
<input type="text" id="{{field.tag}}" ng-model="field.content">
Also using ng-model your variable will be updated when user modify the input, so you can retrieve it to do some treatments much more easily in save_field function, without jQuery:
$scope.save_field = function (index) {
if (norm_value === "") {
return;
}
$scope.some_array[index]["content"] = my_clean($scope.some_array[index]["content"]);
};
More infos: https://docs.angularjs.org/api/ng/directive/ngModel
I've got a fairly simple angular controller method :
$scope.addComment = function () {
if ($scope.newComment.message) {
$scope.can_add_comments = false;
new Comments({ comment: $scope.newComment }).$save(function (comment) {
$scope.comments.unshift(comment);
return $scope.can_add_comments = true;
});
return $scope.newComment = {};
}
};
And in my form I have a textarea that holds the value of comment :
<textarea class="required" cols="40" id="new_comment_message" maxlength="2500" ng-disabled="!can_add_comments" ng-model="newComment.message" required="required" rows="20"></textarea>
Works great so far, however I do want to send some data, hidden data with the comment as well. So I added something to hold that value :
<input id="hash_id" name="hash_id" ng-init="__1515604539_122642" ng-model="newComment.hash_id" type="hidden" value="__1515604539_122642">
However when I inspect the $scope.newComment it always comes back as an object with only message as it's property, which is the value from the text area, and I don't get the property on the object hash_id.
When I make this input non hidden and I manually type in the value into the input field and submit a form, I do get a object with hash_id property. What am I doing wrong here, not setting it right?
As far as I know, ng-model doesn't use the value property as a "default" (i.e. it won't copy it back into your model). If you want a default, it should be placed wherever the model is defined:
$scope.newComment = { hash_id: "__1515604539_122642", /* Other params */ };
Alternatively, changing the ng-init to an assignment should work (though I would recommend the above solution instead):
<input id="hash_id" name="hash_id" ng-init="newComment.hash_id = '__1515604539_122642'" ng-model="newComment.hash_id" type="hidden">
I'm still fairly new to angular.js. This seems like it should be very simple, but I'm stumped.
I have an input field:
<input type="text" placeholder="Search" ng-model="search.txt">
And I have a button that calls this function in my controller on ng-click:
$scope.clearSearch = function() {
$scope.search = {txt:"qqqqq"};
}
Clicking the button behaves as expected - the input value on the page becomes "qqqqq". So the data binding seems correct.
However, if I type anything into the field first and then press the button, the input value does not change on the page - the input field keeps the value I typed. Why is that?
What I'm really trying to do is clear the field, I'm just using "qqqqq" for illustration - setting the value to null has the same behavior.
It works:
Script:
angular.module('myapp',[])
.controller('myctrl',function($scope){
$scope.search = {text:'some input'};
$scope.clearSearch = function () {
$scope.search={text:null};
}
});
Markup:
<div ng-app="myapp" ng-controller="myctrl">
<input type="text" ng-model="search.text"/>
<button ng-click="clearSearch()">clear</button>
</div>
In plunker
I'm using knockoutJS to handle an editable list of website name and URL pairs in a table - there's a button underneath to add a new item. My code is based on the lists and collections tutorial.
....
<tbody data-bind="foreach: website">
<tr>
<td><input data-bind="value: name, hasfocus: true" /></td>
<td><input data-bind="value: url" /></td>
<td>Remove</td>
</tr>
</tbody>
</table>
<button data-bind="click: addWebsite" class="btn">Add another</button>
Note I use hasfocus: true so that each time a new blank field is added they don't have to click it with the mouse. You'll also see in the code below (from the viewmodel function) that I'm checking the contents of the last text box in the list before I allow people to add another one.
// Add and remove
self.addWebsite = function() {
var length = self.website().length;
if (self.website()[length - 1].name == '') {
// Last site in list has no name, don't allow them to add another
return false;
}
self.website.push(new dashboardApp.website());
}
Is there a way of changing the binding during runtime - so I can give the last item focus as a prompt to the user that they should be filling it in rather than creating further blank rows, e.g. something like:
self.website()[length - 1].name.hasfocus = true;
You can accomplish this by using an observable to bind against hasfocus rather than just hasfocus: true.
One way that I like to do something like this is to add a focused sub-observable off of the field's main observable. Something like:
var Site = function(name, url) {
this.name = ko.observable(name);
this.name.focused = ko.observable(true);
this.url = ko.observable(name);
};
Then, bind like data-bind="value: name, hasfocus: name.focused" and set the focused observable in your check for the last name being empty like name.focused(true).
Here is a sample: http://jsfiddle.net/rniemeyer/NkYR5/
If your name and url aren't observables, then you could just add another observable specifically for controlling focus.
I have a form:
<form>
<input type="text" name="email" >
<input type="text" name="phone" >
<input type="button" value="ok" />
</form>
When clicking the button, I'd like to copy the form values to a corresponding model.
I've found Backbone.ModelBinder which will automatically copy values to model whenever the values are changed, but that's not what I want, I just want to copy the values when the button is clicked.
write a custom function into the view where the form is located and bind it to the ok click event:
events: {
...
'click input[name="ok"]': 'copyFormToModel'
...
},
...
copyFormToModel: function() {
var email = $('input[name="email"]').val();
var phone = $('input[name="phone"]').val();
// Perform some sort of validation
this.model.email = email;
this.model.phone = phone;
}
This isn't the prettiest answer, but if you have just one small form in your page, then using some library or plugin might be a bit overkill. If you want to use a plugin or library, then for your case I think backbone-forms could do the trick. It features updating the model bound to the form with a method call rather than every time fields are updated.
This code may be you need:
events: {
...
'click input[value="ok"]': 'collectData2Model'
...
},
...
//suppose employee is your model
collectData2Model: function(e) {
var employee = new Employee();
var attr = {};
$('input').each(function(){
var input = $(this);
attr[input.attr('name')] = input.val();
});
employee.bind('error',function(model,error){
alert(error);
});
// set method will automatically call the model's validate method
employee.set(attr);
}