Search code examples
jqueryknockout.jstimerdurandaldurandal-2.0

Add and run JQuery in Durandal JS framework using Knockout


Having difficulty running JQuery code in a ViewModel and executing the code in a View. The environment is run using Durandal JS framework. Unfortunately, anything from a simple alert('Hello?') to running 60-second timer, isn't working. The timer just stays at 59:59. None of the JQuery code is working/running. Not even a simple $('h1').css('color','red');.

How do I run JQuery in a Durandal / Knockout view? Here's an example of a feature timer I'm trying to work in my View.

shell.html
The timer is found below the footer with a reset button.

<div class="wrapper">

    <!-- navigation -->
    <nav>
        <div class="container">...</div>
    </nav>
    <!-- end: navigation -->


    <!-- body -->
    <div class="container page-host" data-bind="router: { transition:'entrance' }"></div>
    <!-- end: body -->


    <!-- footer -->
    <footer>
        <div class="container">
            <div class="row">

                <div class="col-xs-12">...</div>

            </div>
        </div>
    </footer>
    <!-- end: footer -->


    <!-- timer -->
    <div class="timer affix-top" data-spy="affix" data-offset-top="50">
        <span id="countdown">59:59</span>
        <p id="reset" tabindex="0">
            <strong class="hidden-sm hidden-xs">Click here to reset timer</strong>
            <strong class="hidden-lg hidden-md">Reset timer</strong>
        </p>
    </div>
    <!-- end: timer -->

</div>

shell.js
The JQuery code needs to go in here, but everything I've tried doesn't work. Below is the JQuery code to run the timer.

define(['plugins/router', 'durandal/app'], function (router, app) {
    return {
        router: router,
        search: function() {
            //It's really easy to show a message box.
            //You can add custom options too. Also, it returns a promise for the user's response.
            app.showMessage('Search not yet implemented...');
        },
        activate: function () {
            router.map([

                { route: '', title:'Page Title', moduleId: 'viewmodels/setup', nav: true },
                { route: 'setup', moduleId: 'viewmodels/setup', nav: true },
                { route: 'review', moduleId: 'viewmodels/review', nav: true }

            ]).buildNavigationModel();

            return router.activate();
        }
    };
});

JQuery code (60-Second Timer)
This is the code I'm trying to insert into shell.js to display a timer on the page.

// TIMER: COUNTDOWN FROM 60 MINUTES WHEN PAGE IS LOADED

$(document).ready(function() {

  function countdown(element, minutes, seconds) {

    var time = minutes * 60 + seconds; // CREATE VARIABLE: SET TO 60 MINUTES (FOR ONE HOUR)
    var interval = setInterval(function() {

      var el = document.getElementById(element);

      $('#timer_ok, #timer_cancel, #dialog_overlay').on('click keypress', function() {
        $('#dialog').fadeOut(300);
        $('#dialog_overlay').fadeOut(300);
      });

      $(document).keyup(function(e) {

        // WHEN CLICKING "ESC" KEY CLOSE DIALOG WINDOW
        if (e.keyCode == 27) {
          $('#dialog').fadeOut(300);
          $('#dialog_overlay').fadeOut(300);
        }
      });

      // WHEN CLICKED, RESET THE TIMER...
      $("#reset").on('click keypress', function() {
        time = 3600; // SET TIME TO 60 MINUTES
      });

      var minutes = Math.floor(time / 60);
      if (minutes < 10) minutes = "0" + minutes;

      var seconds = time % 60;
      if (seconds < 10) seconds = "0" + seconds;

      var text = minutes + ':' + seconds;
      el.innerHTML = text;
      time--;

    }, 1000);
  }

  countdown('countdown', 60, 00); // START THE TIMER INSIDE THE ELEMENT ('ID', MINUTES, SECONDS)

});

main.js
We're using RequireJS to run the plugins. Adding this in case this is necessary to determine how the JQuery should be called in the shell.js file.

requirejs.config({
    paths: {
        'text': '../vendor/require/text',
        'durandal':'../vendor/durandal/js',
        'plugins' : '../vendor/durandal/js/plugins',
        'transitions' : '../vendor/durandal/js/transitions',
        'knockout': '../vendor/knockout/knockout-3.4.0',
        'bootstrap': '../vendor/bootstrap/js/bootstrap',
        'jquery': '../vendor/jquery/jquery-1.9.1'
    },
    shim: {
        'bootstrap': {
            deps: ['jquery'],
            exports: 'jQuery'
       }
    }
});

define(['durandal/system', 'durandal/app', 'durandal/viewLocator', 'bootstrap'],  function (system, app, viewLocator) {
    //>>excludeStart("build", true);
    system.debug(true);
    //>>excludeEnd("build");

    app.title = 'EV-CIS: Defects and Recalls Reporting';

    app.configurePlugins({
        router:true,
        dialog: true
    });

    app.start().then(function() {
        //Replace 'viewmodels' in the moduleId with 'views' to locate the view.
        //Look for partial views in a 'views' folder in the root.
        viewLocator.useConvention();

        //Show the app by setting the root view model for our application with a transition.
        app.setRoot('viewmodels/shell', 'entrance');
    });
});

Please help.


Solution

  • You can use the compositionComplete Lifecycle Callback to add all the jquery functionality you'd want to hook after the DOM is ready.

    define(['plugins/router', 'durandal/app'], function (router, app) {
        return {
            router: router,
            search: function() { },
            activate: function () { },
            compositionComplete: function() { 
               // keep the click, keyup, keypress handlers here, outside setInterval
              var minutes = 60, seconds = 0;
              var time = minutes * 60 + seconds;
    
              var interval = setInterval(function () {
    
                  var el = document.getElementById("countdown");
    
                  var minutes = Math.floor(time / 60);
                  if (minutes < 10) minutes = "0" + minutes;
    
                  var seconds = time % 60;
                  if (seconds < 10) seconds = "0" + seconds;
    
                  var text = minutes + ':' + seconds;
                  el.innerHTML = text;
                  time--;
    
              }, 1000);
    
            }
        };
    });
    

    Knockout has keyup and click bindings. So, you can use them instead of jquery event handlers as well.