Is it possible to log a KO observable array? - javascript

I am new to knockoutjs and I have an uber-basic question for you:
I have been able to successfully subscribe to a user changing the on screen twitter handle AND successfully fetch the tweets and display the last recent tweet of a user using console.log(json.results[0].text); However I am not confident that my observable array is working, when I push the json.results into recent tweets: recent_tweets.push(json.results[0].text) I see an [] empty array.
What is going on? Is logging ko.observable array possible?
console.log("TwitterFeedComponent loaded")
TwitterFeedComponent = function(attributes) {
if (arguments[0] === inheriting)
return;
console.log("TwitterFeedComponent() loaded")
var component = this;
var url = 'https://twitter.com/search.json?callback=?';
this.attributes.twitter_user_handle.subscribe(function(value) {
alert("the new value of the twitter handle is " + value);
console.log("I have loaded")
var url = 'https://twitter.com/search.json?callback=?';
var twitter_parameters = {
include_entities: true,
include_rts: true,
q: 'from:' + value,
count: '3'
}
$.getJSON(url,twitter_parameters,
function(json) {
result = json.results[0].text
recent_tweets.push(json.results[0].text);
console.log(recent_tweets);
console.log(json.results[0].text);
});
});
};

To access the actual values of an observable whether it's an array or not you need include parenthesis. For example the following will work:
var recent_tweets= ko.observableArray(["hello", "hi", "how are you"]);
console.log(recent_tweets());
The same is true when assigning variables.
Here is an example of a regular scalar value:
var myObservableName = ko.observable("Luis");
myObservableName("Dany"); // changes the name to: Dany
var Name = myObservableName(); // gets the value and assigns it to the variable Name (in this case the value is "Dany")

To answer this a little differently, you could always use Knockout's subscribe() functionality. Let's assume you have the following view-model:
App.MyViewModel = function() {
var self = this;
self.TestProperty = ko.observable(null);
}
For demonstrations sake, let's assume this property is bound to a text field, as follows:
<input type="text" id="TestPropertyField" data-bind="textInput: TestProperty" />
Now let's assume that you'd like to log any time this value changes. To do this, simply update your view-model as follows:
App.MyViewModel = function() {
var self = this;
self.TestProperty = ko.observable(null);
self.TestProperty.subscribe(function(newValue){
console.log("The new value is: " + newValue);
});
}

Related

Ensure Observable Array is populated before Filter runs

Problem: When My filter method is executing that is used to display an Html element, my observable array has not been populated yet by my web-api call resulting in nothing to filter from.
My Solution :
If I place an alert right before my filter method execution, everything seems to be working.
Length of my observable array is 0 right before my alert call whereas it has a value after my alert.
Question: How can I ensure my array is populated without placing an alert.
This issue is occurring on multiple Pages, placing an alert before my Html is rendered makes everything works fine.
I have a simple Observable Array and a Filter Method on it.
Part of my Knockout Code :
self.currentVendorSupport = ko.observable(new VendorContact());
//Populates Observable Array - allManufactures
self.allManufacturers = ko.observableArray([]);
$.getJSON(serviceRoot + '/api/Manufacturer', function (data) {
var mappedManufacturers = $.map(data, function (item) {
return new Manufacturer(manID = item.manID, name = item.name);
});
self.allManufacturers(mappedManufacturers);
});
//Filters allManufacturers
self.GetCurrentVendor = function () {
alert('allManufacturerLength value again:' + allManufacturerLength);
return ko.utils.arrayFirst(self.allManufacturers(), function (item) {
return item.manID === self.currentVendorSupport().manID();
});
}
It seems to be working.
It is not working with arrayFilter though, is it because of return type difference between the two, wrong syntax or something else?
self.GetCurrentManufacturer = ko.computed(function () {
if (self.allManufacturers().length > 0)
{
return ko.utils.arrayFilter(self.allManufacturers(), function (item)
{
return item.manufacturerID ===
self.currentVendorSupport().manufacturerID() });
}
else return new Manufacturer(0, '...');
}, self);
Html Code:
<label class="control-label readOnlyLabel" data-bind="text: GetCurrentVendor().name"></label>
You can simply make GetCurrentVendor a computedObservable instead so that you can conditionally show a value based on the observable array length. Since it is computed it would react on changes made to the array and update its value.
You can even make it pureComputed so it is only ever activated/computed when called.
For example the computedObservable currentVendor would show "..." when the array is empty and the filtered name when the array is populated.
Computed:
self.currentVendor = ko.computed(function () {
if(this.allManufacturers().length > 0) {
return ko.utils.arrayFirst(this.allManufacturers(), function (item) {
return item.manID === this.currentVendorSupport().manID();
}).name;
} else {
return '...'
}
}, this)
HTML:
<label class="control-label readOnlyLabel" data-bind="text: currentVendor"></label>
Right now, your code is written such that GetCurrentVendor is called only once, by the HTML. And obviously it's called too soon and doesn't get updated afterwards. This is exactly what an observable is for, so that the HTML gets updated when the JS values get updated. So try this:
JS
self.currentVendorSupport = ko.observable(new VendorContact());
//Populates Observable Array - allManufactures
self.allManufacturers = ko.observableArray([]);
//New observable, initially empty
self.currentVendorName = ko.observable();
$.getJSON(serviceRoot + '/api/Manufacturer', function (data) {
var mappedManufacturers = $.map(data, function (item) {
return new Manufacturer(manID = item.manID, name = item.name);
});
self.allManufacturers(mappedManufacturers);
//updated value after api call is complete
self.currentVendorName(self.GetCurrentVendor().name);
});
//Filters allManufacturers
self.GetCurrentVendor = function () {
//alert('allManufacturerLength value again:' + allManufacturerLength);
return ko.utils.arrayFirst(self.allManufacturers(), function (item) {
return item.manID === self.currentVendorSupport().manID();
});
}
HTML
//this automatically updates when a new value is available
<label class="control-label readOnlyLabel" data-bind="text: currentVendorName"></label>

Hold temporary data in JSON

I really need help with an aspect of my project. My ultimate goal is to capture the changes made by a user and once they select confirm, post it to SQL for updating. I will use AJAX and PHP for the latter part of the project but I thought JSON would be a great idea to hold all the changes made by a user (the first part).
I am new to JSON and I'm having trouble putting the results in one large object that will be transferred to the server when the user selects "OK". Can someone help me with the coding of this? Is JSON the best way to accomplish the goal (of storing temporary?
Heres what I have so far (just a snippet):
HTML
<div class="button" data-info='2' data-id='8-7' onclick=addDeskid(e)></div>
<div class="button" data-info='4' data-id='2-5' onclick=remDeskId()></div>
<div class="button" value="submit">submit</div>
JS
function addDeskId(e){
$adjustment;
userObject = $(this);
userObjectChange = 'CHANGE_SEAT_TO'; //This is what i will use with AJAX and PHP to determine the SQL statement
userObjectID = userObject.attr('data-info'); //This is the unique SQL ID for the object being modified
userObjectDeskID = userObject.attr('data-id'); //This is the attribute for the object being modified
userObjectSeatID = 9-4; //This is what the attribute is being modified to, for the question, ill make it a solid value
var addUserObject = new jsonAddTest(userObjectID, userObjectChange, userObjectDeskID, userObjectSeatID,);
//this is what the function is actually doing on the user side
//$dragObject.attr("data-id",newSeat); //change desk number for new user location
//$dragObject.attr("previousseat", "T");
//var extPass = e;
//moveOrSetupAccountLog(extPass);
}
function remDeskId(){
userObject = $dropObject.find('div.dragTest');
userObjectChange = 'REMOVESEAT'; //This is what i will use with AJAX and PHP to determine the SQL statement
userObjectID = userObject.attr('data-info'); //This is the unique SQL ID for the object being modified
userObjectDeskID = userObject.attr('data-id'); //This is the attribute for the object being modified
userObjectDeskIDVal = 0; //This is what the attribute is being modified to
var remUserObject = new jsonRemTest(userObjectID, userObjectChange, userObjectDeskID, userObjectDeskIDVal);
//this is what the function is actually doing on the user side
//userObject.attr({"data-id":""}); //remove desk number for new user location
//userObject.appendTo('#userPool');
}
//JSON functions test
function jsonRemTest(id, change, name, seat, value){
this.ID = id;
this.ChangeType = change;
this.Name = name;
this.Seat = seat;
this.setTo = value;
userMoves.push(jsonRemTest);
}
function jsonAddTest(id, change, name, desk, seat, previousseat, previousseatnewvalue){
this.ID = id;
this.ChangeType = change;
this.Name = name;
this.Seat = desk;
this.setTo = seat;
this.PreviousSeatValue = previousseat;
this.PreviousSeatNewValue = previousseatnewvalue;
userMoves.push(jsonAddTest);
}
console.log(JSON.stringify(userMoves));
I am getting the error: userMoves is undefined. I understand why this is happening but I don't know how to correct it.
TL;DR
Every time the user clicks on this button, it generates an array. I want to combine all the arrays into one object that contains all of them. When the user clicks on a submit button, the object is sent to the server using AJAX/PHP. Is this the best way to do this and if so, how do I combine the output of the JSON functions into one object in preparation for sending?
Thanks in advance
OK, Let's tackle this with some recommendations.
First off your onclick=addDeskid(e) is not properly cased to call your function and well, it is in the markup not the code, so let's address that.
I also changed your markup slightly to work with my event handlers better using a class for myAddButton and myRemButton, do what you will with that but I used it. I also added a button to get the results logged after all the events fired. This is the reason you get [] you have nothing in there when it gets logged. I did nothing with the submit, that is yours to handle (ajax call?)
<div class="button myAddButton" data-info='2' data-id='8-7'>add</div>
<div class="button myRemButton" data-info='4' data-id='2-5'>remove</div>
<div class="button mySubmitButton">submit</div>
<button id="ShowResults" type='button'>ShowResults</button>
Now the code - I re-engineered this to create a "class" for the object using makeClass. This is just one way but does allow for instance objects when needed and makes it easier to namespace some functions. I artificially put a private function in there just to demonstrate use as well as a public function. Note the the "this" inside that function is the instance object NOT a global object. (google makeClass with the attributed authors for more info)
I created a "class" with generic attributes. You COULD create different functions for "add" and "remove" instead of the SetChangeObject function - like one for each...I used a generic one so the "object" has the same signature.
Now the code: this is definitely a bit artificial in some places just to demonstrate use:
// makeClass - By Hubert Kauker (MIT Licensed)
// original by John Resig (MIT Licensed).
function makeClass() {
var isInternal;
return function (args) {
if (this instanceof arguments.callee) {
if (typeof this.init == "function") {
this.init.apply(this, isInternal ? args : arguments);
}
} else {
isInternal = true;
var instance = new arguments.callee(arguments);
isInternal = false;
return instance;
}
};
}
var SeatGroup = makeClass(); //create our class
//the method that gets called on creation instance object of class
SeatGroup.prototype.init = function (id, changeType, name, desk, seat, setToValue, previousseat, previousseatnewvalue) {
// a default value
var defaultSeat = "default";
var defaultName = "default";
this.ID = id;
this.ChangeType = changeType;
this.Name = name ? name : defaultName;
this.Desk = desk ? desk : "";
this.Seat = seat ? seat : privateFunction(defaultSeat);;
this.SetTo = setToValue ? setToValue : this.ID;
this.PreviousSeatValue = previousseat ? previousseat : "";
this.PreviousSeatNewValue = previousseatnewvalue ? previousseatnewvalue : "";
this.changeObject = {};
// public method
this.SetChangeObject = function () {
this.changeObject.ID = this.ID;
this.changeObject.ChangeType = this.ChangeType;
this.changeObject.Name = this.Name;
this.changeObject.Seat = this.Seat;
this.changeObject.Desk = this.Desk;
this.changeObject.SetTo = this.SetTo;
this.changeObject.PreviousSeatValue = this.PreviousSeatValue;
this.changeObject.PreviousSeatNewValue = this.PreviousSeatNewValue;
};
function privateFunction(name) {
return name + "Seat";
}
};
var userMoves = [];//global warning-global object!!
//event handlers
$('.myAddButton').on('click', addDeskId);
$('.myRemButton').on('click', remDeskId);
$('#ShowResults').on('click', function () {
console.log(JSON.stringify(userMoves));//log this after all are pushed
});
//function called with the "add" that can be customized
function addDeskId(e) {
var uo = $(this);//jQuery of the "myAddButton" element
var userObjectChange = 'CHANGE_SEAT_TO';
var userObjectID = uo.data('info');
var userObjectDeskID = uo.data('id');
var userObjectSeatID = '9-4';
// create a private instance of our class (calls init function)
var uChange = SeatGroup(userObjectID, userObjectChange, userObjectDeskID, userObjectSeatID);
uChange.SetChangeObject();//call public function
//log what we created
console.dir(uChange.changeObject);
//does not work, its private: console.log( uChange.privateFunction('hi'));
// push to our global
userMoves.push(uChange.changeObject);
}
// event function, customize as needed
function remDeskId() {
var userObject = $(this);
var userObjectChange = 'REMOVESEAT';
var userObjectID = userObject.data('info');//use jQuery data, easier/better
var userObjectDeskID = userObject.data('id');
var userObjectDeskIDVal = 0;
var remUserObject = SeatGroup(userObjectID, userObjectChange, userObjectDeskID);
remUserObject.PreviousSeatValue = "FreddySeat";//show how we set a properly of our object
remUserObject.SetChangeObject();//call public function
console.dir(remUserObject.changeObject);
userMoves.push(remUserObject.changeObject);
}
Play around with it here: http://jsfiddle.net/q43cp0vd/1/

knockout pass additional parameters to subscription function

What I want to achieve is to create subscription for model properties. This subscription function should call WebApi via Ajax updating property value in database. For ajax call I need three paramaters: "fieldName", "fieldValue" and "modelId", ajax will update database row based on those three parameters.
I have many properties and all of them need the same functionality, so I do not want to subscribe for each property individually, so I found a following suggestion:
ko.subscribable.fn.withUpdater = function (handler) {
var self = this;
this.subscribe(handler);
//support chaining
return this;
};
Add this is how it is "attached" to observables:
self.ModelId= ko.observable();
self.CompanyName = ko.observable().withUpdater(update);
where update is some js function outside model.
However, I have problem, because I am not able to pass three paramaters to update functions (or also I can say in another words - I need to be able to get viewModel.ModelId property value inside update, as well as propertyName).
function update (propertyName, propertyNewValue, anotherPropertyValue) {
//do ajax update
}
As an example for CompanyName property it will be:
update("CompanyName", "New Company value here", 3),
where
3 == viewModel.ModelId
There might be a better way to do this, but the following will work:
First, add a target object to the withUpdate method:
ko.subscribable.fn.withUpdater = function (handler, target, propname) {
var self = this;
var _oldValue;
this.subscribe(function (oldValue) {
_oldValue = oldValue;
}, null, 'beforeChange');
this.subscribe(function (newValue) {
handler.call(target, _oldValue, newValue, propname);
});
return this;
};
The update subscribe function will get scoped to the target property:
var update = function (propertyName) {
console.log('propname is '+ propname + ' old val: ' + oldvalue + ', new val: ' + newvalue + ', model id: ' + this.ModelId());
}
Now you will need to use it a little differently.
self.CompanyName = ko.observable().withUpdater(update, self, "CompanyName");
An example http://plnkr.co/edit/HhbKEm?p=preview
I couldn't get the scope of the withUpdater function to be that of the object without explicitly passing in the target and a string for the company name.
You can declare your function as a variable outside of the 'fn' scope.
var dataservice = 'my class that has the data calls';
var altFunc = function () {
return ko.pureComputed(function () {
var currentItem = this().filter(function (item) {
// Do knockout stuff here and return your data
// also make calls to the dataservice class
}, this, dataservice);
};
ko.observableArray.fn.someNewFunctionality = altFunc;

Unique value validation with Knockout-Validation plugin

I'm using KnockoutJS with the Knockout-Validation plugin to validate fields on a form. I'm having problems validating that a value is unique using the native validation rule - unique
I'm using the Editor Pattern from Ryan Niemeyer to allow the user to edit or create a Location. Here's my fiddle to see my problem in its entirety.
function Location(data, names) {
var self = this;
self.id = data.id;
self.name = ko.observable().extend({ unique: { collection: names }});
// other properties
self.errors = ko.validation.group(self);
// update method left out for brevity
}
function ViewModel() {
var self = this;
self.locations = ko.observableArray([]);
self.selectedLocation = ko.observable();
self.selectedLocationForEditing = ko.observable();
self.names = ko.computed(function(){
return ko.utils.arrayMap(self.locations(), function(item) {
return item.name();
});
});
self.edit = function(item) {
self.selectedLocation(item);
self.selectedLocationForEditing(new Location(ko.toJS(item), self.types));
};
self.cancel = function() {
self.selectedLocation(null);
self.selectedLocationForEditing(null);
};
self.update = function(item) {
var selected = self.selectedLocation(),
updated = ko.toJS(self.selectedLocationForEditing()); //get a clean copy
if(item.errors().length == 0) {
selected.update(updated);
self.cancel();
}
else
alert("Error");
};
self.locations(ko.utils.arrayMap(seedData, function(item) {
return new Location(item, self.types, self.names());
}));
}
I'm having an issue though. Since the Location being edited is "detached" from the locations observableArray (see Location.edit method), when I make changes to name in the detached Location that value isn't updated in the names computed array. So when the validation rule compares it to the names array it will always return a valid state of true since the counter will only ever be 1 or 0. (Please see knockout-validation algorithm below)
Within the options argument for the unique validation rule I can pass in a property for externalValue. If this value is not undefined then it will check to see if the count of matched names is greater or equal to 1 instead of 2. This works except for cases when the user changes the name, goes on to another field, and then goes back to the name and wants to change it back to the original value. The rule just sees that the value already exists in the names array and returns a valid state of false.
Here is the algorithm from knockout.validation.js that handles the unique rule...
function (val, options) {
var c = utils.getValue(options.collection),
external = utils.getValue(options.externalValue),
counter = 0;
if (!val || !c) { return true; }
ko.utils.arrayFilter(ko.utils.unwrapObservable(c), function (item) {
if (val === (options.valueAccessor ? options.valueAccessor(item) : item)) { counter++; }
});
// if value is external even 1 same value in collection means the value is not unique
return counter < (external !== undefined && val !== external ? 1 : 2);
}
I've thought about using this as a base to create a custom validation rule but I keep getting stuck on how to handle the situation when the user wants go back to the original value.
I appreciate any and all help.
One possible solution is to not include the name of the currently edit item (of course when creating a new item you need the full list) in the unique validator.
So the unique check won't be triggered when changing the Location name back to its original value:
self.namesExceptCurrent = function(name){
return ko.utils.arrayMap(self.locations(), function(item) {
if (item.name() !== name)
return item.name();
});
}
self.edit = function(item) {
self.selectedLocation(item);
self.selectedLocationForEditing(
new Location(ko.toJS(item),
self.types,
self.namesExceptCurrent(item.name())));
};
Demo JSFiddle.

Remove a property from knockout viewmodel

I'm just wondering how to remove a property from knockout viewModel. Specifically, a computed one. I have a simple viewModel
function viewModel(){
var self = this;
self.name = ko.observable("John");
self.lastname = ko.observable("Doe");
self.age = ko.observable("22");
self.fullName = ko.computed(function(){
return self.name() + self.lastname();
});
self.fullNameAndAge = ko.computed(function(){
return self.name() + self.lastname() + ': ' + self.age();
});
};
The data is going to be sent to the server, but I want to exclude the computed data from the viewModel.
I thought something like this would get all the computed data and remove it, but didn't find anything like it.
for (observableKey in viewModel) {
if (ko.isComputed(viewModel[observableKey])
{
delete viewModel[observableKey];
}
}
Knockout can return a regular object from which you then can remove anything you want.
var plainJs = ko.toJS(viewModel);
delete plainJs.fullName;
Documented here.
You can loop through keys like this:
for (var key in obj) {
if(ko.isComputed(obj[key]))
{
delete obj[key];
}
}
EDIT
Here is a working fiddle.In fiddle click over the button and check the console, there you can see 2 objects, first one is before removing computed obervables and second one is after removing computed observables.
My preferred approach for this sort of problem is to create a specific transport object for whatever your JSON call requires.
var userSaveRequest = function(data) {
var self = this;
self.name = data.name;
self.lastname = data.lastname;
// etc, etc
}
Then, from my calling code, something like this to get the JSON.
// inside viewModel
self.saveUser = function(){
var queryData = ko.mapping.toJSON(
new userSaveRequest(ko.mapping.toJS(self))
);
// use querydata in AJAX
}
Also, it's worth remembering just how damn flexible Javascript is. You can create an object of your own design on the fly if need be.
var queryData = ko.mapping.toJSON(
{
name: self.name(),
lastname: self.lastname()
}
);

Categories