Friday, 26 January 2018

SuperBadge: Lightning Component Framework Specialist

SuperBadge: Lightning Component Framework Specialist

Step2:

FriendswithBoats.app:

<aura:application extends="force:slds">
<lightning:layout class="flexipageHeader slds-page-header uiBlock oneAnchorHeader">
        <lightning:layoutItem padding="horizontal-small">
        <lightning:icon iconName="custom:custom54" />
    </lightning:layoutItem>    
    <h1>
         Friends with Boats
    </h1>
        
    </lightning:layout>
    <lightning:layout>
    <lightning:layoutItem padding="around-small" size="6">
                 <lightning:card title="Find a Boat" class="slds-m-top_10px" >
                          <c:BoatSearchForm />
                 </lightning:card>
        </lightning:layoutItem>
    </lightning:layout>
   
    <lightning:layout>
        <lightning:card title="Matching Boats" class="slds-m-top_10px" >
            <c:BoatSearchResults />
        </lightning:card>
    </lightning:layout>
</aura:application>

BoatSearchForm

<aura:component description="BoatSearchForm"
        controller="BoatSearchFormController" implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,force:lightningQuickAction" access="global" >
 
   
    <!-- dynamically load the BoatTypes -->
    <aura:attribute name="BoatTypes" type="BoatType__c[]" />
    <aura:attribute name="selectedType" type="String" default="foo"/>
    <aura:attribute name="renderNew" type="Boolean" default="true"/>

    <article class="slds-card slds-m-bottom_large">
        <div class="slds-media__body">
            <div >

                <lightning:layout horizontalAlign="center" verticalAlign="center">
                    <lightning:layoutItem padding="horizontal-medium" >
                        <!-- Create a dropdown menu with options -->
                        <lightning:select aura:id="boatTypes" label="" name="selectType"
                                          onchange="{!c.handleChange}">
                            <option value="">All Types</option>
                            <aura:iteration items="{!v.BoatTypes}" var="boatType">
                                <option value="{!boatType.Id}" text="{!boatType.Name}"/>
                            </aura:iteration>
                        </lightning:select>

                    </lightning:layoutItem>


                    <lightning:layoutItem >
                        <div class="slds-button-group" role="group">
                            <lightning:button class="slds-button" variant="brand" label="Search" onclick="{!c.search}"/>

            <!--
            The form’s controller checks whether the event.force:createRecord event
            is supported by a standalone app and either shows or hides the New button
            according to best practices.
            -->

                            <aura:if isTrue="{!v.renderNew}">
                                <lightning:button class="slds-button" variant="neutral" label="New" onclick="{!c.newBoat}"/>
                            </aura:if>
                        </div>
                    </lightning:layoutItem>
                </lightning:layout>
            </div>
        </div>
    </article>


</aura:component>

BoatSearchForm Controller

({
    doInit : function(component, event, helper){

        helper.loadBoatTypes(component);
    },

    handleChange : function(component, event, helper){
        console.log(component.find("boatTypes").get("v.value"));
        component.set("v.selectedType", component.find("boatTypes").get("v.value"));
    },

    search : function(component, event, helper){
        var selectedType = component.get("v.selectedType");
        console.log("Search button pressed " + selectedType)
    },

    newBoat : function(component, event, helper){
        var boatTypeId = component.get("v.selectedType");
        console.log("New button pressed " + boatTypeId);
        var requestNewBoat = component.getEvent("launchNewBoatForm");
        requestNewBoat.setParams({"boatTypeId": boatTypeId});
        requestNewBoat.fire();
    },

    handleNewBoatForm: function(component, event, helper){
        console.log("handleNewBoatForm handler called.")
        var boatTypeId = component.get("v.selectedType");

        console.log(boatTypeId);
        var createNewBoat = $A.get("e.force:createRecord");
        createNewBoat.setParams({
            "entityApiName": "Boat__c",
        })
        if(! boatTypeId==""){
            createNewBoat.setParams({
                "defaultFieldValues": {'BoatType__c': boatTypeId}
           })
        }
        createNewBoat.fire();
    },
    //more handlers here
})

BoatSearchForm Helper

({
    
    loadBoatTypes: function(component){
    //create the action
            console.log("Helper started");
            var action = component.get("c.getBoatTypes");

            //add the callback behavior for when the response is received
            action.setCallback(this,function(response){
            var state = response.getState();
            if (state === "SUCCESS"){
                component.set("v.BoatTypes", response.getReturnValue());
                console.log(response.getReturnValue());
                }
                else {
                console.log("Failed with state: " + state);
                }
            });

            //send action off to be executed
            $A.enqueueAction(action);
       },

renderNewButton: function (component) {
        var action = component.get('c.getUITheme');
        action.setCallback(this, function (response) {
            if (response.getState() === 'SUCCESS') {
                if (response.getReturnValue() == 'Theme4d' && $A.get('e.force:createRecord')) {
                    component.set('v.showNewButton', true);
                }
            }
        });
        $A.enqueueAction(action);
    },
    
})

BoatSearch

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,force:lightningQuickAction" access="global" >
    
    <lightning:card title="Find a Boat" class="slds-m-top_10px" >
     <c:BoatSearchForm />
</lightning:card>    
    <lightning:card title="Matching Boats" >
         <c:BoatSearchResults aura:id="BSRcmp"/>
    </lightning:card>
</aura:component>

BoatSearchController

({
    onFormSubmit : function(component, event, helper){
        console.log("event received by BoatSearchController.js");
        var formData = event.getParam("formData");
        var boatTypeId = formData.boatTypeId;
        var BSRcmp = component.find("BSRcmp");
        var auraMethodResult = BSRcmp.search(boatTypeId);
        console.log("auraMethodResult: " + auraMethodResult);
    }

})

Step 3:

BoatTile Component

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" >
<aura:attribute name="boat" type="Boat__c" />
    
        <lightning:button class="tile">
            <div class="innertile" style="{!'background-image:' + 'url(' + v.boat.Picture__c+')'}">
              <div class="lower-third">
               <h1 class="slds-truncate">{!v.boat.Contact__r.Name}</h1>
              </div>
            </div>
        </lightning:button>
</aura:component>

BoatTile Css

.THIS.tile {
    position:relative;
    display: inline-block;
    width: 100%;
    height: 220px;
    padding: 1px !important;
}
.THIS .innertile {
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    width: 100%;
    height: 100%;
}
.THIS .lower-third {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    color: #FFFFFF;
    background-color: rgba(0, 0, 0, .4);
    padding: 6px 8px;
}
Note: However, the other two classes are wrapped in .tile. It should be
.THIS.tile .innertile

.THIS.tile .lower-third

OR
.THIS .innertile

.THIS .lower-third

BoatSearchResults Component

<aura:component controller="BoatSearchResults"
                implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,force:lightningQuickAction" access="global" >
    <aura:attribute name="boats" type="Boat__c[]" />
    
    <aura:attribute name="boatTypeId" type="String" description="" />
    <aura:handler name="init" value="{!this}" action="{!c.doSearch}" />
    
    <lightning:layout multipleRows="true">
        <aura:if isTrue="{!v.boats.length > 0}">
            <aura:iteration items="{!v.boats}" var="bot">
                <lightning:layoutItem flexibility="auto" padding="around-small">
                <c:BoatTile boat="{!bot}" />
                </lightning:layoutItem>
            </aura:iteration>
            <aura:set attribute="else">
                <div class="slds-align_absolute-center">
                    No boats found
                </div>
            </aura:set>
        </aura:if>
    </lightning:layout>
    
</aura:component>

BoatSearchResults Controller

({
    doSearch: function(component, event, helper) {
          helper.onSearch(component);
    }
})

BoatSearchReults Helper

({
onSearch : function(component) {
        
var action = component.get("c.getBoats");
        //debugger;
        action.setParam({"boatTypeId":""});
        action.setCallback(this, function(response){
            //debugger;
            var status = response.getState();
            if(status === "SUCCESS") {
                 if(! $A.util.isEmpty(response.getReturnValue())){
                    component.set("v.boats",response.getReturnValue()); 
                }
            }
            
        });
         $A.enqueueAction(action);
}
})

BoatSearchResults Apex Class

public class BoatSearchResults{

   
    @AuraEnabled
    public static list<Boat__c> getBoats(string boatTypeId){
        if(string.IsBlank(boatTypeId)){
            return [SELECT Id, Name,Picture__c,Contact__c,Contact__r.Name from Boat__c];
        }
        else{
            return [SELECT Id, Name,Picture__c,Contact__c,Contact__r.Name from Boat__c where BoatType__c =: boatTypeId];
        }
    }
}

Step 4:

Create an event. Developer Console--->File---> New ---> Lightning Event.
Event name is formsubmit.

formsubmit Event:

<aura:event type="COMPONENT" description="Boat Event" >
    <aura:attribute name="formData" type="Object[]"/>
</aura:event>

BoatSearchForm Component:

Add this line and call function on button.
<aura:registerEvent name="formSubmitEvent" type="c:formsubmit"/>
<lightning:button class="slds-button" variant="brand" label="Search" onclick="{!c.onFormSubmit}"/>

BoatSearchForm Controller

Add this function.

onFormSubmit : function(component, event, helper){
        console.log("event received by BoatSearchController.js");
        var boatTypeId = component.get("v.selectedType");
        console.log("Search button pressed " + boatTypeId);
var formSubmit = component.getEvent("formSubmitEvent");
//var formsubmit = component.getEvent("formsubmit");
        console.log("formSubmit Var" + formSubmit); 
formSubmit.setParams({"formData":{"boatTypeId" : boatTypeId}});
        formSubmit.fire();
     },

BoatSearch Component: 

<aura:handler name="formsubmit" event="c:formsubmit" action="{!c.onFormSubmit}" phase="capture"/>

BoatSearch Controller

onFormSubmit : function(component, event, helper){
        console.log("event received by BoatSearchController.js");
        var formData = event.getParam("formData");
        var boatTypeId = formData.boatTypeId;
        var BSRcmp = component.find("BSRcmp");
        var auraMethodResult = BSRcmp.search(boatTypeId);
        console.log("auraMethodResult: " + auraMethodResult);
    },

BoatSearchResult Component:

<aura:method name="search" description="accepts boatTypeId and executes search that refreshes the boats attribute">
        <aura:attribute name="boatTypeId" type="Id"/>
    </aura:method>

BoatSearchResult Helper

({
onSearch : function(component) {
        
        var boatTypeID = component.get('v.boatTypeId');
var action = component.get("c.getBoats");
        //debugger;
        //action.setParam({"boatTypeId":""});
        action.setParams({"boatTypeId" : boatTypeID});
        console.log("***boatType: " +boatTypeID);
        action.setCallback(this, function(response){
            //debugger;
            var status = response.getState();
            if(status === "SUCCESS") {
                 if(! $A.util.isEmpty(response.getReturnValue())){
                    component.set("v.boats",response.getReturnValue()); 
                }
            }
            else{
                var errors = response.getError();
                if (errors[0] && errors[0].message){
                    console.log("***Error message: " + errors[0].message);
                }
            }
            
        });
         $A.enqueueAction(action);
}
})

Step 5:

Create BoatSelect event.

BoatSelect.evt


<aura:event type="COMPONENT" description="fires when a user clicks a boat on BoatSearchResults.cmp">
    <aura:attribute name="boatId" type="Id"/>
</aura:event>

Boat Tile Component

<aura:component >
    <aura:attribute name="boat" type="Boat__c" />
    <aura:attribute name="selected" type="boolean" default="false"/>
    <aura:registerEvent name="BoatSelect" type="c:BoatSelect"/>
    <aura:registerEvent name="BoatSelected" type="c:BoatSelected"/>

        <lightning:button onclick="{!c.onBoatClick}"
                          class="{! v.selected ? 'tile selected' : 'tile' }">
            <div style="{!'background-image:url(\'' + v.boat.Picture__c + '\')'}"
                 class="innertile">
                <div class="lower-third">
                    <h1 class="slds-truncate">{! v.boat.Contact__r.Name}</h1>
                </div>
            </div>
        </lightning:button>

</aura:component>

BoatTile Controller

onBoatClick : function(component, event, helper) {
        var cmpEvent = component.getEvent("BoatSelect");
        //var boatId = component.get("v.boat.Id");
        var boatId = event.getSource().get("v.name");
        cmpEvent.setParams({
            "boatId" : boatId
        });
        cmpEvent.fire();
    },

BoatTile Css

.THIS.selected {
    border: 3px solid rgb(0, 112, 210);
}

BoatSearchResult Component

Add below code in component.

<aura:handler name="BoatSelect" event="c:BoatSelect" action="{!c.onBoatSelect}"/>

Replace aura iteration code with below one.

<c:BoatTile boat="{!boat}" selected="{!boat.Id == v.selectedBoatId ? 'true' : 'false' }"/>

BoatSearchResult Controller

Add onBoatSelect Function in controller.

onBoatSelect : function(component, event, helper) {
     var boatId = event.getParam("boatId");
        console.log(boatId);
        component.set("v.selectedBoatId",boatId);
    },

Step 6:

Create BoatBoatSelected event.

BoatBoatSelected.evt


<aura:event type="APPLICATION" description="Boat Event">
    <aura:attribute name="boat" type="Boat__c" />
</aura:event>

BoatDetail Component

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" >
    <aura:attribute name="boat" type="Boat__c" />
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
<aura:attribute name="showButton" type="Boolean" default="false"/> 
    <lightning:button label="Full Details" onclick="{!c.onFullDetails }" />
    
    <lightning:card iconName="utility:anchor">
        <aura:set attribute="title">
            {!v.boat.Contact__r.Name}'s boat
        </aura:set>
        <aura:set attribute="actions">
            <aura:if isTrue='{!v.showButton}'>
            <lightning:button label="Full Details" onclick="{!c.onFullDetails}"/>
            </aura:if>
        </aura:set>
        <p class="slds-p-horizontal_small">

            <lightning:layout >
                <lightning:layoutItem flexibility="grow" size="6" mediumDeviceSize="6" largeDeviceSize="6">
                    <div class="slds-p-horizontal--small">
                            <div class="boatproperty">
                                <span class="label">Boat Name:</span>
                                <span><ui:outputText value="{!v.boat.Name}"/></span>
                            </div>
                            <div class="boatproperty">
                                <span class="label">Type:</span>
                                <span><ui:outputText value="{!v.boat.BoatType__r.Name}"/></span>
                            </div>
                            <div class="boatproperty">
                                <span class="label">Length:</span>
                                <span><ui:outputText value="{!v.boat.Length__c}"/> ft</span>
                            </div>
                            <div class="boatproperty">
                                <span class="label">Est. Price:</span>
                                <span><lightning:formattedNumber value="{!v.boat.Price__c}" currencyCode="USD" style="currency" currencyDisplayAs="symbol"/></span>
                            </div>
                            <div class="boatproperty">
                                <span class="label">Description:</span>
                                <span><ui:outputRichText class="slds-text-longform" value="{!v.boat.Description__c}"/></span>
                            </div>
                        </div>
                </lightning:layoutItem>
                <lightning:layoutItem flexibility="grow" size="6" mediumDeviceSize="6" largeDeviceSize="6">
                    <div class="imageview" style="{!'background-image:url(\'' + v.boat.Picture__c + '\')'}"/>
                </lightning:layoutItem>
            </lightning:layout>
        </p>
    </lightning:card>
</aura:component>

BoatDetail Controller

({
onFullDetails: function(component, event, helper) {
        var navEvt = $A.get("e.force:navigateToSObject");
        navEvt.setParams({
            "recordId": component.get("v.boat.Id")
        });
        navEvt.fire();
    },
})

BoatDetail CSS

.THIS.boatproperty {
    margin-bottom: 3px;
}
.THIS .label {
    font-weight: bold;
    display: block;
}

.THIS.imageview {
    background-repeat: no-repeat;
    background-size: contain;
    height: 200px;
    margin: 2px;   
}
.THIS {
}

BoatDetails Component

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" >
    <aura:attribute name="id" type="Id" access="public"/>
    <aura:attribute name="boat" type="Boat__c" access="public"/>
    <aura:handler event="c:BoatSelected" action="{!c.onBoatSelected}"/>
    <force:recordData aura:id="service"
                      recordId="{!v.id}"                          
                      fields="Id,Name,Description__c,Price__c,Length__c,Contact__r.Name,Contact__r.Email,Contact__r.HomePhone,BoatType__r.Name,Picture__c"
                      targetFields="{!v.boat}"
                      recordUpdated="{!c.onRecordUpdated}"/>
                      
    
    <lightning:tabset >
        <lightning:tab label="Details">
            <aura:if isTrue="{!not(empty(v.boat))}">
                <c:BoatDetail boat="{!v.boat}"/>
            </aura:if>
        </lightning:tab>
        
        <lightning:tab label="Reviews">
        </lightning:tab>
        
        <lightning:tab label="Add Review">
        </lightning:tab>
        
    </lightning:tabset>
</aura:component>

BoatDetails Controller

({
onBoatSelected : function(component, event, helper) {
        var boatSelected = event.getParam("boat");
        component.set("v.id",boatSelected.Id);
        var service = component.find("service");
        service.reloadRecord() ;
    },
    onRecordUpdated : function(component, event, helper) {
    },
})

BoatTile Component

Add this line in Boat tile component
<aura:registerEvent name="boatselected" type="c:BoatSelected"/>

BoatTile Controller

({
    onBoatClick : function(component, event, helper) {
        var cmpEvent = component.getEvent("BoatSelect");
        //var boatId = component.get("v.boat.Id");
        var boatId = event.getSource().get("v.name");
        cmpEvent.setParams({
            "boatId" : boatId
        });
        cmpEvent.fire();
        //New event
        var BoatSelectedEvt = $A.get('e.c:BoatSelected');
        BoatSelectedEvt.setParams({
            "boat" : boat
        });       
        BoatSelectedEvt.fire();
    },
})

Add BoatDetails component in your lightning page.

Step 7

Create an event name BoatReviewAdded

BoatReviewAdded Event

<aura:event type="COMPONENT" description="Boat Review Add Event">
   <aura:attribute name="BoatReviewAdded" type="Boat__c"/>
</aura:event>

AddBoatReview Component

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" >
<aura:attribute name="boat" type="Boat__c" />    
    <aura:attribute name="boatReview" type="BoatReview__c" access="public"/>
    <aura:attribute name="boatReviewRecord" type="Object" access="public"/>
    <aura:attribute name="id" type="Id" />
    <aura:attribute name="recordError" type="String" access="private"/>
    <aura:registerEvent name="BoatReviewAdded" type="c:BoatReviewAdded"/>
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    
    <force:recordData aura:id="service"
                      recordId="{!v.id}" 
                      targetRecord="{!v.boatReviewRecord}"
                      fields="Id,Name,Comment__c,Boat__c"
                      targetFields="{!v.boatReview}"
                      targetError="{!v.recordError}"
                      recordUpdated="{!c.onRecordUpdated}"/>
    
    <lightning:layout multipleRows="true">
        <lightning:layoutItem padding="horizontal-medium" size="12">
            <lightning:input name="title" label="Title" value="{!v.boatReview.Name}"/>
        </lightning:layoutItem>
        <lightning:layoutItem padding="horizontal-medium" size="12">
            <label class="slds-form-element__label" for="input-id-01">Description</label>
                <lightning:inputRichText value="{!v.boatReview.Comment__c}" disabledCategories="FORMAT_FONT"/>
        </lightning:layoutItem>
        <lightning:layoutItem padding="horizontal-medium" size="12">
            <lightning:button iconName="utility:save" label="Submit" onclick="{!c.onSave}"/>
        </lightning:layoutItem>
    </lightning:layout>
    <aura:if isTrue="{!not(empty(v.recordError))}">
        <div class="recordError">
            <ui:message title="Error" severity="error" closable="true">
                {!v.recordError}
            </ui:message>
        </div>
    </aura:if>
</aura:component>

AddBoatReview Controller

({
onSave : function(component, event, helper) {
component.set("v.boatReview.Boat__c",component.get("v.boat.Id"));
        component.find("service").saveRecord(function(saveResult) {
if (saveResult.state === "SUCCESS" || saveResult.state === "DRAFT") {

                var cmpEvent = component.getEvent("BoatReviewAdded");
                cmpEvent.fire();
                var resultsToast = $A.get("e.force:showToast");
                if (resultsToast) {
                    resultsToast.setParams({
          "title": "Submitted",
          "message": "The review was saved."
        });
        resultsToast.fire();
                }
                else {
                    alert('The review was saved.');
                }
            }
            else if (saveResult.state === "ERROR") {
                var errMsg='';
                console.log('Problem saving record, error: ' + JSON.stringify(saveResult.error));
                for (var i = 0; i < saveResult.error.length; i++) {
                    errMsg += saveResult.error[i].message + "\n";
                }
                component.set("v.recordError", errMsg);
            }
            var boatReviewAddedEvnt=component.getEvent("boatReviewAdded");
            boatReviewAddedEvnt.fire();
            helper.onInit(component,event,helper);
                
        });
},
    doInit: function(component, event, helper) {
        helper.onInit(component,event);
    },
    onRecordUpdated: function(component, event, helper) {
        var eventParams = event.getParams();
        if(eventParams.changeType === "CHANGED") {
            var changedFields = eventParams.changedFields;
            var saveResultsToast = $A.get("e.force:showToast");
            if(saveResultsToast!='undefined'){
                saveResultsToast.setParams({ "title": "Saved","message": "Boat Review Saved"});
                saveResultsToast.fire(); 
            }
            else{
                alert('Boat Review Saved');
            }
        }
    },
})

AddBoatReview Helper

({
onInit : function(component,event) {
component.find("service").getNewRecord(
            "BoatReview__c", null,false,
            $A.getCallback(function() {
                var rec = component.get("v.boatReview");
      var error = component.get("v.recordError");
      console.log('hey', JSON.stringify(rec));
                if (error || (rec === null)) {
        console.log("Error initializing record template: " + error);
                } else{
                    rec.Boat__c = component.get("v.boat").Id;
        //component.set("v.boatReview", rec);
        component.set("v.boatReview.Boat__c",component.get("v.boat").Id);
        console.log("Record template initialized: " + rec.sobjectType);
                }
            })
        );
}
})

BoatDetails Component

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" >
    <aura:attribute name="id" type="Id" />
    <aura:attribute name="boat" type="Boat__c" />
    <aura:attribute name="selectedTabId" type="string" />
    <aura:registerEvent name="BoatReviewAdded" type="c:BoatReviewAdded"/>
    <aura:handler event="c:BoatSelected" action="{!c.onBoatSelected}"/>
    <aura:handler name="BoatReviewAdded" event="c:BoatReviewAdded" action="{!c.onBoatReviewAdded}" />

    <force:recordData aura:id="service"
                      recordId="{!v.id}"                          
                      fields="Id,Name,Description__c,Price__c,Length__c,Contact__r.Name,Contact__r.Email,Contact__r.HomePhone,BoatType__r.Name,Picture__c"
                      targetFields="{!v.boat}"
                      recordUpdated="{!c.onRecordUpdated}"/>
                      
    
    <lightning:tabset variant="scoped" selectedTabId="{!v.selectedTabId}" aura:id="tabsDetail">
        <lightning:tab label="Details" id="details">
            <aura:if isTrue="{!not(empty(v.boat))}">
                <c:BoatDetail boat="{!v.boat}"/>
            </aura:if>
        </lightning:tab>
        
        <lightning:tab label="Reviews" id="boatReviewTab">
        </lightning:tab>
        
        <lightning:tab label="Add Review" id="addReview">
            <c:AddBoatReview boat="{!v.boat}"/>
        </lightning:tab>
        
    </lightning:tabset>
     <aura:if isTrue="{! !empty(v.boat)}">
        <article class="slds-card">
                <lightning:tabset >
                    <lightning:tab label="Details" id="details">
                        <c:BoatDetail boat="{!v.boat}"/>
                    </lightning:tab>
                    <lightning:tab label="Reviews" id="boatReviewTab">
                        Sample review
                    </lightning:tab>
                    <lightning:tab label="Add Review" id="addReview">
                        Sample add review
                    </lightning:tab>
                </lightning:tabset>
        </article>
    </aura:if>
</aura:component>

BoatDetails Controller

({
onBoatSelected : function(component, event, helper) {
        var boatSelected = event.getParam("boat");
        component.set("v.id",boatSelected.Id);
        var service = component.find("service");
        service.reloadRecord() ;
    },
    onRecordUpdated : function(component, event, helper) {
    },
    onBoatReviewAdded : function(component, event, helper) {
        console.log("Event started");
        component.find("tabsDetail").set("v.selectedTabId", "boatReviewTab");
        //component.set("v.selTabId", "boatReviewTab");
        

    },
    
})

Step 8:

BoatReviews Component

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" 
                controller="BoatReviews" access="global" >
    <aura:attribute name="boat" type="Boat__c" />
    <aura:attribute name ="boatReviews" type= "BoatReview__c[]" access="private"/>
    <aura:handler name="change" value="{!v.boat}" action="{!c.refresh}"/>
    <!-- set up the aura:method for refresh -->
    <aura:method name="refresh" action="{!c.doInit}" access="public"
                 description="BoatDetailsController.js invokes refresh whenever boat is updated">
    </aura:method>
    <!--<aura:handler name="init" value="{!this}" action="{!c.doInit}"/> -->
    
    <ui:scrollerWrapper class="scrollerSize">
        <!--Scrollable content here -->
        <aura:if isTrue="{!v.boatReviews.length==0}">
            <lightning:layoutItem class="slds-align_absolute-center" flexibility="auto" padding="around-small">   
                <ui:outputText value="No Reviews Available" />
            </lightning:layoutItem>
        </aura:if>
        <div class="slds-feed">
            <ul class="slds-feed__list">
                <aura:iteration items="{!v.boatReviews}" var="boatReview">
                    <li class="slds-feed__item">
                        <div class="slds-media__body">
                       <div class="slds-grid slds-has-flexi-truncate">
                            <a href="javascript:void(0)" onclick="{!c.onUserInfoClick}"
                              data-userid="{!boatReview.CreatedBy.Id}">
                              {!boatReview.CreatedBy.Name}
                          </a>
                                            &nbsp; &mdash; &nbsp; {!boatReview.CreatedBy.CompanyName}
                       </div>
                         <p><lightning:formattedDateTime value="{!boatReview.CreatedDate}" 
                                   year="numeric" month="short" day="numeric"  
                                   hour="2-digit" minute="2-digit" hour12="true"/></p>
                        </div>
                    </li>
                </aura:iteration>
            </ul>
        </div>
    </ui:scrollerWrapper>
    
</aura:component>

BoatReviews Controller

({
doInit: function(component, event, helper) {
        helper.onInit(component,event);
    },
    refresh : function(component,event,helper){
        console.log("refresh called")
        this.doInit;
    },
    onUserInfoClick : function(component,event,helper){
        var userId = event.currentTarget.getAttribute("data-userid");
        var navEvt = $A.get("e.force:navigateToSObject");
        navEvt.setParams({
            "recordId" : userId,
        });
        navEvt.fire()
    },


})

BoatReviews Helper

({
onInit : function(component,event) {
        var boat = component.get("v.boat");
        var action = component.get("c.getAll");
        //action.setParams({"boatId " : boatId });
        action.setParams({"boatId" : boat.Id});
        console.log("***boatId: " +boatId);
        action.setCallback(this, function(response){
            var status = response.getState();
            if(status === "SUCCESS") {
                 //if(! $A.util.isEmpty(response.getReturnValue())){
                     component.set("v.boatReviews", response.getReturnValue());
                    //component.set("v.boats",response.getReturnValue()); 
               // }
            }
            else{
                var errors = response.getError();
                if (errors[0] && errors[0].message){
                    console.log("***Error message: " + errors[0].message);
                }
            }
        });
$A.enqueueAction(action);
}
})

BoatDetails Component

Add these lines in code

<lightning:tab label="Reviews" id="boatReviewTab">
            <c:BoatReviews boat="{!v.boat}"/>
        </lightning:tab>

BoatDetails Controller

Add these lines in onBoatReviewAdded function

var BoatReviews = component.find("boatReviewTab");
        //BRcmp is the aura:id for the component when invoked in BoatDetails.cmp
        BoatReviews.refresh();

BoatReviews Apex Class


public class BoatReviews{

    @AuraEnabled
    public static list<BoatReview__c> getAll(Id boatId ){
         return [SELECT Id, Name,Comment__c,Rating__c,LastModifiedDate,CreatedDate,CreatedBy.Name,
          CreatedBy.SmallPhotoUrl, CreatedBy.CompanyName from BoatReview__c];
    }
}

Step 9

FiveStarRating Component

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" >
<aura:attribute name='value' type='Integer' default='0' />
    <aura:attribute name='readonly' type='boolean' default='false' />
    <ltng:require styles="{!$Resource.fivestar + '/rating.css'}" scripts="{!$Resource.fivestar + '/rating.js'}" 
                  afterScriptsLoaded="{!c.afterScriptsLoaded}" />
    <aura:handler name='change' value="{!v.value}" action="{!c.onValueChange}" />
    <ul aura:id='ratingarea' class="{!v.readonly ? 'readonly c-rating' : 'c-rating' }" />
</aura:component>

FiveStarRating Controller

({
    afterScriptsLoaded : function(component, event, helper) {
        //var domEl = document.getElementsByTagName("UL")[0];
        var domEl = component.find("ratingarea").getElement();
        var currentRating = component.get('v.value');
        var readOnly = component.get('v.readonly');
        var maxRating = 5;
        var callback = function(rating) {
            component.set('v.value',rating);
        }
        component.ratingObj = rating(domEl,currentRating,maxRating,callback,readOnly); 
    },
    
    onValueChange: function(component,event,helper) {
        if (component.ratingObj) {
            var value = component.get('v.value');
            component.ratingObj.setRating(value,false);
        }
    },
})

AddBoatReview Component

 <lightning:layoutItem padding="horizontal-medium" size="12">
            <label class="slds-form-element__label" for="input-id-01">Description</label>
            <c:FiveStarRating value="{!v.boatReview.Rating__c}" readonly="false"/>
        </lightning:layoutItem>

BoatReview Component

<c:FiveStarRating aura:id="FiveStarRating" value="{!BoatReview.Rating__c}" readonly="true"/>

Step 10

PlotMapMarker Event

<aura:event type="APPLICATION" description="PlotMapMarker">
    <aura:attribute name="sObjectId" type="String"/>
    <aura:attribute name="lat" type="String"/>
    <aura:attribute name="long" type="String"/>
    <aura:attribute name="label" type="String"/>
</aura:event>

Map Component

<aura:component implements="flexipage:availableForAllPageTypes" access="global" >
    
    <aura:attribute name="width"  type="String" default="100%" />
    <aura:attribute name="height" type="String" default="200px" />
    <aura:attribute name="location" type="SObject"/>
    <aura:attribute name="jsLoaded" type="boolean" default="false"/>
    <aura:attribute access="private" name="leafletMap" type="Object" />

    <aura:handler event="c:PlotMapMarker" action="{!c.onPlotMapMarker}"/>
    
    <ltng:require styles="{!$Resource.Leaflet + '/leaflet.css'}" 
                  scripts="{!$Resource.Leaflet + '/leaflet-src.js'}"
                  afterScriptsLoaded="{!c.jsLoaded}" /> 
    <lightning:card title="Current Boat Location" >

        <div aura:id="map" style="{!'width: ' + v.width + '; height: ' + v.height}">
            <div style="width:100%; height:100%" class="slds-align_absolute-center">Please make a selection</div>
        </div>
    </lightning:card>
    
</aura:component>

Map Controller

({
    jsLoaded: function(component) {
        component.set("v.jsLoaded", true);
    },
    onPlotMapMarker: function(component, event, helper) {
    var id = event.getParam('sObjectId');
    var latitude = event.getParam('lat');
    var longitude = event.getParam('long');
    var label = event.getParam('label');
component.set("v.location, "{'latitude' : latitude, 'longitude' : longitude});

var leafletMap = helper.getLeafletMap(component, latitude, longitude);
L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}', {
    attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
}).addTo(leafletMap);
L.marker([latitude, longitude]).addTo(leafletMap)
    .bindPopup(label)
    .openPopup();
    }  
})

Map Helper

({
getLeafletMap : function(component, latitude, longitude) {
var leafletMap = component.get('v.leafletMap');
if (!leafletMap) {
    var mapContainer = component.find('map').getElement(); 
    leafletMap = L.map(mapContainer, {zoomControl: false, tap: false})
    .setView([latitude, longitude], 13);
    component.set('v.leafletMap', leafletMap);
            component.set("v.location, "{'latitude' : latitude, 'longitude' : longitude});

}

return leafletMap;
}
})

Map Design

<design:component label="Map">
    <design:attribute name="width" label="Width" description="The width of the map as a percentage (100%) or pixels (100px)" />
    <design:attribute name="height" label="Height" description="The height of the map as a percentage (100%) or pixels (100px)" />
</design:component>

BoatTile Component

Add this line
<aura:registerEvent name="PlotMapMarker" type="c:PlotMapMarker"/>

BoatTile Controller

Add these lines in onBoatClick function
var boat = component.get('v.boat');
        var lat = boat.Geolocation__Latitude__s;
        var long = boat.Geolocation__Longitude__s;
        var label = boat.Name;
        var sObjectId;
        var plotMapMarkerAppEvent = $A.get("e.c:PlotMapMarker");
        plotMapMarkerAppEvent.setParams({
            "lat"   : lat,
            "long"  : long,
            "label" : label,
            "SObjectId" : boat.Id});
        plotMapMarkerAppEvent.fire();
        console.log('lat ',lat);

Happy Coding!!!

Tuesday, 19 December 2017

Salesforce: Restrict User from Deleting Files Once Record Status Changed

Salesforce: Restrict User from Deleting Files Once Record Status Changed

Business Requirement: 

There is a custom object in Salesforce having a status field. If the status of that record is marked as "Completed" user is not allowed to attach/upload new files with it, nor user can upload new version of file nor he can delete file.

Solution:

On the front end we see Files object ,its related list and tab and in my requirement data is saved from front end in files. From back-end  Salesforce use 3 diffrent object.
  1. Content Document
  2. Content Version
  3. Content Document Link

ContentDocument object is to retrieve, query, update, and delete the latest version of a document, but not a content pack, in a library or a file in Chatter.

Use the ContentVersion object to create, query, retrieve, search, edit, and update a specific version of a Salesforce CRM Content document or Chatter file.
So in order to achieve this functionality we have to write trigger on all three objects.

Content Document Trigger:

trigger ContentDocumentTrg on ContentDocument (before delete,before update) {
    
    Map<Id,String> MapOfStatus = new Map<Id,String>();
    Set<Id> SetOfCdl = new Set<Id>();
    Set<Id> SetOfCd = new Set<Id>();
    map<Id,Id> MapOfCdNCdl = new map<Id,Id>();
    if(Trigger.IsUpdate){
    for(ContentDocument cd :Trigger.new){
    SetOfCd.add(cd.Id);
    }
    }
    if(Trigger.Isdelete){
    for(ContentDocument cd :Trigger.old){
    SetOfCd.add(cd.Id);
    }
    }
   
    for(ContentDocumentLink cdl : [SELECT LinkedEntityId,ContentDocumentId FROM ContentDocumentLink WHERE ContentDocumentId IN:SetOfCd]){
    string cdlentityId = cdl.LinkedEntityId;
    if (cdlentityId.substring(0,3) == 'a0h') {
    SetOfCdl.add(cdl.LinkedEntityId);
    MapOfCdNCdl.put(cdl.ContentDocumentId, cdl.LinkedEntityId);
    }
    }
MapOfStatus = ContectDocLinkHandler.CheckStatus(SetOfCdl);
if(Trigger.IsUpdate){
for(ContentDocument cd :Trigger.new){
Id cdlId = MapOfCdNCdl.get(cd.Id);
if(MapOfStatus.get(cdlId) == 'Completed'){
cd.addError('Sorry you cannot add/update this file.');
}
}
}
if(Trigger.Isdelete){
for(ContentDocument cd :Trigger.old){
Id cdlId = MapOfCdNCdl.get(cd.Id);
if(MapOfStatus.get(cdlId) == 'Completed'){
cd.addError('Sorry you cannot delete this file.');
}
}
}
}

Content Version Trigger


trigger ContentVersionTrg on ContentVersion (before update) {
    
    if(Trigger.IsBefore && (Trigger.IsInsert || Trigger.IsUpdate)){
   
    Map<Id,String> MapOfStatus = new Map<Id,String>();
    Set<Id> SetOfCdl = new Set<Id>();
    Set<Id> SetOfCd = new Set<Id>();
    map<Id,Id> MapOfCdNCdl = new map<Id,Id>();
    for(ContentVersion cv :Trigger.new){
    SetOfCd.add(cv.ContentDocumentId);
    }
    system.debug('Set Of Content Doc'+SetOfCd);
    for(ContentDocumentLink cdl : [SELECT LinkedEntityId,ContentDocumentId FROM ContentDocumentLink WHERE ContentDocumentId IN:SetOfCd]){
    string cdlentityId = cdl.LinkedEntityId;
//Hard coding start 3 character of my custom object
    if (cdlentityId.substring(0,3) == 'a0h') {
   
    SetOfCdl.add(cdl.LinkedEntityId);
    MapOfCdNCdl.put(cdl.ContentDocumentId, cdl.LinkedEntityId);
    }
    }
MapOfStatus = ContectDocLinkHandler.CheckStatus(SetOfCdl);
for(ContentVersion cv :Trigger.new){
Id cdlId = MapOfCdNCdl.get(cv.ContentDocumentId);
if(MapOfStatus.get(cdlId) == 'Completed'){
cv.addError('Sorry you cannot add/update this file');
}
}
    }
}

Content Document Link Trigger:

trigger ContectDocLinkTrg on ContentDocumentLink (before delete,before update) {
    set<Id> SetOfCdlLinkId = new set<Id>();
    map <Id,String> MapOfStatus = new map<Id,String>();
    
for(ContentDocumentLink cdl :Trigger.old){
SetOfCdlLinkId.add(cdl.LinkedEntityId);
}
MapOfStatus = ContectDocLinkHandler.CheckStatus(SetOfCdlLinkId);
for(ContentDocumentLink cdl :Trigger.old){
if(MapOfStatus.get(cdl.LinkedEntityId) == 'Completed'){
cdl.addError('Sorry you cannot delete this file');
}
}
}

Content Document Link Handler :

public without sharing class ContectDocLinkHandler {

       
 // function used to check status of Jira Issue object as complete
    public Static Map<Id,String> CheckStatus(set<Id> SetOfCdl)
    {
    map <Id,String> MapOfJIStatus = new map<Id,String>();
   
   
    if(SetOfCdl.size() > 0){
    for(Jira_Issues__c js : [select Id, Status__c from Jira_Issues__c where Id IN: SetOfCdl]){
    MapOfJIStatus.put(js.Id, js.Status__c);
    }
    }
    return MapOfJIStatus;
    }
}

Happy Coding !!! 

Sunday, 10 December 2017

Lightning Components Basics: Connect Components with Events

Lightning Components Basics: Connect Components with Events 

CampingListComp:


<aura:component controller="CampingListController"
implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId,force:lightningQuickAction" access="global" >
<aura:handler name="init" action="{!c.doInit}" value="{!this}"/>
    <aura:handler name="addItem" event="c:addItemEvent"
    action="{!c.handleAddItem }"/> 


    <aura:attribute name="items" type="Camping_Item__c[]"/>
    <!--<aura:attribute name="newItem" type="Camping_Item__c" default="{ 'sobjectType': 'Camping_Item__c',
                        'Name': '',
                        'Quantity__c': 0,                                          
                        'Price__c': 0,
                        'Packed__c': false }"/>-->
    <ol>
        <li>Bug Spray</li>
        <li>Bear Repellant</li>
        <li>Goat Food</li>
    </ol> 
    <!-- PAGE HEADER -->
    
    <c:campingHeader />
    <lightning:layout >
<lightning:layoutItem padding="around-small" size="6">
<c:campingListForm />
        </lightning:layoutItem>
    </lightning:layout>

    
    <c:campingHeader />

<div class="slds-card slds-p-top--medium">
        <header class="slds-card__header">
            <h3 class="slds-text-heading--small">Items</h3>
        </header>
         
        <section class="slds-card__body">
            <div id="list" class="row">
                <aura:iteration items="{!v.items}" var="items">
                    <c:campingListItem item="{!item}"/>
                </aura:iteration>
            </div>
        </section>
    </div>

</aura:component>

CampaignListController:


({
     // Load expenses from Salesforce
    doInit: function(component, event, helper) {
    
        // Create the action
        var action = component.get("c.getItems");
    
        // Add callback behavior for when response is received
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                component.set("v.items", response.getReturnValue());
            }
            else {
                console.log("Failed with state: " + state);
            }
        });
    
        // Send action off to be executed
        $A.enqueueAction(action);
    },
     handleAddItem: function(component, event, helper) {
         var newItem = event.getParam("item");
         var action = component.get("c.saveItem");
         action.setParams({"item": newItem});
action.setCallback(this, function(response){
                var state = response.getState();
                if (component.isValid() && state === "SUCCESS") {
                    // all good, nothing to do.
                }
            });
            $A.enqueueAction(action);
       
     }
})

Camping List Form Component

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,force:hasRecordId" access="global" >
<aura:attribute name="newItem" type="Camping_Item__c" default="{ 'sobjectType': 'Camping_Item__c',
                        'Name': '',
                        'Quantity__c': 0,                                          
                        'Price__c': 0,
                        'Packed__c': false }"/>
    <aura:registerEvent name="addItem" type="c:addItemEvent"/> 
    <lightning:layout >
        <lightning:layoutItem padding="around-small" size="6">
<div aria-labelledby="newcampaignform">
                <fieldset class="slds-box slds-theme--default slds-container--small">
    <legend id="newcampaignform" class="slds-text-heading--small slds-p-vertical--medium">
                    Add Campaign List
                    </legend> 
                    <form class="slds-form--stacked">
                        <lightning:input aura:id="campaignform" label="Campaign Item Name"
                         name="campaignitemname"
                         value="{!v.newItem.Name}"
                         required="true"/>
                        <lightning:input type="number" aura:id="expenseform" label="Quantity"
                         name="campaignitemprice"
                         min="1"
                         formatter="number"
                         step="0.1"
                         value="{!v.newItem.Quantity__c}"
                         messageWhenRangeUnderflow="Enter quantity that's at least 1."/>
<lightning:input type="number" aura:id="expenseform" label="Price"
                         name="campaignitemprice"
                         min="0.1"
                         formatter="currency"
                         step="0.01"
                         value="{!v.newItem.Price__c}"
                         messageWhenRangeUnderflow="Enter an amount that's at least $0.10."/>
<lightning:input type="checkbox" aura:id="expenseform" label="Packed?" 
                         name="expreimbursed"
                         checked="{!v.newItem.Packed__c}"/>
<lightning:button label="Create Camping" class="slds-m-top--medium"
                                 variant="brand" onclick="{!c.clickCreateItem}"/>
                    </form>
                </fieldset>
            </div>
            
</lightning:layoutItem>
    </lightning:layout>
</aura:component>

Camping List Form Controller

({
clickCreateItem: function(component, event, helper) {
var validCamping = component.find('campingform').reduce(function (validSoFar, inputCmp) {
            // Displays error messages for invalid fields
            inputCmp.showHelpMessageIfInvalid();
            return validSoFar && inputCmp.get('v.validity').valid;
        }, true);
if(validCamping){
            var addItm = event.getParam("v.newItem");
            helper.createItem(component, addItm);
        }
}
})

Camping List Form Helper

({
addItem : function(component, campaign) {
        var createEvent = component.getEvent("addItem");
     createEvent.setParams({ "item": campaign });
    createEvent.fire();
component.set("v.newItem",{ 'sobjectType': 'Camping_Item__c',
                    'Name': '',
                    'Quantity__c': 0,
                    'Price__c': 0,
                    'Packed__c': false });
}
})

AddItemEvent

Create File | New | Lightning Event,
<aura:event type="COMPONENT">
    <aura:attribute name="item" type="Camping_Item__c"/> 
</aura:event>

Wednesday, 6 December 2017

Visualforce: Showing nearest account locations on google map

Visualforce: Showing nearest account locations on google map

Few days back i got a requirement from my client that salesperson need a app in salesforce which allows them to do following.

Requirements:
  • When they are onsite to visit an account or prospect this salesforce app will tell them other nearby(get current location as they will be using this app from mobile) accounts location on google map.This will help them to visit more than 1 account in a day.
  • Allow them to enter a dynamic address and on the bases of that address find out nearby accounts.
Problem:
  • In order to get current/dynamic location latitude and longitude to show on map we have to use any API which will provide us latitude and longitude values in return of address.
  • All the account in the system should have their latitude and longitude values saved in a field in order to show them on map.
  • Client do not want to pay for any API.
Solution:

  • Many of you know about data integration rule. This feature is released by Salesforce few releases back. You can get a better idea of this feature from this link.
  • This will help us to get lat and lon values of all accounts resit in our database. We will use javascript to get lat and lon values of current location and dynamic entered address.
Enough of talk lets do some code :)

Visualforce page:

<apex:page docType="html-5.0" sidebar="false" Controller="accountMappingController" showHeader="true" id="page">  
    <apex:pagemessages id="message"/>
<apex:stylesheet value="{!URLFOR($Resource.bootstrap, '/dist/css/bootstrap.min.css')}"/>
<apex:includeScript value="/support/console/34.0/integration.js"/>
<script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=false"></script>



  <script> var previousOnload = window.onload; window.onload = function() { if (previousOnload) { previousOnload(); } setUserLocation(); } </script> 

<!-- Javascript to get the user's current location and set appropriate URL call-->
<script type="text/javascript">

    
      function geocodeAddress() {
        var street = document.getElementById('page:MyId:Pb:street1').value;
        var city = document.getElementById('page:MyId:Pb:city1').value;
        var state = document.getElementById('page:MyId:Pb:state1').value;
        var country = document.getElementById('page:MyId:Pb:country1').value;
        var address = street + ' ' + city + ' ' +state + ' ' +country;
        var geocoder = new google.maps.Geocoder();
        geocoder.geocode({'address': address}, function(results, status) {
          if (status === 'OK') {
            
            var array = results[0].geometry.location.toString();
            array = array.replace(/\(|\)/g,'').split(",");
            document.getElementById("page:MyId:Pb:Lat").value = array[0];
            document.getElementById("page:MyId:Pb:Long").value = array[1];
            NewCurrentLocation();
          } else {
            alert('Geocode was not successful for the following reason: ' + status);
          }
        });
}

    

    function setUserLocation() {
        if("{!street}" && "{!city}" && "{!state}"){
        }
        else{
            if (navigator.geolocation) { 
                navigator.geolocation.getCurrentPosition(function(loc){
                    var latlon = loc.coords.latitude + "," + loc.coords.longitude;
                    var el = document.querySelector("input.currentPosition");
                    el.value = latlon;
                    generateMap();
                });
            }
            
            var gURL;
            var el2 = document.querySelector("input.googleURL");
            
            if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
                gURL = "comgooglemaps://?"; // if User device is mobile, call Google Maps mobile app url
            }
            else {
                gURL = "https://www.google.com/maps?"; // else, call Google Maps for Desktop url
            } 
            
            el2.value =  gURL;
        }
    }
    

</script>

<div class="bs" id="firstdiv">

    <div class="row">        
        <div class="col-xs-12"> <!--Block of code used to show accounts coming from controller on page  -->
            <apex:outputPanel rendered="{!IF(accounts.size==0,false,true)}" id="sectionsPanel" >              
                <apex:map width="100%" height="500px" center="{!userLocation}">   
                    <apex:repeat value="{!accounts}" var="a">
                        <apex:mapMarker icon="http://maps.google.com/intl/en_us/mapfiles/ms/micons/red-dot.png" id="accountMapMarker"
                        position="{latitude: {!a.BillingLatitude}, longitude: {!a.BillingLongitude}}">
                            
                            <apex:mapInfoWindow >
                                <apex:outputPanel layout="block" style="font-weight: bold;  font-size: 14px;">
                                    <apex:outputText >{!a.Name}</apex:outputText>
                                </apex:outputPanel>                              
                                <a href="tel:{!a.Phone}">{!a.Phone}</a>                                                             
                                <apex:outputPanel layout="block">
                                    <apex:outputText ><b>Account Type</b> {!a.Type} </apex:outputText>
                                </apex:outputPanel>
                                
                                <apex:outputPanel layout="block">
                                    <apex:outputText >{!a.BillingStreet}, {!a.BillingCity}, {!a.BillingState}</apex:outputText>
                                </apex:outputPanel>
                                <button type="button" onclick="window.open('{!googleURL}daddr={!a.BillingLatitude},{!a.BillingLongitude}&saddr={!currentPosition}'); return false;" >Get Directions</button>
                                
                               
                            </apex:mapInfoWindow>
                        </apex:mapMarker>
                    </apex:repeat>
             
                       <apex:mapMarker id="UserLocationMapMarker" position="{!currentPosition}" icon="http://maps.google.com/mapfiles/ms/micons/green-dot.png" > 
                       
                        <apex:mapInfoWindow >
                            <apex:outputPanel layout="block" style="font-weight: bold;  font-size: 14px;">
                                <apex:outputText >Current Location</apex:outputText>
                            </apex:outputPanel>
                            <apex:outputPanel layout="block">
                                <apex:outputText >Search Google Maps<BR/>for "current Location"<BR/>in your area</apex:outputText>
                            </apex:outputPanel>
                            <button type="button" onclick="window.open('{!googleURL}q=Garage+Doors&center={!currentPosition}&zoom=20'); return false;">Get Directions</button>
                            
                        </apex:mapInfoWindow>
                    </apex:mapMarker>  
                </apex:map>
            </apex:outputPanel>
        </div>
    </div>
</div> 

<apex:form id="MyId">
    <apex:outputText rendered="{!IF(accounts.size==0,true,false)}"><b>Loading Account Map...</b></apex:outputText>
    
    <apex:actionFunction action="{!findbyNewCurrentLocation}" name="NewCurrentLocation"/>
    
    <apex:actionFunction action="{!redirectByLocation}" name="generateMap" rerender="page"/> 
    <apex:input size="30" id="currentPosition" styleClass="currentPosition"  style="visibility: hidden;"  value="{!currentPosition}" />
    <apex:input size="30" id="resultsAvailable" styleClass="resultsAvailable" style="visibility: hidden;"   value="{!resultsAvailable}" />
   
    <apex:input size="1" id="googleURL" styleClass="googleURL" style="visibility: hidden;" value="{!googleURL}" /> 
    
    
  
    <apex:pageBlock title="Change Current Location" id="Pb">
            <table width="60%" align="center">
                <tr><td>
                    <b><apex:outputLabel value="Street " for="street" style="width:5%" /></b></td><td>
                    <apex:inputtext value="{!street}" id="street1" style="width:65%"/></td><td>
                    <b><apex:outputLabel value="City"  for="city" style="width:10%"/></b></td><td>
                    <apex:inputtext value="{!city}" id="city1" style="width:55%"/></td><td>
                    <b><apex:outputLabel value="State"  for="state" style="width:10%"/></b></td><td>
                    <apex:inputtext value="{!state}" id="state1" style="width:55%"/></td><td>
                    <b><apex:outputLabel value="Country"  for="country" style="width:10%"/></b></td><td>
                    <apex:inputtext value="{!country}" id="country1" style="width:55%"/></td>
                    </tr>
            </table>
  <apex:inputHidden id="Long" value="{!CusLong}" />
  <apex:inputHidden id="Lat" value="{!CusLat}" />
 
        <apex:commandButton value="Change Current Location" styleClass="buttonStyle" onclick = "geocodeAddress();"
                style="font-weight: bold;" rerender="page,MyId,message" status="loadingStatus" id="submit"/>
    </apex:pageBlock>
<apex:actionstatus id="loadingStatus">
          <apex:facet name="start">
              <div style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; opacity: 0.25; z-index: 1000; background-color: black;">
                  &nbsp;
              </div>
              <div style="position: fixed; left: 0; top: 0; bottom: 0; right: 0; z-index: 1001; margin: 15% 50%">
                  <div style="display: inline-block; padding: 2px; background-color: #fff; width: 125px;">
                      <img src="/img/loading.gif" style="float: left; margin: 8px;" />
                      <span style="display: inline-block; padding: 10px 0px;">Please Wait...</span>
                  </div>
              </div>
          </apex:facet>
      </apex:actionstatus>
</apex:form>

</apex:page>
Note: Billing address is used to show location on map. This works on SF1 too.

Controller Code:

public with sharing class accountMappingController {

    public Map<String, Double> userLocation { get; private set; }  
    
    public List<Account> accounts { get; set; } 
    public List<Account> prospects { get; private set; }
    public List<Account> suspects { get; private set; }
    public String googleURL { get; set; }
    public String street{ get; set; }
    public String city{ get; set; }
    public String state{ get; set; }
    public String country {get;set;}
    public string CusLat {get;set;}
    public string CusLong {get;set;}
    
    public accountMappingController (){
        Apexpages.currentPage().getHeaders().put('X-UA-Compatible', 'IE=10');
        
        
    }
    
    public String currentPosition {
        get {
            if(String.isBlank(street) && String.isBlank(city) && String.isBlank(state)){
                if (String.isBlank(currentPosition)) {
                    currentPosition = '0,0';
                }
            }
            return currentPosition;
        }
        set;
    }
    
    public Boolean resultsAvailable {
        get {
            if(accounts == Null) {
                return false;
            }
            return true;
        }
        
        set;
    }
    

    public PageReference findNearby() {
        String lat, lon;
        
        System.debug('Inside findNearby, Position='+street);
        
        List<String> latlon;
      
        latlon = currentPosition.split(',');
       
        lat = latlon[0].trim();
        lon = latlon[1].trim(); 
        
        userLocation = new Map<String, Double>{
            'latitude' =>  decimal.valueOf(lat),
            'longitude' =>  decimal.valueOf(lon)
        };
        
        accounts = new List<Account>(); 

        
        //Limited to 99 Results, Update LIMIT code Lines to change results ratio
        
         // SOQL query to get Accounts
        String queryString1 = 
            'SELECT Id, Name, Type, Phone, BillingLatitude,BillingLongitude,BillingStreet, BillingCity, BillingState ' +
            'FROM Account ' +
            'WHERE BillingLatitude != null AND BillingLongitude != null ' + 
            'ORDER BY DISTANCE(BillingAddress, GEOLOCATION('+lat+','+lon+'), \'mi\') NULLS LAST ' +
            'LIMIT 60';

            
        // run query
        accounts = database.Query(queryString1);
        
        if (0 == accounts.size()) 
            System.debug('No results. Query: ' + queryString1);
         
                 
        return null;
    }
    
    //This method is a copy of findNearby() method with the only difference that current
    //location is calculated on the basis of street, city, and state field on the UI
    public PageReference findbyNewCurrentLocation() {
        try{
            String lat, lon;
                      
            accounts = new List<Account>(); 

            currentPosition = CusLat +','+ CusLong;
            lat = CusLat.trim();
            lon = CusLong.trim();

            userLocation = new Map<String, Double>{
                'latitude' =>  decimal.valueOf(lat),
                'longitude' =>  decimal.valueOf(lon)
            };
           
       
            //Limited to 99 Results, Update LIMIT code Lines to change results ratio
            
             // SOQL query to get Accounts
            String queryString1 = 
                  'SELECT Id, Name, Type, Phone,BillingLatitude,BillingLongitude,BillingStreet, BillingCity, BillingState '+ 
                'FROM Account ' +
                'WHERE BillingLatitude !=null AND BillingLongitude !=null ' + 
                'ORDER BY DISTANCE(BillingAddress, GEOLOCATION('+lat+','+lon+'), \'mi\') NULLS LAST ' + 
                'LIMIT 60'; 
  
            // run query
            accounts = database.Query(queryString1);
            
            
            if (0 == accounts.size()) 
                System.debug('No results. Query: ' + queryString1); 
                    
            return null;
        }
        catch(exception e){ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, Label.ERR_Location_Not_found)); system.debug('Error'+ e);return null;}
        
   
        
    }
    
    //returns location data on the condition of whether street field is empty or not on UI
    public PageReference redirectByLocation(){
     
        
        //if(street !='' && city != '' && state != ''){
        if(!String.IsBlank(street) && !String.IsBlank(city) && !String.IsBlank(state) && !String.IsBlank(country)){
            return findbyNewCurrentLocation();
        }
        else{
            return findNearby();
        }
    }
    
    
    
}
Note: Label is used for showing error message.

Page View: