Show a toolbar in shell header that is bound to the current viewmodel in Durandal - 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);
}

Related

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.

Durandal view composition order

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.

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";
}
}
}

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")
};

Loading jquery plugin result into Durandal view

I am using the Durandal Starter Template for mvc4. I have set the following simple View:
<section>
<h2 data-bind="html: displayName"></h2>
<h3 data-bind="html: posts"></h3>
<button data-bind="click: getrss">Get Posts</button>
<div id="rsstestid" ></div>
</section>
and ViewModel:
define(function (require) {
var http = require('durandal/http'),
app = require('durandal/app');
return {
displayName: 'This is my RssTest',
posts: ko.observable(),
activate: function () {
return;
},
getrss: function () {
$('#rsstestid').rssfeed('http://feeds.reuters.com/reuters/oddlyEnoughNews');
return;
}
};
});
As you can see, it is simply using the zRssReader plugin to load posts into a div when the 'Get Posts' button is clicked. Everything works fine, the display name is populated and the posts show up as expected.
Where I am having trouble is when I try to eliminate the button and try to load the posts at creation time. If I place the plugin call in the activate function, I get no results. I assume this is because the view is not fully loaded, so the element doesn't exist. I have two questions:
How do I delay the execution of the plugin call until the view is fully composed?
Even better, how do I load the plugin result into an the posts observable rather than using the query selector? I have tried many combinations but no luck
Thanks for your help.
EDIT** the below answer is for durandal 1.2. In durandal 2.0 viewAttached has changed to attached
Copy pasted directly from durandaljs.com
"Whenever Durandal composes, it also checks your model for a function called viewAttached. If it is present, it will call the function and pass the bound view as a parameter. This allows a controller or presenter to have direct access to the dom sub-tree to which it is bound at a point in time after it is injected into its parent.
Note: If you have set cacheViews:true then viewAttached will only be called the first time the view is shown, on the initial bind, since technically the view is only attached once. If you wish to override this behavior, then set alwaysAttachView:true on your composition binding."
--quoted from the site
There are many ways you can do it but here is just 1 quick and dirty way:
<section>
<h2 data-bind="html: displayName"></h2>
<h3 data-bind="html: posts"></h3>
<button data-bind="click: getRss">Get Posts</button>
<div id="rsstestid"></div>
</section>
and the code:
define(function (require) {
var http = require('durandal/http'),
app = require('durandal/app');
var $rsstest;
return {
displayName: 'This is my RssTest',
posts: ko.observable(),
viewAttached: function(view) {
$rssTest = $(view).find('#rsstestid');
},
getRss: function() {
$rssTest.rssfeed('http://feeds.reuters.com/reuters/oddlyEnoughNews');
}
};
});
In general, I think it's wise to refrain from directly touching UI elements from within your view model.
A good approach is to create a custom KO binding that can render the rss feed. That way, you're guaranteed that the view is in place when the binding executes. You probably want to have the feed url exposed as a property on your view model, then the custom binding can read that when it is being updated.
Custom bindings are pretty simple - if I can do it, then it must be :)
Here's a link to the KnockOut custom bindings quickstart: http://knockoutjs.com/documentation/custom-bindings.html
I too am having the same problem, I'm trying to set a css property directly on an element after the durandal view model and view are bound together. I too assume that it's not working because the view is not fully composed at the point I am setting the value.
Best I have come up with is using the viewAttached lifecycle event in durandal, which I think is the last event in the loading cycle of a durandal viewmodel, and then using setTimeout to delay the setting of the property still further.
It's a pretty rubbish workaround but it's working for now.
var viewAttached = function (view) {
var _this = this;
var picker = new jscolor.color($(view).children('.cp')[0], {
onImmediateChange: function() {
_updateCss.call(_this, this.toString());
}
});
picker.fromString(this.color());
setTimeout(function() {
_updateCss.call(_this, _this.color());
}, 1000);
};
var activate = function (data) {
system.log('activated: ' + this.selectors + ' ' + this.color());
};
var deactivate = function (isClose) {
system.log('deactivated, close:' + isClose);
};
return {
viewAttached: viewAttached,
deactivate: deactivate,
activate: activate,
color: this.color
};
I was having a similar issue with timing. On an initial page load, where a partial view was being loaded on the page I could call the viewAttached function and use jQuery to bind some elements within the partial view. The timing worked as expected
However, if I navigated to a new page, and then back to the initial page, the same viewAttached + jQuery method failed to find the elements on the page... they had not yet been attached to the dom.
As best as I have been able to determine (so far) this is related to the transition effects in the entrance.js file. I was using the default transition which causes an object to fade out and a new object to fade in. By eliminating the fadeOutTransition (setting it to zero in entrance.js) I was able to get the viewAttached function to actually be in sync with the partial views attachment.
Best guess is that while the first object is fading out, the incoming object has not yet been attached to the dom but the viewAttached method is triggered anyway.