Read content from DOM element with KnockoutJS - javascript

The goal
Read content from DOM element with KnockoutJS.
The problem
I have a list of products in my HTML. The code is something like this:
<li>
<div class="introduction">
<h3>#Model["ProductName"]</h3>
</div>
<form data-bind="submit: addToSummary">
<input type="number"
placeholder="How much #Model["ProductName"] do you want?" />
<button>Add product</button>
</form>
</li>
When I click on <button>Add Product</button>, I want to send to KnockoutJS the text inside <h3></h3> of the element that was submitted.
The file to work with KnockoutJS is external and independent of HTML. It name is summary.js and the code is below:
function ProductReservation(id, name, unity, quantity) {
var self = this;
self.id = id;
self.name = name;
self.unity = unity;
self.quantity = quantity;
}
function SummaryViewModel() {
var self = this;
self.products = ko.observableArray([
new ProductReservation(1, "Testing", "kgs", 1)
]);
self.addToSummary = function () {
// Do Something
}
}
What I'm thinking about
HTML:
<li>
<div class="introduction">
<h3 data-bind="text: productName">#Model["ProductName"]</h3>
</div>
[...]
</li>
JS:
productName = ko.observable("text: productName");
But, of course, no success — this is not the correct context or syntax, was just to illustrate.
So I ask: what I need to do?

You're binding addToSummary via the submit binding. By default KO sends the form element to submit-bound functions.
So addToSummary should have a parameter -
self.addToSummary = function (formElement) {
// Do Something
}
You can pass additional parameters to this function (as described in KO's click binding documentation under 'Note 2'), or you could just add a hidden field to your form element and pull it from there.
<li>
<div class="introduction">
<h3>#Model["ProductName"]</h3>
</div>
<form data-bind="submit: addToSummary">
<input type="number" name="quantity"
placeholder="How much #Model["ProductName"] do you want?" />
<input type="hidden" name="productName" value="#Model["ProductName"]" />
<button>Add product</button>
</form>
</li>
Then in your knockout code you could use jQuery to process the form element to pull out the data -
self.addToSummary = function (formElement) {
var productName = $(formElement).children('[name="productName"]')[0].val();
var quantity= $(formElement).children('[name="quantity"]')[0].val();
// ...
self.products.push(new ProductReservation(?, productName, ?, quantity));
}

One strategy that has worked well in my experience is to implement a toJSON extension method that serializes your model (if you use a library like JSON.NET you can have a lot of control over what gets serialized and what does not).
Inside of the view where you initialize KnockoutJS, you could serialize your model and pass it into the KnockoutJS ViewModel you're creating (assuming :
Main view:
<script type="text/javascript">
var viewModel = new MyViewModel(#Model.ToJSON());
</script>
ViewModel:
function MyViewModel(options) {
this.productName = ko.observable(options.ProductName);
}

Related

cant make an object I added to a knockout model observable

Here is the fiddle demonstrating the problem http://jsfiddle.net/LkqTU/31955/
I made a representation of my actual problem in the fiddle. I am loading an object via web api 2 and ajax and inserting it into my knockout model. however when I do this it appears the attributes are no longer observable. I'm not sure how to make them observable. in the example you will see that the text box and span load with the original value however updating the textbox does not update the value.
here is the javascript.
function model() {
var self = this;
this.emp = ko.observable('');
this.loademp = function() {
self.emp({
name: 'Bryan'
});
}
}
var mymodel = new model();
$(document).ready(function() {
ko.applyBindings(mymodel);
});
here is the html
<button data-bind="click: loademp">
load emp
</button>
<div data-bind="with: emp">
<input data-bind="value: name" />
<span data-bind="text: name"></span>
</div>
You need to make name property observable:
this.loademp = function(){
self.emp({name: ko.observable('Bryan')});
}

AngularJS creating json from ng-repeat

Inspired from this codepen I used a JSON file to load some basic input fields and toggles. When the user change something and press save, I would like to save these new property values in a new JSON object with same property names.
My code looks the this
JS
.controller('page', function($scope, templateSettingsFactory, savedSettingsFactory) {
$scope.template = templateSettingsFactory;
$scope.saved = savedSettingsFactory;
$scope.saveSettings = function(){
var temp = $scope.template;
var jsonObj = {};
for(var key in temp){
jsonObj[key]=temp[key];
}
$scope.saved.$add(jsonObj);
};
});
HTML
<label ng-repeat="item in template">
<input type="text" placeholder="{{item}}">
</label>
<button class="button" ng-click="saveSettings()">Save</button>
The problem is that calling the saveSettings() don't get the updated property values of $scope.template - perhaps it's not doing two-way binding?
You need to use ng-model on your form elements to bind their input to the scope.
<input type="text" ng-model="item.property">
Here is an example of binding to a single object with arbitrary keys:
<div ng-repeat="(key,value) in template">
<div>{{key}}</div>
<input type="text" ng-model="template[key]"/>
</div>
https://docs.angularjs.org/api/ng/directive/ngModel

Handlebar.js template with dynamic form inputs using Underscore.js - Zendesk App

I am trying to figure out how to submit and capture all form inputs in a dynamically created form on a Handlebar.js template using Underscore.js. This is occurring in a Zendesk custom app.
There is a .hdbs template and app.js file in the scenario. The form loads and shows correctly. The underscore.js function I am using does see the form input but only returns the very first input value/key. It does not return the dynamically generated list of checkboxes.
.hdbs:
<div class="row-fluid">
<p>
</p>
<form id="updateChecklist">
{{listname}}: {{complete}} / {{total}}
<input type="hidden" name="listid" value="{{listid}}">
<br/>
<ul>
{{#each tasks}}​
<li class={{fontcolor}}>
<input type="checkbox" name="{{this.id}}" id="{{this.id}}" {{ this.state }}> {{this.name}}
</li>​
{{/each}}
</ul>
<button class="btn btn-inverse btn-large" id="update" type="button">Update Tasks</button>
</form>
</div>
app.js:
events: {
'click #update' : 'updateSingleChecklist'
},
...
updateSingleChecklist: function(event) {
var serializedArray = this.$("#updateChecklist").serializeArray();
var itemIdsArray = _.map(serializedArray, function(item, key){
return {item, key};
});
console.log(JSON.stringify(itemIdsArray));
Current Incorrect Result:
[{"item":{"name":"listid","value":"56eafe771e9824def46a7d7d"},"key":0}]
What I am looking for is an array of the form inputs, including unchecked checkboxes.
I need:
[{"item":{"name":"listid","value":"56eafe771e9824def46a7d7d"},
{"name":"checkbox1","value":"56eafe771e9824def46a7d8c", "checked":"0"},
{"name":"checkbox2","value":"24eafe771e9824def46a7c6d", "checked":"1"},
{"name":"checkbox3","value":"71eafe771e9824def46a3r5g", "checked":"0"},
...
"key":0}]
It turned out to be lot simpler than I initially realized. The solution to this issue for now is updated at https://jsfiddle.net/sde13q3m/3/ .
Essentially, the map array function with a simple evaluation in the return statement allowed me to collect all form fields and checkboxes for the controller.
var serialized = this.$('#myform input').map(function() {
return { name: this.name, id: this.id, value: this.checked ? "checked" : "false" };
});

KnockoutJS Validation with dynamic observables

I am using this plugin https://github.com/ericmbarnard/Knockout-Validation and i am trying to validate an object that is loaded dynamically.
Javascript:
function VM() {
var self = this;
// This is a static observable, just to ensure that basic validation works fine.
self.static = ko.observable();
self.static.extend({required: true});
// This is the observable that will be updated to my model instance.
self.person = ko.observable({});
// This is an handler for manual trigger.
// I'm not even sure this is needed.
self.a = function(){
self.errors.showAllMessages();
self.staticErrors.showAllMessages();
}
// Here i'm loading current person from somewhere, i.e. a rest service.
self.load = function() {
// Update observable
self.person(new Model());
// Define validation rules
self.person().name.extend({required: true});
self.person().email.extend({required: true});
// Set person data
self.person().name('Long');
self.person().email('John');
// Set validators
self.errors = ko.validation.group(self.person);
self.staticErrors = ko.validation.group(self.static);
}
}
// Just a test model.
function Model() {
this.name = ko.observable();
this.email = ko.observable();
}
ko.validation.init();
var vm = new VM();
ko.applyBindings(vm);
Markup
<ul>
<li>1. Hit "Load"</li>
<li>2. Hit "Show errors", or maunally change input data.</li>
</ul>
<button data-bind='click: load'>Load</button>
<br/>
<h1>This is working properly.</h1>
<input type='text' data-bind='value: static' />
<br/>
<h1>This is not working.</h1>
<input type='text' data-bind='value: person().name' />
<input type='text' data-bind='value: person().email' />
<br/>
<button data-bind='click: a'>Show errors</button>
Fiddle
http://jsfiddle.net/qGzfr/
How do I make this work?
The validation plugin only gets applied in your bindings only if by the time when the binding is parsed by Knockout your properties are validate.
In different words: you cannot add validation to a property after the property was bound on the UI.
In your example you are using an empty object in self.person = ko.observable({}); as a default value, so when Knockout executes the data-bind='value: person().name' expression you don't have a name property so the validation won't work even if you later add the name property to your object.
In your example you can solve this with changing your Model constructor to include the validation rules:
function Model() {
this.name = ko.observable().extend({required: true});
this.email = ko.observable().extend({required: true});
}
And use an empty Model object as the default person:
self.person = ko.observable(new Model());
And when calling Load don't replace the person object but update its properties:
self.load = function() {
// Set person data
self.person().name('Long');
self.person().email('John');
}
Demo JSFiddle.
Note: Knockout does not always handles well if you replace whole object like self.person(new Model()); so it is anyway a better practice to only update the properties and not throw away the whole object.
A different solution would be to use the with binding because inside the with binding KO will reevaluate the bindings if the bound property changes.
So change your view:
<!-- ko with: person -->
<input type='text' data-bind='value: name' />
<input type='text' data-bind='value: email' />
<!-- /ko -->
In this case you need to use null as the default person:
self.person = ko.observable();
And in your Load you need to add the validation before assigning your person property so by the time KO applies the bindings your properties have the validation:
self.load = function() {
var model = new Model()
model.name.extend({required: true});
model.email.extend({required: true});
self.person(model);
// Set person data
self.person().name('Long');
self.person().email('John');
}
Demo JSFiddle.
I was able to make it work, this are the changes required:
<head>
<script type="text/javascript" src ="knockout-2.3.0.js"></script>
<script type="text/javascript" src ="knockout.validation.min.js"></script>
</head>
<body>
<!-- no changes -->
<script>
function VM() { ... }
function Model() { ... }
// ko.validation.init();
var vm = new VM();
ko.applyBindings(vm);
</script>
</body>
What was done?
Include KnockoutJS and the validation plugin.
Bind after the elements have been added. Remeber that HTML pages are parsed from top to bottom.
How could you tell? In the console this errors appeared:
Cannot read property 'nodetype' of null
and
Cannot call method 'group' of undefined

Handling observables in an array

Note: This is not a question about ObservableArrays.
Let's say I have the following viewmodel:
var viewmodel = {
arrayOfBooleans: [
ko.observable(false),
ko.observable(false),
ko.observable(false)
]
}
And a view like so:
<div data-bind="foreach: arrayOfBooleans">
<button data-bind="click: ????">Set to true</button>
</div>
What can I do inside the foreach to get the <button> to set the observable to true when clicked? Using data-bind="click: someFunction", the first argument someFunction gets is the unwrapped values of observables in the array (not the observables themselves), and seemingly no way to get back at the observables or to pass custom arguments.
Hope it will give some idea .
var viewmodel = {
var self = this;
self.arrayOfBooleans = ko.observableArray([]);
self.arrayOfBooleans.push(new _newBoolean());
self.arrayOfBooleans.push(new _newBoolean());
self.arrayOfBooleans.push(new _newBoolean());
function _newBoolean() {
self.one = ko.observable(false);
}
self.setToTrue = function(index){
self.arrayOfBooleans()[index].one(true);
};
}
If you want it as button
<div data-bind="foreach: arrayOfBooleans">
<button data-bind="click: $root.setToTrue($parent.arrayOfBooleans.indexOf($data))">
Set to true
</button>
<input type=hidden data-bind="value:one" />
</div>
If you want it as radio button than it is much more simple
<div data-bind="foreach: arrayOfBooleans">
<span>Set To True</span><input type=radio data-bind="value:one" />
</div>
If you like this..Please Click uplink
*or*
If it solves your problem .. Mark this as answer

Categories

Resources