HTML5 Applications for iPhone Mobile Devices using Visualforce - El Toro - Find articles about Visualforce, Apex, Force.com and Salesforce in general

Print Preview

HTML5 Applications for iPhone Mobile Devices using Visualforce

Winter '14 has released new features to help build mobile applications using HTML5. I have decided to build a template for a simple interface to input the different type of elements, so I created an object that has all the different type of fields available and see what would be the best/easiest way of building these imputs. This is what the page looks like:

This is the Visualforce code to build this page:

<apex:page docType="html-5.0" sidebar="false" showHeader="false" cache="false" Controller="Mobile" >
<apex:form >
<apex:pageMessages />
<html>
    <head>
        <meta charset="utf-8" />
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
        <script>
            window.addEventListener('load', function(e) {
                setTimeout(function() { window.scrollTo(0, 1); }, 1);
            }, false);
        </script>
    </head>
    <body>
        <font size="+6"><a href="{!$CurrentPage.url}" target="_top">Open Only top frame</a></font><br/>
        <apex:pageBlock >
            <apex:pageBlockButtons >
                <apex:commandButton value="Save" action="{!Save}" />
            </apex:pageBlockButtons>
            <apex:pageblockSection title="Read Only" columns="2">
                <apex:outputField value="{!record.ID}" />
                <apex:outputField value="{!record.Name}" />
                <apex:outputField value="{!record.AutoNumber__c}" />
                <apex:outputField value="{!record.Formula__c}" />
                <apex:inputField value="{!record.MasterDetail__c}" />
            </apex:pageblockSection>
            <apex:pageblockSection title="HTML5 + InputField" columns="2">
                <apex:inputField type="date" showDatePicker="false" value="{!record.Date__c}" />
                <apex:inputField label="Local Datetime" type="datetime-local" showDatePicker="false" value="{!record.DateTime__c}" />
                <apex:inputfield type="email" value="{!record.Email__c}" />
                <apex:inputField type="number" value="{!record.Geolocation__Latitude__s}" />
                <apex:inputField type="number" value="{!record.Geolocation__Longitude__s}" />
                <apex:inputField type="number" value="{!record.Number__c}" />
                <apex:inputField value="{!record.Picklist__c}" />
                <apex:inputField value="{!record.Text__c}" />
                <apex:inputField value="{!record.TextArea_Long__c}" />
                <apex:inputField value="{!record.TextArea__c}" />
                <apex:inputField type="tel" value="{!record.Phone__c}" />
                <apex:inputField type="url" value="{!record.URL__c}" />
            </apex:pageblockSection>
            <apex:pageblockSection title="HTML5 + Input" columns="2">
                <apex:pageBlockSectionItem >
                    <apex:outputLabel >Currency</apex:outputLabel>
                    <apex:input type="number" value="{!aCurrency}" />
                </apex:pageBlockSectionItem>
                <apex:pageBlockSectionItem >
                    <apex:outputLabel >Time</apex:outputLabel>
                    <apex:input type="time" value="{!aTime}" />
                </apex:pageBlockSectionItem>
                <apex:pageBlockSectionItem >
                    <apex:outputLabel >Week</apex:outputLabel>
                    <apex:input type="week" value="{!aWeek}" />
                </apex:pageBlockSectionItem>
                <apex:pageBlockSectionItem >
                    <apex:outputLabel >Percentage</apex:outputLabel>
                    <apex:input type="number" value="{!aPercent}" />
                </apex:pageBlockSectionItem>
            </apex:pageblockSection>
            <apex:pageblockSection title="Standard Input" columns="2">
                <apex:inputField value="{!record.Checkbox__c}" />
                <apex:pageBlockSectionItem >
                    <apex:outputLabel >Encrypted</apex:outputLabel>
                    <apex:inputSecret value="{!record.Encrypted__c}" />
                </apex:pageBlockSectionItem>
                <apex:inputField value="{!record.Lookup__c}" />
                <apex:inputField value="{!record.MultiSelect__c}" />
            </apex:pageblockSection>
            <apex:pageblockSection columns="1">
                <apex:inputField value="{!record.TextArea_Rich__c}" />
            </apex:pageblockSection>
        </apex:pageBlock>
    </body>
</html>
</apex:form>
</apex:page>

I also wanted to load a picture using the new IOS 6 feature that allows the <input type="file" /> to use the camera, but I discovered the pictures were huge and I was struggling while loading those huge pictures in Salesforce, not to mention that storing large pictures in the database would be less than ideal. To accomplish this, I created this Visualforce page:

<apex:page docType="html-5.0" sidebar="false" showHeader="false" cache="false" Controller="Mobile" >
<apex:pageMessages />
<html>
    <head>
        <meta charset="utf-8" />
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
        <script>
            window.addEventListener('load', function(e) {
                setTimeout(function() { window.scrollTo(0, 1); }, 1);
            }, false);
        </script>
        <script src="{!URLFOR($Resource.CanvasResize, 'jquery-1.7.2.min.js')}"></script>
        <script src="{!URLFOR($Resource.CanvasResize, 'jquery.exif.js')}"></script>
        <script src="{!URLFOR($Resource.CanvasResize, 'jquery.canvasResize.js')}"></script>
        <script src="{!URLFOR($Resource.CanvasResize, 'canvasResize.js')}"></script>
        <style>
            .thumb {
                border: 1px solid #000;
                margin: 10px 5px 0 0;
            }
        </style>
    </head>
    <body>
        <font size="+6"><a href="{!$CurrentPage.url}" target="_top">Open Only top frame</a></font><br/>
        <apex:pageBlock >
            <apex:pageblockSection title="Load Field" columns="1">
                <apex:pageBlockSectionItem >
                    <apex:outputLabel >File</apex:outputLabel>
                    <apex:outputPanel >
                        <input type="File" onchange="fileChosen(this)" />
                        <br/><output id="list"></output>
                    </apex:outputPanel>
                </apex:pageBlockSectionItem>
            </apex:pageblockSection>
        </apex:pageBlock>
    </body>
    <script>
        var fileOriginal;
        var bigImageReader = new FileReader();
        var vfrData = {"apexType":"c.Mobile.vfrDocument"};

        j$ = jQuery.noConflict();
        Visualforce.remoting.timeout = 120000;

        function fileChosen(fileChosenEvent) {
            // Get file
            fileOriginal = fileChosenEvent.files[0];
            
            // Is it an image?
            if(!fileOriginal.type.match('image')) {
                alert('Must use an image! Received: ' + fileOriginal.type);
                return;
            }
            
            // Process large image
            bigImageReader.readAsDataURL(fileOriginal);
        }
        
        bigImageReader.onload = function(bigImageReaderEvent) {
            // drawImage(bigImageReaderEvent.target.result);
            resizeImage();
        };
        
        function drawImage(data) {
            var span = document.createElement('span');
            span.innerHTML = ['<img class="thumb" src="', data, '" title="', escape(fileOriginal.name), '"/>'].join('');
            document.getElementById('list').insertBefore(span, null);
        }
            
        function resizeImage() {    
            // Resize Image
            j$.canvasResize(fileOriginal, {
                width: 500,
                height: 0,
                crop: false,
                quality: 80,
                //rotate: 90,
                callback: function(data, width, height) {
                    vfrData.name = fileOriginal.name;
                    vfrData.contentType = fileOriginal.type;
                    vfrData.sData = data;
                    vfrData.sData.bodyLength = vfrData.sData.length;
                    // vfrData.bData = j$.canvasResize('dataURLtoBlob', data);
                    drawImage(vfrData.sData);
                    vfrSubmitFiles(vfrData.sData);
                }
            });
        };
        
        function vfrSubmitFiles() {
            if (vfrData.sData != null) {
                alert('Please wait, sending picture to Salesforce');
                Mobile.loadFile(vfrData, function(result, event) {
                    if(event.status) {
                        alert(result);
                    } else {
                        alert("Visualforce Remoting Failed");
                    }
                });
            }
        }
        </script>
</html>
</apex:page>

Which uses the same Apex controller from the first Visualforce code above:

global class Mobile {
    public AllType__c record { get; set; }
    public Document doc { get; set; }
    
    public Double aCurrency {
        get { return record.Currency__c; }
        set { record.Currency__c = value; }
    }
    public Double aPercent {
        get { return record.Percent__c; }
        set { record.Percent__c = value; }
    }
    public Time aTime { get; set; }
    public dateTime aWeek { get; set; }
    
    public mobile() {
        doc = new Document();
        record = [SELECT ID, Name, AutoNumber__c, Checkbox__c, Currency__c, DateTime__c, Date__c, Email__c,
                Encrypted__c, Formula__c, Geolocation__Latitude__s, Geolocation__Longitude__s, Lookup__c,
                MasterDetail__c, MultiSelect__c, Number__c, Percent__c, Phone__c, Picklist__c, TextArea_Long__c,
                TextArea_Rich__c, TextArea__c, Text__c, URL__c FROM AllType__c LIMIT 1];
        // record = new AllType__c();
        // List<Account> acts = [SELECT ID FROM Account LIMIT 2]; 
        // record.lookup__c = acts[0].id;
        // record.MasterDetail__c = acts[1].id;
    }
    
    private static AllType__c getParentRecord() {
        return [SELECT ID, Name, AutoNumber__c, Checkbox__c, Currency__c, DateTime__c, Date__c, Email__c,
                    Encrypted__c, Formula__c, Geolocation__Latitude__s, Geolocation__Longitude__s, Lookup__c,
                    MasterDetail__c, MultiSelect__c, Number__c, Percent__c, Phone__c, Picklist__c, TextArea_Long__c,
                    TextArea_Rich__c, TextArea__c, Text__c, URL__c FROM AllType__c LIMIT 1];
    }
    
    @RemoteAction
    global static String loadFile(vfrDocument doc) {
        String b64;
        String dataType;
        Attachment att;
        
        try {
            // Get Data
            List<String> docParts = doc.sData.split(',');
            String metadata = docParts[0];
            b64 = docParts[1];            
            List<String> metadataParts = metadata.split(';');
            dataType = metadataParts[0].split(':')[1];            
            doc.bData = EncodingUtil.base64Decode(b64);
            
            // Attach file
            att = new Attachment();
            att.Body = doc.bData;
            att.ContentType = doc.contentType;
            att.Name = doc.name;
            att.ParentId = getParentRecord().id;
            Insert att;
        } catch (Exception ex) {
            return 'Apex Exception: ' + ex.getStackTraceString() + ' : ' + ex.getMessage();
        }
        return 'Apex completed Succesfully. Attachment ID: ' + att.ID; 
    }
    
    global class vfrDocument {
        public Blob bData { get; set; }
        public String name { get; set; }
        public String sData { get; set; }
        public String contentType { get; set; }
        public Integer bodyLength { get; set; }
    }
        
    public void save() {
        insert record;
    }
}

At this time, I am still strougling with some issues which include:

  • I was not able to compile <apex:input type="range" />
  • Although <apex:input type="week"> compiled, it just renders a simple textbox
  • <apex:inputField type="datetime" /> compiled and rendered properly, I could not send the data back to Salesforce beacuase it gave me a validation error with the format of the date.

comments powered by Disqus

© El Toro . IT @ 2013
Andrés Pérez