Creating a pdf from a Knockout JS view & viewModel - asp.net-mvc-4

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;
};

Related

Modal Pop-Up Not Closing Properly After Form Submission

This is a follow-up to the following post:
Modal Pop-Up Displaying Incorrectly When ModelState.IsValid = false Redirect
My Pop-Up validates correctly but after it process the form data it is not getting closed. Once the data gets loaded in the db, I run the following:
TempData["ID"] = status.IssueID;
return RedirectToAction("edit");
Since the Modal doesn't close, the view data gets populated in the modal and not the window.
If I try to use return View("edit"); the underlying page fails because there is no model data on the page.
Here is the current code that I implemented from the post referenced above:
<script>
$('body').on('click', '.modal-link', function () {
var actionUrl = $(this).attr('href');
$.get(actionUrl).done(function (data) {
$('body').find('.modal-content').html(data);
});
$(this).attr('data-target', '#modal-container');
$(this).attr('data-toggle', 'modal');
});
$('body').on('click', '.relative', function (e) {
e.preventDefault();
var form = $(this).parents('.modal').find('form');
var actionUrl = form.attr('action');
var dataToSend = form.serialize();
$.post(actionUrl, dataToSend).done(function (data) {
$('body').find('.modal-content').html(data);
});
})
$('body').on('click', '.close', function () {
$('body').find('#modal-container').modal('hide');
});
$('#CancelModal').on('click', function () {
return false;
});
$("form").submit(function () {
if ($('form').valid()) {
$("input").removeAttr("disabled");
}
});
</script>
Here is the code that I run to open the modal:
<div id="modal-container" class="modal fade" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
</div>
</div>
</div>
Add New Status
And here are the actions when I submit data from the modal:
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult CreateEdit(StatusViewModel model)
{
if (ModelState.IsValid)
{
StatusModel status = new StatusModel();
status.IssueID = model.IssueID;
status.StatusDate = DateTime.Today;
status.Status = model.Status;
status.ColorCode = model.ColorCode;
status.NextStep = model.NextStep;
if (model.addedit == "edit")
{
status.UpdatedByNTID = AppHttpContext.Current.Session.GetString("userntid").ToString();
string done = _adoSqlService.UpdateStatus(status);
}
else
{
status.EnteredByNTID = AppHttpContext.Current.Session.GetString("userntid").ToString();
string done = _adoSqlService.InsertStatus(status);
}
TempData["ID"] = status.IssueID;
return RedirectToAction("edit");
}
else
{
return PartialView("_CreateEdit", model);
}
}
Before I implemented the Javascript code as identified in the link, the modal form closed properly but I couldn't validate. After implementation, the modal form validates but the modal receives the redirect instead of closing. What am I doing wrong
the Modal doesn't close, the view data gets populated in the modal and not the window.
It's the expected result, Ajax render the result of redirection to the modal. You should do the redirection in the done function.
Modify the CreateEdit method:
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult CreateEdit(StatusViewModel model)
{
if (ModelState.IsValid)
{
StatusModel status = new StatusModel();
status.IssueID = model.IssueID;
status.StatusDate = DateTime.Today;
status.Status = model.Status;
status.ColorCode = model.ColorCode;
status.NextStep = model.NextStep;
if (model.addedit == "edit")
{
status.UpdatedByNTID = AppHttpContext.Current.Session.GetString("userntid").ToString();
string done = _adoSqlService.UpdateStatus(status);
}
else
{
status.EnteredByNTID = AppHttpContext.Current.Session.GetString("userntid").ToString();
string done = _adoSqlService.InsertStatus(status);
}
TempData["ID"] = status.IssueID;
}
return PartialView("_CreateEdit", model);
}
Add a hidden input in the partial view to mark if the returned model has passed the validation.
<input name="IsValid" type="hidden" value="#ViewData.ModelState.IsValid.ToString()" />
Then determine whether to redirect in the script:
$('body').on('click', '.relative', function (e) {
e.preventDefault();
var form = $(this).parents('.modal').find('form');
var actionUrl = form.attr('action');
var dataToSend = form.serialize();
$.post(actionUrl, dataToSend).done(function (data) {
$('body').find('.modal-content').html(data);
var isValid = $('body').find('[name="IsValid"]').val() == 'True';
if (isValid) {
$('body').find('#modal-container').modal('hide');
window.location.href = "/Issue/Edit";
}
});
})
Result:

Form data not updating after calling action with ajax

I have a table in my form and I don't want it to display until a link on the page is clicked which would call an action to get the data and redirect back to the same view so the table is populated. In essence like a master/detail form. I added a css class to hide the table but the only way I know of to call the action is to use ajax. I wrote the code for the function but and it seems to work fine but the table does not populate and I can't figure out why. I am using a partial view for the table which is supposed to be populated after the link is clicked. I'm very new to MVC so I have no idea what I'm missing, any help would be appreciated.
This is the link used in the jquery click event
<td><a id="viewOrder" href="#" data-PONumber-id=#osOrder.PurchaseOrderNumber>View Order</a></td>
This is my jQuery to call the action in the controller
$(document).ready(function() {
$("#viewOrder").on("click",
function(e) {
var button = $(this);
$.ajax({
type: "GET",
url: '#Url.Action("Details", "Receiving")',
data: { "id": button.attr("data-PONumber-id") },
success: function() {
var orderButton = $(".js-Order");
orderButton.removeClass("invisible");
orderButton.addClass("visible");
}
});
});
});
This is my controller code.
public ActionResult Details(int id)
{
var purchaseOrder = _context.PurchaseOrders.Single(p => p.PurchaseOrderNumber == id);
var viewModel = new ReceivngFormViewModel
{
PO = purchaseOrder.PurchaseOrderNumber,
Vendor = purchaseOrder.Vendor.VendorName,
Contact = purchaseOrder.Vendor.Phone,
OutstandingOrders = _context.PurchaseOrders.Where(od =>
od.Closed == false && !String.IsNullOrEmpty(od.PurchaseOrderNumber.ToString()) &&
od.OrderDate != null).ToList(),
ReceivedOrderDetails = _context.PurchaseOrderDetails
.Where(pod => pod.PurchaseOrderID == purchaseOrder.PurchaseOrderID && (pod.Quantity - pod.ReceiveOrderDetails
.Sum(rod => rod.QuantityReceived)) != 0)
.Select(pod => new ReceivedOrderDetail
{
PurchaseOrderId = pod.PurchaseOrderID,
PurchaseOrderDetailId = pod.PurchaseOrderDetailID,
PartId = pod.PartID,
PartDescription = pod.Part.Description,
QtyOnOrder = pod.Quantity,
QtyOutstanding = pod.ReceiveOrderDetails.Select(rod => rod.QuantityReceived).Any() ?
pod.Quantity - pod.ReceiveOrderDetails.Sum(rod => rod.QuantityReceived) : pod.Quantity
}).ToList()
};
return View("Index", viewModel);
}

MVC ResultStream only working with link

I'm creating a barcode using ASP.Net MVC
The problem is it's working with links. But it needs to work with form values.
This is my controller :
[HttpPost]
public ActionResult Barcode(BarcodeModel B)
{
string BarcodeTitle = B.BarkodTitle;
string BarcodeNumber = B.BarcodeNumber;
Codec objbar = new Codec();
Byte[] BarcodeImage = objbar.getBarcodeImage(BarcodeNumber, BarcodeTitle);
Stream Memory = new MemoryStream(BarcodeImage);
Memory.Position = 0;
var Result = new FileStreamResult(Memory, "image/png");
Result.FileDownloadName = String.Format("{0}.png", BarcodeTitle);
return Result ;
}
This is my JavaScript code :
<script type="text/javascript">
function BarkodKaydet()
{
var BarkodModel = new Object();
BarkodModel.DeweyCode = document.getElementById('DeweyCode').value;
BarkodModel.CutterNumber = document.getElementById('CutterNumber').value;
BarkodModel.Year = document.getElementById('Year').value;
BarkodModel.Bind = document.getElementById('Bind').value;
BarkodModel.Copy = document.getElementById('Copy').value;
BarkodModel.BarcodeTitle = document.getElementById('BarcodeTitle').value;
BarkodModel.BarcodeNumber = document.getElementById('BarcodeNumber').value;
$.ajax({
type: "post",
url: "#Url.Action("Barcode")",
dataType : "json",
contentType: "application/json; charset=utf-8",
data: JSON.stringify(BarkodModel),
success: function (data) { console.log(data); },
failure: function (errMsg) {
alert(errMsg);
}
});
}
</script>
But it's not working.
It' just working with link how I'm changing controller to ;
public ActionResult Barcode(string barcodeNumber, string barcodeText)
{
Codec objbar = new Codec();
Byte[] BarkodImage = objbar.getBarcodeImage(barcodeNumber, barcodeTitle);
Stream Memory = new MemoryStream(BarcodeImage);
Memory.Position = 0;
var Result = new FileStreamResult(Memory, "image/png");
Result.FileDownloadName = String.Format("{0}.png", BarcodeTitle);
return Result ;
}
this and change view to ;
<img src="/Home/BarcodeImage?barcodeText=Can&barcodeNo=12345" />
to this.
When the page opening, the linked image sending values to controller and showing barcode to me.
But I need;
When the form values submitting to model, ActionResult needs to create barcode from model, refresh the same page and It will show only barcodeImage to me.
How can I configure it ?
Best regards.

Calling HttpGet Method Using Ajax on IpagedListPager Mvc

I want to call HttpGet method on every page using ajax using IpagedList in MVC
Controller [HttpGet]
public ActionResult TestStarted(int TestId,DateTime End_Time,int page=1)
{
ViewBag.ct = 0;
ViewBag.TestId = TestId;
var Questions = GetNoOfQuestions().ToList();
ViewBag.Questions = Questions;
EAssessmentNew.BAL.StudentBal studBal = new EAssessmentNew.BAL.StudentBal();
EAssessmentNew.Dal.Student_Answer_Master _studAnsdal = new EAssessmentNew.Dal.Student_Answer_Master();
String TestName = studBal.FetchTestName(TestId);
ViewBag.TestName = TestName;
ViewBag.EndTime = End_Time;
List<Question> model = new List<Question>();
model = new Test_Planning().Fetch_Question_By_Test(TestId);
ViewBag.total = model.Count();
if (Request.QueryString["cnt"] != null)
{
int count = Convert.ToInt16(Request.QueryString["cnt"].ToString());
List<int> ChkOptions = studBal.GetCheckedAnswers((int)TestId, model[count].QuestionId, (int)(studBal.getStudentId(Session["sname"].ToString())));
ViewBag.ChkOptions = ChkOptions;
int cnt = 0;
if (ChkOptions.Count() != 0)
{
for (int i = 0; i < model[count].Options.Count(); i++)
{
if (model[count].Options[i].OptionId == ChkOptions.ElementAt(cnt))
{
model[count].Options[i].IsChecked = true;
cnt++;
}
else
{
model[count].Options[i].IsChecked = false;
}
if (cnt >= ChkOptions.Count() - 1)
{
cnt = ChkOptions.Count() - 1;
}
}
}
return View(model.OrderByDescending(v => v.Question_Id).ToPagedList(page, 1));
}
else
{
return View(model.OrderByDescending(v => v.Question_Id).ToPagedList(page, 1));
}
}
My View
<script type="text/javascript">
var TestId ='#ViewBag.TestId'
function loadQuestions() {
alert("ok")
$.ajax({
url: '#Url.Action("Student","TestStarted")',
data: { TestId:TestId },
contentType:"application/json",
success:function(responce){
}
});
}
</script>
<div class="pagedList">
#Html.PagedListPager(Model, page => Url.Action( "",new { onclick="loadQuestions()"}), PagedListRenderOptions.TwitterBootstrapPager)
</div>
I have done paging using IpagedList i want to call HttpGet Method Of controller on each and every page but i want this to perform without page refresh i have written ajax for it now i just want to know how can i call that ajax method using #Html.PagedListPager and on onClick event
Just Correct your url in ajax request as :
Instead of this
url: '#Url.Action("Student","TestStarted")'
It should be this
url: '#Url.Action("TestStarted","Student")'
and #Html.PagedListPager produces 'anchor' tag in html so you can put a click event on document as shown :-
$(document).on('click', 'a', function() {
$.ajax({
url: this.href,
type: 'GET',
datatype: "html",
data :{ TestId : $("#TestId").val(), End_Time :$("#End_Time").val(), page :$("#page").val() }
cache: false,
success: function(result) {
$('#results').html('');
$('#results').html(result);
}
});
return false;
});
In above code click event binds with every 'anchor' tag so if you need for specific 'anchor' tags then you can specify class as $(.pager).on('click', 'a', function() {}) and here '#results' is the target div id whose html is coming from controller action in your case this id may be different.

vline add remote stream for callee fail

I am trying to use your api in a custom app with imported users.
Everything works fine (auth_token, login, call initiation) , but when the callee should get a response and add the remotestream nothing happens. no errors get shown in the console.
I would appreciate if someone takes a look at the code and tells me what i m missing.
I tried the vline demo at https://freeofcinema.vline.com and it worked with the same browsers and conditions between the two computers. In my app it is a http , but i tried it also with https, and the same problem came up. This is some simplified code i used to test the api.
var Streams = [];
var Vsession = null;
var Vline = (function(){
var Client;
var authToken;
var service_id = 'freeofcinema';
var profile = null;
var Person;
var Calls = [];
var onMessage = function(event){
//alert('message');
var msg = event.message, sender = msg.getSender();
console.log(sender.getDisplayName() +'sais: '+ msg.getBody());
console.log(event);
}
var onMediaSession = function(event){
console.log(event);
var mediaSession = event.target;
InitSession(mediaSession);
}
function Call(mediaSession) {
mediaSession.
on('change', alert_info);
}
function alert_info(b){
console.log(b);
}
function InitSession(mediaSession){
mediaSession.on('mediaSession:addRemoteStream', function(event) {
alert('addRemoteStream');
});
mediaSession.on('mediaSession:addLocalStream', function(event) {
alert('addLocalStream');
});
mediaSession.on('mediaSession:removeLocalStream mediaSession:removeRemoteStream', function(event) {
console.log('removedStream');
});
Calls.push(new Call(mediaSession));
}
return {
init : function(){
if(profile){
return;
}
profile = {
"displayName" : //some getusrname function...
};
$.post('vtoken.php',{//get auth token
id : Comm.Voip_user().id
},function(data){
authToken = data;
Client = vline.Client.create({
"serviceId": service_id,
"ui" : true
});
Client.on('recv:im', onMessage , this);
Client.on('add:mediaSession', onMediaSession, this);
Client.on('login', function(e) {
Vsession = e.target;
//alert('loged in');
});
Client.login(service_id, profile, authToken);
});
},
getPerson : function(id){//id of user to call
if(Vsession){
Vsession.getPerson(id).
done(function(person){
Person = person;
Vsession.startMedia(id);
});
}
}
}
}());
Thank you for your response.
I tried with one user from the app, and another from the https://freeofcinema.vline.com, and the same problem occured. Also the call (in pending state) gets terminated after a short while..
When passing ui:true when creating the client, you do not have to handle the media sessions yourself. Just comment the line Client.on('add:mediaSession', onMediaSession, this); and it should just work.