Firefox 4 Impressions

I installed Firefox 4 a few days ago and I have to say I like some of the improved features a lot. I've been using Firefox as my primary browser for several years now and aside from some past(?) issues with memory bloating, I've continued to prefer it even as I transitioned from Windows (IE – no contest) to Mac (Safari – close, but no cigar).

Now, browsers are a pretty commoditized product and I wasn't expecting any major changes in the overall experience with this new release. But, I have to say that two of the new features really impressed me and are going to have definite impact on the way I use my browser.

App Tabshttps://support.mozilla.com/en-US/kb/what-are-app-tabs
This is a great idea. Rather than using bookmarks for the web applications you're always in (Gmail, Facebook, Typepad, Mint, Salesforce), just have a permanent tab for each.

Tab Groupshttp://support.mozilla.com/en-US/kb/what-are-tab-groups
I love Expose on the Mac. I swipe four fingers on my trackpad and get a clean view of the kajillion windows I have open at any given time. Tab Groups is a similar feature for Firefox. Hit Command + Shift + E and you get a similar view of all the open tabs. When I'm doing Salesforce development, I may have a dozen or so open tabs all with the same tab title. Being able to see them all and quickly identify the one I want on sight is a great time-saver.

I was also really pleased to see that Firebug had released it's FF4 compatible update the day prior. That's an add-on I can't do without, so it was wonderful to see that Mozilla had done a good job of keeping partners in the loop on the impending upgrade. Well done, gentlemen.

Advertisements

Visualforce & Apex Syntax Highlighter for Typepad

** Update: Aug 22nd, 2015 – I’ve recently moved this site to WordPress.com which includes native support for the SyntaxHighlighter plugin, but not the ability to add additional brushes. As a result, the samples below may not display exactly as they would in Typepad. **

In a recent post and really in a lot of my posts, I’m presenting code samples. Typepad lacks a nice ‘Insert Code’ button in its WYSIWYG editor, so I’ve been using indents and italics to offset code from the rest of the text. Not ideal since its not easily read, doesn’t wrap well, and is hard to copy and paste.

A quick Google of the Interweb located a nice tutorial on adding Syntax Highlighter to Typepad. However, in looking at the list of default ‘brushes’ available I saw the usual suspects (ie Javascript, CSS, Perl) but not Apex or VisualForce.

But I’ve seen this on the Saleforce Developer Wiki, so I poked around in View Source and Voilà! Looks like Salesforce has a custom brush and style for Syntax Highlighter.

I don’t have the Pro ver$ion of Typepad and couldn’t add the JS and CSS directly to a custom template. So, I followed the steps of the tutorial above to add a custom component with the necessary HTML. Here’s a step by step with a few more details specific to the VF/Apex theme.

Download the Syntax Highlighter. Then in the Typepad Library, create a folder called syntaxhighlighter and two subfolders; scripts and styles.


Syntax1

Upload Script Files -There are a bunch of JS files in the Syntax Highlighter download; each of the type of codes you want to display (ie Javascript, Perl, CSS). At a minimum you need shCore.js and one other. Upload that to the scripts directory along with shBrushVisualForce.js and any others you desire.

 

Syntax2

Upload Style Files – Again there are a bunch of CSS files here each for a different theme. I like the default theme so I only added shCore.css, shThemeDefault.css, and the custom theme shThemeApexVF.css.

In Typepad, navigate to your blog (if you have several you’ll have to repeat this process for each). Click the Design tab at top, and then the Content tab on the left.

Syntax 3

Select the Embed your own HTML module and click the Add button.

Name your component Syntax Highlighter or something like that. The name won’t appear on your site. Copy and paste the following, replacing http://www.YOURWEBSITE.com with your website’s name. (If I have to explain this further, you probably shouldn’t be posting code on your site.)

<!-- Include required JS files -->
<script src="http://www.YOURWEBSITE.com/syntaxhighlighter/scripts/shCore.js" type="text/javascript"></script>
<script src="http://www.YOURWEBSITE.com/syntaxhighlighter/scripts/shBrushVisualForce.js" type="text/javascript"></script>
<!-- Include *at least* the core style and default theme -->
<link href="http://www.YOURWEBSITE.com/syntaxhighlighter/styles/shCore.css" rel="stylesheet" type="text/css">
<link href="http://www.YOURWEBSITE.com/syntaxhighlighter/styles/shThemeDefault.css" rel="stylesheet" type="text/css">
<link href="http://www.YOURWEBSITE.com/syntaxhighlighter/styles/shThemeApexVf.css" rel="stylesheet" type="text/css">
<!-- Important options, and finally the main call -->
<script type="text/javascript">
 SyntaxHighlighter.config.tagName = "code" // use <code> tags
 SyntaxHighlighter.config.stripBrs = true // disregard trailing 
 SyntaxHighlighter.all()
</script>

Click Save Changes at the bottom and you’re done.

Now to test your work, start a new post.

Click the HTML tab on the WYSIWG. And then cut and past the following.

<code class="brush: VisualForce">
<apex:page standardcontroller="Account">
  <apex:outputfield value="{!Account.Name}"></apex:outputfield>
  <apex:outputfield value="{!Account.OwnerId}"></apex:outputfield>
</apex:page>
</code>

Click Preview and you should see a nicely formatted code like below.

<apex:page standardcontroller="Account">
  <apex:outputfield value="{!Account.Name}"></apex:outputfield>
  <apex:outputfield value="{!Account.OwnerId}"></apex:outputfield>
</apex:page>

I’ve noted a few odd behaviors. First off, Typepad is inserting <![CDATA[ into the code when I flip from HTML to Rich Text view after pasting in some code with the javascript brush class.

Secondly, the Highlighter doesn’t seem to like self closed tags and inserts the closing tag, though incorrectly. This happens even with the Plain text brush. For example;

<apex:page StandardController=”Account”>
<apex:outputLabel value=”{!$ObjectType.Opportunity.fields.Amount.label}” for=”Amount”/>
<apex:outputField value=”{!Account.Name}”/>
</apex:page>

gets replaced with …

<apex:page standardcontroller="Account">
  <apex:outputlabel value="{!$ObjectType.Account.fields.Name.label}" for="AcctName">
  <apex:outputfield value="{!Account.Name}" id="AcctName">
</apex:outputfield></apex:outputlabel></apex:page>

Also, the code doesn’t always appear in the Rich Text editor though it is still there and visible in HTML view.

So while not ideal, but I can live with it these idiosyncrasies for now as I get more familiar with the tool. Just be sure to adjust your post workflow to paste code in HTML mode last (and not flipping back to Rich Text) and previewing the code to check for any odd replacements.

Hope you find this helpful.

Upload and Parse CSV via VisualForce

Update August 22nd, 2015 – Please note that this is an experiment in reading a CSV and pulling the data into Visualforce. In no way should this be part of a Production-ready solution as it DOES NOT SCALE. You will hit limits as the number of rows in the CSV increases. As a solution, this would NOT be considered a best practice.

I recently had to develop a Visualforce page that allowed a Salesforce user to upload a CSV file and then generate some records from the parsed values. Yes, I know that is what the Data Loader is for, but the intended user here was not technically adept. And I thought it was an interesting experiment.

Csv upload

The Salesforce Developer Wiki already had a sample class for parsing the file – http://wiki.developerforce.com/index.php/Code_Samples#Parse_a_CSV_with_APEX . So, I had to just create the VF page and my controller. Though, I did modify the parsing method from the Wiki to use the carriage return in the string split. CSV’s produced on my Mac didn’t parse correctly using the newline ‘\n’. I found a good post here explaining how Windows and Mac/Unix handle carriage returns differently. Here’s the results.

Visualforce page: uploadCSV

<apex:page controller=”uploadCSVcontroller” >
  <apex:form >
  <apex:inputFile value=”{!contentFile}” filename=”{!nameFile}” /><br/>
  <apex:commandButton value=”Upload” id=”theButton”/>
  </apex:form>
  <apex:outputPanel id=”results”>
  <p>nameFile: {!nameFile}</p>
  <p>rowCount: {!rowCount}</p>
  <p>colCount: {!colCount}</p>
    <table title=”CSV Output” border=”1″ width=”100%”>
       <apex:repeat value=”{!results}” var=”row”>
           <tr>
               <apex:repeat value=”{!row}” var=”cell”>
                   <td> {!cell} </td>
               </apex:repeat>
           </tr>
       </apex:repeat>
     </table>
  </apex:outputPanel>
</apex:page>

Apex Class:

public class uploadCSVcontroller {

    public Blob contentFile { get; set; }
    public String nameFile { get; set; }
    public Integer rowCount { get; set; }
    public Integer colCount { get; set; }

    public List<List<String>> getResults() {
        List<List<String>> parsedCSV = new List<List<String>>();
        rowCount = 0;
        colCount = 0;
        if (contentFile != null){
            String fileString = contentFile.toString();
            parsedCSV = parseCSV(fileString, false);
            rowCount = parsedCSV.size();
            for (List<String> row : parsedCSV){
                if (row.size() > colCount){
                    colCount = row.size();
                }
            }
        }
        return parsedCSV;
    }
    /*
    Credit to
    http://wiki.developerforce.com/index.php/Code_Samples#Parse_a_CSV_with_APEX
    */
    public static List<List<String>> parseCSV(String contents,Boolean skipHeaders) {
        List<List<String>> allFields = new List<List<String>>();

        // replace instances where a double quote begins a field containing a comma
        // in this case you get a double quote followed by a doubled double quote
        // do this for beginning and end of a field
        contents = contents.replaceAll(‘,”””‘,’,”DBLQT’).replaceall(‘”””,’,’DBLQT”,’);
        // now replace all remaining double quotes – we do this so that we can reconstruct
        // fields with commas inside assuming they begin and end with a double quote
        contents = contents.replaceAll(‘””‘,’DBLQT’);
        // we are not attempting to handle fields with a newline inside of them
        // so, split on newline to get the spreadsheet rows
        List<String> lines = new List<String>();
        try {
            //lines = contents.split(‘\n’); //correction: this only accomodates windows files
            lines = contents.split(‘\r’); // using carriage return accomodates windows, unix, and mac files
            //http://www.maxi-pedia.com/Line+termination+line+feed+versus+carriage+return+0d0a
        } catch (System.ListException e) {
            System.debug(‘Limits exceeded?’ + e.getMessage());
        }
        Integer num = 0;
        for(String line: lines) {
            // check for blank CSV lines (only commas)
            if (line.replaceAll(‘,’,”).trim().length() == 0) break;

            List<String> fields = line.split(‘,’); 
            List<String> cleanFields = new List<String>();
            String compositeField;
            Boolean makeCompositeField = false;
            for(String field: fields) {
                if (field.startsWith(‘”‘) && field.endsWith(‘”‘)) {
                    cleanFields.add(field.replaceAll(‘DBLQT’,'”‘));
                } else if (field.startsWith(‘”‘)) {
                    makeCompositeField = true;
                    compositeField = field;
                } else if (field.endsWith(‘”‘)) {
                    compositeField += ‘,’ + field;
                    cleanFields.add(compositeField.replaceAll(‘DBLQT’,'”‘));
                    makeCompositeField = false;
                } else if (makeCompositeField) {
                    compositeField +=  ‘,’ + field;
                } else {
                    cleanFields.add(field.replaceAll(‘DBLQT’,'”‘));
                }
            }

            allFields.add(cleanFields);
        }
        if (skipHeaders) allFields.remove(0);
        return allFields;      
    }

}