Durandal view composition order - durandal

Durandal 2.0 - view composition order
I have view, that has a number of child views that I am using just to break up the HTML into separate files.
index.html(index.js)
- menu.html
- header.html
- footer.html
The menu view is populated by the index view after it's loaded.
Inside the index.html I compose the menu view like this (there is no module menu.js, just html composition here):
<!--ko compose: { view: 'menu'}--><!--/ko-->
The problem is the order of the view composition.
I have an attached event wired up in index.js that then calls a function to populate the menu div that's sat in menu.html.
But, the attached view event is called before the menu.html view has been composed.
The events looks something like this from the console:
Binding views/footer
Binding views/index
binding complete: <section data-view=​"views/​index" style=​"display:​ none;​">​…​</section>​
[Index] Main View Attached
Binding views/header
Binding views/menu
Binding views/menudropdown
So, the main view is attached before the children.
Is there a way to change the composition order, or wait for all the child views to be composed/loaded before the main view is attached/complete?
Thanks

The short answer is "no" - you can't change the composition order.
The longer answer: perhaps I've misunderstood, but it sounds a little bit suspicious that you're populating the menu div from your view model. Could you use a custom binding to do this instead? If you can do it in a binding, then you could have a look at using a Delayed Binding Handler. From the documentation:
Sometimes your binding handler needs to work with an element only
after it is attached to the DOM and when the entire composition of the
view is complete. An example of this is any code that needs to measure
the size of an HTML element. Durandal provides a way to register a
knockout binding handler so that it does not execute until the
composition is complete. To do this, use
composition.addBindingHandler.
Alternatively, if you're happy with whatever code is running in your viewModel, you probably want to use the compositionComplete event rather than the attached event. From memory, the attached events run from parent to child (which is why the event is being called on index before the menu has been composed). In contrast, compositionComplete bubbles from child to parent:
Finally, when the entire composition process is complete, including
any parent and child compositions, the composition engine will call
the compositionComplete(view, parent) callback, bubbling from child to
parent.
You can attach a compositionComplete handler on your viewModel in the same fashion as an activate method:
// Secured Shell
define([], function () {
var viewModel = {
activate: function () {
// do stuff
},
compositionComplete: function (parent, child, settings) {
// do more stuff
debugger;
}
};
return viewModel;
});
Hope that helps.

Related

How to observer child vue finish render

I am working on a chat tool using vue single file components.I am facing a problem,root .vue has child .vue and grandchild .vue, I want to observer children's rendering,to get the root div's height,to change scrollbar position,
i used the $nextTick() http://rc.vuejs.org/api/#vm-nextTick,but it cant observer children's render,so ,is there any way can I try? thanks a lot.
You can use parent child communication of vue to get to know when you child has rendered. Vue provided following event interface:
Listen to an event using $on(eventName)
Trigger an event using
$emit(eventName)
A parent component can listen to the events emitted from a child component using v-on directly in the template where the child component is used.
So you can emit a event in your child component's lifecycle hook like following:
mounted () {
this.$emit('childRendered')
}
In parent component, you can listen to this event or create a method, like following:
this.$on('childRendered', function () {
// change scrollbar position
})
An example of parent child communication: fiddle
This is just for everyone to easily see the workaround that was mentioned by Saurabh in his comment to his answer. I also used the updated lifecycle to wait for the DOM to finish rendering and patching before manipulating it again.
Since updated does not guarantee that all child components have also been re-rendered, use vm.$nextTick inside of updated.
updated: function () {
this.$nextTick(function () {
// Code that will run only after the
// entire view has been re-rendered
})
}

Broadcasting event from parent to child fires all parent's children in Vue.js

I have a parent component and a child component.
In my parent component I have an event on a span:
<span #click="toggle(model.id)" class="open-folder">[{{open ? '-' : '+'}}]</span>
which fires the toggle function
methods: {
toggle: function(id_epic) {
this.open = !this.open;
this.$broadcast('loadTasks', id_epic);
}
},
in this function I call the loadTasks method from the child component with id_epic parameter.
The parent can have n children linked to it, but I only want to fire the first child method not all.
When I click on the span it fires the event for all n children of the parent's tree.
EDIT: The parent and it's children are generated recursively, so the child can also be a parent on it's own.(Like a folder structure)
How can I fix this ?
Thank you.
$broadcast is used to send event to child components. In your parent component you're triggering the event loadTasks from the toggle method. This is good.
You're sending a parameter along in the event, in your case id_epic
Now in your child component, you will need something like this. You need to get the id you're sending in your child component.
events : {
'loadTask' : function(param) {
//check for the id_epic here
}
}
EDIT: The parent and it's children are generated recursively, so the child can also be a parent on it's own.(Like a folder structure)
You should also re-think that part, if you have too many nested components, things can get easily out of hand.
You should be able to stop the propagation of an event by using a second parameter in the child listener. The second parameter is passed the event object to which you can can call stopProgation() and prevent additional children from also receiving the broadcasted event.
Consider adding the following code to your child listener.
events:{
'loadTask': function(param,event){
event.stopPropagtion();
// Handle the param as needed.
}
}
Of course this system of event handling is only for Vue.js 1.0 as in Vue.js 2.0+ event broadcasting and dispatching has been depreciated in favor of a data store such as Vuex. So you may want to consider using a method which will upgrade compatible.
You can try using this.$children[0].$emit() to send the event to the first child instance. though it would probably be better to use a more explicit scheme and have children register handlers with the parent or vice versa.

Show a toolbar in shell header that is bound to the current viewmodel in Durandal

I'm trying to achieve a context dependent toolbar in the header of my shell. The goal is to have the page-specific toolbar view bound to current viewmodel, not the shell or a separate toolbar vm. (Kinda like #RenderSection in Layout using asp.net Razor syntax).
When the current viewmodel is activated, I trigger a message with an object literal that describes the toolbar view location and that it's viewmodel should be this.
function activate() {
app.trigger('toolbar', { view: 'views/ediToolbar', model: this });
}
The shell correctly listens and updates the activeToolbar binding, but in the process calls the activate method on vm creating an infinite loop. Is there a way to prevent the infinite loop or am I going about this completely the wrong way?
shell.js
activeToolbar = ko.observable();
function activate() {
// normal setup stuff
app.on('toolbar', function(data) {
activeToolbar(data)
}
}
shell.html
<div data-bind="if: activeToolbar" class="toolbar">
<!-- ko compose: { view: activeToolbar().view, model: activeToolbar().model } -->
<!-- /ko -->
</div>
Actually, setting activate to false is not the solution. Not really. The problem lies in your architecture. We do the same thing. Our approach is that we simply make the navigation menu a composable part of the hosted view (as opposed to the shell). There's no need to get the shell involved, except to the extent that it either needs to get out of the way or offer up a hosting site for each module's contextual toolbar.
I should have paid closer attention to the related SO questions in the side bar! This one gave me the clue I needed.
In the compose binding set activate to false:
<!-- ko compose: { view: activeToolbar().view, model: activeToolbar().model, activate: false } -->
<!-- /ko -->
You can have your viewmodel directly access the toolbar in the activate function and update an observable with itself (With require, the module will be around, so you can just access properties directly on them). Then from the toolbar, you can bind the view to that observable.
Something like this:
function activate()
{
toolbar.activeViewModel(this);
}

accessing dojo attach point outside templated widget

I have a dojo attach point for list item which is inside a templated widget.I need to access the dojo attach point outside the widget in order to assign onclick to the list item created by the template.How do I do it?
Well, if you want to attach an event handler to it, you have to provide a function. You can override functions/properties from outside using getters and setters.
I also suggest using data-dojo-attach-event if you only need the node for attaching event handlers. For example by using: data-dojo-attach-event="onclick: myFunction". By doing this, it needs a function called myFunction in your templated widget, provide a default function (in your widget) for example:
myFunction: function() {
/** Stub */
},
And then you can do something like this from outside:
myWidget.set("myFunction", function(evt) {
console.log("Someone clicked on the list item");
});
Because the myFunction event handler is overriden, it will execute the function provided in the setter.
You could also directly access the attach points from outside using:
myWidget.listItemNode
When you have a data-dojo-attach-point="listItemNode". However, I don't think it's recommended to use it this way because now your widget is tightly coupled (you use the internal functionality of the widget).
HTML template:-
<div data-dojo-attach-point="infoDialog" >
</div>
Save this as "Template.html"
---------------------------------------------
load this html file using "dojo\text" plugin in your widget i.e.
"dojo/text!./templates/Template.html",
and store as template in widget main function
-----------------------------------------------
assign it as template string to the widget.
templateString: template,
now this template attachpoint(infoDialog) will be the part of your current widget scope.
-----------------------------------------------
attach event:-
this.infoDialog.onclick=function(){
alert("hello world")
};

ComponentQuery for a parent with ExtJS4?

Is there a way to query "up"? I'm in a Component and want to register listeners to it's parents events with control(). This requires a Query which gets me the parent of my main view.
In ExtJS4, you can use 'up()' from an Ext Element.
The params are a string of the parent element you wish to find e.g:
var parentEl = Ext.get('childID').up('div.parentClass');
If you provide some details about the structure of your components/elements I can give a specific example which should fit.
EDIT: To show going 'up' from a component
var myComponent = // however you've got it
var theParentEl = myComponent.getEl().up('div.parentClass');
Usually up('PARENTCLASS') is enough for what you're trying to do. Here is what I do all over the code so elements generates event for the form they are in:
items: [
...
{ xtype: 'checkbox', listeners: {
change: function() { this.up('window').fireEvent('checkboxchanged'); }
}}
...
]
As I understand, you want to listen to events dispatched by a component's parent from the child component's controller control function specifically.
There is not a query selector like 'parent < component' which you can put in the child controller's control function to listen to parent events.
Normally I would just add the parent view to the child's controller, then you could listen to it's events. But I assume you are not doing this because you are trying to delegate to different controllers or something.
You could fire an event in the child component whenever that parent event occurs. Inside the parent controller you could do it like this:
var child = parent.down('child');
child.fireEvent('myOwnEventName', arg1, arg2, arg3, etc);
Then you would add a handler for 'myOwnEventName' in the child controller's control function to run the logic you wanted for it.
If the parent doesn't have a controller then you should just add the parent component as a view in the child's controller.
The Sencha help says "Member expressions from candidate Components may be tested. If the expression returns a truthy value, the candidate Component will be included in the query:" in the http://docs.sencha.com/ext-js/4-0/#!/api/Ext.ComponentQuery help.
Took me a while to realize I can do the following in my controller:
this.control({
'window{down("testcomp")}[down]': { beforedestroy: this.doNotCloseIfUnsaved }
});
Using the {} operation, we can call any arbitrary code. Really bad from an efficiency standpoint, but it got the job done. I had to add the [down] because it runs component queries from right to left, so we have to be sure down() exists before we try running it (on every component). Thus, I was able to attach an event to whatever window holds my component.
Of course, other functions can be used too, like child() instead of down() if you want to ensure it is the immediate child rather than just anywhere below.