Question

I am fairly new to Durandal. I have a little experience with KnockoutJS. I am trying to populate an observableArray from json retrieved from a php file. I am then trying to load the array into a table. There seems to be an issue when loading the data into the table upon first load of the page. I perform a system.log(customers) just to make sure the data is in the observableArray, which it is. When I refresh the page, the data is loaded into the table like I want it to. It appears as if the observableArray is getting populated, but the view is not updating.

I was wondering what I am doing wrong for it to not load on the first load of the page. Here is the test site I've been playing with durandal: http://dev.codeplanetapps.com/spa/. Once it loads, click on "Customers." The page in question is http://dev.codeplanetapps.com/spa/#/customers. When that page is loaded directly, it works fine, but when I load the app from the main page and then switch to customers, it doesn't load the table correctly. Thanks in advance for any help.

Here is the view:

<div>
<!-- Form to add new customer -->
<form class="form-inline customerForm">
    <span>Add New Customer</span>
    <input type="text" data-bind="value: inputName" placeholder="Name" />
    <input type="text" class="date input-small" data-bind="value: inputDOB" placeholder="Date of Birth" />
    <input type="text" class="input-small" data-bind="value: inputPhone" placeholder="Phone" />
    <input type="text" data-bind="value: inputEmail" placeholder="Email" />
    <button type="submit" class="btn btn-primary" data-bind="click: submitCustomer">Add</button>
</form>

<table class="table table-hover table-bordered customerTable">
    <thead>
        <tr>
            <th>Name</th>
            <th>DOB</th>
            <th>Phone Number</th>
            <th>Email</th>
            <th></th>
        </tr>
    </thead>
    <tbody data-bind="foreach: customers">
        <tr>
            <td data-bind="text: name"></td>
            <td data-bind="text: dob"></td>
            <td data-bind="text: phone"></td>
            <td data-bind="text: email"></td>
            <td><button class="btn btn-danger btn-mini" data-bind="click: $root.removeCustomer">Delete</button></td>
        </tr>
    </tbody>
</table>

And here is the viewmodel:

define(function(require){ 
    // Load System for debugging
    var system = require('durandal/system');

    // Customer Structure
    function Customer(data) {
        this.name  = ko.observable(data.name);
        this.dob   = ko.observable(data.dob.substring(5,7) + '/' 
            + data.dob.substring(8,10) + '/' + data.dob.substring(0,4));
        this.phone = ko.observable(data.phone);
        this.email = ko.observable(data.email);
    };

    // Form observables
    var inputName  = ko.observable('');
    var inputDOB   = ko.observable('');
    var inputPhone = ko.observable('');
    var inputEmail = ko.observable('');
    // Customers array
    var customers = ko.observableArray([]);

    return {
        inputName: inputName,
        inputDOB: inputDOB,
        inputPhone: inputPhone,
        inputEmail: inputEmail,
        customers: customers,
        // This performs any needed functionality after the page loads
        activate: function(data) {
            // Change the selected nav item 
            $('.customerNav').addClass('active');
            $('.firstNav').removeClass('active');
            $('.secondNav').removeClass('active');

            // Get current customers from database and add to customers observableArray
            $.getJSON(
                // Backend script
                'php/query.php', 
                // Variables sent to query.php
                {
                    mode: 'select', 
                    table: 'customers', 
                    'fields[]': '*', 
                    'values[]': '*'
                }, 
                // Callback function
                function(data) {
                    var customer = $.map(data, function(item) {return new Customer(item) });
                    customers(customer);
                }
            );

            // For some reason I couldn't get the datepicker to work without make the page 
            // wait 50 milliseconds.
            setTimeout(function() {
            $('.date').datepicker();
            },500);
        }
    };
});

Also, as a side note, the jQuery at the top and bottom of the activate function only work when I surround them in a setTimeout() function. I left the datepicker as an example. It's not as crucial, but if anyone knows how to fix that, too, I would greatly appreciate it.

Was it helpful?

Solution

Thanks, Paul. I was up late last night. I forgot to reply to add the answer. I got an answer from Rob Eisenberg, the creator of Durandal. My mistake was that I needed to return the promise from the .getJSON() call. I also needed to add another function, viewAttached(), to the module for all of my jQuery calls that refer to an element in the DOM.

Here is Rob's reply to Help with observableArray not updating in view. They are really helpful in this group. Rob is pretty quick to answer most questions.

Below is the corrected code for the viewmodel:

define(function(require){ 
// Load System for debugging
var system = require('durandal/system');

// Customer Structure
function Customer(data) {
    this.name  = ko.observable(data.name);
    this.dob   = ko.observable(data.dob.substring(5,7) + '/' 
        + data.dob.substring(8,10) + '/' + data.dob.substring(0,4));
    this.phone = ko.observable(data.phone);
    this.email = ko.observable(data.email);
};

// Form observables
var inputName  = ko.observable('');
var inputDOB   = ko.observable('');
var inputPhone = ko.observable('');
var inputEmail = ko.observable('');
// Customers array
var customers = ko.observableArray([]);

return {
    inputName: inputName,
    inputDOB: inputDOB,
    inputPhone: inputPhone,
    inputEmail: inputEmail,
    customers: customers,
    // This allows us to add jQuery as usual
    viewAttached: function() {
        // Change the selected nav item 
        $('.customerNav').addClass('active');
        $('.firstNav').removeClass('active');
        $('.secondNav').removeClass('active');

        $('.date').datepicker();
    },
    // This performs any needed functionality after the page loads
    activate: function(data) {
        // Capture the module instance
        var self = this;

        // Get current customers from database and add to customers observableArray
        var promise = $.getJSON(
            // Backend script
            'php/query.php', 
            // Variables sent to query.php
            {
                mode: 'select', 
                table: 'customers', 
                'fields[]': '*', 
                'values[]': '*'
            }, 
            // Callback function
            function(data) {
                var customer = $.map(data, function(item) {return new Customer(item) });
                system.log(customers);
                self.customers(customer);
            }
        );

        return promise;
    }
};
}); // define

OTHER TIPS

I don't know if you've changed anything since posting the question, but it works for me in IE 9, Opera 12 and Chrome. There is an error thrown in IE 9, lines 45 and 46 of shell.js, event.currentTarget.classList is sometimes null, so you should check that, but if I go to your app and click on Customers, there is data in the table.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top