Adler
Adler

Reputation: 1492

KnockoutJS - pushing object into observable array isn't databinding

My view model is a game with scene and each scene have some messages.

The view model was a plain JSON object that mapped to be observable with the mapping plugin. The whole app is working fine, all data binding and other features are working as accepted. but...

I'm using drag and drop in order the add messages to scene so I've rendered empty messages at the side (not included in the scene messages array but as a stand alone object) and when dropping the dragged message I've took it pushed it to the scene messages observable array, and when I'm looking in my game object I can see that a new message has been added but there is no effect in the ui, lets say we started with 2 messages in the array and they rendered fine in the load of the app, now I have 3 messages in the array but only 2 are shown in the ui.

Here are some pieces of the code:

This is the drag and drop code -

(function () {
        var _dragged;
        ko.bindingHandlers.drag = {
            init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
                var dragElement = $(element);
                var dragOptions = {
                    helper: 'clone',
                    appendTo: 'body',
                    revert: true,
                    revertDuration: 0,
                    start: function () {
                        _dragged = ko.utils.unwrapObservable(valueAccessor().value);
                    },
                    cursor: 'default'
                };
                dragElement.draggable(dragOptions).disableSelection();
            }
        };

        ko.bindingHandlers.drop = {
            init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
                var dropElement = $(element);
                var dropOptions = {
                    drop: function (event, ui) {
                        valueAccessor().value.push(ko.mapping.fromJS(_dragged));
                    }
                };
                dropElement.droppable(dropOptions);
            }
        };
    })();

This the messages foreach binding with the drop -

<div id="sceneMessages" data-bind="foreach: game.ContentScenes()[0].ContentMessages(), drop: { value: game.ContentScenes()[0].ContentMessages() }">
    <span data-bind="text: id"></span>
    <br />
</div>

And this is the empty new messages with the drag -

<ul data-bind="foreach: messagesTypes">
    <li data-bind="text: MessageType.name, drag: { value: $data }"></li>
</ul>

Any idea what I'm doing worng? could it be because of the mapping plugin?! as I mentioned the new message is added to the array properly.

EDIT:

Actually, I've just realized that even a simple push without the drag and drop isn't working as accepted..

the JSON from the server is looks something like that:

"ContentScenes": [
{
  "ContentMessages": [
    {
      "PrivateMessageType": null,
      "MessageType": {
        "id": 1,
        "name": "Public"
      },
      "id": 7,
      "scene_id": 3,
      "parent_id": null,
      "time_offset": null,
      "from": null,
      "to": null,
      "msg_type": 1,
      "is_system": true
    },
    {
      "PrivateMessageType": null,
      "MessageType": {
        "id": 1,
        "name": "Public"
      },
      "id": 8,
      "scene_id": 3,
      "parent_id": null,
      "time_offset": null,
      "from": null,
      "to": null,
      "msg_type": 1,
      "is_system": true
    }
  ],
  "ContentResources": [],
  "id": 3,
  "game_id": 28,
  "name": null,
  "discussion_duration": null,
  "break_duration": null,
  "vote_duration": null,
  "is_template": true
},
{
  "ContentDilemmaOptions": [],
  "ContentMessages": [],
  "ContentResources": [],
  "id": 4,
  "game_id": 28,
  "name": null,
  "discussion_duration": null,
  "break_duration": null,
  "vote_duration": null,
  "is_template": false
}
],
"id": 28,
"mainImage": null,
"sideImage": null,
"description": "Game description",
"name": "Game Name",
"locale": null

And this JSON is mapped like this:

 var Scene = function (data){
        ko.mapping.fromJS(data,{},this);
        this.isCurrent = function(){
            return pageOptions.queryString("scene") == this.id();
        };
        this.url = function(){
            return _url + this.id();
        };
    }

 var mappingOptions = {
        'ContentScenes':{
            create:function(options){
                return new Scene(options.data);
            }
        },
        'ContentCharacters': {
            key: function(data) {
                return ko.utils.unwrapObservable(data.id);
            }
        }
    }

 var _gameJson = {

        game: ko.mapping.fromJS(_jsonObject,mappingOptions)
 }

  ko.applyBindings(_gameJson);

So, any idea what I'm doing worng?

Upvotes: 0

Views: 1430

Answers (2)

Adler
Adler

Reputation: 1492

Well, thank you KnockoutJS for your 'convenient' syntax that took me a while to notice it

Here is a quote from this documentation:

The syntax is more convenient. To call KO’s push method, just write myObservableArray.push(...). This is slightly nicer than calling the underlying array’s push method by writing myObservableArray().push(...).

It means that when you want to add an object to array you can not do it in the normal and intuitive way by reading the object get the array and push to it like that: myArray().push(obj), you should get the observable object and push to the object like that: myArray.push(obj). just great!

Upvotes: 1

beauXjames
beauXjames

Reputation: 8418

So, when you initialize your drag you are only evaluating the state of the world once on load and not when an element is updated...for example, here's a method I implemented

ko.bindingHandlers.DragEmployee = {
    update: function (element, valueAccessor) {
        var dragElement = $(element);
        if (valueAccessor()) {

            dragElement.toggleClass('draggable', true);
            dragElement.kendoDraggable({
                hint: function(e) {
                    return $("<label class='grabbedObject' style='font-weight: bold;'>" + $(e).text() + "</label>");
                },
                cursorOffset: { top: -4, left: -20 },
                dragstart: function(e) { $(e.currentTarget).kendoAddClass('grabbed'); },
                dragend: function (e) { $(e.currentTarget).kendoRemoveClass('grabbed'); }
            });
        } else {
            if (dragElement.data('kendoDraggable')) {
                dragElement.toggleClass('draggable', false);
                dragElement.data('kendoDraggable').destroy();
            }
        }
    }

};

It works great...however I am using kendo's drag features...but it really shouldn't matter much. I keep my drop methods in only the init however the value I pass to it is a property of the event...in my case ::

$(e.draggable.element[0])

Then, when I want to get the data bound to this element

ko.dataFor(e.draggable.element[0])

Boom...then I have access to the entire model I was binding.

Upvotes: 0

Related Questions