سؤال

Please consider the following view:

<p>Count <span data-bind="text: unreadCount()">&nbsp;</span></p>
<div data-bind='template: { name: "conversationTemplate", data: currentList }'> </div>
<script type="text/html" id="conversationTemplate">
 <table>
  <tbody>
   {{each $data}}
    <tr id="conversation_${conversation_id}" class="conversation-item ${status}">
     <td><input type="checkbox" data-bind="click: function() { alert(this.value) }, checked: read" /></td>
    </tr>
   {{/each}}
  </tbody>
 </table>
</script>

And the following code:

$(function() {

 var viewModel = {
  currentList : ko.observableArray([])
 };

 ko.dependentObservable(function () {
  $.ajax({
   url: '/conversations/inbox.json', dataType: 'json',
   success: function(data) {
    viewModel.currentList(data.conversationlist);
   }
  });  
 }.bind(viewModel));

 viewModel.unreadCount = ko.dependentObservable(function() {
  var unreadCount = 0;
  for (var i = 0; i < viewModel.currentList().length; i++)
   if (viewModel.currentList()[i].read == true) {
    unreadCount++;
   }
  return unreadCount;
 });

 ko.applyBindings(viewModel);

The above appears to be working, though I'm not sure if I built this correctly. What I want to learn how to do is when you change a checkbox, how to have the unreadCount be automatically updated to reflect the change. I thought that using ko would provide this automatically, but maybe I need to do something in the template's checkbox data-bind?

Also, once I can modify the checkbox and have that automatically update the View Model & Unread count, what is the right way to then post that update back to the server (Rails)?

Here's an example JSON response from server:

{
    "conversationlist": [{
        "project_name": "Proj 1",
        "preview": "xxxxx",
        "status": "unread",
        "participants": [{
            "image": "XXXXXX"
        }, {
            "image": "XXXXXX"
        }],
        "conversation_id": 193,
        "title": "Hi Ho",
        "time_ago": "1 day",
        "read": true
    }, {
        "project_name": "Proj 2",
        "preview": "xxxx",
        "status": "unread",
        "participants": [{
            "image": "XXXXXX"
        }, {
            "image": "XXXXXX"
        }],
        "conversation_id": 193,
        "title": "Hi Ho",
        "time_ago": "1 day",
        "read": true
    }, {
        "project_name": "Proj 3",
        "preview": "xxxxx",
        "status": "unread",
        "participants": [{
            "image": "XXXXXX"
        }, {
            "image": "XXXXXX"
        }],
        "conversation_id": 193,
        "title": "Hi Ho",
        "time_ago": "1 day",
        "read": true
    }]
}
هل كانت مفيدة؟

المحلول

Seems like you could execute a function on click (inside data-bind='click: function() {...}') for each item that increments or decrements the unread counter depending on the value of the checkbox that was checked. This way, you wouldn't ever have to loop through the currentList and update the unread count that way.

You could also subscribe to the viewmodel's read property explicitly and execute your own code when read changes (see "Explicitly subscribing to observables" in the middle of the observables documentation).

Edit: Here's a thread where one user describes how they set up an observable array with items with observable properties. Here's an example that the author (Steve Sanderson) came up with demonstrating an observable arrays with observable properties.

With both methods, it seems like you could perform an AJAX call to POST back to the server.

Update: Here's an example of how you could implement this:

$(function() {
    var initialData = {/*Data retrieved by AJAX or on page load*/};

    var markRead = function(conversation) {
        // Make AJAX POST here to update read status of
        // the conversation
    };

    // Convenience object for conversations.
    var Conversation = function(conversation_id, status, read) {
        this.conversation_id = conversation_id;
        this.status = status;
        this.read = ko.observable(read);
    };

    var initialUnread = 0;

    var viewModel = {
        // Map the conversation list to a new array containing
        // objects with observable properties.
        conversationList: ko.observableArray(ko.utils.arrayMap(
        initialData.conversationlist, function(data) {
            if (!data.read === false) {
                initialUnread++;
            }
            return new Conversation(
                data.conversation_id,
                data.status,
                data.read);
        })),
        unreadCount: ko.observable(initialUnread),

        // Executed when the user toggles a conversation's
        // read status.
        toggleRead: function(conversation) {
            // Update the unread count.
            viewModel.unreadCount(
                viewModel.unreadCount() + 
                (conversation.read() ? 1 : -1));

            // Update the conversation via AJAX
            markRead(conversation);
            return true;
        }
    };
    ko.applyBindings(viewModel);
});

Demo here.

Notes:

  • Doesn't use the mapping plugin. The general logic should be the same though.
  • If the AJAX call fails, you should probably update the checkbox with its status before the click.
  • You can update the unreadCount property in the viewModel anywhere by calling viewModel.unreadCount(<value>).
  • This is based on several of the examples, specifically this one. The complex examples are particularly eye-opening and demonstrate some cool stuff you can do with KO.
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top