I'm trying to implement knockout.js in Rails 3.2.6. I'm using the Gem https://github.com/jswanner/knockoutjs-rails.
I'm having trouble loading the ViewModel.
Here's my HTML for debugging
<div data-bind="text: ko.toJSON(users)"></div>
Here's the JavaScript compiled from CoffeeScript. The file is users.js.coffee and is included in the asset pipeline via //= require_tree . in application.js
(function() {
jQuery(function() {
var User, UserViewModel;
User = function(id, name) {
var self;
self = this;
self.id = ko.observable(id);
self.name = ko.observable(name);
return self.followers_count_message = ko.computed(function() {});
};
UserViewModel = function() {
var self;
self = this;
self.users = ko.observableArray([new User('1111', 'test name'), new User('1112', 'test name2')]);
self.addUsers = function() {
return self.users.push(new User('1113', 'test name'));
};
return self.addUsers();
};
ko.applyBindings(new UserViewModel());
return alert('done');
});
}).call(this);
Upon loading, the div displays this: [null,null,null]
The odd thing is if I implement this in jsFiddle, it seems to work. See this: http://jsfiddle.net/netwire88/HXYHU/2/
Thoughts, ideas?
UPDATE: 7/31/2012
I was able to fix this by changing the functions to remove the return. However, I would like to move it to another CoffeeScript file.
I tried this in users.js.coffee, however, I'm getting error Uncaught TypeError: Cannot read property 'nodeType' of null
User = (id, name) ->
self = undefined
self = this
self.id = ko.observable(id)
self.name = ko.observable(name)
#
UserViewModel = ->
self = this
self.users = ko.observableArray([new User("1111", "test name"), new User("1112", "test name2")])
self.addUsers = ->
self.users.push new User("1113", "test name")
self.addUsers()
#
ko.applyBindings new UserViewModel()
You should notice a very, very important difference between the code you posted, and the code in the fiddle: your viewmodels return single properties, the fiddle's viewmodels have no return.
Your code is creating invalid viewmodels. If you uncomment those returns in your fiddle, it breaks in exactly the same way. You didn't post the coffeescript for us to look at, but this is definitely the issue.
Bad Viewmodel:
User = function(id, name) {
var self;
self = this;
self.id = ko.observable(id);
self.name = ko.observable(name);
return self.followers_count_message = ko.computed(function() {});
};
Good Viewmodel
User = function(id, name) {
var self;
self = this;
self.id = ko.observable(id);
self.name = ko.observable(name);
};
Same goes for the UserViewModel.
Related
Scenario: In our application a user can create a invoice by filling in certain fields on a Knockout view. This invoice can be previewed, via another Knockout page. I want to use the preview url within our PDF creator (EVOPdf), so we can provide the user with a PDF from this invoice.
To preview the invoice we load the data (on document ready) via an ajax-request:
var InvoiceView = function(){
function _start() {
$.get("invoice/GetInitialData", function (response) {
var viewModel = new ViewModel(response.Data);
ko.applyBindings(viewModel, $("#contentData").get(0));
});
};
return{
Start: _start
};
}();
My problem is within the data-binding when the PDF creator is requesting the url: the viewModel is empty. This makes sense because the GetInitialData action is not called when the PDF creator is doing the request. Calling this _start function from the preview page directly at the end of the page does not help either.
<script type="text/javascript">
$(document).ready(function() {
InvoiceView.Start();
});
</script>
Looking at the documentation of EvoPdf, JavaScript should be executed, as the JavaScriptEnabled is true by default: http://www.evopdf.com/api/index.aspx
How could I solve this, or what is the best approach to create an pdf from a knockout view?
Controller action code:
public FileResult PdfDownload(string url)
{
var pdfConverter = new PdfConverter();
// add the Forms Authentication cookie to request
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
pdfConverter.HttpRequestCookies.Add(
FormsAuthentication.FormsCookieName,
Request.Cookies[FormsAuthentication.FormsCookieName].Value);
}
var pdfBytes = pdfConverter.GetPdfBytesFromUrl(url);
return new FileContentResult(pdfBytes, "application/pdf");
}
Javascript:
var model = this;
model.invoiceToEdit = ko.observable(null);
model.downloadInvoice = function (invoice) {
model.invoiceToEdit(invoice);
var url = '/invoice/preview';
window.location.href = '/invoice/pdfDownload?url=' + url;
};
The comment of xdumaine prompted me to think into another direction, thank you for that!
It did take some time for the Ajax request to load, but I also discovered some JavaScript (e.g. knockout binding) errors along the way after I put a ConversionDelay on the pdf creator object
pdfConverter.ConversionDelay = 5; //time in seconds
So here is my solution for this moment, which works for me now:
To start the process a bound click event:
model.downloadInvoice = function (invoice) {
var url = '/invoice/preview/' + invoice.Id() + '?isDownload=true';
window.open('/invoice/pdfDownload?url=' + url);
};
which result in a GET resquest on the controller action
public FileResult PdfDownload(string url)
{
var pdfConverter = new PdfConverter { JavaScriptEnabled = true };
// add the Forms Authentication cookie to request
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
{
pdfConverter.HttpRequestCookies.Add(
FormsAuthentication.FormsCookieName,
Request.Cookies[FormsAuthentication.FormsCookieName].Value);
}
pdfConverter.ConversionDelay = 5;
var absolutUrl = ToAbsulte(url);
var pdfBytes = pdfConverter.GetPdfBytesFromUrl(absolutUrl);
return new FileContentResult(pdfBytes, "application/pdf");
}
The Pdf creator is requesting this action on the controller, with isDownload = true (see bound click event):
public ActionResult Preview(string id, bool isDownload = false)
{
return PartialView("PdfInvoice", new InvoiceViewModel
{
IsDownload = isDownload,
InvoiceId = id
});
}
Which returns this partial view:
PartialView:
// the actual div with bindings etc.
#if (Model.IsDownload)
{
//Include your javascript and css here if needed
#Html.Hidden("invoiceId", Model.InvoiceId);
<script>
$(document).ready(function () {
var invoiceId = $("#invoiceId").val();
DownloadInvoiceView.Start(invoiceId);
});
</script>
}
JavaScript for getting the invoice and apply the knockout bindings:
DownloadInvoiceView = function() {
function _start(invoiceId) {
$.get("invoice/GetInvoice/" + invoiceId, function(response) {
var viewModel = new DownloadInvoiceView.ViewModel(response.Data);
ko.applyBindings(viewModel, $("#invoiceDiv").get(0));
});
};
return {
Start: _start
};
}();
DownloadInvoiceView.ViewModel = function (data) {
var model = this;
var invoice = new Invoice(data); //Invoice is a Knockout model
return model;
};
I have a problem with module.export on titanium. I tried following https://wiki.appcelerator.org/display/guides/CommonJS+Modules+in+Titanium but it doesn't work at all.
I have 2 little pieces of code. App.js:
var fenetreBase = Titanium.UI.createWindow({fullscreen:true,backgroundColor:"white",exitOnClose:true});
fenetreBase.open();
var vueimage = new (require('UI/viewimage'))();
vueimage.test();
fenetreBase.add(vueimage);
and viewimage.js in the folder UI.
function viewimage() {
var lavue = Ti.UI.createView({backgroundColor:'red' });
var item =...
lavue.add(item...);
return lavue;
}
viewimage.prototype.test = function() {
Ti.API.info("test");
};
module.exports = viewimage;
I have an error saying
Object #<view> has no method 'test' in app.js vueimage.test()
In my mind, I follow the example of "Instantiable Objects" in the wiki above but I may have not understand something. I expect I made a stupid mistake. I tried many other things, each uglier than other and it doesn't work anyway.
Can somebody tell me where the mistake is?
your mistake is assuming that you have an instance of viewimage when you run:
var vueimage = new (require('UI/viewimage'))();
you are getting an instance of
var lavue = Ti.UI.createView({backgroundColor:'red' });
which doesn't have a test property.
Perhaps you could use an object like this instead:
function viewimage() {
var result = {};
var lavue = Ti.UI.createView({backgroundColor:'red' });
var item =...
lavue.add(item...);
result.lavue = lavue;
result.test = function() {
Ti.API.info("test");
};
return result;
}
EDIT
In your App.js:
var vueimage = new (require('UI/viewimage'))();
vueimage.test();
fenetreBase.add(vueimage.lavue);
I'm trying to work on knockout and unable to validate using knockout validation plugin.
[EDIT: update fiddle]
sample fiddle: jsfiddle.net/EHDD8
var CustVM = function () {
var self = this;
self.name = ko.observable().extend({ required: "Name is required" });
self.contact = ko.observable();
self.phone1 = ko.observable();
self.email = ko.observable().extend({email: true});
self.website = ko.observable().extend({required: "Website is required"});
self.Errors = ko.validation.group(self);
self.save = function () {
if (self.isValid()) {
alert("no error");
}
else {
alert("error");
}
alert("save clicked ");
};
self.cancel = function() {
alert("cancel clicked");
};
};
ko.applyBindings(new CustVM());
isValid is true even though i have not entered any required elements.
You are not using
ko.validation.registerExtenders();
that's why you are having problem, you can check my this ARticle:-
http://www.c-sharpcorner.com/UploadFile/cd7c2e/apply-knockout-validations-in-mvc-application/
The goal of my code is to search on the API search string:
So if you fill out the form you get the hits bij name.
I used the following Knockout.js script:
var viewModel=
{
query : ko.observable("wis"),
};
function EmployeesViewModel(query)
{
var self = this;
self.employees = ko.observableArray();
self.query = ko.observable(query);
self.baseUri = BASE + "/api/v1/search?resource=employees&field=achternaam&q=";
self.apiurl = ko.computed(function() {
return self.baseUri + self.query();
}, self);
//$.getJSON(baseUri, self.employees);
//$.getJSON(self.baseUri, self.employees);
$.getJSON(self.apiurl(), self.employees);
};
$(document).ready(function () {
ko.applyBindings(new EmployeesViewModel(viewModel.query()));
});
The html binding is:
<input type="text" class="search-query" placeholder="Search" id="global-search" data-bind="value: query, valueUpdate: 'keyup'"/>
But if i fill the text box i onley get the default "wis" employees? What am I doing wrong?
Not entirely sure what is wrong here, but have you debugged it and seen what the value of query is in apiurl?
One potential issue is that you are passing employees to getJSON as the observable, not the underlying array, so you could try:
$.getJSON(self.apiurl(), self.employees());
After some digging I found a solution.
var employeesModel = function(){
var self = this;
self.u = base +'/api/v1/search';
self.resource = 'employees';
self.field = 'achternaam';
self.employees = ko.observableArray([]);
self.q = ko.observable();
//Load Json when model is setup
self.dummyCompute = ko.computed(function() {
$.getJSON(self.u,{'resource': self.resource, 'field': self.field, 'q':self.q }, function(data) {
self.employees(data);
});
}, self);
};
ko.applyBindings(new employeesModel());
I have a fairly simple controller that gets a simple json list of objects ...
function ProductGroupsCtrl($scope, $http, $routeParams, sharedService, popupService) {
$scope.list = null;
$scope.selectedItem = null;
$scope.selectedItemJsonString = '';
$scope.selectItem = function (item) {
$scope.selectedItem = item;
$scope.selectedItemJsonString = JSON.stringify(item);
//alert(JSON.stringify(item));
};
$scope.closePopup = function () {
$scope.selectedItem = null;
$scope.selectedItemJsonString = '';
};
// sharedService.prepForBroadcast($routeParams.anotherVar);
$http({
method: 'GET',
url: '/ProductGroup'
}).success(function (data) {
$scope.list = data;
}).
error(function (data) {
$scope.message = 'There was an error with the data request.';
});
}
I then try to mock the request in the test class:
var scope, ctrl, $httpBackend, sharedServiceMock = {}, popupServiceMock = {};
beforeEach(inject(function (_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httsypBackend.expectGET('/ProductGroup').
respond([{
ProductGroupID: 5,
MenuTitle: "Promotional Products",
AlternativeText: "Coming soon - a collection of environmentally friendly Promotional Products",
OrdinalPosition: 5,
Active: false
}]);
scope = $rootScope.$new();
ctrl = $controller(ProductGroupsCtrl, {
$scope: scope,
$http: $httpBackend,
sharedService: sharedServiceMock,
popupService: popupServiceMock
});}));
However I receive an error in the testacular window object undefined. What have I done wrong here?
Found the answer. If I remove the error callback function from the $http.get method then it works, i.e. remove the following ...
error(function (data) {
$scope.message = 'There was an error with the data request.';
}
I have to say Angular sure is a steep learning curve for someone who is not a day to day JavaScript programmer (although I seem to be doing more and more). Thanks for the help anyway KatieK :-)