Passing nested knockout view model to controller - javascript

This is fleet management app in mvc EF and knockout , there are VEHICLES , DRIVERS and specific ROUTES that the drivers travel.
I have a situation where I would like to gather field values from three models nested within my view models into a single object and post it my backend via ajax. I want this to be triggered by clicking a button.
There is a problem is in my populateFleetInformation function. When I gather the field values from my various models, some of the observables are losing their values and appear as null.
The view model that attempts to make the ajax call
function FleetViewModel() {
var vvm = this;
vvm.regNumber = ko.observable(0);
vvm.make = ko.observable("");
vvm.model = ko.observable("");
vvm.RouteDetail = new RouteViewModel();
vvm.SaveFleetInfo = function(item){
if (!pageViewModel.isAuthenticated()) return;
populateFleetInformation(item);
$.when(postSecureData("/api/Fleet/", ko.toJSON(pageViewModel.FleetViewModel.RouteViewModel.RouteDriverViewModel)))
.done(function () {
document.getElementById("save-Fleet-btn").innerHTML = "Saving...";
setTimeout(function () { document.getElementById("save-fleet-btn").innerHTML = "Save" }, 2500);
$.msgGrowl({
type: 'info',
title: 'Fleet Information',
text: 'fleet information succesfully saved',
position: 'bottom-right',
lifetime: 3000
});
})
}
}
function that gathers data
function PopulateFleetInformation(item)
{
pageViewModel.fleetVM.regNumber(item.regNumber);
pageViewModel.fleetVM.make(item.make);
pageViewModel.fleetVM.model.(item.model);
pageViewModel.fleetVM.routeDetail.routeID(item.routeId);
pageViewModel.fleetVM.routeDetail.routeName(item.routeName);
pageViewModel.fleetVM.routeDriver.nationalId(item.nationalId);
pageViewModel.fleetVM.individualMsisdn.licenseId(item.licenseId);
pageViewModel.fleetVM.individualMsisdn.driverName(item.driverName);
}
The trigger button
<button type="submit" id="save-fleet-btn" class="btn"data-bind="click: $root.fleetVM.sensorDetail.SaveFleetInfo"></button>

This function:
function PopulateFleetInformation(item)
{
pageViewModel.fleetVM.regNumber(item.regNumber);
pageViewModel.fleetVM.make(item.make);
pageViewModel.fleetVM.model.(item.model);
pageViewModel.fleetVM.routeDetail.routeID(item.routeId);
pageViewModel.fleetVM.routeDetail.routeName(item.routeName);
pageViewModel.fleetVM.routeDriver.nationalId(item.nationalId);
pageViewModel.fleetVM.individualMsisdn.licenseId(item.licenseId);
pageViewModel.fleetVM.individualMsisdn.driverName(item.driverName);
}
is not doing what you think it is at all.
itemis coming from the context passed into SaveFleetInfo by knockout when your button is clicked. This function is then setting the values of the fields you are trying to retrieve to whatever is in item; I'm guessing you're getting all sorts of undefined exceptions here.
What you want to do is this:
function PopulateFleetInformation()
{
return {
regNumber: pageViewModel.fleetVM.regNumber();
make: pageViewModel.fleetVM.make();
model: pageViewModel.fleetVM.model.();
routeId: pageViewModel.fleetVM.routeDetail.routeID();
routeName: pageViewModel.fleetVM.routeDetail.routeName();
nationalId: pageViewModel.fleetVM.routeDriver.nationalId();
licenseId: pageViewModel.fleetVM.individualMsisdn.licenseId();
driverName: pageViewModel.fleetVM.individualMsisdn.driverName();
}
}
This function returns the values you are after as an object, then you can post this data like this:
var data = populateFleetInformation();
$.when(postSecureData("/api/Fleet/", ko.toJSON(data)))

Related

Angular.js factory object not refreshed in view

I have single view displaying investments + two others which are modals to register new investment which show up when user clicks 'add' (two modals because of two steps of registration). I created factory which is used in step1 and then in step2 in order to keep information regarding investment being registered - it works when you switch between step1 and step2, back and forth.
The problem is that within first view displaying investments I have icon "edit" and within its handler (edit method) I assign selected investment to factory but no change is reflected in step1 view, alas.
View displaying investments:
var module = angular.module("application", []);
module.controller("investmentsController", function ($scope, investmentsFactory, newInvestmentFactory) {
$scope.edit = function (id) {
for (var i = 0; i < $scope.Investments.length; i++) {
if ($scope.Investments[i].Id == id) {
newInvestmentFactory.update($scope.Investments[i]);
}
}
$("#addInvestmentStep1Modal").modal("show");
};
});
View step1 of registration
var module = angular.module("application");
module.factory("newInvestmentFactory", function () {
return {
investment: {
Name: "",
Description: "",
Category: "",
InterestRate: "",
Duration: "",
AmountRange: "",
Url: "",
Subscription: 0,
PaymentType: "",
Icon: ""
},
update: function (investment) {
this.investment = investment;
}
};
});
module.controller("newInvestmentStep1Controller", function ($scope, newInvestmentFactory) {
$scope.Investment = newInvestmentFactory.investment;
});
View step2 of registration
var module = angular.module("application");
module.controller("newInvestmentStep2Controller", function ($scope, newInvestmentFactory) {
$scope.Investment = newInvestmentFactory.investment;
});
The step1 view displaying registration is following
<form id="newInvestmentStep1Form" class="form-horizontal">
<div class="input-group">
<span class="input-group-addon input-group-addon-register">Name</span>
<input id="Name" name="Name" type="text" class="form-control" ng-model="Investment.Name" required title="Pole wymagane" />
</div>
Assignining new object to factory's object (newInvestmentFactory.investment) does not seem to be working but when I assign brand new value to some property of factory like
newInvestmentFactory.investment.Name = "name"
then it displays value correctly.
I can only suspect newInvestmentFactory's update method code. It is reassigning investment object to new investment object like this.investment = investment. By that line new investment object gets created, and old investment loose the reference. To keep the investment object to not create a new variable in update method, you could use angular.extend/angular.merge method. This method will not create a new reference of an object, but it ensures that all object property got updated.
update: function (investment) {
angular.extend(this.investment, investment);
}
In your step controllers
$scope.Investment = newInvestmentFactory.investment;
is just one time assignment to $scope variable, this is not two way binding, so even if value of newInvestmentFactory.investment changes scope won't be updated. What you can do is to watch the factory variable newInvestmentFactory.investment and on change update the scope manually.
Hope this helps

Meteor: Insert Checkbox (as Boolean) into Sub-Schema (aldeed2)

I'm trying to insert the checkbox value (as boolean) into a subschema of my collection. Not clear on 1) how to pass the checkbox value (can do it for normal input field) and 2) how to insert into subschema. I am using collection2 and handlebars.
1-This is what I have in the HTML form that needs to be submitted:
`<div class="checkbox">
<label><input type="checkbox" id="byow" checked="{{isChecked}}" value="">Bring Your Own Wine</label></div>`
2-This is what I have in my helper (in controller) to get the value of the form and the checkbox value, submit it and call the method that inserts it into the collection:
`BackendController.events({
//Add Venue - Add New Venue Submit Form Helper
'submit #add-venue-form' : function(event) {
event.preventDefault();
var venueName = event.target.venueName.value;
var byow = event.target.byow.checked;
var params = {
venueName: venueName,
byow: byow
}
//Insert Venue
Meteor.call('addVenue', params);
toastr.success('VenueAdded');
Router.go('/admin/manage-venues')
}
3-This is my method that is called to insert into my Venues collection (first part) and the structure of my collection and sub-collection:
`Meteor.methods({
'addVenue': function (params) {
Venues.insert(params);
}
// MAIN SCHEMA for the Venues colleciton.
Schema.Venues = new SimpleSchema({
venueName: {
type: String,
label: "Venue Name",
max: 200,
optional: false
},
//Attach schema for venue attributes (cuisine type, amenities, etc)
venueAttributes: {
type: Schema.VenueAttributes,
optional: true
}
});
//schema for venue attributes. Attached to main schema
Schema.VenueAttributes = new SimpleSchema({
byow: {
type: Boolean,
optional: true
}
});
Would really appreciate any help - I've managed to get the venueName to be passed successfully (so all my permissions/pub/sub is correct) but stuck at checkbox and subcollection.
Thanks!
Dan.
Finally figured it out with the help of #lokenx on Meteor Chef Slack channel.
The checkbox code (first part) returned the correct value (true) when written as above.
My error was that I was not specifying byow as a subproperty (was treating it as root property of the simpleschema). The correct params I should have been passing are:
var params = {
venuaName: venueName,
venueAttributes: {
byow: byow
}
}

Pass Angular typeahead object result to another function in controller

In this scenario I'm using the ui-bootstrap typeahead to capture an object from an external api. Using the select callback I'm getting that object and have the results set in a separate function within my controller.
The issue is that I want to take those results and send them off to a separate api with a click function I already have set up. My question is how do i get the results of the type-ahead into the click function to post? The user flow is as follows.
<div>
<input type="text" placeholder="Find A Game"
typeahead-on-select="setGames($item)"
ng-model="asyncSelected"
typeahead="test.name for test in getGames($viewValue)"
typeahead-loading="loadingLocations" typeahead-min-length="3"
typeahead-wait-ms="500" typeahead-select-on-blur="true"
typeahead-no-results="noResults">
</div>
<div ng-show="noResults">
No Results Found
</div>
<button ng-disabled="!asyncSelected.length"
ng-click="addtodb(asyncSelected)">Add To Database</button>
As you can see the label is set to the items name and this works fine. When the user selects the name I then use typeahead-on-select="setGames($item)" to send off the entire object to its own funtion. From there I want to take the object and pass it to another function that you can see within the button tags ng-click. I currently have it passing the model, but what I really want is to pass the entire object within $item from the select event. So far my controller looks like this:
angular.module('2o2pNgApp')
.controller('GiantCtrl', function ($scope, $http, TermFactory, $window, SaveFactory) {
$scope.getGames = function(val) {
return $http.jsonp('http://www.example.com/api/search/?resources=game&api_key=s&format=jsonp&limit=5&json_callback=JSON_CALLBACK', {
params: {
query: val
}
}).then(function(response){
return response.data.results.map(function(item){
return item;
});
});
};
$scope.setGames = function (site) {
var newsite = site;
};
$scope.addtodb = function (asyncSelected, newsite) {
TermFactory.get({name: asyncSelected}, function(data){
var results = data.list;
if (results === undefined || results.length === 0) {
SaveFactory.save({vocabulary:'5', name:newsite.name, field_game_id:newsite.id}, function(data) {
$window.alert('All Set, we saved '+asyncSelected+' into our database for you!')
});
} else {
// do stuff
});
}
});
No matter what I do I cant seem to pass the entire $item object into this click function to post all the info i need.
Via New Dev in Comments:
$item is only available locally for typeahead-on-select... you can
either assign it to some model within your controller, or, in fact,
make the model of typeahead to be the item: typeahead="test as
test.name for test in getGames($viewValue)" – New Dev

Rendering collection data in a template using a reactive join with Meteor JS

I would like to output data from two collections using a reactive join into my template, then pair the users, post and comments through a common id.
So far, I can see with Mongo commands that the JSON data exist, but my template doesn't render any data. What am I doing wrong?
FYI, the meteorpad doesn't compile but the github repo will.
Repo:
https://github.com/djfrsn/frontend-interview-test-long/tree/master/ontra/ontra
Meteor Pad Example:
http://meteorpad.com/pad/SvwkNrv5grgv2uXxH/Copy%20of%20Leaderboard
There's so much wrong that it's hard to know where to start.
1) When you're loading the initial post and user data you're inserting the whole returned array as one element rather than inserting each element individually into your posts collection.
2) You're creating a publish subscription with the name "postsSet", but you're trying to subscribe to it with a different name.
3) You're not calling publishComposite correctly at all. You should be publishing the user required for each post as part of the children array.
4) The template needs updating based on the above
5) The username needs to be supplied via a helper.
6) You should really map the "id" attributes to Mongo's "_id" instead.
Here's come code which works. Note that you'll need to call meteor reset everytime you restart, otherwise you'll get duplicate id errors since you currently reimport the data every time.
Posts = new Mongo.Collection("Posts");
var groundPosts = new Ground.Collection(Posts);
Users = new Mongo.Collection("Users");
var groundUsers = new Ground.Collection(Users);
if (Meteor.isClient) {
Meteor.subscribe("postsSet");
console.log('POSTS DATA = ' + Posts.find().fetch());
console.log('USERS DATA = ' + Users.find().fetch());
Template.body.events({
"submit .ontra": function (event) {
// This function is called when the new task form is submitted
var text = event.target.text.value;
Posts.insert({
content: text,
date: new Date() // current time
});
// Clear Form
event.target.text.value = "";
// Prevent default form submit
return false
}
});
Template.body.helpers({
posts: function() {
return Posts.find();
},
});
Template.post.helpers({
username: function() {
return Users.findOne({_id: this.userId}).username;
}
});
}
Meteor.methods({
'fetchJSONData': function() {
var postsResponse = Meteor.http.call("GET","https://raw.githubusercontent.com/djfrsn/frontend-interview-test-long/master/codetest/data/posts.json");
var usersResponse = Meteor.http.call("GET","https://raw.githubusercontent.com/djfrsn/frontend-interview-test-long/master/codetest/data/users.json");
var postsData = JSON.parse(postsResponse.content);
var usersData = JSON.parse(usersResponse.content);
postsData.forEach(function (post) {
post.date = new Date();
post._id = String(post.id)
delete post.id
post.userId = String(post.userId)
Posts.insert(post);
});
usersData.forEach(function (user) {
user.date = new Date() // current time
user._id = String(user.id)
delete user.id
Users.insert(user);
});
}
});
if (Meteor.isServer) {
Meteor.publishComposite('postsSet', {
find: function () {
return Posts.find({});
},
children: [
{
find: function (post) {
console.log("%j", post.userId);
console.log("%j", Users.findOne({ _id: post.userId }));
return Users.find({ _id: post.userId });
}
}
]
});
Meteor.call("fetchJSONData");
//console.log('POSTS DATA = %j', Posts.find().fetch());
//console.log('USERS DATA = %j', Users.find().fetch());
}
HTML:
<head>
<title>ontra</title>
</head>
<body>
<div class='container'>
<header>
<h1>ontra</h1>
<form class='ontra'>
<input type='text' name='text' placeholder="Type to add new post">
</form>
</header>
<ul>
{{#each posts}}
{{> post}}
{{/each}}
</ul>
</div>
</body>
<template name='post'>
<li>
<span class="text">{{content}}</span>
<span class="text">{{username}}</span>
</li>
</template>
Your code doesn't run on meteorpad because the fetchJSONData method is executed on the server before it is defined in the common.js file. You should probably be calling the method after an event triggered on the client, or not use a method at all and simply fetch your JSON data on Meteor.startup.
Regarding the reactive join, it seems you want to do something very similar to Example 1 of the documentation: https://github.com/englue/meteor-publish-composite

Having issues tying together basic javascript chat page

I have the skeleton of a chat page but am having issues tying it all together. What I'm trying to do is have messages sent to the server whenever the user clicks send, and also, for the messages shown to update every 3 seconds. Any insights, tips, or general comments would be much appreciated.
Issues right now:
When I fetch, I append the <ul class="messages"></ul> but don't want to reappend messages I've already fetched.
Make sure my chatSend is working correctly but if I run chatSend, then chatFetch, I don't retrieve the message I sent.
var input1 = document.getElementById('input1'), sendbutton = document.getElementById('sendbutton');
function IsEmpty(){
if (input1.value){
sendbutton.removeAttribute('disabled');
} else {
sendbutton.setAttribute('disabled', '');
}
}
input1.onkeyup = IsEmpty;
function chatFetch(){
$.ajax({
url: "https://api.parse.com/1/classes/chats",
dataType: "json",
method: "GET",
success: function(data){
$(".messages").clear();
for(var key in data) {
for(var i in data[key]){
console.log(data[key][i])
$(".messages").append("<li>"+data[key][i].text+"</li>");
}
}
}
})
}
function chatSend(){
$.ajax({
type: "POST",
url: "https://api.parse.com/1/classes/chats",
data: JSON.stringify({text: $('input1.draft').val()}),
success:function(message){
}
})
}
chatFetch();
$("#sendbutton").on('click',chatSend());
This seems like a pretty good project for Knockout.js, especially if you want to make sure you're not re-appending messages you've already sent. Since the library was meant in no small part for that sort of thing, I think it would make sense to leverage it to its full potential. So let's say that your API already takes care of limiting how many messages have come back, searching for the right messages, etc., and focus strictly on the UI. We can start with our Javascript view model of a chat message...
function IM(msg) {
var self = this;
self.username = ko.observable();
self.message = ko.observable();
self.timestamp = ko.observable();
}
This is taking a few liberties and assuming that you get back an IM object which has the name of the user sending the message, and the content, as well as a timestamp for the message. Probably not too far fetched to hope you have access to these data elements, right? Moving on to the large view model encapsulating your IMs...
function vm() {
var self = this;
self.messages = ko.observableArray([]);
self.message = ko.observable(new IM());
self.setup = function () {
self.chatFetch();
self.message().username([user current username] || '');
};
self.chatFetch = function () {
$.getJSON("https://api.parse.com/1/classes/chats", function(results){
for(var key in data) {
// parse your incoming data to get whatever elements you
// can matching the IM view model here then assign it as
// per these examples as closely as possible
var im = new IM();
im.username(data[key][i].username || '');
im.message(data[key][i].message || '');
im.timestamp(data[key][i].message || '');
// the ([JSON data] || '') defaults the property to an
// empty strings so it fails gracefully when no data is
// available to assign to it
self.messages.push(im);
}
});
};
}
All right, so we have out Javascript models which will update the screen via bindings (more on that in a bit) and we're getting and populating data. But how do we update and send IMs? Well, remember that self.message object? We get to use it now.
function vm() {
// ... our setup and initial get code
self.chatSend = function () {
var data = {
'user': self.message().username(),
'text': self.message().message(),
'time': new Date()
};
$.post("https://api.parse.com/1/classes/chats", data, function(result) {
// do whatever you want with the results, if anything
});
// now we update our current messages and load new ones
self.chatFetch();
};
}
All right, so how do we keep track of all of this? Through the magic of bindings. Well, it's not magic, it's pretty intense Javascript inside Knockout.js that listens for changes and the updates the elements accordingly, but you don't have to worry about that. You can just worry about your HTML which should look like this...
<div id="chat">
<ul data-bind="foreach: messages">
<li>
<span data-bind="text: username"></span> :
<span data-bind="text: message"></span> [
<span data-bind="text: timestamp"></span> ]
</li>
</ul>
</div>
<div id="chatInput">
<input data-bind="value: message" type="text" placeholder="message..." />
<button data-bind="click: $root.chatSend()">Send</button>
<div>
Now for the final step to populate your bindings and keep them updated, is to call your view model and its methods...
$(document).ready(function () {
var imVM = new vm();
// perform your initial search and setup
imVM.setup();
// apply the bindings and hook it all together
ko.applyBindings(imVM.messages, $('#chat')[0]);
ko.applyBindings(imVM.message, $('#chatInput')[0]);
// and now update the form every three seconds
setInterval(function() { imVM.chatFetch(); }, 3000);
});
So this should give you a pretty decent start on a chat system in an HTML page. I'll leave the validation, styling, and prettifying as an exercise to the programmer...

Categories