Unable to Reference Nested JSON Object with KnockoutJS - json

I have a JSON string that has a nested JSON object called data. I am trying to reference the status portion of the nested JSON object, but when I refer to it in my HTML, KnockoutJS does not populate the cells pertaining to Status in my table. KnockoutJS does, however, populate the sender portion of the table.
JSON:
[{"statusmsg":"OK","data":{"status":"running"},"sender":"hostname","statuscode":0}]
KnockoutJS (service.js):
function ServiceViewModel() {
var self = this;
self.rows = ko.observableArray();
$.ajax({
method: "GET",
url: "/mcollective/service/status/servicename",
success: function(data) {
var observableData = ko.mapping.fromJSON(data);
var array = observableData();
self.rows(array);
}
});
};
$(document).ready(function() {
ko.applyBindings(new ServiceViewModel());
});
HTML:
<tbody data-bind="foreach: rows">
<tr>
<td data-bind="text: sender"></td>
<td>
<span data-bind="text: data.status,
css: { 'label-success': data.status == 'running',
'label-danger': data.status == 'stopped',
'label': true }">
</span>
</td>
<td>
</tr>
</tbody>
Note: I am also using Bootstrap for the CSS.
I have checked Firefox web developer console and there are no errors pertaining to my script.

The mapping plugin turns your properties into observables.
This means that our data.status property will be a ko.observable which is a function what you need to call without any arguments to get its value.
So you need to fix your css binding and write data.status() there:
<span data-bind="text: data.status,
css: { 'label-success': data.status() == 'running',
'label-danger': data.status() == 'stopped',
'label': true }"></span>

Related

HTML validation not working for input field loaded using Jquery

I have a form input field loaded by jquery based on user selection but the HTML form validation and even jquery form validation not working for the input field loaded by jquery.
<tr>
<td colspan="2">
<select name="usdtnetwork" required="required" id="usdtnetwork" onChange="getaddressForm()" title="Please select your USDT Network">
<option>::: Choose your USDT Network :::</option>
<option value="ERC20">ERC20 (ETH)</option>
<option value="TRC20">TRC20 (TRON)</option>
<option value="BEP20">BEP20 (BNB)</option>
</select>
</td>
</tr>
<tr>
<td colspan="2">
<div align="left">
<span id="showinputfield"></span>
</div>
</td>
</tr>
This is my jquery (Noticed I tried e.preventDefault() but can't figure what am doing wrong so I commented it out)
<script>
/*$('.thisbuyUSDT').click(function (e) {
var myForm = jQuery( "#catalog_formusdt" );
// html 5 is doing the form validation for us,
// so no need here (but backend will need to still for security)
if ( ! myForm[0].checkValidity() )
{
// bonk! failed to validate, so return true which lets the
// browser show native validation messages to the user
return true;
}
e.preventDefault(); */
function getaddressForm() {
//e.preventDefault();
$("#loaderIcon").show();
jQuery.ajax({
url: "usdt_form_field.php",
data:'usdtnetwork='+$("#usdtnetwork").val(),
type: "POST",
success:function(data){
$("#showinputfield").html(data);
$("#loaderIcon").hide();
},
error:function (){}
});
}
//}
</script>
you have to reset validation on form after add some element (after $("#showinputfield").html(data);) by :
myForm.removeData("validator").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse(myForm );
if it doesn't work please share complete html and jquery code

refreshing razor view after new data is returned from the controller

I have the following view that displays gaming related data from a controller.
When the page initially loads, it hits an Index Controller that just lists all the gaming sessions ever created (100 total).
However, there is an input field, where the user can input a date, and then click a button.
When clicked, this button sends the date & time to another method called GamingSessionsByDate.
The GamingSessionsByDate method then returns new data which only contains Gaming Sessions with a start date of whatever the user entered.
Here is the view:
#model IEnumerable<GamingSessions.Session>
#{
ViewData["Title"] = "GamingSessionsByDate";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Gaming Sessions By Date</h2>
<input type="date" name="gameSession" id="gameSession">
<input type="Submit" id="postToController" name="postToController" Value="Find" />
#section Scripts
{
<script type="text/javascript">
$("#postToController").click(function () {
var url = '#Url.Action("GamingSessionsByDate", "GameSession")';
var inputDate = new Date('2019-01-23T15:30').toISOString();
$.ajax({
type: "POST",
url: url,
data: "startdate=" + inputDate,
success: function (data) {
console.log("data: ", data);
}
});
});
</script>
}
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.GameName)
</th>
<th>
#Html.DisplayNameFor(model => model.PlayDuration)
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.GameName)
</td>
<td>
#Html.DisplayFor(modelItem => item.PlayDuration)
</td>
</tr>
}
</tbody>
</table>
Here is the controller that returns the gaming sessions by date:
public IActionResult GamingSessionsByDate(DateTime startdate)
{
var response = GetGameSessionsList(startdate);
var r = response.Results;
return View(r);
}
By the way, I have hard-coded a date time value into the AJAX call above that I know contains 5 gaming sessions.
Please note that I am writing out the data returned from the controller in the AJAX success method.
So when I click the button, nothing happens on the screen, I just see the initially-loaded 100 gaming sessions from the call to the Index controller.
However, behind the scenes, I can see the 5 gaming sessions I need being written to the console via the console.log command in the Ajax call.
I also see the correct data when I step-through the project in Visual Studio.
So it looks like everything is working, but it appears as if the view/page is not getting refreshed.
So, how do I get that data to display on the page?
Thanks!
The XMLHttpRequest object in JavaScript (what actually makes "AJAX" requests) is what's known as a "thin client". Your web browser is a "thick client", it does more than just make requests and receives responses: it actually does stuff automatically such as take HTML, CSS, and JavaScript that's returned and "runs" them, building a DOM and rendering pretty pictures and text to your screen. A thin client, conversely, literally just makes requests and receives responses. That's it. It doesn't do anything on its own. You are responsible, as the developer, for using the responses to actually do something.
In the case here, that means taking the response you receive and manipulating the DOM to replace the list of game sessions with the different game sessions retrieved. How you do that depends on what exactly you're returning as a response from your AJAX call. It could be HTML ready to be inserted or some sort of object like JSON. In the former case, you'd literally just select some parent element in the DOM and then replace its innerHTML with the response you received. In latter case, you'd need to use the JSON data to actually build and insert elements into the DOM.
Returning straight HTML is easier, but it's also less flexible. Returning JSON gives you ultimate freedom, but it's more difficult out of the box to manipulate the DOM to display that data. That's generally the point where you want to employ a client-side framework like Vue, Angular, React, etc. All of these can create templated components. With that, you need only change the underlying data source (i.e. set the data to the JSON that was returned), and the component will react accordingly, manipulating the DOM as necessary to create the view.
I personally like to use Vue, since it has the least friction to get started with an it's almost stupidly simple to use. For example:
<div id="App">
<input type="date" v-model="startDate" />
<button type="button" v-on:click="filterGameSessionsByDate">Find</button>
<table class="table">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.GameName)
</th>
<th>
#Html.DisplayNameFor(model => model.PlayDuration)
</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items">
<td>{{ item.GameName }}</td>
<td>{{ item.PlayDuration }}</td>
</tr>
</tbody>
</table>
</div>
Then a bit of JS to wire it up:
(function (options) {
let vm = new Vue({
el: '#App",
data: {
items: options.items,
startDate: null
},
methods: {
filterGameSessionsByDate: function () {
let self = this;
$.ajax({
type: "POST",
url: options.filterByDateUrl,
data: "startdate=" + self.startDate,
success: function (data) {
self.items = data;
}
});
}
}
});
})(
#Html.Raw(Json.Encode(new {
items = Model,
filterByDateUrl = Url.Action("GamingSessionsByDate", "GameSession")
}))
)
That may look a little funky if you're not that used to JS. I'm just using what's called a closure here: defining and calling a function in place. It takes an options param, which is being filled by the parenthesis at the bottom. Inside those, I'm creating an anonymous object that holds info I need, such as the initial items to display and the URL to get filtered results from. Then, that object is encoded into JSON and dumped to the page.

Angular search in a table

I want to make a search on the column SN in a table.
there many information in my table, I want to be able to search based on SN but when I add the filter it does not even load my table
This is what I did:
in My controler my List is filled :
$scope.List = {};
MyServices.getList()
.success(function (data) {
angular.forEach(data, function (value, index) {
$scope.List[value.SN] = {
Description: value.Description,
SN: value.SN
}
});
})
.error(function (error) {
$scope.status = 'Unable to load customer data: ' + error.message;
});
and this is my HTML:
<label>Search: <input ng-model="search.SN"></label>
<tr ng-repeat="V in List| filter:search">
<td>{{V.SN}}</td>
<td>{{V.Description}}</td>
</tr>
You must write as follow:
<label>Search: <input ng-model="search.SN"></label>
<tr ng-repeat="V in List| filter: {SN: search.SN}">
<td>{{V.SN}}</td>
<td>{{V.Description}}</td>
</tr>
Remove the object declaration on the input field. It will match the whole object for your specified value on the input field:
<label>Search: <input ng-model="search"></label>
<tr ng-repeat="V in List| filter: search">
<td>{{V.SN}}</td>
<td>{{V.Description}}</td>
</tr>

Angular ng-repeat with nested json objects?

I have a JSON object, represented as such:
{
"orders" : [
{
"ordernum" : "PRAAA000000177800601",
"buyer" : "Donna Heywood"
"parcels" : [
{
"upid" : "UPID567890123456",
"tpid" : "TPID789456789485"
},
{
"upid" : "UPID586905486090",
"tpid" : "TPID343454645455"
}
]
},
{
"ordernum" : "ORAAA000000367567345",
"buyer" : "Melanie Daniels"
"parcels" : [
{
"upid" : "UPID456547347776",
"tpid" : "TPID645896579688"
},
{
"upid" : "UPID768577673366",
"tpid" : "TPID784574333345"
}
]
}
]
}
I need to do a repeater on the second level of this, a list of the "upid" numbers.
I know already how to get the top level
<li ng-repeat="o in orders">{{o.ordernum}}</li>
But I am unclear on the sequence to loop a level down. For example, this is wrong:
<li ng-repeat="p in orders.parcels">{{p.upid}}</li>
I also know how to nest repeaters to get this, but in this case i don't need to display the top level at all.
CLARIFICATION
The goal here is to have one list with the 4 "upid" numbers (there are 2 for each parcel, and there are 2 parcels in the order).
Actually its same answer of #sylwester. The better way to put it in filter. And you can reuse it by passing propertyName parameter.
In your case we passed parcels
JS
myApp.filter('createarray', function () {
return function (value, propertyName) {
var arrayList = [];
angular.forEach(value, function (val) {
angular.forEach(val[propertyName], function (v) {
arrayList.push(v)
});
});
return arrayList;
}
});
HTML
<ul>
<li ng-repeat="o in ordersList.orders | createarray: 'parcels'">{{o.upid}}</li>
</ul>
Here is working Fiddle
You can just create new array 'parcels' like in demo below:
var app = angular.module('app', []);
app.controller('homeCtrl', function($scope) {
$scope.data = {
"orders": [{
"ordernum": "PRAAA000000177800601",
"buyer": "Donna Heywood",
"parcels": [{
"upid": "UPID567890123456",
"tpid": "TPID789456789485"
}, {
"upid": "UPID586905486090",
"tpid": "TPID343454645455"
}]
}, {
"ordernum": "ORAAA000000367567345",
"buyer": "Melanie Daniels",
"parcels": [{
"upid": "UPID456547347776",
"tpid": "TPID645896579688"
}, {
"upid": "UPID768577673366",
"tpid": "TPID784574333345"
}]
}]
};
$scope.parcels = [];
angular.forEach($scope.data.orders, function(order) {
angular.forEach(order.parcels, function(parcel) {
$scope.parcels.push(parcel)
})
})
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="homeCtrl">
<ul>
<li ng-repeat="o in parcels">{{o.upid}}</li>
</ul>
</div>
</div>
Seems like you just need a double-nested for loop -
<ul>
<div ng-repeat="o in orders">
<li ng-repeat="p in o.parcels">{{p.upid}}</li>
</div>
</ul>
The HTML might be a little ugly here, but I'm not sure what exactly you are going for. Alternatively you could just create a new array of the parcels via mapping.
Searching a lot for nice and simple solution for iterating dynamically. I came up with this
JAVASCRIPT (angular): a person is an example of nested object. the is_object function will be use in the HTML view.
$scope.person = {
"name": "john",
"properties": {
"age": 25,
"sex": "m"
},
"salary": 1000
}
// helper method to check if a field is a nested object
$scope.is_object = function (something) {
return typeof (something) == 'object' ? true : false;
};
HTML: define a template for simple table. the 1st TD is the key which is displayed. another TD (2 or 3, but never both) will be show the value if its not an object (number / string), OR loop again if its an object.
<table border="1">
<tr ng-repeat="(k,v) in person">
<td> {{ k }} </td>
<td ng-if="is_object(v) == false"> {{ v }} </td>
<td ng-if="is_object(v)">
<table border="1">
<tr ng-repeat="(k2,v2) in v">
<td> {{ k2 }} </td>
<td> {{ v2 }} </td>
</tr>
</table>
</td>
</tr>
</table>
The reason that <li ng-repeat="p in orders.parcels">{{p.upid}}</li> does not work the way you expect is because the parcels array is an object inside each individual order in your order array, i.e. it is not an object of the orders array itself.
If your orders array is defined on the $scope of a controller, then you create the array on the $scope variable:
$scope.allParcels = $scope.orders
.map(function (elem) {
return elem.parcels;
}) // get an array where each element is an array of parcels.
.reduce(function (previousValue, currentValue) {
return previousValue.concat(currentValue);
}); // concat each array of parcels into a single array of parcels
then on the template, you can use <li ng-repeat='p in allParcels'>{{p.upid}}</li>
If, however, you do not want to place the array on the $scope, I believe you can do something similar to this:
<li ng-repeat="p in orders
.map(function (elem) {
return elem.parcels;
})
.reduce(function (previousValue, currentValue) {
return previousValue.concat(currentValue);
})">{{p.upid}}</li>
although I'm not 100% sure that Angular will evaluate the .map/.reduce in the ng-repeat expression (also having an array generated this way in an ng-repeat is ill-advised since angular would have to constantly generate a new array via map/reduce on each $digest cycle).

Durandal widget "this.settings is undefined"

I have a Durandal widget (hot towel template) named "selector" in App>durandal>widgets>selector
The code of controller.js of widget:
define(function (require) {
var widget = require('durandal/widget');
var selector = function (element, settings) {
settings.selectedTopLevel = ko.observable();
settings.showTopLevel = ko.observable(true);
this.settings = settings;
};
selector.prototype.enableSelect = function() {
this.settings.showTopLevel(true);
this.settings.selectedTopLevel(null);
};
selector.prototype.showSelect = function () {
var selected = this.settings.selectedTopLevel;
alert(this.selected.name().toString());
};
return selector;
});
The view.html of widget:
<span>
Click to enable
<span data-bind="visible: settings.showTopLevel">
<select data-bind="options: settings.items, optionsText: 'name', optionsCaption: 'Select...', value: settings.selectedTopLevel"></select>
</span>
<br/>
<span data-bind="foreach: settings.items">
<a href="#" data-bind="click: $parent.showSelect">
<span data-bind="text: name"></span>
</a>
</span>
The use of widget:
<div>
<h2>Widget Selector:</h2>
<div data-bind="widget: { kind: 'selector', items: $root.projects }"></div>
But I have some problems in function selector.prototype.showSelect in line var selected = this.settings.selectedTopLevel; the principal error is:
this.settings is undefined
The other problem appear in that line of html:
Click to enable
The function selector.prototype.enableSelect isn't call when I clicked in "Click to enable".
I am new in Durandal widget, please any help much appreciated!
It's a KO issue. You need to wrap those calls to $parent in a function in order to preserve the "this" context I believe.
Reposted from comment above to make easier to read:
I think the issue is these are not updating:
this.settings.showTopLevel(true);
this.settings.selectedTopLevel(null);
For some reason observables that are declared in durandal widgets don't enter the knockout stack or aren't bound to the view (not sure which one)...
Not sure why yet. To get knockout observables I had to declare any observables needed at the widget level at the parent viewmodel's level and pass it in using the settings.
Let me know if you get it to work!