I want to return true if my property is a match in my search input. I managed to do that with my other properties from my JSON data, but in my nested data I cannot. Tried it with for loop and also just with if but without success.
this is how i get my json from DB:
db.query("One/docs").then(function(dataone){
var oness = [];
for(var i=0; i < dataone.rows.length; ++i) {
var x = new one();
x.fromJS(dataone.rows[i].value);
oness.push(x);
}
self.OneInfos(oness);
})
Maybe I'm forgetting something, I don't really know :P.
My Json data:
code 1 :
function one(){
var self = this;
self.AssemblyName = ko.observable();
self.Description = ko.observable();
self.Name = ko.observable();
self.Obsolete = ko.observable();
self.TypeName = ko.observable();
self.Properties = ko.observable();
self.Implements = ko.observable();
self.Implements.Interfaces = ko.observable();
self.IsInSearch = ko.pureComputed(function(){
var searchRegEx= App.instance.SearchRegEx();
if(!searchRegEx)
return true;
if(searchRegEx.test(self.TypeName())) {
return true;
}
if(searchRegEx.test(self.Name())) {
return true;
}
if(searchRegEx.test(self.Description())) {
return true;
}
//todo - look if there is a filter for interfaces
if (searchRegEx.test(self.Implements.Interfaces.TypeName())){
return true;
}
return false;
});
}
And here is my second solution with for loop (it's in the same line under "todo - comment"):
for(var i=0; i < Implements.length;++i) {
var Implement = Implements[i];
if (searchRegEx.test(self.Interfaces.TypeName)){
return true;
}
}
Notice: My implements are displayed with a select input
you don't need the if (self.Implements() && self.Implements().Interfaces).
var interfaces = self.Implements();
for(var i=0; i < interfaces.length;++i) {
var currentInterface = interfaces[i];
if (searchRegEx.test(currentInterface.TypeName)){
return true;
}
}
Modified the code from your answer above.
I think this works as it searches through your Implements and property TypeName.
Hope this helps
I'm not sure why your data mapping works, since fromJS doesn't seem to be in your one viewmodel. It is in ko.mapping however, so I'll just assume you're somehow accessing it correctly.
var x = new one();
x.fromJS(dataone.rows[i].value);
According to the knockout documentation, ko.mapping.fromJS works like so:
All properties of an object are converted into an observable. If an update would change the value, it will update the observable.
This means Implements is an observable. Implements.Interfaces would then be undefined, but Implements().Interfaces would return your array.
To search the TypeName values of this array, you'll have to use:
if (self.Implements() && self.Implements().Interfaces) {
var interfaces = self.Implements().Interfaces;
for(var i=0; i < interfaces.length;++i) {
var currentInterface = interfaces[i];
if (searchRegEx.test(currentInterface.TypeName)){
return true;
}
}
}
EDITS: Note that I've made some edits after our conversation in the comments. Seems that this question wasn't just about accessing data inside an observable, but about checking for undefined as well...
Related
Im struggling to find a way to get the properties Override & Justification available outside of the function. The code is:
self.CasOverridesViewModel = ko.observable(self.CasOverridesViewModel);
var hasOverrides = typeof self.CasOverridesViewModel === typeof(Function);
if (hasOverrides) {
self.setupOverrides = function() {
var extendViewModel = function(obj, extend) {
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
extend(obj[property]);
}
}
};
extendViewModel(self.CasOverridesViewModel(), function(item) {
item.isOverrideFilledIn = ko.computed( function() {
var result = false;
if (!!item.Override()) {
result = true;
}
return result;
});
if (item) {
item.isJustificationMissing = ko.computed(function() {
var override = item.Override();
var result = false;
if (!!override) {
result = !item.hasAtleastNineWords();
}
return result;
});
item.hasAtleastNineWords = ko.computed(function() {
var justification = item.Justification(),
moreThanNineWords = false;
if (justification != null) {
moreThanNineWords = justification.trim().split(/\s+/).length > 9;
}
return moreThanNineWords;
});
item.isValid = ko.computed(function() {
return (!item.isJustificationMissing());
});
}
});
}();
}
I've tried it by setting up a global variable like:
var item;
or
var obj;
if(hasOverrides) {...
So the thing that gets me the most that im not able to grasp how the connection is made
between the underlying model CasOverridesviewModel. As i assumed that self.CasOverridesViewModel.Override() would be able to fetch the data that is written on the screen.
Another try i did was var override = ko.observable(self.CasOverridesViewModel.Override()), which led to js typeError as you cannot read from an undefined object.
So if anyone is able to give me some guidance on how to get the fields from an input field available outside of this function. It would be deeply appreciated.
If I need to clarify some aspects do not hesitate to ask.
The upmost gratitude!
not sure how far outside you wanted to go with your variable but if you just define your global var at root level but only add to it at the moment your inner variable gets a value, you won't get the error of setting undefined.
var root = {
override: ko.observable()
};
root.override.subscribe((val) => console.log(val));
var ViewModel = function () {
var self = this;
self.override = ko.observable();
self.override.subscribe((val) => root.override(val));
self.load = function () {
self.override(true);
};
self.load();
};
ko.applyBindings(new ViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
I'm new to knockout.js (and this is also my first stackoverflow post) and I'm now facing the following problem.
I'm not able to bind the data from web api to a ko.observablearray. Why is the length of this Announcements ko.observablearray always 0? The code works fine with client side data (by adding new announcements)..
Here's the JS-code:
var AnnouncementModel = function () {
var self = this;
self.AnnouncementText = ko.observable();
self.AllDepartmentsBool = ko.observable();
self.Editable = ko.observable(false);
self.Add = function () {
viewModel.Announcements.push(self);
viewModel.AnnouncementToEdit(new AnnouncementModel());
};
self.Delete = function () {
ajaxHelper(announcementsUri + self.ID, 'DELETE').done(
viewModel.Announcements.remove(self));
};
self.Edit = function () {
self.Editable(!self.Editable());
};
}
//The ViewModel
function AnnouncementsViewModel() {
var self = this;
self.InitialData = ko.observableArray();
self.Announcements = ko.observableArray();
self.AnnouncementToEdit = ko.observable(new AnnouncementModel());
self.error = ko.observable();
function getAllAnnouncements() {
ajaxHelper(announcementsUri, 'GET').done(function(data) {
self.InitialData(data);
});
};
getAllAnnouncements();
};
var viewModel = new AnnouncementsViewModel();
ko.applyBindings(viewModel, document.getElementById("announcements-container"));
function createAnnouncement(announcementDto) {
var announcement = new AnnouncementModel();
announcement.AnnouncementText = ko.observable(announcementDto.AnnouncementText);
announcement.AllDepartmentsBool = ko.observable(announcementDto.AllDepartmentsBool);
announcement.Editable = ko.observable(false);
return announcement;
}
var length = viewModel.InitialData.length;
for (i = 0; i < length; i++) {
var newAnnouncement = createAnnouncement(InitialData[i]);
viewModel.Announcements.push(newAnnouncement);
}
The HTML:
<div id="announcements-container" style="display: inline-block; float: right">
<ul id="announcements-list" class="newsticker" data-bind="foreach: Announcements">
<li>
<span data-bind="html: AnnouncementText"></span>
</li>
</ul>
#Html.Partial("_AnnouncementsModal")
</div>
The InitialData gets populated from the api as it should:
GOT IT WORKING! :
Thanks for the quick answers. I got the code working by iterating the data with .forEach(). Another problem was that the initialData didn't get populated in it's current scope so I edited the getAnnouncements function to work like this :
function getAllAnnouncements() {
ajaxHelper(announcementsUri, 'GET').done(function(data) {
data.forEach(function (entry) {
var newAnnouncement = createAnnouncement(entry);
self.Announcements.push(newAnnouncement);
});
});
};
This line is the likely culprit:
var length = viewModel.InitialData.length;
Remember that InitialData is a function. Functions have a length (it's their "arity", the number of formal arguments they have), but the observable function for an observable array's length isn't the array's length..
You probably wanted the length of the array inside it:
var length = viewModel.InitialData().length;
// -------------------------------^^
Your various calls to push on observable arrays work even though length doesn't because Knockout provides push (and several other things) on the observable array function, as James points out.
Similarly, this line:
var newAnnouncement = createAnnouncement(InitialData[i]);
probably wants to be using the array as well (and is missing viewModel. in front of InitialData).
So that whole section probably wants to be refactored a bit:
viewModel.InitialData().forEach(function(entry) {
var newAnnouncement = createAnnouncement(entry);
viewModel.Announcements.push(newAnnouncement);
});
or without forEach (but really, it's nearly 2016, and it's shimmable on obsolete browsers);
var data = viewModel.InitialData();
for (var i = 0; i < data.length; ++i) {
var newAnnouncement = createAnnouncement(data[i]);
viewModel.Announcements.push(newAnnouncement);
}
Side note: Your code (at least as it is in the question) was also falling prey to The Horror of Implicit Globals by not declaring the i that you use in that for loop. I've added a var above, but this is another reason for using forEach to loop through arrays.
You can also use EcmaScript 6 style enumeration as follows:
viewModel.InitialData().forEach(item => {
let newAnnouncement = createAnnouncement(item);
viewModel.Announcements.push(newAnnouncement);
});
I have problem with below code. I have prices factory which returns object containing prices received from server by websocket. Prices are sent after button Create is clicked. Problem is that main.prices variable is not updated at all. I can check everything by Check button, which confirms this. Prices.data is updated, but this.prices is not, but it refers the same object, so I thought it should be updated as well. Do you have any ideas why below does not work as expected?
angular.module('myApp', ['ngWebSocket'])
.factory('ws', ['$websocket', function($websocket){
var url = 'ws://localhost/websocket';
var ws = $websocket(url);
return ws;
}])
.factory('prices', ['ws', function(ws){
var prices = {
data: [],
clear: function(){
this.data = [];
},
create: function(){
ws.send('send')
}
}
ws.onMessage(function(message){
message = JSON.parse(message.data);
var type = message.type;
if (type == 'new prices'){
prices.data = message.data;
}
});
return prices;
}])
.controller('main', ['prices', function(prices){
this.prices = prices.data;
this.check = function(){
console.log('works ', prices.data);
console.log('not works ', this.prices);
};
this.create = function(){
prices.create();
};
this.stop = function(){
prices.clear();
};
}]);
<div ng-controller="main as main">
{{ main.prices }}
<button ng-click="main.create()">Create</button>
<button ng-click="main.stop()">Stop</button>
<button ng-click="main.check()">Check</button>
</div>
There are a lot of issues with the code you posted (working on a fiddle so i can help rework it) ...
First change :
if (type == 'new prices'){
prices.data = message.data;
}
To:
if (type == 'new prices'){
prices.data.length = 0;
prices.data.push.apply(prices.data,message.data) ;//copy all items to the array.
}
From a readability / maintainability point of view you should just use this.prices vs this.prices.data. It's confusing to map them to other variables, when you can just use prices. Also note that I updated it to use "that" constantly to avoid any type of context this issues.
.controller('main', ['prices', function(prices){
var that = this;
that.prices = prices;
that.check = check;
that.create = create;
that.stop = stop;
function check(){
console.log('works ', that.prices.data);
console.log('not works ', that.prices);
}
function create(){
that.prices.create();
}
function stop(){
that.prices.clear();
}
}]);
To add to the previous response, you also have an issue on the clear():
var prices = {
...
clear: function(){
this.data = [];
},
...
}
when you do the clear with this.data = [] you are actually creating a new empty array an storing that in the this.data prop, and since this is a NEW array, the reference on main controller -> this.prices = prices.data; is still pointing to the old one. If you need to delete elements on the array just use this.data.length = 0 as Nix pointed out for the other method. that will keep all references in sync since you are re using the original array
I have a question similar to Bind text to property of child object and I am having difficulty properly creating the KO observable for a child object.
For example, I do an HTTP Get to return a JSON array of People, and the People array is inside a property called "payload". I can get the basic binding to work, and do a foreach on the payload property, displaying properties of each Person; however, what I need to do is add a "status" property to each Person, which is received from a different JSON, example
/api/people (firstname, lastname, DOB, etc.)
/api/people/1/status (bp, weight, height, etc)
I have tried binding to status.bp, and status().bp, but no luck.
The js example:
var TestModel = function (data) {
var len = data.payload.length;
for (var i = 0; i < len; i++) {
var elem = data.payload[i];
var statusdata = $.getJSON("http://localhost:1234/api/people/" + elem.id + "/status.json", function (statusdata) {
elem.status = statusdata;
data.payload[i] = elem;
});
}
ko.mapping.fromJS(data, {}, this);
};
var people;
var data = $.getJSON("http://localhost:1234/api/people.json", function (data) {
people= new TestModel(data);
ko.applyBindings(people);
});
2 important things I will need:
1) properly notify KO that "payload" is an array to key on ID property
2) make "status" an observable
Help!
[UPDATE] EDIT with working fix based on Dan's answer:
var TestModel = function(data) {
...
this.refresh = function () {
$.getJSON("http://localhost:1234/api/people", function (data) {
self.payload = ko.observableArray(); // this was the trick that did it.
var len = data.payload.length;
for (var i = 0; i < len; i++) {
var elem = data.payload[i];
$.getJSON("http://localhost:1234/api/people/" + elem.id + "/status", function (statusdata) {
// statusdata is a complex object
elem.status = ko.mapping.fromJS(statusdata);
self.payload.push(elem);
});
}
// apply the binding only once, because Refresh will be called with SetInterval
if (applyBinding) {
applyBinding = false;
ko.applyBindings(self);
}
}
I am still new to Knockout and improvements to the refresh function are most welcome. The mapping is still being reapplied each time.
You need to define an observable array and then push your data into it.
elem.status = ko.observableArray();
for (var i = 0; i < statusdata.length; i++) {
elem.status.push(statusdata[i]);
}
I can't tell what the full structure of the data is by the example. But if status is a complex object, you may what to give it its own model.
for (var i = 0; i < statusdata.length; i++) {
elem.status.push(new statusModel(statusdata[i]));
}
I'm working with the latest draft of the twitter annotations api. An example bit of data looks like
status {
annotations : [
{myAnnotationType:{myKey:myValue}},
{someoneElsesAnnotationType:{theirKey:theirValue}},
]
}
now i want to check a status to see if it has an annotation with myAnnotationType in it. If annotations was a hash instead of an array I could just write var ann = status.annotations.myAnnotationType. But its not so I wrote this instead:
function getKeys(obj){
var keys = [];
for (key in obj) {
if (obj.hasOwnProperty(key)) { keys[keys.length] = key; }
}
return keys;
}
function getAnnotation(status, type){
for(var i=0;i<status.annotations.length;i++){
var keys = getKeys(status.annotations[i]);
for(var j=0;j<keys.length;j++){
if(keys[j] == type){
return status.annotations[i];
}
}
}
}
var ann = getAnnotation(status, "myAnnotationType");
There must be a better way! Is there?
PS I can't use jquery or anything as this is js to be used in a caja widget and the container doesn't support external libs
If I understand the purpose of this function correctly, you are trying to return the first object in an array (status.annotations) that contains a key (type)
function getAnnotation(status, type){
for (var i=0; i<status.annotations.length ; i++) {
var obj = status.annotations[i];
if (obj.hasOwnProperty(type)) return obj;
}
}
Ah man, jQuery would make it easy if you could be sure they'd never collide:
var annotations = $.extend.apply($, status['annotations'] || []);
var annotation = annotations['myAnnotationType'];
I guess you could write your own budget extend:
function collapse(annotations) {
var result = {};
for (var i = 0; i < annotations.length; i++) {
var annotation = annotations[i];
for (var key in annotation) {
if (annotation.hasOwnProperty(key)) {
result[key] = annotation[key]; // Using a deep cloning function possible here
}
}
}
return result;
}
Or you could have a hack at adapting jQuery's extend.