ObservableArray does not reflect its value outside of function - javascript

In this example you can see that I initiate empty observableArray([]) and then update it after 2 seconds, and click on buttons you can see that direct reference gives me empty array and function that calls within object gets the actual value.
Why is it behaving like that? How can I get a direct reference outside?
Note: I need to use ko.mapping.fromJS(ko.toJS(self.orig)) because I want to copy data so it does not link to the original array.
Code:
var vm = function(){
var self = this;
self.orig = ko.observableArray([{
somevalue: ko.observable("testst")
}]);
self.diff = ko.observableArray([]);
self.init = function() {
self.diff = ko.mapping.fromJS(ko.toJS(self.orig));
}
setTimeout(self.init,2000);
return {
check: function() { alert(self.diff());console.log(self.diff());},
diff: self.diff
}
}
Update
It seems the problem was that I needed to change this
self.diff = ko.mapping.fromJS(ko.toJS(self.orig));
to this
ko.mapping.fromJS(ko.toJS(self.orig),{},self.diff);

I see the issue.
You are not setting the value of the KO array self.diff. You are actually just setting it equal to it and when you do that, it is no longer the same KO observable array you initialized.
Additionally, as far as I know, the ko.toJS creates a plain JS copy so you don't need the ko.mapping.fromJS call.
//What you are doing
self.diff = ko.mapping.fromJS(ko.toJS(self.orig));
//What you need to do
self.diff(ko.toJS(self.orig));
Updated JSFiddle here: http://jsfiddle.net/VGqvx/6/

Related

Javascript not setting this to value with apply or call

Edit: the code below was made up on the spot to show how I was going about what I was doing. It definietely won't run, it is missing a lot of things.
Here is a working example in codepen: https://codepen.io/goducks/pen/XvgpYW
much shorter example: https://codepen.io/goducks/pen/ymXMyB
When creating a function that is using call or apply, the this value stays null when using getPerson. however, when I use apply or call with getPerson it returns the correct person.
Please critique, I am really starting to learn more and more. I am in the middle of a project section so it might be hard to change all the code, but my next project could implement this better.
call and apply are setting to the window and not the object.
I will provide code that is much simpler with the same concept of what I am talking about.
function createPerson(){
this.manager = null;
this.teamManager = null;
this.setTeamManager = function(val){
this.teamManager = val;
}
this.setManager = function(val){
console.log('setting manager to',val);
this.teamManager = val;
}
this.getTeamManager = function(){
console.log('setting team manager to',val);
return this.teamManager ;
}
this.getManager = function(){
return this.manager;
}
this.appendSelect = function(elem){
var that = this;
createOtherSelects(that,elem);
}
//some functions that create selects with managers etc
//now assume there are other selects that will filter down the teams,
//so we might have a function that creates on change events
function createOtherSelects(that){
//code that creates locations, depending on location chosen will
//filter the managers
$('#location').on('change',function(){
//do some stuff
//... then call create management
createManagement(that,elem);
});
}
function createManagement(that,elem){
var currentLocation = that.location; //works
var area = that.area;//works ... assume these are set above
//code that returns a filter and unique set of managers back
that.teamManager = [...new Set(
data.map(person=>{
if(person.area==area &&
person.currentLocation==currentLocation
){
return person;
}
})
)].filter(d=>{if(d){return d}});
if(elem.length>0){
var selectNames = ['selectManager','selectTeamManager'];
var fcns = [that.setManager,that.setTeamManager];
for(var i = 0; i < selectNames.length;i++){
//do stuff
if(certainCriteriaMet){
// filter items
if(filteredManager == 1){
fcns[i].call(null,currentManager);//
}
}
}
}
}
}
var xx = new createPerson()
In console I see setting manager and setting team manager to with the correct values.
however when I call xx in console, I see everything else set except for
xx.teamManager and xx.manager
instead it is applying to the window, so if I type teamManager in the console, it will return with the correct person.
If I straight up say
that.setManager('Steve')
or even it works just fine.
xx.setManager('steve')
the this value in setManager is somehow changing from the current instance of the object to this window. I don't know why, and I would like to learn how to use apply and call using that for future reference.
I think the issue is with your following code
fcns[i].call(null,currentManager)
If you are not supplying "this" to call, it will be replaced with global object in non-strict mode.
fcns[i].call(that,currentManager)
See mdn article here
From your codepen example, you need to change that line
fcnset[0].apply(that,[randomName]);
The first argument of the apply method is the context, if you are not giving it the context of your method it's using the global context be default. That's why you end up mutating the window object, and not the one you want !

Knockoutjs: How can I pass a single item from an observable array into my viewmodel?

I'm creating a bug-handling program in KnockoutJS, and I've run into an issue. I have a list of "bug" reports on my main page that is displayed using an observable array. When I click "View" on one of the bug reports in the list, I have a Bootstrap modal pop up. I want to populate the contents of the modal with report details, but for some reason the viewmodel is not getting passed in correctly.
Here is my view model, along with my ajax mapping below that:
function BugsViewModel(doJSON) { //doJSON determines whether to populate the observable with JSON or leave it empty
var self = this;
self.bugs = ko.observableArray();
self.removeBug = function (bug) {
$.ajax({
url: "/ajax/getbugs.ashx?deleteBug=" + bug.Id()
})
self.bugs.remove(bug);
}
self.currentBug = ko.observable(); // <--this is the bug that I want to reference in the modal
self.setModalBug = function (obj, event) { //obj is the individual array item
self.currentBug = obj; //setting currentBug so that I can use it in my view
alert(self.currentBug.Id()); //<--these alert properly, so I know that the value I'm passing in (obj) is correct
alert(self.currentBug.BugName());
}
if(doJSON)
getViewModelJSON(self.bugs); //this function loads my array (self.bugs) with observable items
}
function getViewModelJSON(model) {
$.ajax({
url: "/ajax/getbugs.ashx"
})
.done(function (data) {
ko.mapping.fromJSON(data, {}, model);
});
}
$(document).ready(function () {
viewModel = new BugsViewModel(true);
ko.applyBindings(viewModel);
});
I have my "View" button, which calls "setModalBug" and opens the modal:
View
Then, inside my details modal, I have the textbox I want to populate with data. This is where I'm having the problem--currentBug.BugName is not populating the value correctly.
<input type="text" id="txtBugName" data-bind="textInput: currentBug.BugName" />
(Note that I am using the Mapping plugin for Knockout, so even though you don't see "BugName" defined in my viewmodal, it is being generated from the JSON when I call "ko.mapping.fromJSON()".)
I'm a little befuddled. Is this a runtime issue, or am I calling the viewmodel improperly? Or something entirely different?
Thanks
You're not assigning your value to the observable correctly. You're doing:
self.currentBug = ko.observable();
self.currentBug = obj;
The first line creates an observable, but the second line throws it away and replacing it with the bug object. You want to assign to the observable, not throw it away, like:
self.currentBug = ko.observable();
self.setModalBug = function (obj, event) {
self.currentBug(obj); //<-- Assign the current bug the currentBug observable
alert(self.currentBug().Id()); //note the additional () to read the value of the observable
alert(self.currentBug().BugName());
}
I got it working. Apparently in my code I actually didn't add the extra '()' to my alerts. Sorry--not my best moment. Now it works:
self.currentBug(obj);
alert(self.currentBug().Id());
alert(self.currentBug().BugName());
Then I added an extra '()' to my textInput data-bind:
<input type="text" id="txtBugName" data-bind="textInput: currentBug().BugName" />
I didn't realize that I had to use '()' anywhere on data-binds. I thought it figured out that you were working with observables and treated it that way (which it does on the property "BugName", but I had to access currentBug with '()' at the end).
Thanks, Retsam! I spent quite a lot of time on this and it was getting frustrating. Turns out it was a really simple fix.

angular.copy and angular.extend - copies still referencing the original array

I checked SO and found this question angular.copy() isn't creating an independent copy of an object - The title describes the issue i'm having, though the answers didn't help.
I am attempting to copy an array, broadcast an event with that array and then clear the source array. In doing this my copy is also empty. I can verify that the copy works in as much as my destination is filled by commenting out code that clears the source.
I have tried both angular.copy and angular.extend (ensuring i have the parameters the correct way around, as they are opposite for these 2 methods)
I have also tried with arrays that are on the scope rather than just local variables too - this made no difference
The parts of code that are using this are:
dz.on('queuecomplete', function () {
console.log('queue complete')
console.log(uploadedFiles)
var filesOut = [];
angular.copy(uploadedFiles, filesOut);
$rootScope.$broadcast('file.upload.complete', { files: filesOut });
scope.uploadComplete = true;
scope.completeMessage = 'Wooohoo!'
resetAll();
});
function resetAll() {
dz.removeAllFiles();
scope.hasTooMany = false;
scope.hasBadTypes = false;
scope.manualUpload = false;
hasHadErrors = false;
// If i comment this out then my listener (below) shows all files
// if i leave this as-is then both source and dest arrays are empty
uploadedFiles = [];
scope.$apply();
}
In another controller I am listening to the $broadcast
var filesUploaded = $scope.$on('file.upload.complete', function (event, data) {
console.log('files uploaded:')
// if 'uploadedFiled = []' is not commented out then data.files is an empty array
console.log(data);
});
As mentioned, i've tried this with extend too.
What am i missing? Or am i expecting something that the methods aren't designed for?

Object Reference Persisting Beyond Variable Declaration

I'm building up a client-side collection of data to post to a server. Using the onClick event of an Add button, I'm collecting the form data and storing it in an internal array. This is some faux/sample code to try to demonstrate the issue (I can't share the source, unfortunately).
var getDataFromForm = (function() {
var _internal = {}; /* Based on feedback, I think this is the reason for the behavior I'm experiencing */
return function() {
var form = {};
/* Get the form data */
_internal.name = form.name;
_internal.id = form.id;
return _internal;
};
}());
var internal = { vals: [] };
function onClick() {
var newData = getDataFromForm();
doAjaxValidation({
success: function() {
internal.vals.push(newData); /* All values in array have been updated to this newData reference on click */
internal.vals.push( JSON.parse(JSON.stringify(newData)) ); /* ugly, but the reference is broken and future clicks work as expected */
internal.vals.push( $.extend({}, newData) ); /* reference to the object is still persisted through jQuery's extend */
}
});
}
I'm confused as to why the newData reference is handed to the new assignment of newData each button click. Each click event is a new function call and newData (theoretically) should be a new object which is not related to the newData from any other button click.
What am I missing?
Edit: this sample is from a much larger block of code, and I tried to reduce it to the simplest possible expression. Obviously, as-is, this is not a functional piece of code.
It sounds as though you're passing an object,and expecting a copy.
In JavaScript, objects themselves are never copied in an assignment. It's a by value language, but in the case of Objects, the value that is copied is the reference to the object. So returning newData from getDataFromForm will result in a copy of the reference to the same object in each onClick.

Nested Objects With YUI and Javascript

I am working on a Javascript object that contains some YUI objects. The key thing is, my object needs to contain it's own set of YUI tabs so that I can display multiple instances of my object on the same page and have the tabs control their own object instance.
I set it up as follows:
var Scheduler = function(divid,startDate,mode){
this.tabView = null;
...
this.init = function(){
this.tabView.appendTo(this.calendar_cell);
this.tabView.addTab( new YAHOO.widget.Tab({
label: 'Day',
content:'<div id="'+ this.calendar_day_div +'" style="width:100%; height:auto;"></div>'
}));
var tab0 = this.tabView.getTab(0);
tab0.addListener('click', this.showWeek);
}
this.showWeek(){
alert(this);
}
});
Here's the problem. I would expect the alert(this); in this.showWeek to alert the instance of scheduler. Instead, it's giving me the tab li. I tried alerting this.parent and am given 'undefined' as an answer.
How should I set this up to do what I need to do?
The addListenter method takes a scope argument. So you can change your call to the following to solve your problem (since you are using YUI):
tab0.addListener('click', this.showWeek, undefined, this);
When you attach a function to an event of an object (in this case the object held by tab0) then its usually that object that becomes the this context of the function when it executes.
Adjust your code like this:-
var self = this;
this.showWeek(){
alert(self);
}

Categories