Is it possible to have disjoint nested views in marionette - backbone.js

So basically what I'm trying to do is to have a layoutView and inside that layoutView have a subview that is wrapped with the layoutView. The catch is that layoutView should also render information from model.
Something like this: http://i.stack.imgur.com/oLODv.png
So far, I didn't have any joy or success in achieving this, because it seems to me that views are somehow unitary and homogeneous, ergo can't be disjoint
For the layoutView I'd have following template:
<div id="someLayoutView">
Hi, <%= userName %>
<div id = "someRegion"></div>
<div>Some other information needed from model: <%= someVariable %></div>
</div>
with #someRegion showing the subView.
Is this possible or is there any similar approach to achieve this kind of nesting/wrapping views?

Related

ui-router: intermediate templates

Final Edit: working plunker with the transcluded directive.
Edit: I made a first plunker with the solution given in the first answer. It works, but it's not the desired behaviour, because the template contains all the partial.
I made a second plunker with what I hope to achieve (but it doesn't work, obviously). I think it's mostly because the template is not the parent of the partial, but it is contained in it, so ui-router doesn't understand very well what I want.
Any help with this would be greatly appreciated!
We are building a website with Angular Material and ui-router, and all our content page share the same "container", because we always want the same responsive behaviour.
The code of this generic container would be something like:
<div class="layout-content">
<div layout="column" layout-align="center">
<div layout="row" layout-align="center center">
<section class="layout-fixed-width md-whiteframe-z1" flex-sm="100" flex-gt-sm="90">
{{content placed here}}
</section>
</div>
</div>
</div>
The header can differ in all pages, so the structure we have would basically be:
The question is, how can this be achieved in ui-router? We have done some nested views, but I don't see how to do a generic template so the code could be something like:
<form>
<md-toolbar/>
<div ui-view="generic-template">
<div ui-view="my-content"></div>
</div>
</form>
Ideally we would want to define only one time the generic-template view, and use it in all our modules.
In the nested states and nested views documentation I see mostly nested state stuff, but what we want is really only a plain html template, so maybe we are over-complicating this, and an easier way is possible (I'm quite sure it's the case). I've also checked this issue, where one of the answers say that ui-router should be the solution, but not much more.
Maybe we should do a directive instead?
It can be achieved combining named views and abstract states.
The 'key' here is to define an abstract state with a view for the layout (or generic template, if we follow the nomenclature of your original post).
Abstract state:
.state('master', {
abstract: true,
views: {
generic_template: {
templateUrl: 'genericTemplate.html'
}
}
})
Then, you have to set this abstract state as parent to the child views. So, the child view will inherit the generic template view. Example:
.state('one', {
url: '/one',
templateUrl: 'one.html',
parent: 'master'
})
In your index.html, you have to use a named view for the generic template, and inside it, another unnamed view. Something like this:
<body>
<div ui-view="generic_template">
<div ui-view></div>
</div>
</body>
Here is a plunker with a complete working example.
Hope it helps.
Maybe we should do a directive instead?
A directive with a transcluded ui-view certainly seems to give you what you're looking for. This saves you from cluttering up your routing logic with something that has nothing to do with routing.
genericTemplate.html:
<div>
<p>Generic content</p>
<ng-transclude></ng-transclude>
</div>
Somewhere in your js:
angular.module('formApp')
.directive('genericTemplate', function () {
return {
replace: true,
transclude: true,
templateUrl: 'genericTemplate.html'
};
});
In your html:
<body ng-app='formApp'>
<div generic-template>
<div ui-view></div>
</div>
</body>
Edit: working plunker

Manually notify kendo-angular directive when using ng-repeat

I'm using the kendo-angular tooltip directive with ng-repeat like so:
<div ng-repeat="thing in things"
kendo-tooltip="tooltip"
k-options="thingTooltipModel"
data-thingname="{{thing.name}}>
</div>
<script type="text/x-kendo-template" id="thingTooltipTemplate">
<span>Thing Name: #= target.data('thingname') #</span>
</script>
As stated in the kendo-angular documentation, the kendo widget isn't notified when I update things, so the tooltip continues to display the initial data. Is there a way to manually tell kendo to re-read the data?
In the controller's method Maybe you should put $scope.$apply(function(){//Your CODE for the Update})
You can use k-rebind:
<div ng-repeat="thing in things"
kendo-tooltip="tooltip"
k-options="thingTooltipModel"
k-rebind="thing"
data-thingname="{{thing.name}}>
</div>
That will destroy the old widget and create a new one.

What's the best way for an angular controller to alter an ancestor HTML element?

I have an Angular controller which lives in a portion of the page however that application needs to get access to an HTML element that lives above it.
The best way to imagine the problem is that you have an embedded video which wants to request to be made full view-port:
<html ng-app="videoApp">
<body>
<div>
Other stuff that doesn't relate to the video player...
</div>
<div ng-controller="videoCtrl">
... stuff relating to the video controller
</div>
</body>
</html>
The exact problem is that the videoCtrl needs to be able to add a class to the body class such that it can switch the page layout to being full-page and dominated by the video.
Desired outcome, status of video app adds a "full-page" class to body tag:
<body ng-class="video.fullPage ? 'full-page', : ''">
What's would be the correct way for the video to add a class to an ancestor tag?
Under normal circumstances where the element we want to manipulate lies inside the controller that's doing the manipulation we can bind elements to variables in the scope:
<body> <!-- the videoCtrl scope is not available to <body> -->
<div ng-controller="videoCtrl">
<div ng-class="video.fullPage ? 'fullPage' : ''"></div>
</div>
</body>
However the body tag is not contained within the scope of the video controller and so has no access to the variables in its scope so we can't bind to them.
We could always reach out directly and change the class on the body using dom manipulation but that's not very Angular. What is the correct pattern for the video controller to alter the class of the body tag?
To meet a very similar requirement, I used Angular events. From the controller, $broadcast an event on $rootScope. Then have some sort of screen layout controller handle the event and toggle the possible screen configurations.
So:
child controller:
$rootScope.$broadcast('layout-action', { configuration: 'video' });
layout controller:
$scope.$on('layout-action', function(event, args) {
if (args.configuration == 'video') {
$scope.showVideo = true;
}
});
html:
<body ng-class="{'full-page': showVideo}">
*Note: this does slightly tie the child functionality to another part of the layout. However, perhaps try to generalize the video layout. For example, maybe you want a full screen mode instead.
If ng-app="videoApp" is the only parent, you could also do something on the button that would trigger the full-page class like this:
<body ng-class="{'full-page': showVideo}">
<div ng-controller="videoCtrl">
<button ng-click="$parent.showVideo = !$parent.showVideo">I'm in the video controller!</button>
</div>
</body>
In this case i'm just toggling showVideo to true or false. But like Davin said, this may not be as nice because it depends more on where it sits in the app.
See this plunkr for an example http://plnkr.co/edit/UxtA0YvCUckofAflsy9G

Using a directive to add content to areas outside ng-view

I'm attempting to port an existing Ruby on Rails frontend to Angular. So far I've managed to get a single page app in place that switches out the content of ng-view depending on your angular route. This is great, however - in my RoR layout I have several defined areas where content can be placed, all of these are contextual to the main view. For example: Sidebar and Heading.
In RoR I can do the following from within an action view to set sidebar content.
<p>Product page content</p>
<% content_for :sidebar do %>
<% render :partial => 'product_sidebar' %>
<% end %>
I am struggling to determine the best method to achieve this in Angular. I've got it working with two methods:
SidebarUrl added to route definitions, route change event updates a scope variable which an ng-include directive uses in the layout.
Custom directive that is served with the template loaded into ng-view, e.g.
<p>Main content for the view</p>
<sidebar>
Content for sidebar
</sidebar>
The directive basically copies its innerHTML to the sidebar element in the main layout and then removes itself. It could be written to place the content into a target element defined by an attribute to make it more generic and reusable.
This way is more natural to me as the result is closest to the Ruby on Rails way but I'm not sure if its a decent solution or something that I will run into problems with later on (I'm very new to Angular).
Any thoughts or suggestions welcome.
UPDATE 18/06
I've found the Angular UI Router project: https://github.com/angular-ui/ui-router which seems to cover my requirements in an official way. Leaning towards that as a solution at the moment.
Try this (not tested).
You can have another element outside of your <div ng-view> element: say, <div id="sidebar" ng-controller="sidebarContr">. Since you can inject as many dependencies as you like in to sidebarContr, you can use $location as one of its parameters and check the location there and add properties to $scope to make the div display what you need.
For example:
in your controllers JS:
(angular
.module('app.controllers', ['ng'])
.controller('sidebarContr', [
/******/ '$scope', '$location',
function ($scope, $location) {
if ($location.path() === '/') {
$scope.file = 'include_0.html';
else {
$scope.file = 'include_1.html';
}
}
])
);
in your HTML:
<div id="sidebar" ng-controller='sidebarContr'>
<div ng-include src='file'></div>
</div>
EDIT: The Angular-UI Router component that you mention seems to be what you need, it looks more powerful. But my solution can be useful when you just need something simple and do not want to have one more dependency.

Backbone.Marionette Layout + Region + View inserts an extra element, breaking Bootstrap styles

I'm running into a problem getting Backbone.Marionette to cooperate with Twitter Bootstrap. Bootstrap expects markup to conform to predefined patterns in order to work, but the way that Backbone and Marionette handle things, it seems to be impossible to use a Marionette.Layout to generate Bootstrap-compliant markup for a navbar with an embedded dropdown.
Here's the problem:
Let's say I have a Marionette.Layout representing the navbar, and I want to have a region, where I will put the CollectionView or CompositeView that manages the items in the dropdown. The region is a selector, so in this case if we have:
<div class="navbar">
<ul class="nav">
<li>Static item</li>
<li id="dynamic-dropdown"></li>
</ul>
</div>
Then the Layout would specify the region for the dynamic dropdown as follows:
Marionette.Layout.extend({
regions: {
dropdown: '#dynamic-dropdown'
}
...
});
I would then have a CompositeView that takes care of rendering the dropdown item models, specifies the extra markup for the Bootstrap dropdown, and so forth. The problem is that it appears to be impossible to make #dynamic-dropdown be the $el for the CompositeView, as Backbone always inserts an extra div (or whatever you specify in tagName). In order for the dropdown to appear as expected, I need to get rid of that extra element and have the view's root be #dynamic-dropdown.
I've put together a jsfiddle illustrating what I mean.
tl;dr How do I make a View's root element be the region specified in a Marionette.Layout?
You just need to make sure that the markup that is generated in the end follows the same as required by Bootstrap (use the console to debug).
The required markup is as follows:
The markup generated by your code has the following problems:
So, to fix it you have to change your nav template to:
<script type="text/html" id="nav">
<div class="navbar">
<ul class="nav" id="dropdown">
</ul>
</div>
</script>
And your views to render <li> instead of <div>
var DropdownItem = Marionette.ItemView.extend({
tagName: 'li',
template: "#dropdown-item"
});
var DropdownView = Marionette.CompositeView.extend({
template: "#dropdown-collection",
className: 'dropdown',
tagName: 'li',
itemView: DropdownItem,
itemViewContainer: '#dropdown-items'
});
Here is the working version: http://jsfiddle.net/7auhR/6/

Resources