5

I am looking for a way to add an item to an array that belongs to an item within another array using knockout and knockout mapping.

I have the following, a Person which has an array of WorkItems that has an array of ActionPlans. Person > WorkItems > ActionPlans

Knockout code is as follows -

var PersonViewModel = function(data) {
var self = this;
ko.mapping.fromJS(data, trainingCourseItemMapping, self);

self.addWorkItem = function() {
    var WorkItem = new WorkItemVM({
        Id: null,
        JobSkillsAndExpDdl: "",
        JobSkillsAndExperience: "",
        ActionPlans: ko.observableArray(),
        PersonId: data.Id
        })
   self.WorkItems.push(WorkItem)
};

self.addActionPlan = function () {
    var actionPlan = new ActionPlanVM({
        Id: null,
        priorityAreaStage: "",
        goal: "",
        action: "",
        byWho: "",
        byWhen: ""
        WorkItemId: data.Id
    });
    self.ActionPlans.push(actionPlan);
};
}

Array mapping

var trainingCourseItemMapping = {
'WorkItem': {
    key: function(workitem) {
        return ko.utils.unwrapObservable(workitem.Id);
    },
    create: function(options) {
        return new WorkItemVM(options.data);
    },
    'ActionPlans': {
        key: function (actionPlanItem) {
            return ko.utils.unwrapObservable(actionPlanItem.id);
        },
        create: function (options) {
            return new ActionPlanVM(options.data);
        }

    }
}

Array item mapping

var WorkItemVM = function(data) {
    var self = this;
    ko.mapping.fromJS(data, trainingCourseItemMapping, self);
}

var ActionPlanVM = function(data) {
    var self = this;
    ko.mapping.fromJS(data, {}, self);
}

And within my view i want to have the following (edited) -

<tbody data-bind="foreach: WorkItems">
//body table html here
</tbody>

<!--ko foreach: WorkItems-->
<tbody data-bind="foreach: ActionPlans">
//body table html here
</tbody>
<!--/ko-->

Error

The error i am currently getting is -

Unable to process binding "click: function(){return addActionPlan }"

How can i push an item to the "nested" action plan array of WorkItems? Thanks

Edit -

Image as requested -

enter image description here

Preceding this is a "add work item" button within the main Form. When Save is pressed the WorkItem is shown within a table row (all working fine)

2
  • Please do add the image, I'm interested in this question but not sure how to help. Commented Oct 11, 2018 at 5:39
  • Image added thanks Commented Oct 11, 2018 at 8:05

1 Answer 1

2
+50

One thing to note is that you have ActionPlans WITHIN WorkItems, so your binds should reflect that too:

<tbody data-bind="foreach: WorkItems">
//body table html here
    <tbody data-bind="foreach: ActionPlans"> /*ActionPlans exists in this context*/
    //body table html here
    </tbody>
</tbody>

with your current HTML ActionPlans are not defined in their binding context This is where the specific error comes from, ActionPlans are not defined in a "sibling" context, they are properties of each WorkItem

EDIT: You might also try virtual elements, knockout's containerless syntax, allthough this is not advised in general ( bad performance relative to the rest of the framework, some problems may occur with minifier's removing comments etc)

<tbody data-bind="foreach: WorkItems">
    //body table html here

    </tbody>

<!-- ko foreach: WorkItems -->
    <tbody data-bind="foreach: ActionPlans"> 
    </tbody>
<!-- /ko -->

You best option thought is to restruct your VM!

EDIT 2: Try this in your personVM

self.addActionPlanToWorkItem = function (workItem) {
    var actionPlan = new ActionPlanVM({
        Id: null,
        priorityAreaStage: "",
        goal: "",
        action: "",
        byWho: "",
        byWhen: ""
        WorkItemId: workItem.Id
   });
    workItem.ActionPlans.push(actionPlan);
};

Call it from anywhere and pass in your current active WorkItem, it will add a new empty ActionPlan to your model.

Or you can maybe try out this way: Replace workItemVM with this

    var WorkItemVM = function(data) {
        var self = this;
        ko.mapping.fromJS(data, trainingCourseItemMapping, self);
        self.addActionPlan = function(actionPlanToAdd) 
        {
            self.ActionPlans.push(actionPlanToAdd);
         };
    }

and call it on your workItems as

someWorkItemInstance.addActionPlan(new ActionPPlanVM())
Sign up to request clarification or add additional context in comments.

11 Comments

Thanks for the reply, however...i cant seem to implement that type of binding due to ActionPlans being with a bootstrap modal. Is there syntax that can reflect this within the foreach: binding such as "foreach: WorkItems.ActionPlans"?
You could do it probably using knockouts containerless syntax a.k.a virtual elements. I will edit the answer in a min with an example
Sorry for the delayed reply on this one (ive been on another project) unfortunately i am not having any luck with the virtual elements...you mention restructuring my VM any thoughts on the best way to do this? thanks
How do you want to display them exactly? i mean you have a list of WorkItems on each person, and each WorkItem has a list of ActionPlans.How do you want to represent them in the ui? With your current html it is as if you are trying to render 2 tables, so say you have 3 workitems, each with 2 action plans you want 1 table only with the items and a compeltely seperate table only with all the action plans? Are you sure you are not trying to have html where the first tables entry have a corellation with the 2nd one? Draw me something using 2x3 relation and upload an img :P
Thanks for the reply. Basically Person is a form where you can add work items. Clicking the add work item button opens a bootstrap modal. Here you can fill a Work Item form - works perfectly. The bit that does not is where you can dynamically add/remove rows to a table of ActionPlans hence the collection..il knock up an image if you still need one...
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.