0

I have several <input> fields on a page to collect user input. The page uses jQuery tabs. Once the user is finished inputting data they advance to the next jQuery tab until they are finally at the last tab which displays information retrieved via Ajax. The page never refreshes.

The above works fine, but now I am implementing an option where the user can convert the results to a PDF so they can save or print. Originally, the jQuery to trigger PDF conversion was:

$('#btnViewPrintSavePDF').click(function (event) {
    event.preventDefault;

    $(this).parents("form")
        .attr("method", "POST")
        .attr("action", "/Controller/ConvertToPDF");
        .submit();
});

In the Controller:

[HttpPost]
public ActionResult ConvertToPDF()
{
    object model = null;
    ViewDataDictionary viewData = new ViewDataDictionary(model);

    viewData.Add("MyValue1", Request["MyValue1"]);
    viewData.Add("MyValue2", Request["MyValue2"]);
    viewData.Add("MyValue3", Request["MyValue3"]);

    // The string writer where to render the HTML code of the view
    StringWriter stringWriter = new StringWriter();

    // Get the base URL
    String currentPageUrl = this.ControllerContext.HttpContext.Request.Url.AbsoluteUri;
    String baseUrl = currentPageUrl.Substring(0, currentPageUrl.Length - "Controller/PDFTemplate".Length);

    viewData.Add("BaseUrl", baseUrl);

    // Render the Index view in a HTML string
    ViewEngineResult viewResult = ViewEngines.Engines.FindView(ControllerContext, "PDFTemplate", null);
    ViewContext viewContext = new ViewContext(
        ControllerContext,
        viewResult.View,
        viewData,
        new TempDataDictionary(),
        stringWriter
    );
    viewResult.View.Render(viewContext, stringWriter);

    // Get the view HTML string
    string htmlToConvert = stringWriter.ToString();

    // Create a HTML to PDF converter object with default settings
    HtmlToPdfConverter htmlToPdfConverter = new HtmlToPdfConverter();

    // Set license key
    htmlToPdfConverter.LicenseKey = "...hidden...";

    // Convert the HTML string to a PDF document in a memory buffer
    byte[] outPdfBuffer = htmlToPdfConverter.ConvertHtml(htmlToConvert, baseUrl);

    // Send the PDF file to browser
    FileResult fileResult = new FileContentResult(outPdfBuffer, "application/pdf");
    fileResult.FileDownloadName = "MyPDF.pdf";

    return fileResult;
}

Again, the above code works fine. The PDF conversion of a .cshtml view occurs and I am prompted with the usual dialog box asking if I want to open the PDF or save it.

Note that the original page never gets refreshed and that's the way it needs to remain.

THE PROBLEM:

I have a Json object/string that gets created in the original page. It needs to be submitted to the above ConvertToPDF() routine along with all of the form fields (checkbox, text, etc). I can't figure out how to do this.

Ajax breaks ConvertToPDF() in that the dialog box no longer pops up asking if you want to Open or Save the PDF.

My intention is to include the following code in ConvertToPDF():

GroupChoiceIds GroupChoiceIds = null;
if (Request["GroupChoiceIds"] != null)
{
    JavaScriptSerializer jss = new JavaScriptSerializer();
    GroupChoiceIds = jss.Deserialize<GroupChoiceIds>(Request["GroupChoiceIds"]);
}

The above code snippet works elsewhere in my application using an Ajax POST like this:

$.ajax({
    type: "POST",
    url: "/Central/ConvertToPDF",
    dataType: "json",
    data: {
        GroupChoiceIds: ('{ 'Key1' : 'Value1', 'Key2' : 'Key2', 'Key3' : 'Value3','SpecialKey1' : 'Special_|_Value' }')
            },

    ....

});

So, how can I submit the GroupChoiceIds Json string to ConvertToPDF() without causing a page refresh and not breaking the Open/Save functionality?

EDIT

I've tried placing the Json string in a hidden <input> field, but the field doesn't get submitted and doesn't show up in the Controller after form submission. The input field looks like this:

<input type="hidden" value="{ 'Key1' : 'Value1', 'Key2' : 'Value2', 'Key3' : 'Value3', 'SpecialKey' : 'Special_|_Value' }" id="GroupChoiceIds">

Apparently value= contains invalid characters, but I'm not sure which ones. Is there standard way of getting this Json string to be valid for use in a hidden value= attribute?

UPDATE I was unable to add a hidden input field to the .cshtml and have it available in the controller no matter what I tried. Then I used @Html.Hidden("GroupChoiceIds") and it worked. I'm confused by this - why can't I manually add a hidden input field? The controller function doesn't have a model specified and I assume it will accept/detect any input field submitted by the view?

I don't mind using @Html.Hidden (it works!) but I just wanted to learn why I am having to do it this way. Can somebody tell me what the difference is between:

<input id="GroupChoiceIds" type="hidden" value="" />

and

@Html.Hidden("GroupChoiceIds")
rwkiii
  • 5,716
  • 18
  • 65
  • 114
  • Add `GroupChoiceIds` as hidden control inside the form and update its value like `$('#GroupChoiceIds').val(jsonStringVal)` and submit the form. In MVC Action access it using `Request["GroupChoiceIds"]` – cackharot Aug 19 '14 at 06:46
  • I put it in a type=hidden input field but it always shows as null when I break in the controller. – rwkiii Aug 19 '14 at 07:15
  • Test with static value in hidden field control, remember to place this control inside `
    ` tag `
    – cackharot Aug 19 '14 at 12:02
  • @cackharot, At first I thought I might have placed the field outside of the ending form tag, but I didn't. I've double-checked everything and basically it looks correct - except I'm not sure if the `_|_` might be causing the problem? – rwkiii Aug 19 '14 at 14:38
  • Actually, I set the value of the hidden input field to a plain string of "test" and it doesn't get submitted either. I created another input field of type=text with a value of "test" and it doesn't get submitted either. I've double checked the HTML and these fields are definitely inside the form begin/end. Realizing that I can't get another input field submitted to the controller no matter what changes my question (and symptoms) considerably. This isn't making any sense whatsoever.... – rwkiii Aug 19 '14 at 16:58
  • @cackharot, please see my latest update to this question. – rwkiii Aug 19 '14 at 17:28
  • 1
    `` `name` attribute is important this is the key that gets submitted by the browser to the server. Hope this helps! – cackharot Aug 20 '14 at 03:24
  • Absolutely helps! I wasn't including the `name` attribute. Thank you. – rwkiii Aug 20 '14 at 06:59
  • @cackhart, please post an answer and I'll mark it as correct. I had already fixed my original issue by using @Html.Hidden() but I was dealing with it on another control and came back to see if anyone responded to my update. You saved me some time. ;) – rwkiii Aug 20 '14 at 07:02
  • can you just pull the form data from `FormCollection`? add a `FormCollection` parameter to your MVC Action and all of the Form data will be present. – Glenn Ferrie May 31 '16 at 17:58

2 Answers2

1

You can use the Data URI scheme, supported in relatively modern browsers. This allows you to create an special URL that, instead of refering to an external resource, contains the encoded data itself.

I.e. the idea is to use AJAX to download the data, and compose an special url which contains that data. Here you can see something similar. In this answer the special URL is used for the <img>'s src attribute. But you can expose it in a regular link (<a>), so that the user can click and download it.

Another solution is to create an <iframe> in your page and use it to download the PDF just as you've explained. You can do that using javascript. When the download starts, the user will see the dialog, and only the <iframe> will be "refreshed", keeping the rest of the page "as is".

If you can ensure that all the browsers are modern enough, you can use the HTML5 FileSystem APIs to store data downloaded using JSON, and expose it with an special filesystem: URL. You can learn about these APIs here. The URL is explained in the "filesystem: URLs" section.

Community
  • 1
  • 1
JotaBe
  • 38,030
  • 8
  • 98
  • 117
  • I have noted your answer and might have to use one of these approaches. Thank you. I've also edited my question and included the source for the input field. Are you saying that the input field's value won't work and I'll need to use a different approach? – rwkiii Aug 19 '14 at 14:36
  • If you use the JSON in the hidden field there will be no automatic binding: the controller will receive the JSON string, and it will have to deserialize,for example by using JSON.NET. I think any of the previous options is better. I've updated the answer with an additional option. – JotaBe Aug 19 '14 at 23:21
  • thank you for these options. I have gone through the 2 links you provided. I know there is a place for those methods, but cackharot above gave me the simplest way to resolve my issue - include a `name` attribute. I've been so use to client side coding in jQuery and using razor helpers that I totally forgot that the name attribute is used when posting to the server. It resolved my issue completely. – rwkiii Aug 20 '14 at 07:07
  • I think your question is really confusing. You're stating these two problems: "Ajax breaks ConvertToPDF() in that the dialog box no longer pops up asking if you want to Open or Save the PDF", "So, how can I submit the GroupChoiceIds Json string to ConvertToPDF() without causing a page refresh and not breaking the Open/Save functionality?" Ajax, by itself, will not pop up the dialog box, so adding the name attribute doesn't address these problems. If your only problem is that the value isn't posted, please make your question more clear, so that it becomes useful for other people. – JotaBe Aug 20 '14 at 07:36
  • 2 people tried helping me. 1 person seemed to understand the question or at least see the problem with the code I posted. Thank you for your help. Sorry you didn't understand the question/problem. Your abbreviated version of my question does seem to paint my question as unclear. I think you should reread the question. – rwkiii Aug 20 '14 at 07:50
  • Don't misunderstand me. You're asking and explaining too much things in your question for a single problem. If it was so easy to understand, you shouldn't have exchanged so much comments, and updated it. I simply think that the question would be much more useful for other people if you made it more concise, and aimed to the root of the problem. Please, think as a "newcomer" that reads your question. Does he need to read so much information? Will he see at once what the problem is? I just wanted to point this out to make the Q&A more useful. It's just a personal opinion. Don't take it amiss. – JotaBe Aug 20 '14 at 08:00
  • I do understand. The posted question might have been too long. I wanted readers to know how I was posting the data and how I was rendering the view. To me it's different than the norm, and so I wanted to make it clear. cackharot just told me, put the value in a hidden field. I made an EDIT and showed my code for the hidden field. It was short and sweet. It showed the missing `name`. Then I made an UPDATE which further showed the missing `name` attribute. I know you put in an effort to help me and I really do appreciate it. – rwkiii Aug 20 '14 at 08:12
1

Add GroupChoiceIds as hidden control inside the second form

<input id="GroupChoiceIds" type="hidden" value="" name="GroupChoiceIds"/>

And update its value $('#GroupChoiceIds').val(jsonStringVal) in form submit event before the ajax call. In MVC Action access it using Request["GroupChoiceIds"] and de-serialize the jsonString using JSON.NET library.

cackharot
  • 688
  • 1
  • 6
  • 13