How do I update a knockoutjs observable array item - javascript

I have a view model setup with an observable array of user objects. The add/remove of items are working correctly but how do I update items? I can find the values using the ko indexOf function.
function User( username, date_inactive, date_active ) {
this.username = username;
this.date_active = date_active;
this.date_inactive = date_inactive;
};
User.prototype.inactivateMe = function() {
json_responses.push( this );
$.getJSON( "url" + this.username, function( json ) {
original = json_response.pop();
//do update here
});
};
userModel = [ ], //Where the loaded usernames are stored.
viewUserModel = {
users: ko.observableArray(userModel)
//.......
//This is how I'm adding users to the array.
addUser: function () {
$.getJSON( "url",
{ username: usern },
function( json ) {
if( json.STATUS != undefined && json.STATUS == 'success' ) {
newuser = new User( json.USERNAME, json.DATE_ACTIVE, json.DATE_INACTIVE );
viewUserModel.users.push( newuser );
}
}
});
}
The values of viewUserModel.users are pushed into the array from a server json reponse.
I want to be able to update the date_active and date_inactive values when the user clicks a button and the server responses with success.
My setup is an adaption from http://net.tutsplus.com/tutorials/javascript-ajax/into-the-ring-with-knockout-js-the-title-fight/

An observable array only tracks changes made to the array (such as pushes and pops), not the data itself. You will need to make date-active and date_inactive observables as #Ianzz has specified.
function User( username, date_inactive, date_active ) {
this.username = username;
this.date_active = ko.observable(date_active);
this.date_inactive = ko.observable(date_inactive);
};
Afterwards in your html, do something such as
<div data-bind="foreach: Users">
<input data-bind="value: date_active"/>
<input data-bind="value: date_inactive"/>
<div>​
See fiddle for full example.

Related

Meteor Collection.insert() not returning Id

I'm trying to get the Id of the new insert so I can push the Id onto another collection.
According to this post =>
Meteor collection.insert callback to return new id and this post => Meteor collection.insert callback issues, I should be able to
return Collection.insert(obj);
and it will return the ID of the newly inserted data to my client.
Instead I'm getting an Observable like this:
{_isScalar: false}
_isScalar: false
__proto__:
constructor: f Object()
hasOwnProperty: f hasOwnProperty()
//many other object properties
The documentation seems pretty clear that I should be getting the ID in return. Is this a bug? https://docs.meteor.com/api/collections.html#Mongo-Collection-insert]
My version of meteor is 1.4.4.5...
I've been working on this issue for a few days and I've tried getting the ID many different ways, nothing I've tried results in the ID.
Here's my full code for reference:
Server:
submitStuff: function(data): string{
var loggedInUser = Meteor.user();
if (!loggedInUser || !Roles.userIsInRole(loggedInUser,['user','admin'])){
throw new Meteor.Error("M504", "Access denied");
} else {
try{
let insertedData = null;
const model: MyModel[] = [{
data.stuff //bunch of data being inserted
}];
model.forEach((obj: MyModel) => {
insertedData = Collection.insert(obj);
});
return insertedData;
} catch(e) {
throw new Meteor.Error(e + e.reason, "|Throw new error|");
}
}
},
Client:
Meteor.call('submitData', data, (error, value) => {
if(error){
this.errors.push(error + error.reason + "|new Error code|");
}
else{
Meteor.call('userPushId', value, (error) => { //this pushes Id onto the second collection
if(error){
this.errors.push(error + error.reason + "|new Error code|");
}
});
}
Server
// ...
try {
const myModels = [{ ...whatever }];
// I'm using map so I return an array of id's.
//your forEach about technically should end up with only one id,
// which is the last insertion
const insertedDataIds = myModels.map(model => Collection.insert(model));
// you can also write to the secondary location here with something like:
const secondaryWrite = SecondaryCollection.insert({ something: insertedDataIds });
return insertedDataId's
}
//...
Client
also I don't know if this is just a typo on stack but your Meteor.call('submitData') should be Meteor.call('submitStuff') but that is probably not your actual issue.
Alright, so after searching around I realized I'm using angular-meteors rxjs package to create a MongoObservable when creating a collection instead of Mongo's regular collection.
Because I'm using MongoObservable and trying to invoke .insert() on that, the return is a bit different then a regular Mongo Collection and will return an Observable instead of the Id. Documentation Here
If you add a .collection after the collection name you will get the Id in return like so:
submitStuff: function(data): string{
var loggedInUser = Meteor.user();
if (!loggedInUser || !Roles.userIsInRole(loggedInUser,['user','admin'])){
throw new Meteor.Error("M504", "Access denied");
} else {
try{
let id = new Mongo.ObjectID();
const model: MyModel[] = [{
data.stuff //bunch of data being inserted
_id:
}];
model.forEach((obj: MyModel) => {
insertedData = Collection.collection.insert(obj); //added .collection
});
return insertedData;
} catch(e) {
throw new Meteor.Error(e + e.reason, "|Throw new error|");
}
}
},

React: Object properties added in LoadDataFrom Server are undefined in component

I’m new to React and have a problem I don’t understand. My data is an array of objects with properties of “sketches” (name, number of “links”, number of “nodes”, and other stuff) from a JSON file. I load the data and send some of it to a list component that I filter by the number of links and nodes. All that works fine. My problem is that I want to add an ID number to what I send to the filtered list (for returning a click). Here is the LoadDataFrom Server part of my code:
const MyApp = React.createClass({
loadDataFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({data: data});
const sketz = [];
for (let i=0; i<data.length; i++) {
const sid = i;
sketz[i] = {sid: sid,
name: data[i].name,
numberOflinks: data[i].numberOflinks,
numberOfnodes: data[i].numberOfnodes
};
}
this.setState({
sketches: sketz
})
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
}, .....
The problem is that, while any property value from data gets to the list component OK, anything else I try to add, such as “sid”, but any other property and value that did not come from data, shows up in the list component as undefined. I have checked sketches in LoadDataFromServer and the assigned IDs are there, but they are gone in the list. I am really not understanding why the sketches object array behaves this way and would like to (1) understand why, and (2) know how do this correctly. Thanks!
Added by request: the list component (actually, two components). It follows the code used in the React docs under the heading of Thinking in React, where it is for a table, not an unordered list. The first console.log (for the ID) gives undefined; the second gives the correct name for each pass through the forEach.
const SketchList = React.createClass({
render: function() {
const rows = [];
const lf = this.props.numlnk; // "link filter"
const nf = this.props.numnde; // "node filter"
this.props.sketches.forEach(function(sketch) {
console.log('in sketch, ID is ' + sketch.sid);
console.log('in sketch, name is ' + sketch.name);
if(lf == 0 || lf == sketch.numberOflinks) {
if (nf == 0 || nf == sketch.numberOfnodes) {
rows.push(<SketchRow
sketch={sketch}
key={sketch.name}
/>);
}
}
});
return (
<div className="row voffset3 divcolor">
<ul className="nobull mar-left list-scroll">
{rows}
</ul>
</div>
);
}
});
const SketchRow = React.createClass({
render: function() {
return (
<li >
{
this.props.sketch.name
}
</li>
);
}
});
Thanks Isiah. OPERATOR ERROR. I was resetting sketches to data in the render, left over from an earlier version where I was only working out the filtered list.

Firebase push - deletes unique object and inserts new one (Overwrites content basically)

I have an application that is running with firebase. When I try to use the push() method, it basically overwrites the existing JSON. Here's an example:
First time around, the following JSON is generated:
JSON
"deviceIDs" : {
"-JzCx5C_13eoXPEgklMW" : {
"author" : "gracehop22",
"deviceID" : "99alpha",
"title" : "Announcing COBOL, a New Programming Language"
}
}
Next time around, if I call the same function, the above JSON is deleted and a new JSON is inserted such as this one:
JSON
"deviceIDs" : {
"-JzCxbuEj2V1kmvvgqnc" : {
"author" : "gracehop22",
"deviceID" : "99alpha",
"title" : "Announcing COBOL, a New Programming Language"
}
}
Here's my code snippet:
function CreateUserProfile(UID, name, email, deviceID) {
var ref = new Firebase($scope.firebaseurl + '/' + UID);
var profileArray = {UserProfile:{}};
profileArray.UserProfile.UID = UID;
profileArray.UserProfile.name = name;
profileArray.UserProfile.email = email;
profileArray.UserProfile.deviceID = deviceID;
var onComplete = function (error) {
if (error) {
console.log('Synchronization failed');
} else {
//1. On Success, Store Key User Profile Elements
localStorage.setItem("kasimaProfileInfo",JSON.stringify(profileArray));
$rootScope.username = name;
//2. Hide the feedback and change screens
$timeout(function () {
$scope.HideFeedback();
$scope.ChangeLoc('/featured');
}, 1500);
}
};
ref.set(profileArray, onComplete);
var postsRef = ref.child("deviceIDs");
var newPostRef = postsRef.push();
newPostRef.set({
deviceID: deviceID,
author: "gracehop22",
title: "Announcing COBOL, a New Programming Language"
});
}
You're overwriting the entire ref when you're setting profileArray:
...
ref.set(profileArray, onComplete);
var postsRef = ref.child("deviceIDs");
...
You'll probably want to use update() there:
...
ref.update(profileArray, onComplete);
var postsRef = ref.child("deviceIDs");
...
Update
The Firebase update() functions set the value of the properties in the JSON object you pass it. So your new profileArray.UserProfile will replace the existing data.
The solution is to not build a nested JSON structure locally, but instead update the data at the lower location where it needs updating:
ref.child('UserProfile').update(profileArray.UserProfile, onComplete);
This removes the entire need for the profileArray:
var userProfile = {
UID: UID,
name: name,
email: email,
decideID: deviceID
};
ref.child('UserProfile').update(userProfile, onComplete);
For a working example, see: http://jsbin.com/ciqoge/edit?js,console
For a next time: if you provide such a jsbin/jsfiddle straight away, it will be much easier to quickly help you.

Meteor: How to publish cursor that is depending on cursor of other collection

I'm having the following data structure in my Meteor project:
- Users with a set of list-ids that belong to the user (author)
- Lists that actually contain all the data of the list
Now I'm trying to publish all Lists of a user to the client. Here is a simple example:
if (Meteor.isClient) {
Lists = new Meteor.Collection("lists");
Deps.autorun(function() {
Meteor.subscribe("lists");
});
Template.hello.greeting = function () {
return "Test";
};
Template.hello.events({
'click input' : function () {
if (typeof console !== 'undefined')
console.log(Lists.find());
}
});
}
if (Meteor.isServer) {
Lists = new Meteor.Collection("lists");
Meteor.startup(function () {
if ( Meteor.users.find().count() === 0 ) {
Accounts.createUser({ //create new user
username: 'test',
email: 'test#test.com',
password: 'test'
});
//add list to Lists and id of the list to user
var user = Meteor.users.findOne({'emails.address' : 'test#test.com', username : 'test'});
var listid = new Meteor.Collection.ObjectID().valueOf();
Meteor.users.update(user._id, {$addToSet : {lists : listid}});
Lists.insert({_id : listid, data : 'content'});
}
});
Meteor.publish("lists", function(){
var UserListIdsCursor = Meteor.users.find({_id: this.userId}, {limit: 1}).lists;
if(UserListIdsCursor!=undefined){
var UserListIds = UserListIdsCursor.fetch();
return Lists.find({_id : { $in : UserListIds}});
}
});
Meteor.publish("mylists", function(){
return Meteor.users.find({_id: this.userId}, {limit: 1}).lists;
});
//at the moment everything is allowed
Lists.allow({
insert : function(userID)
{
return true;
},
update : function(userID)
{
return true;
},
remove : function(userID)
{
return true;
}
});
}
But publishing the Lists doesn't work properly. Any ideas how to fix this? I'm also publishing "mylists" to guarantee that the user has access to the field "lists".
Solution
Lists = new Meteor.Collection('lists');
if (Meteor.isClient) {
Tracker.autorun(function() {
if (Meteor.userId()) {
Meteor.subscribe('lists');
Meteor.subscribe('myLists');
}
});
}
if (Meteor.isServer) {
Meteor.startup(function() {
if (Meteor.users.find().count() === 0) {
var user = {
username: 'test',
email: 'test#test.com',
password: 'test'
};
var userId = Accounts.createUser(user);
var listId = Lists.insert({data: 'content'});
Meteor.users.update(userId, {
$addToSet: {lists: listId}
});
}
});
Meteor.publish('lists', function() {
check(this.userId, String);
var lists = Meteor.users.findOne(this.userId).lists;
return Lists.find({_id: {$in: lists}});
});
Meteor.publish('myLists', function() {
check(this.userId, String);
return Meteor.users.find(this.userId, {fields: {lists: 1}});
});
}
Changes
Declare the Lists collection outside of the client and server (no need to declare it twice).
Ensure the user is logged in when subscribing. (performance enhancement).
When inserting the test user, use the fact that all insert functions return an id (reduces code).
Ensure the user is logged in when publishing.
Simplified lists publish function.
Fixed myLists publish function. A publish needs to return a cursor, an array of cursors, or a falsy value. You can't return an array of ids (which this code doesn't access anyway because you need to do a fetch or a findOne). Important note - this publishes another user document which has the lists field. On the client it will be merged with the existing user document, so only the logged in user will have lists. If you want all users to have the field on the client then I'd recommend just adding it to the user profiles.
Caution: As this is written, if additional list items are appended they will not be published because the lists publish function will only be rerun when the user logs in. To make this work properly, you will need a reactive join.
The real problem here is the schema.
Don't store "this user owns these lists" eg, against the users collection. Store "this list is owned by this user"
By changing your example to include an ownerId field on each List then publishing becomes easy - and reactive.
It also removes the need for the myLists publication, as you can just query client side for your lists.
Edit: If your schema also includes a userIds field on each List then publishing is also trivial for non-owners.
Solution
Lists = new Meteor.Collection('lists');
if (Meteor.isClient) {
Deps.autorun(function() {
if (Meteor.userId()) {
Meteor.subscribe('lists.owner');
Meteor.subscribe('lists.user');
}
});
}
if (Meteor.isServer) {
Lists._ensureIndex('userIds');
Lists._ensureIndex('ownerId');
Meteor.startup(function() {
if (Meteor.users.find().count() === 0) {
var user = {
username: 'test',
email: 'test#test.com',
password: 'test'
};
var userId = Accounts.createUser(user);
var listId = Lists.insert({data: 'content', ownerId: userId});
}
});
//XX- Oplog tailing in 0.7 doesn't support $ operators - split into two publications -
// or change the schema to include the ownerId in the userIds list
Meteor.publish('lists.user', function() {
check(this.userId, String);
return Lists.find({userIds: this.userId});
});
Meteor.publish('lists.owner', function() {
check(this.userId, String);
return Lists.find({ownerId: this.userId});
});
}
Meteor.users.find() returns a cursor of many items but you're trying to access .lists with
Meteor.users.find({_id: this.userId}, {limit: 1}).lists;
You need to use findOne instead.
Meteor.users.findOne({_id: this.userId}).lists;
Additionally you're running .fetch() on an array which is stored in the user collection. If this is an array of ._id fields you don't need fetch.
You can't also do .lists in your second publish because its a cursor you have to check lists client side so just use Meteor.users.find(..) on its own since you can only publish cursors.

Wrong collection.length when passing JSON array to Backbone Collection

I'm new to Backbone, and I'm very confused about what's happening when I pass a JSON array (of objects) to a Backbone Collection.
I'm fetching some JSON from a spreadsheet hosted on Google Drive. I'm parsing that data, as the actual data that I want to use in my collection is deeply nested. In my parse function, if I log the length of my desired array, I get 157 (that's correct). I then pass that array into a Backbone Collection, and the length of my collection is 1 (incorrect). It's as though foo.bar.length = 157, but there is only one 'bar' in 'foo', so when I pass foo.bar into the collection, it takes foo.bar and not the contents of foo.bar! Very confused.
Code below...
var table = new TableView();
TableItem = Backbone.Model.extend(),
TableItemCollection = Backbone.Collection.extend( {
model : TableItem,
url : 'https://spreadsheets.google.com/feeds/list/0AjbU8ta9j916dFdjSVg3YkNPUUJnWkZSWjBDWmZab3c/1/public/basic?alt=json-in-script',
sync : function( method, model, options ) {
var params = _.extend( {
type: 'GET',
dataType: 'jsonp',
url: this.url,
processData: false
}, options );
return $.ajax( params );
},
parse : function( resp, xhr ) {
console.log( resp.feed.entry.length ); // THIS LOGS 157
return resp.feed.entry;
}
} ),
TableView = Backbone.View.extend( {
initialize : function ( options ) {
this.collection = new TableItemCollection();
this.collection.on( 'reset', this.parseResponse, this );
this.collection.fetch( {
reset : true,
success : function ( model, response, options ) {
console.log( 'OK' ); // THIS LOGS 'OK'
},
error : function ( model, response, options ) {
console.log( 'ERROR' );
}
} );
},
parseResponse : function () {
console.log( this.collection.length ); // THIS LOGS 1
}
} );
If you dump one of the items returned by Google Spreadsheets, you will see that the data is nested in multiple objects, something like this
{
"id":{"$t":"https://spreadsheets.google.com/feeds/list/..."},
"updated":{"$t":"2013-07-30T12:01:24.000Z"},
"category":[{"scheme":"...","term":"..."}],
"title":{"type":"text","$t":"ACIW"},
"content":{},
"link":[{"rel":"self","type":"application/atom+xml","href":"..."}]
}
In a Fiddle http://jsfiddle.net/nikoshr/kHBvY/
Note how the id property is wrapped in an object "id":{"$t":"https://spreadsheets.google.com/feeds/list/0AjbU8ta9j916dFdjSVg3YkNPUUJnWkZSWjBDWmZab3c/1/public/basic/cokwr"}
Backbone collections don't allow duplicates and duplicates are determined based on their id. All your items are considered duplicates and collapsed into one. If you remove the id or disambiguate it, you will get your 157 items. For example,
parse : function( resp, xhr ) {
var data = resp.feed.entry, i;
console.log(data.length); // THIS LOGS 157
for (i=data.length-1; i>=0; i--)
data[i].id = data[i].id['$t'];
return data;
}
http://jsfiddle.net/nikoshr/kHBvY/2/ for a demo
You probably will have to unwrap all the attributes to use them in a non hair pulling way.

Categories

Resources