durandal compose - how to pass data in and out of child viewmodel? - durandal

Anyone know the right way to pass data back out from a child viewmodel?
My child viewmodel performs and action and then needs to trigger a refresh of things in the parent models.
For example I am doing something like this in my child model:
vm.activate = function (params) {
vm.TaskTypeId = params.TaskTypeId;
vm.Sections = params.Sections;
vm.FieldId = params.FieldId;
vm.Dimensions = params.Dimensions;
and calling it like this
<div data-bind="sortable: {data: Fields,beforeMove:$root.preOrder,afterMove:$root.saveOrder}">
<div class="item"><a data-bind="text:Name,click:function() {$root.edit(Id());}" style="margin-left:20px;"></a>
<!-- ko if: $root.selectedFieldId()==Id() -->
<!-- ko compose:{model:'tasktype/edittasktype/addedittasktype',activationData:{TaskTypeId:$root.TaskTypeId, Sections:$root.Sections,FieldId:$root.selectedFieldId,Dimensions:$root.Dimensions } } --><!-- /ko -->
<!-- /ko -->
</div>
</div>
And this seems to work, but I am wondering if there is a better way?

You can instantiate your child view model yourself (in JS code, as opposed to letting the compose handler do it) then assign it to a property of your parent view model, which you can in turn compose.
function Parent() {
this.child = new Child;
}
return Parent;
...
<!-- ko compose: child --> ...
You can then access all child properties from the parent by using the child property.
If you need to access the parent properties from the child view model, you can pass a reference to the parent in the constructor of the child (this may or may not be a good idea depending on how coupled you want them to be).

Related

Aurelia compose - Parent not accessing child elements - two-way bind not working back to parent

I have a parent called client.html.
I have a child called address.html.
I display the child in the parent view-model using a compose element:
<compose view-model="../address/address" model.bind="client.address"></compose>
client.address type is AddressDetails (below).
in the client-view (address.ts) I access the data from the parent - "client.Address" using the activate function:
activate(model: AddressDetails) {
if (model) {
this.address.deserialize(model);
}
}
I also tried:
activate(model: AddressDetails)
this.address = model;
}
in address.ts I set my object for binding in address.ts as follows:
export class Address {
address = new AddressDetails;
...
to set the address object to the model as this sets the pointer of address to model.
..and my addressDetails class (which has the deserialize function) is:
export class AddressDetails implements Serializable<AddressDetails> {
address1: string;
address2: string;
suburb: string;
postcode: string;
stateShortName: string;
addressLocationId: number;
deserialize(input) {
console.log("INPUT: ", input)
this.address1 = input.address1;
this.address2 = input.address2;
this.suburb = input.suburb;
this.postcode = input.postcode;
this.stateShortName = input.stateShortName;
this.addressLocationId = input.addressLocationId;
return this;
}
}
part of Address.html (child) is below showing the bind for Address1:
<input type="text" value.bind="address.address1" class="form-control" id="address1" placeholder="Address 1st line...">
So when I pass client.address to the compose element it works and I get all the fields that client.address have filled showing up on the composed element.
so data travels down to the child (address.html)
However, when it comes time to save the data I do not get two way binding. For those instances where client.address was passed to the address view/viewmodel any change is not picked up on the client (the parent). Its passing the data down but not back up.
I have set model to address in the child however the model is not being updated back in the parent.
How do you do composition when passing data objects so that it works two ways?
does it go both ways? What have I done wrong if so?
Compose is not designed to work like a real custom element, for many reasons. What you can do is to pass an object in parent to compose and then bind with property of that object in your children:
<!-- parent template -->
<template>
<compose model.bind="details"></compose>
</template>
<!-- child template -->
<template>
Address:
<input value.bind="details.address" />
Postcode:
<input value.bind="details.postcode" />
</template>

Vue js sync parent and child data with Vue-Router

In my vuejs application i am using vue-router for routing. Everything is working fine except this one.
In my parent i have list view having a link as below on the left side .
<div class="col-md-5">
<ul>
....
<a v-link="{ name: 'task-detail', params: { taskId: task.id }}">{{ task.title }}</a>
</ul>
</div>
<div class="col-md-7">
<router-view></router-view>
</div>
When i click it, my nested route gets activated and i display detail view on the right side.
Now my problem is
In my parent view i can toggle if the task is completed or not.
I have a label showing if the task is completed or not in child view.
<label class="label label-success pull-right" v-show="task.is_completed">Completed</label>
How do i reflect the status change on my child view when i do it in parent view.
Do i need to refresh the page ? or is there simpler solution.
In simpler terms
When i toggle completion status on parent view my label should change on child view.
So vue wants you to implement a 'data down/actions up' pattern. So if your data is loaded in the parent (eg as a collection) and again loaded - separately in your child (eg as a model instance) - you have two sets of what starts as the same data. But when you change one, its independent of the other. So, you either need to communicate between the two (which can be fiddly), use a central data store, where both routes access the same instance of data (possibly using Vuex, if you're inclined), or if you have all the data you need in the collection, simply pass it downstream with, for example:
<router-view :task="selectedTask" /> where the selected task is driven by the $route.params.id in your URL (as a computed property in the parent, likely)
for example if your task list was called 'tasks' in the parent, your computed property might look like this (in ES6 JS):
selectedTask () {
return this.tasks.find(t => t.id === this.$route.params.id)
}
This data-down is the easiest to implement, but if you find you're doing this a lot, suggest using a central data store.
Does it work if you try this in your child view<label class="label label-success pull-right" v-show="$parent.task.is_completed">Completed</label>

Is it possible to call a custom-bound widget's methods in Durandal?

I have created and registered a widget in Durandal, so now I am able to use it in other views using this markup:
<div data-bind="MyWidget: { activationData }" />
I would like to call methods on that widget from the parent view model, for example:
ParentViewModel.prototype.buttonClick = function() {
this.myWidget.doSomething();
}
Is there a neat way to access a widget composed in this way from the parent view model?
I've been working on this problem since posting the question, and the best solution I have come up with is this:
Add an observable, let's call it "myWidget", to the parent view model
Pass the empty observable to the widget upon activation, using widget binding
During activation, the widget sets the parent's observable to itself
For example, in the View Model definition:
this.myWidget = ko.observable(null);
Use widget binding in the parent view:
<DIV data-bind="MyWidget: { theirWidget : myWidget }" />
Set the parent view's reference in the widget's activate method:
MyWidget.prototype.activate = function(activationObject) {
activationObject.theirWidget(this);
}
While this is a reasonable solution, I'll wait and see whether anyone else provides an alternative before accepting this answer.

Two-way binding between parent and child custom element in Aurelia

I thought I was trying to do something very simple but I just can't make this work. This entire example is on plunkr
I have a very basic custom element that present a #bindable data member that it displays and monitors with a changed event. It look like this:
import {bindable} from "aurelia-framework";
export class ChildElementCustomElement {
#bindable childData;
childDataChanged(value) {
alert("Child Data changed " + value);
}
}
and the view:
<template>
<div style="border: solid 1pt green;">
<h2>This is the child</h2>
This is the child data : ${childData}
</div>
</template>
The parent shows the child element but I want a member in its view model that's bound to the child so any change in the parent member is automatically reflected in the child. Here's the parent code:
import {bindable} from "aurelia-framework";
export class App {
parentData = "this is parent data";
}
and the view:
<template>
<h1>Two-way binding between parent and child custom elements</h1>
<require from="./child-element"></require>
<child-element childData.bind="parentData"></child-element>
<hr/>
<label>The following is the parent data:</label>
<input type="text" value.bind="parentData"></input>
</template>
What I'd like to see is any updates typed in the input field will automatically appear in the child (plus the changed event fires) but the child doesn't appear bound at all! I've also tried swapping bind for two-way just in case the convention has bound one-way but that still hasn't worked.
Please highlight my stupidity :) because currently I'm stuck thinking this should just work.
The default convention for #bindable is to turn the camel-cased property names to attribute names using the naming convention 'myProperty' -> 'my-property' (dash-casing).
From the documentation:
#bindable({
name:'myProperty', //name of the property on the class
attribute:'my-property', //name of the attribute in HTML
changeHandler:'myPropertyChanged', //name of the method to invoke when the property changes
defaultBindingMode: bindingMode.oneWay, //default binding mode used with the .bind command
defaultValue: undefined //default value of the property, if not bound or set in HTML
})
The defaults and conventions are shown above. So, you would only need
to specify these options if you need to deviate.
So you need to write child-data.bind in your HTML
<child-element child-data.bind="parentData"></child-element>
Plunkr

Stop Durandal from looking for the viewmodel

Hi I have a situation where I need to compose only a view and not a viewModel for this I have set this composition statement in my html:
<!-- ko compose: { view : content }-->
<!--/ko-->
Content represent an observable from my viewmodel.
The problem is that it seems the framework is also trying to download the viewmodel which does not exist and has no reason to exist.
Does anyon no how to stop Durandal from looking for the viewModel?
I have tryed setting the model : null but it did not work
You can't stop Durandal looking for the view model if you're using the compose binding, but there are a number of things you can do to prevent loading a new model:
Point Durandal to a dummy object to use as the model (e.g. create a singleton dummyModel.js);
Use a "dumb" object (for example an array) for your model:
<!-- ko compose: { view : content, model: [] }--><!--/ko-->
Use the current model, and turn off activation (to prevent activate being called on the model twice):
<!-- ko compose: { view : content, model: $data, activate: false }--><!--/ko-->
Basically, Durandal doesn't care what you give it as a model, as long as it has something to use. Note that it will still bind whatever model you specify to your view though!
try this
<div>
<div data-bind="compose:'views/content.html'"></div>
</div>
I am not sure if this will answer your question but I did come across a similar situation where I wanted to load views of my application which had no viewmodels. I created a module which given the view would load the view for me. All I had to was overwrite the getView function of my custom viewmodel which loaded my views.
//viewLoader --> it's job is to load the views which do not have any viewmodels
define(['plugins/router], function(router){
return {
getView: function() {
return "views/"+router.activeInstruction().config.file +".html";
}
}
}