Saturday, July 28, 2012

Generate CSV files from Salesforce data on client side using Javascript Remoting and Downlodify

This post is a continuation from the previous post that described how to show data in a JQGrid using Javascript Remoting. I started it but not sure if it is able to generate c CSV for me, so first I tried passing in a simple array and thanks to my positive thinking it did generate a CSV!!


So, why did I experiment with Downloadify to generate the CSV from the client side?
There was a requirement to generate the CSV file from the salesforce data containing more than 40,000 records, I could have used a visualforce page to render as a CSV, but the issues are:
a. Visualforce components cannot hold more then 1000 records.
b. Tried HTML components, but got caught in various limitations of apex.

What did I use to acheive the functionalty?
I used a open-source JQuery plugin "Downloadify" that generates CSV files at the client side using a Adobe Flash plugin. Copying the text directly from GitHub:

"This library is a tiny JavaScript + Flash library that allows you to generate files on the fly, in the browser, without server interaction. Web applications that allow you to generate vCards, color palettes, custom code, etc would benefit from using this library. In addition to increasing speed (no round trip to the server) this solution can reduce the database and server load of existing web applications. This is not a library to ‘force download’ a file from a server. It does not interact with a server at all"


Find more details about the plugin at publishers Github page:
https://github.com/dcneiner/Downloadify

More to that, here is a video from publishers web-site:
<iframe src="http://player.vimeo.com/video/7735312" width="400" height="296" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>

What are the references being used for arriving at this awesome workaround?
1. http://dougneiner.com/post/255998581/downloadify
2. http://davidwalsh.name/downloadify
3. A hundred thanks to this blog : http://geekswithblogs.net/JoostPloegmakers/archive/2005/06/17/44045.aspx, it helped me to retrieve data from the javascript array.

Visualforce Code:
<apex:page standardController="Contact" sidebar="false" showHeader="true" standardStylesheets="true" extensions="DeveloperExtension2" id="page1" >

<link rel="stylesheet" type="text/css" href="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.16/themes/redmond/jquery-ui.css" />
<link rel="stylesheet" type="text/css" href="http://www.trirand.net/aspnetmvc/Content/themes/ui.jqgrid.css" />
<script type="text/JavaScript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"/>
<script type="text/javascript" src="http://www.trirand.net/aspnetmvc/Scripts/trirand/i18n/grid.locale-en.js"/>
<script type="text/javascript" src="http://www.trirand.net/aspnetmvc/Scripts/trirand/jquery.jqGrid.min.js"/>
<apex:includeScript value="{!URLFOR($Resource.Downloadify, '/dcneiner-Downloadify-652377f/js/swfobject.js')}"/>
<apex:includeScript value="{!URLFOR($Resource.Downloadify, '/dcneiner-Downloadify-652377f/src/downloadify.js')}"/>

<script type="text/JavaScript">  
        var gridData;
        var obj;
    function search(jsonString) {
        $("#pdata").jqGrid("GridUnload");
        gridData=JSON.stringify(jsonString);    
        obj = JSON.parse(gridData);
        jQuery("#pdata").jqGrid({
        data: jsonString,
        datatype: 'local',
        colNames:['Name','Email','Account Name'],
        colModel:[
            {name:'Name',index:'Name', width:40},
            {name:'Email',index:'Email', width:40},
            {name:'Account Name',index:'Name', width:40}],
        rowNum:10,
        rowList:[5,10,20,30,50,100, 1000],
        pager: '#ppdata',
        sortname: 'name',
        viewrecords: true,
        sortorder: "desc",
        caption:"Contact Data",
        width: 800,
        height: 180,   
    });   
   $("#pdata").trigger("reloadGrid");
 }

function showDataInJqGrid(){
    var accName = document.getElementById("query").value;
    contactSearch(accName);
}

function contactSearch(name) {
    var jsonString;
    myp1.DeveloperExtension2.showContacts(name,handleContacts);
}

function handleContacts(result, event) {
    if(event.type == 'exception'){
        alert(event.message);
    }else{
        jsonString = result;
        search(jsonString);
        gridData = JSON.stringify(jsonString);
    }
}

function downloadRecords(){
    var downloadData = 'Name,Email\r\n';
    for(var i = 0; i < jsonString.length; i++) {
       var obj = jsonString[i];
       downloadData += obj.Name + ',' + obj.Email + '\r\n';      
    }
    Downloadify.create('downloadify',{
    filename: function(){
      return 'file1.csv';
    },
    data: function(){
      return downloadData;
    },
    onComplete: function(){
      alert('Your File Has Been Saved!');
    },
    onCancel: function(){
      alert('You have cancelled the saving of this file.');
    },
    onError: function(){
      alert('You must put something in the File Contents or there will be nothing to save!');
    },
    dataType: 'string',
    transparent: false,   
    swf: "{!URLFOR($Resource.Downloadify, '/dcneiner-Downloadify-652377f/media/downloadify.swf')}",
    downloadImage: "{!URLFOR($Resource.Downloadify, '/dcneiner-Downloadify-652377f/images/download.png')}",
    width: 100,
    height: 30,
    transparent: true,
    append: false
  });
}
</script> 

<apex:sectionHeader title="Contact Search using AccountID" subtitle="Search Contact" />
  <apex:pageBlock mode="maindetail">
    <apex:form id="qform" >
            <apex:pageBlockSection title="Search Contact for the Account" collapsible="false" columns="1" >
                <table width="100%">
                    <tr>
                        <td><h3>Enter Account Name </h3>&nbsp;&nbsp;<input type="text" id="query" />&nbsp;&nbsp;&nbsp;
                            <input type="button" value="Show Contacts" class="btn" onclick="showDataInJqGrid();" />&nbsp;&nbsp;&nbsp;
                            <input type="button" value="Download Records" class="btn" onclick="downloadRecords();" />&nbsp;&nbsp;&nbsp;
                        </td>
                    </tr>
                </table>
            </apex:pageBlocksection>
    </apex:form>
   
    <apex:pageBlockSection title="Contacts in Response" collapsible="false" rendered="true">               
        <div id="response" style="font-size: 16px;width: 300px;font-family: monospace; font-stretch: expanded" />               
        <table id="pdata"></table>
        <div id="ppdata"></div>      
        <div id="downloadify"></div>
    </apex:pageBlocksection>   
  </apex:pageblock>
</apex:page>

Controller:
global class DeveloperExtension2 { 
    public DeveloperExtension2(ApexPages.StandardController controller){}
    @RemoteAction
    global static List<Contact> showContacts(String accName){
        accName = '%'+ accName+'%';
        List<Contact> lst_contacts = new List<Contact>([select id, name, email, Account.Name from contact /* limit 10 Account.Name LIKE : accName */ ]);
        List<Contact> lst_Contact = new List<Contact>();
        for(Contact c : lst_contacts){
            c.Email= 'RemoteTestContact@sdf.com';
            lst_Contact.add(c);
        }
        system.debug('###### ' + JSON.serialize(lst_Contact));
        return lst_Contact ;
    }
}

How to use it?
It's simple, you need to enter the AccountName and click on show contacts and it will populate the contacts for the AccountName provided. If you will leave it blank the table will show all the contacts where account is null.

Now, click on "Download Records" and keep your eyes below the bottom left corner of the JQGrid, you will see a button "Save to Disk", click on it and you will be asked for the location to save the file. Enter your file name and appending ".csv" will yield you a CSV file containing the records.

Isn't it simple?

Some advantages that can be briefed are:
1. Less processing time, its only for running the SOQL on the server.
2. No Server side processing of data, and the client side is JS processing is much faster.
3. Good riddance from apex/visualforce issues such as Heap size, View State etc.
4. I have tried using it for more than 45K records and to my surprize it was successfull!!

NOTE: Please not that "myp1" is the namespace for my Org, so, if you using any namespace then can prefix  it, or if you are not using it then remove it.

Monday, June 18, 2012

Using Javascript Remoting to populate data in JQGrid

Writing a post after quite some time, this requirement was given by a friend who is working on a POC for his project and I took it as a perfect opportunity to get some hands-on on JQuery :)

So, what was the requirement?
The requirement was simple, I needed to show contact data for a given account name in a JQuery grid.

What was the solution that came out after 8 hrs of research on JQuery and JS Remoting?
Surprisingly, the solution was pretty simple, I created a controller, a @RemoteAction annotated method that returned a List of Contacts to the Javascript being used on visualforce page. The callback remoting function converted the javascript array returned by the controller to the JSON which is then passed to the JQGrid.

What are the references being used for arriving at the solution?
1. http://www.tgerm.com/2010/02/visualforce-salesforce-jquery-ajax-how.html
2. http://www.trirand.com/blog/jqgrid/jqgrid.html
3. http://www.salesforce.com/us/developer/docs/pages/Content/pages_js_remoting.htm

What is the solution?
Here it is...

Working demo: http://testdomain5-developer-edition.na3.force.com/MySite/myp1__DeveloperPage3
Sample Data: Test Date Account 1


Visualforce page:

<apex:page standardController="Contact" showHeader="true" standardStylesheets="true" extensions="DeveloperExtension2" id="page1" >
<link rel="stylesheet" type="text/css" href="http://ajax.aspnetcdn.com/ajax/jquery.ui/1.8.16/themes/redmond/jquery-ui.css" />
<link rel="stylesheet" type="text/css" href="http://www.trirand.net/aspnetmvc/Content/themes/ui.jqgrid.css" />
<script type="text/JavaScript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"/>
<script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery/jquery-1.7.1.min.js"/>
<script type="text/javascript" src="http://www.trirand.net/aspnetmvc/Scripts/trirand/i18n/grid.locale-en.js"/>
<script type="text/javascript" src="http://www.trirand.net/aspnetmvc/Scripts/trirand/jquery.jqGrid.min.js"/>
    
<script type="text/JavaScript">  
    function search(jsonString) {
        $("#pdata").jqGrid("GridUnload");
        var gridData;
        var obj;          
        gridData=JSON.stringify(jsonString);    
        obj = JSON.parse(gridData);      

        jQuery("#pdata").jqGrid({
        data: jsonString,
        datatype: 'local',
        colNames:['Name','Email'],
        colModel:[
            {name:'Name',index:'Name', width:40},
            {name:'Email',index:'Email', width:40}],
        rowNum:10,
        rowList:[5,10,20,30,50,100, 1000],
        pager: '#ppdata',
        sortname: 'name',
        viewrecords: true,
        sortorder: "desc",
        caption:"Contact Data",
        width: 800,
        height: 180,   
    });   
   $("#pdata").trigger("reloadGrid");
 }



function showDataInJqGrid(){
    var accName = document.getElementById("query").value;
    contactSearch(accName);
}

function contactSearch(name) {
    var jsonString;
    myp1.DeveloperExtension2.showContacts(name,handleContacts);
}

function handleContacts(result, event) {
    if(event.type == 'exception'){
        alert(event.message);
    }else{
        jsonString = result;
        search(jsonString);
    }
}
</script>

<apex:sectionHeader title="Contact Search using AccountID" subtitle="Search Contact" />
  <apex:pageBlock mode="maindetail">
    <apex:form id="qform" >
            <apex:pageBlockSection title="Search Contact for the Account" collapsible="false" columns="1" >
                <table width="100%">
                    <tr>
                        <td><h3>Enter Account Name </h3>&nbsp;&nbsp;<input type="text" id="query" />&nbsp;&nbsp;&nbsp;
                            <input type="button" value="Show Contacts " class="btn" onclick="showDataInJqGrid();" />&nbsp;&nbsp;&nbsp;
                        </td>
                    </tr>
                </table>
            </apex:pageBlocksection>
    </apex:form>

    <apex:pageBlockSection title="Contacts in Response" collapsible="false" rendered="true">               
        <div id="response" style="font-size: 16px;width: 300px;font-family: monospace; font-stretch: expanded" />               
        <table id="pdata"></table>
        <div id="ppdata"></div>
    </apex:pageBlocksection>
  </apex:pageblock>
</apex:page>


The controller extension:
global class DeveloperExtension2 {
    public DeveloperExtension2(ApexPages.StandardController controller){}

    @RemoteAction
    global static List<Contact> showContacts(String accName){
        accName = '%'+ accName+'%';
        List<Contact> lst_contacts = new List<Contact>([select id, name, email from contact where Account.Name LIKE : accName]);
        return lst_contacts;
    }
}

Wednesday, January 18, 2012

Experimenting with Visualforce Charting(Pilot) for the first time


I recently got Visualforce Charting enabled for my dev org. So, started worked on it. Its a nice feature for visualforce pages for displaying charts and navigating to data by clicking on those chart values.    

Visualforce charts are rendered client-side using JavaScript. This allows charts to be animated and visually exciting, and chart data can load and reload asynchronously, which can make the page feel more responsive. Visualforce charting is designed to be flexible, but also easy to use. It offers variations on bar, line, and pie charts. If you would like to use different chart types, or want to add advanced user or page interactions.

So, how can the data be passed to the <apex:chart> component??
You can pass the chart data via either of the 3 methods listed below:
    1. As an expression that represents a controller method reference
    2. As a string representing a JavaScript function, retrieving the values using JavaScript Remoting.
    3. As a string representing a JavaScript array, but remember, it has to be an array of arrays.

Below is the simple method that returns a list of opportunities to the VF page and displays a chart with vertical bars/lines showing opportunity amounts against their names.  

Controller:
    public List<Opportunity> getChartData() {      

        List<Opportunity> Opp = [select name, amount, stageName from opportunity where amount!=null AND stageName!=null limit 10];         

        return Opp;

    }


Visualforce Page:
<apex:page controller="CharVF1Controller" sidebar="false" >

<apex:chart height="400" width="700" data="{!ChartData}" rendered="true" id="chart1" name="mychart">

    <apex:axis type="Category" position="bottom" fields="Name"
        title="Opportunities" grid="true"/>
    <apex:axis type="Numeric" position="right" fields="Amount"
        title="Opportunity Amounts"/>
    <apex:axis type="Category" position="left" fields="StageName"
        title="Opportunity Stage"/>

    <apex:barSeries title="Monthly Sales" orientation="vertical" axis="right"
        xField="Name" yField="Amount" id="bar1">
        <apex:chartTips height="20" width="120"/>
    </apex:barSeries>

    <apex:lineSeries title="Closed-Won"
         axis="left" xField="Name" yField="Amount"
         markerType="circle" markerSize="4" markerFill="#00FF00"/>

</apex:chart>
</apex:page>

Here is an example of a Pie Chart rendered with VF charting feature:
<apex:chart height="350" width="450" data="{!ChartData}">
   <apex:pieSeries dataField="Amount" labelField="Amount"/>
   <apex:legend position="right"/>
</apex:chart>

Below are some known limitations for this pilot feature:
  1. Visualforce charts will only render in browsers which support scalable vector graphics (SVG). For more information, see WC3 SVG Working Group.
  2. Visualforce charting uses JavaScript to draw the charts. These charts won't display in pages rendered as PDFs.
  3. Email clients do not generally support JavaScript execution in messages. Don't use Visualforce charting in email messages or email templates.
  4. Visualforce charting sends errors and messages to the JavaScript console. You'll want to keep a JavaScript debugging tool, such as Firebug, active during development.

Link to demo page: http://testdomain5-developer-edition.na3.force.com/MySite/myp1__ChartingVF1