Sunday, September 22, 2013

A Website That May Never Be Public

Around two years ago a friend of mine and I started working on a project on fashion business in our spare times. My friend has some connections in clothing manufactures and found some opportunity on it. So he came up an idea that we could build a platform to bridge fashion designers and the customers directly.

Unlike TeeSprint and TeeSpring where you could get commodity products, we offered limited quantity of custom made clothing by named designers. We targeted on those designers who received rewards in recent years. They may not be as famous as those top names, but they have great potential and many of them already have quite a number of followers. With our platform, designers will have a channel to sell their products, and those people who desire unique fashion can buy the custom design clothing with moderate price. The price will be relative low because they will be made directly from the manufacture without other intermediate costs. In addition, designers themselves will introduce our site to their followers, another save of marketing cost. Of course we expected a lot of challenges but we believed there's still a chance. He was in charge of the business aspects and I mainly worked on technical stuff.

About a year ago we had built a complete eCommerce website with some cool features. We also had an agreement with a manufacturer who makes high-end clothing (they OEM some famous brands). Everything was ready to set our business online except the key part: not many designers trust us. They just don't believe an unknown small company in Canada without fashion background would actually help them to sell their design and products. Some designers who had positive feedback at the beginning didn't end up doing business with us.

That was really tough. At some point we realized that without some help from an influential person in the fashion field our approach may not work at all. But it's hard to find such a person to join us. We didn't have money to promote our idea and prove ourselves. If we would have met those designers in person and presented them some nice samples made by the manufacture, there may be another story of ending.

Time flies and lives move on. My friend got his first baby last month and he enjoys the new role of being a father. We haven't talked much about the website and the designer sign-on topic recently as we both knew it's not working out and it's basically failed. The initial business build up can't last that long. If you can't launch such online business after two years of work, then it may never be launched in the future. The website is still running in a secret domain and it may never be public. It's simply sad.

On the other hand, although we did not succeed the way we would have hoped for, we did learn a lot in the whole process. For me those are valuable experience.

Friday, September 06, 2013

Page Buttons Not Responding After Getting PDF in SharePoint

Today I have been working on a SharePoint 2010 WebPart in which the user can click a button to get a PDF report. Open source iTextSharp library is used to generate the PDF report. The code is quite straightforward:

        void GeneratePDF()
        {
            Document doc = new Document(PageSize.A4);
            MemoryStream pdfStream = new MemoryStream();
            PdfWriter.GetInstance(doc, pdfStream);
            doc.Open();
            // Populate document with business data
            doc.Close();

            Response.Clear();
            Response.ClearHeaders();
            Response.ContentType = "application/pdf";
            Response.AddHeader("Content-Disposition", "attachment;filename=report.pdf");
            Response.BinaryWrite(pdfStream.ToArray());
            Response.Flush();
            Response.End();   
        }

That PDF function works fine, but all other buttons on the same page are not responding (postback doesn't occur) after the PDF button is click. Such behavior only happens in SharePoint environment and everything is okay in a regular ASP.NET page. It looks like some special validation in SharePoint causing the problem. I debugged into the JavaScript and found the setting of "__spFormOnSubmitCalled" variable is the culprit.

ASP.NET validation process triggered by the click of a button includes invocation of JavaScript function called WebForm_OnSubmit. SharePoint overrides this function for each page:

<script type="text/javascript">
//<![CDATA[
    if (typeof(Sys) === 'undefined') 
        throw new Error('ASP.NET Ajax client-side framework failed to load.');
    if (typeof(DeferWebFormInitCallback) == 'function') 
        DeferWebFormInitCallback();
    function WebForm_OnSubmit() {
        UpdateFormDigest('webUrl..', 1440000);
        if (typeof(vwpcm) != 'undefined') {
            vwpcm.SetWpcmVal();
        };
        return _spFormOnSubmitWrapper();
    }
//]]>
</script>

The JavaScript function __spFormOnSubmitWrapper is defined in /_layouts/1033/init.js:

function _spFormOnSubmitWrapper() {
    if (_spSuppressFormOnSubmitWrapper)
    {
        return true;
    }
    if (_spFormOnSubmitCalled)
    {
        return false;
    }
    if (typeof(_spFormOnSubmit) == "function")
    {
        var retval = _spFormOnSubmit();
        var testval = false;
        if (typeof(retval) == typeof(testval) && retval == testval)
        {
            return false;
        }
    }
    _spFormOnSubmitCalled=true;
    return true;
}

The "_spFormOnSubmitCalled" field is false by default when the page is loaded. It's set to true when you click a button on the page. This machanism ensures only the first button click will take action and prevents other clicks from posting back to the server. The "_spFormOnSubmitCalled" field is reset to false once the page is reloaded. A postback will usually result in a page reloading, but not in above PDF out case where the server writes the PDF attachment to the client then ends the interaction. So the "_spFormOnSubmitCalled" field remains true which blocks any future postback.

So theoretically the issue is not limited to PDF output. Directly writing and ending on the Response object in the server side would result in the same problem. There're a few approaches to resolve the problem:

  • 1. Reset "_spFormOnSubmitCalled" to false after the PDF button is clicked. Note that the reset timing is important, and it must be later then the submission process (after the WebForm_OnSubmit method is called), for example:
       function resetSharePointSubmitField() {
            setTimeout(function () { _spFormOnSubmitCalled = false; }, 1000); // set the field after 1 second
            return true;
        }
  • 2. Overide the WebForm_OnSubmit function and make it always return true:
        function resetSharePointSubmitField() {
            window.WebForm_OnSubmit = function() {return true;};
        }

Apply the JavaScript to a button:

    <asp:Button ID="btnGeneratePDF" runat="server" Text="Get PDF" OnClientClick="resetSharePointSubmitField();" />

The other option is add a client side script "JavaScript: _spFormOnSubmitCalled = false;" for all buttons on the page, but that is not scalable and not recommended.

Bonus tip The regular pdf export function by Response.Write() won't work inside a modal dialog (the popup window open by window.showModalDialog() from JavaScript). To resolve this particular problem you can replace the PDF export button with a hyper link and set its target to an empty iframe:

    <a id="popupPDF" target="pdfTarget" runat="server">Get PDF</a>
    <iframe name="pdfTarget" id="pdfTarget" width="0" height="0" style="display:none;"></iframe>

Then simply assign a query string the anchor which tells the server to generate the PDF:

    protected void Page_Load(object sender, EventArgs e)
    {
        popupPDF.HRef = Request.Url.AbsoluteUri + "&pdf=true";
        if (Request.QueryString["pdf"] == "true")
            GeneratePDF();
    }