I want to build a HTML page that allows the user to construct an object that can then be posted as JSON to an internally hosted service (similar to the Chrome Advanced Rest Client). The user must be able to add and remove properties.
My model is not correct because each property is treated like an object with the properties 'name' and 'value'. I end up with an array of objects instead of an object with properties.
Here is a snippet of the HTML:
<table>
<thead>
<tr>
<th>Property Name</th>
<th>Property Value</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: myFieldList">
<tr>
<td><input data-bind="value: name" /></td>
<td><input data-bind="value: value" /></td>
<td><span class="removeVar" data-bind="click: removeProperty">Remove property</span></td>
</tr>
</tbody>
</table>
<p>
<span id="addVar" data-bind="click: addProperty">Add Property</span>
</p>
<textarea name="tasks" data-bind="value: ko.toJSON(myFieldList)"></textarea>
Here is the JS:
<script type="text/javascript">
function dynamicProperty(name, value) {
var self = this;
this.name = name;
this.value = value;
}
function fieldModel() {
var self = this;
//start with 2 empty properties
self.myFieldList = ko.observableArray([
new dynamicProperty("", ""),
new dynamicProperty("","")
]);
var noTracker = self.myFieldList.length;
self.removeProperty = function (dynamicProperty) {
self.myFieldList.remove(dynamicProperty);
}
self.addProperty = function () {
noTracker++;
self.myFieldList.push(new dynamicProperty(this.name,this.value));
}
}
ko.applyBindings(fieldModel);
</script>
What I get in the textarea is output like this:
[{"name":"test name 1","value":"test value 1"},{"name":"test name 2","value":"test value 2"}]
What I want is output like this:
{"test name 1":"test value 1","test name 2":"test value 2"}
I fear this is rather trivial but in my defense I am very new to JS and Knockout, so any help you can offer would be hugely appreciated. Thanks.
You may want to do something like this to get it done .
Example : https://jsfiddle.net/9aLvd3uw/79/
HTML
<table>
<thead>
<tr>
<th>Property Name</th>
<th>Property Value</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: myFieldList">
<tr>
<td><input data-bind="textInput: name" /></td>
<td><input data-bind="textInput: value" /></td>
<td><span class="removeVar" data-bind="click: removeProperty">Remove property</span></td>
</tr>
</tbody>
</table>
<p>
<span id="addVar" data-bind="click: addProperty">Add Property</span>
</p>
<textarea name="tasks" data-bind="value: myFieldList2"></textarea>
JS
function dynamicProperty(name, value) {
var self = this;
self.name = ko.observable(name || '');
self.value = ko.observable(value || '');
}
function fieldModel() {
var self = this;
self.name = ko.observable();
self.value = ko.observable();
self.myFieldList = ko.observableArray([
new dynamicProperty("test_name_1", "test value 1"),
new dynamicProperty("test_name_2","test value 2")
]);
var noTracker = self.myFieldList.length;
self.myFieldList2 = ko.computed(function () {
var string = '{';
ko.utils.arrayForEach(self.myFieldList(), function (item) {
string += item.name() + ': ' + item.value() + ',';
});
string = string.replace(/,\s*$/, "");
string+='}';
return string;
});
self.removeProperty = function (dynamicProperty) {
self.myFieldList.remove(dynamicProperty);
}
self.addProperty = function () {
noTracker++;
self.myFieldList.push(new dynamicProperty('',''));
}
}
ko.applyBindings(fieldModel);
What you need is a "reducer".
A simple (naive too) implementation would be this:
function reduce(input, step, init) {
for(var i = 0; i < input.length; i++) {
init = step(init, input[i]);
}
return init;
}
Then you call it like this:
var in = [{"name":"test name 1","value":"test value 1"},{"name":"test name 2","value":"test value 2"}];
var out = reduce(in, function(result, item) {
result[item.name] = item.value;
return result;
}, {});
console.log(out);
What it does is that it iterates through your array and "accumulates" the result of each step in a single item. Could be the sum of numbers in an array where the "accumulator" would be a number instead of an object.
I advise you don't write your own but instead use lodash, it comes with a _.reduce function that's optimized.
Related
On input change I create an array of objects. When any value enter within the input field, it pushes objects into array but the problem is when a text field is updated, it does again push items into array. I need to update the array instead of pushing more items.
var tableData = [];
$('.aantalNumber').change(function(){
var aantalNumberVal = $(this).val()
var Productnummer = $(this).closest('tr').find('.product_number').text();
var Productnaam = $(this).closest('tr').find('.product_name').text();
var verpakking =$(this).closest('tr').find('.verpakking').text();
tableData.push({aantalNumber:aantalNumberVal,Productnummer:Productnummer,Productnaam:Productnaam,verpakking:verpakking });
console.log(tableData);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<tr>
<td><input type="number" class="aantalNumber" name="Aantal1"></td>
<td class="product_number">01454</td>
<td class="product_name">Vendor Handdoeken ZZ vouw</td>
<td class="verpakking">5000 velper verpakking</td>
</tr>
<tr>
<td><input type="number" class="aantalNumber" name="Aantal2"></td>
<td class="product_number">218031</td>
<td class="product_name">Vendor Handdoeken ZZ vouw</td>
<td class="verpakking">5000 velper verpakking</td>
</tr>
<!-- Repeated tr and so on -->
First check if value exist, if available then update else push into tableData
var tableData = [];
$('.aantalNumber').change(function() {
var aantalNumberVal = $(this).val()
var Productnummer = $(this).closest('tr').find('.product_number').text();
var Productnaam = $(this).closest('tr').find('.product_name').text();
var verpakking = $(this).closest('tr').find('.verpakking').text();
if (tableData.some(tableData => tableData.Productnummer === Productnummer)) {
updateTableData(Productnummer, aantalNumberVal);
} else {
tableData.push({
aantalNumber: aantalNumberVal,
Productnummer: Productnummer,
Productnaam: Productnaam,
verpakking: verpakking
});
}
console.log(tableData);
});
function updateTableData(value, aantalNumber) {
for (var i in tableData) {
if (tableData[i].Productnummer == value) {
tableData[i].aantalNumber = aantalNumber;
break; //Stop this loop, we found it!
}
}
}
Working Demo
I'm trying to use the select 2 Multi-select boxes with Knockout JS.
The box displays a list of countries and the user can select multiple countries.
The box is displaying multiple countries correctly as expected, however the observable array is only showing the first entry.
My intention is to get all the selected countries and not the first one.
At first i thought i cant use a select 2 multi select with knockout, however if if i add two for example (MT,NL) the observable shows MT, however if i remove MT it updates to NL (so i dont think thats the issue)
Logic below:
// Class to represent a row in the dpos grid
function DpoItem(address, preferredLanguage, countries)
{
var self = this;
self.address = address;
self.preferredLanguage = preferredLanguage;
self.countries = ko.observableArray(countries);
}
// Class to represent a language
function LanguageItem(code, name)
{
var self = this;
self.code = code;
self.name = name;
}
// Class to represent a country
function CountryItem(code, name)
{
var self = this;
self.code = code;
self.name = name;
}
// Overall viewmodel for this screen, along with initial state
function DposViewModel()
{
var self = this;
// Populate countries
var countriesObject = JSON.parse(countriesJSON);
var countries = [];
for (var cKey in countriesObject)
{
countries.push(new CountryItem(cKey, countriesObject[cKey]));
}
self.countriesList = ko.observableArray(countries);
// Populate languages
var languagesObject = JSON.parse(languagesJSON);
var languages = [];
for (var lKey in languagesObject)
{
languages.push(new LanguageItem(lKey, languagesObject[lKey]));
}
self.languagesList = ko.observableArray(languages);
// parse JSON DTOs and put them in the viewmodel
var dposObject = JSON.parse('[{"countries":[],"type":"dpo","address":"dpo #avis.com","preferredLanguage":"en - GB"},{"countries":["GB", "MT"],"type":"dpo","address":"dpo #avis.co.uk","preferredLanguage":"en - GB"},{"countries":["MT"],"type":"dpo","address":"dpo #avis.com.mt","preferredLanguage":"mt - MT"}]');
var dpos = [];
dposObject.forEach(dpo =>
{
dpos.push(new DpoItem(dpo.address, dpo.preferredLanguage, dpo.countries));
});
self.dpos = ko.observableArray(dpos);
self.addDpo = function ()
{
self.dpos.push(new DpoItem("", "", ""));
};
self.removeDpo = function ()
{
self.dpos.remove(this);
};
self.checkDpos = function ()
{
for (i = 0; i < self.dpos().length; i++)
{
var dpo = self.dpos()[i];
var dpoCountries = dpo.countries();
}
};
}
ko.applyBindings(new DposViewModel());
$(document).ready(function ()
{
$('.js-example-basic-multiple').select2();
});
UI below:
<div id="table" class="table-editable">
<table class="table">
<thead>
<tr>
<th>Email</th>
<th>Preferred Language</th>
<th>Countries</th>
<th><span id="table-add" class="table-add glyphicon glyphicon-plus" data-bind="click: addDpo"></span></th>
</tr>
</thead>
<tbody data-bind="foreach: dpos">
<tr>
<td contenteditable="true" data-bind="text: $data.address"></td>
<td>
<select class="js-example-basic-single" data-bind="options: $parent.languagesList, optionsText: 'name', optionsValue: 'code', value: $data.preferredLanguage"></select>
</td>
<td>
<select class="js-example-basic-multiple" multiple="multiple" data-bind="options: $parent.countriesList, optionsText: 'name', optionsValue: 'code', value: $data.countries"></select>
</td>
<td>
<span class="table-remove glyphicon glyphicon-remove" data-bind="click: $parent.removeDpo"></span>
</td>
</tr>
</tbody>
</table>
</div>
<button data-bind="click: checkDpos">Click me</button>
<div>
<h1>Summary</h1>
<div data-bind="foreach: dpos">
<p>Address: <strong data-bind="text: address"></strong></p>
<p>Preferred Language: <strong data-bind="text: preferredLanguage"></strong></p>
<p>Countries: <strong data-bind="text: countries"></strong></p>
</div>
</div>
Any ideas why this is happening?
I'm trying to clear and reset my input and select fields using knockoutJS. below is a snippet on how it kinda works but edited for brevity.
Javascript Code
view = (function()
{
var self = this;
self.anItem = ko.observable(new AnItem());
ko.applyBindings(self)
self.addItem = function()
{
self.somewhere.push(this);
self.anItem = new AnItem(); /////this doesn't clear the form
}
})();
function AnItem()
{
this.Name= "";
this.Type= 1
}
Html Code
<tfoot data-bind="with: anItem">
<tr>
<td><input type="text" data-bind="value= Name" /></td>
<td><select data-bind="options: pretendThisIsPopulated, optionsValue:'Value', optionsText:'Text', value:Type" /></td>
<td>add</td>
</tr>
</tfoot>
self.anItem(new AnItem()); //should say instead
You can clear those like this.
Edited
view = (function()
{
var self = this;
var AnItem = function (name,type)
{
var $this = this;
this.Name = ko.observable(name | "");
this.Type = ko.observable(type | 1);
};
self.anItem = ko.observable(new AnItem("Name",1));
ko.applyBindings(self)
self.addItem = function()
{
self.somewhere.push(this);
self.anItem = new AnItem("",1);
};
})();
Use self.addItem(); to clear the data where you want.
I'm having some trouble in binding dynamically created dom elements
Code:
var i=0;
$.each(data.info, function(index, element) {
$("#div1").append("<tr><td>" + element.Name + "</td><td>"+ element.Major +"</td><td>" + element.Sex +"</td><td>" + "<input data-bind='value: eng"+i+"' ></td><td>" + "<input data-bind='value: jap"+i+"' ></td><td>" + "<input data-bind='value: cal"+i+"' ></td><td>" + "<input data-bind='value: geo"+i+"' ></td><td>" + "<strong data-bind='text: total'></td>" )
i++;
});
This creates row with input data-bind values eng0, eng1, jap0, jap1, etc.
I want to bind these as observables
Code
function AppViewModel() {
this.eng = ko.observable(element.English);
this.jap = ko.observable(element.Japanese);
this.cal = ko.observable(element.Calculus);
this.geo = ko.observable(element.Geometry);
this.total = ko.computed(function() {
var tot=parseFloat(this.eng()) + parseFloat(this.jap()) + parseFloat(this.cal()) + parseFloat(this.geo());
return (tot);
}, this);
}
ko.applyBindings(new AppViewModel());
This code is also inside $.each(data.info, function(index, element){}
I want some thing like
Var i=0;
$.each(data.info, function(index, element) {
function AppViewModel() {
this.eng+i = ko.observable(element.English);
this.jap+i = ko.observable(element.Japanese);
this.cal+i = ko.observable(element.Calculus);
this.geo+i = ko.observable(element.Geometry);
this.total+i = ko.computed(function() {
var tot=parseFloat(this.eng()) + parseFloat(this.jap()) + parseFloat(this.cal()) + parseFloat(this.geo());
return (tot);
}, this);
}
i++;
}
That get me result this.eng0 = ko.observable()
Note: the data is obtained from a JSON object. I have only included the iteration path
May I suggest that using a foreach binding may be better than using jQuery's each and generating the HTML yourself? I'd suggest changing your view model to something like this:
function AppViewModel() {
this.items = ko.observableArray();
}
function ItemViewModel(element) {
this.eng = ko.observable(element.English);
this.jap = ko.observable(element.Japanese);
this.cal = ko.observable(element.Calculus);
this.geo = ko.observable(element.Geometry);
this.name = ko.observable(element.name);
this.major = ko.observable(element.major);
this.sex = ko.observable(element.sex);
this.total = ko.computed(function () {
var tot = parseFloat(this.eng()) + parseFloat(this.jap()) + parseFloat(this.cal()) + parseFloat(this.geo());
return (tot);
}, this);
};
Here, the AppViewModel is a container for the list of elements, and each element is its own ItemViewModel, with the properties you seem to have.
The html to bind this would be something like this:
<table>
<tbody data-bind="foreach: items">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: major"></td>
<td data-bind="text: sex"></td>
<td><input data-bind='value: eng' /></td>
<td><input data-bind='value: jap' /></td>
<td><input data-bind='value: cal' /></td>
<td><input data-bind='value: geo' /></td>
<td><strong data-bind='text: total' /></td>
</tr>
</tbody>
</table>
When you get the JSON from your server you can use Knockout's built-in JSON stuff, the mapping plugin, or parse them yourself. I created an example using the latter option in this jsfiddle.
This is similar to, but different from other questions around this topic.
I have a table with a list of records, each having a select checkbox.
In the table header I have a "Select All" checkbox.
When the user checks/unchecks "Select All" the records are selected/unselected. This works fine.
However, I need to deselect my "Select All" checkbox when one or more of the records are deselected.
My markup:
<table>
<thead>
<tr>
<th>Name</th>
<th><input type="checkbox" data-bind="checked: SelectAll" /></th>
</tr>
</thead>
<tbody data-bind="foreach: $data.People">
<tr>
<td data-bind="text: Name"></td>
<td class="center"><input type="checkbox" data-bind="checked: Selected" /></td>
</tr>
</tbody>
</table>
My script (edited):
function MasterViewModel() {
var self = this;
self.People = ko.observableArray();
self.SelectAll = ko.observable(false);
self.SelectAll.subscribe(function (newValue) {
ko.utils.arrayForEach(self.People(), function (person) {
person.Selected(newValue);
});
});
}
my.Person = function (name, selected) {
var self = this;
self.Name = name;
self.Selected = ko.observable(false);
}
This works
http://jsfiddle.net/AneL9/
self.SelectAll = ko.computed({
read: function() {
var item = ko.utils.arrayFirst(self.People(), function(item) {
return !item.Selected();
});
return item == null;
},
write: function(value) {
ko.utils.arrayForEach(self.People(), function (person) {
person.Selected(value);
});
}
});
but will give you a ordo n ^ 2 problem when selecting deselecting all, you can use a pasuable computed to get around that
http://www.knockmeout.net/2011/04/pausing-notifications-in-knockoutjs.html
edit: You can also extend the computed with a throttle, this way you avoid the ordo n^2 problem
.extend({ throttle: 1 })
http://jsfiddle.net/AneL9/44/
You should make SelectAll computed observable like this:
self.SelectAll = ko.computed({
read: function() {
var persons = self.People();
for (var i = 0, l = persons.length; i < l; i++)
if (!persons[i].Selected()) return false;
return true;
},
write: function(value) {
ko.utils.arrayForEach(self.People(), function(person){
person.Selected(value);
});
}
});
and strip SelectAll.subscribe out.
http://jsfiddle.net/Yqj59/