1

I've tried to search for even a Javascript based solution, but seems that I'm missing something, being quite new to this as well... I've implemented this timer as a separate component. While I was able to make it work with the given buttons, but I'm stuck at how to 'start' the timer function automatically via component initialization. I'm thinking of doing this using aura:handler doInit.

Here's the timer component shown in the parent markup:

                <lightning:card>
                    <aura:set attribute="title">
                        <h2 class="slds-section-title--divider slds-text-heading--small slds-truncate">                         
                            <lightning:icon iconName="action:new_task" size="small" alternativeText="New Task"/>
                            New Task
                        </h2>
                    </aura:set>

                    <c:SCC_Timer />

                    <div class="slds-grid slds-gutters">
                        <div class="slds-col slds-size_1-of-2">
                            <div class="slds-form--stacked slds-p-around--medium slds-m-bottom--x-small slds-text-title_caps">

Here's the actual timer component markup, to which I just removed Bootstrap references and the the buttons (for now):

    <aura:component implements="force:appHostable,flexipage:availableForAllPageTypes" access="global">
<!---
    <ltng:require styles="/resource/bootstrapcssmin" afterScriptsLoaded="{!c.doScriptLoad}"/>
-->
    <aura:attribute name="start" type="String" default="start"/>
    <aura:attribute name="stop" type="String"/>
    <aura:attribute name="sObj" type="String"/>
    <aura:attribute name="field" type="String"/>
    <aura:attribute name="stopwatch" type="Object"/>
    <!-- Handle component initialization in a client-side controller -->
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <div class="slds-card">
        <div class="slds-p-around_small">
            <div aura:id="time" class="slds-text-heading_small" style="font-weight: bold;">
                Call Time Recorded: 00:00:00:000
            </div>
        </div>
    </div>

<!--
        <div class="bootstrap-sf1 row">
            <div class="bootstrap-sf1 col-xs-12">
                <button id="start" class="bootstrap-sf1 btn btn-primary" onclick="{!c.onClick}">Start</button>
                <button id="stop" class="bootstrap-sf1 btn btn-primary" onclick="{!c.onClick}">Stop</button>
                <button id="reset" class="bootstrap-sf1 btn btn-primary" onclick="{!c.onClick}">Reset</button>
            </div>
        </div>
-->
</aura:component>

Here's the controller (no helper functions):

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

    this.onClick(component, event, helper); 

},
doScriptLoad : function(component, event, helper) {

},
onClick : function(component, event, helper) {

    var div = component.find("time").getElement();
    var id = event.target.id;
    var clsStopwatch = function() {
        // Private vars
        var startAt = startAt || 0; // Time of last start / resume. (0 if not running)
        var lapTime = lapTime || 0; // Time on the clock when last stopped in milliseconds

        var now = function() {
            return (new Date()).getTime();
        };

        // Public methods
        // Start or resume
        this.start = function() {
            startAt = startAt ? startAt : now();
        };

        // Stop or pause
        this.stop = function() {
            // If running, update elapsed time otherwise keep it
            lapTime = startAt ? lapTime + now() - startAt : lapTime;
            startAt = 0; // Paused
        };

        // Reset
        this.reset = function() {
            lapTime = startAt = 0;
        };

        // Duration
        this.time = function() {
            return lapTime + (startAt ? now() - startAt : 0);
        };
    };

    var stopwatch = component.get("v.stopwatch");
    var x = stopwatch || new clsStopwatch();
    if(!stopwatch){
        component.set("v.stopwatch", x);
    }

    function pad(num, size) {
        var s = "0000" + num;
        return s.substr(s.length - size);
    }

    function formatTime(time) {
        var h = 0;
        var m = 0; 
        var s = 0; 
        var ms = 0;
        var newTime = '';

        h = Math.floor( time / (60 * 60 * 1000) );
        time = time % (60 * 60 * 1000);
        m = Math.floor( time / (60 * 1000) );
        time = time % (60 * 1000);
        s = Math.floor( time / 1000 );
        ms = time % 1000;

        newTime = pad(h, 2) + ':' + pad(m, 2) + ':' + pad(s, 2) + ':' + pad(ms, 3);
        return newTime;
    }

    function update() {
        div.innerHTML = "Time: " + formatTime(x.time());
    }

    var clocktimer;

    switch (id) {
        case "start":
            clocktimer = setInterval(update, 1);
            x.start();
            break;
        case "stop":
            x.stop();
            clearInterval(clocktimer);
            break;
        case "reset":
            x.stop();
            x.reset();
            update();
            break;
        default:
            stop();
            break;
    }
}

})

The this.onClick call on the doInit is a crude and wrong try on my side to call the function, but that's how I imagined to execute the code. Not sure if I need to pass a parameter 'start' as well.

Lastly, how will I able to issue a 'stop' command on a different part of the controller (at post-record processing). Thanks in advance for your help.

  • 1
    yes, for the onInit part, however, not sure how your app works, so hard to tell what/how you would like to stop your timer. please edit your post to include further details. thanks – glls Jun 27 '18 at 22:33
  • 1
    yes, a bit of code would be handy. – Caspar Harmer Jun 27 '18 at 22:41
  • I've updated my post. – Francis A. Jun 27 '18 at 22:47
0

The controller does not have a this that you can call methods from. There are three ways you can call one method from another, but ... seriously, it's more complicated than you really want to it to be.


Call Client-Side Action

You can call one method from another the same way you'd call a server method:

doInit: function(component, event, helper) {
  var onClickAction = component.get("c.onClick");
  $A.enqueueAction(onClickAction);
}

Unfortunately, if you do this, you won't have an "event" you can do anything with. There will be no "target" or anything else. You probably don't want to do this.


Event Handling

You can listen for an event and then call your method. This requires you to have an event you can fire.

<aura:event type="COMPONENT" description="Stopwatch Event">
  <aura:attribute name="action" type="String" />
</aura:event>

...

<aura:registerEvent name="stopWatchTrigger" type="c:stopWatchEvent" />
<aura:handler name="stopWatchTrigger" event="c:stopWatchEvent" action="{!c.onClick}" />

...

doInit: function(component, event, helper) {
  var event = component.getEvent("stopWatchTrigger");
  event.setParams({ action: "start" });
  event.fire();
}

...

doClick: function(component, event, helper) {
  var actionParam = event.getParam("action"),
    id = actionParam || (event.target && event.target.id);
  // do something with id

Method

You can set a method on your component that you then call:

<aura:method name="onClickHandler" action="{!c.doClick}">
  <aura:attribute name="action" type="String" />
</aura:method>

...

component.onClickHandler("start");

...

doClick: function(component, event, helper) {
  var params = event.get("arguments"),
    id = (params && params.action) || (event.target && event.target.id);
  // do something with id

Or, you can avoid all the insanity and just copy your existing controller code to a helper, then simply change your controller to call the helper:

({
  doInit: function(component, event, helper) {
    helper.doInit(component, event, helper); 
  },
  doScriptLoad: function(component, event, helper) {
    helper.doScriptLoad(component, event, helper);
  },
  onClick: function(component, event, helper) {
    helper.onClick(component, event, helper);
  }
})

Once your code is in the helper, this will become accessible and you don't need to register events, handlers, methods, etc. I'm pretty sure that this design choice was intentional to encourage you to move as much code as you can to the helper for performance reasons.

  • Thanks sfdcfox. I've actually moved the code over to the helper, but came to the point where the timer is running but not updating the values and reflecting the elapsed time on to the Div markup. It's showing null for the code var div = component.find("time").getElement(); and I did try a JS lookup by way of var timer = document.getElementById("time"); which didn't work at all. Any idea how will I able to push the timer values back to the component? – Francis A. Jul 3 '18 at 4:29
  • @FrancisA. Why don't you just use attributes? It seems like a lot of effort just to try and make this a class. – sfdcfox Jul 3 '18 at 6:24
0

i would actually recommend passing what you can to your helper, this will allow you to still init your timer when the component initializes (or any other event). Afterwards, you mentioned stopping the timer on "record post-record processing", this is the part you will find that having helper methods quite usefull.

Init --> Controller Metod--> helper.startTimer()

Process Record

Init Record Process --> Controller Method --> helper.ProcessRecord()

if your server response from helper.ProcessRecord is successfull, you can call

this.stopTimer()

in your helper. Unfortunatelly, you wont be able to do this in your controller, hence the helper coming in useful.

  • Thanks glls. I've moved the code over to the helper but having problems reflecting the changing time value back to the component. Please see my reply below sfdcfox's answer and let me know what you think. – Francis A. Jul 3 '18 at 5:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.