Data Binding Fails after app.setRoot('viewmodels/shell') for Durandal - javascript

On Durandal, I have a login page that's styled different from my other pages, so taking the advice I saw on some of the posts here, I've set up main.js to do a:
app.setRoot('viewmodels/login');
And on the login page, I provide the below method in login.js that resets the root:
loginRedirect: function() {
app.setRoot('viewmodels/shell');
}
The idea is that the user should go to the login page by default, and once logged in, I will invoke the method loginRedirect, which sets shell.html to be the root and thus reloads the content, and he should be able to navigate other pages from there.
Here's my login.html:
<div class="login">
<div data-bind="compose:'header-nav-plain.html'"></div>
<div class="container">
<!-- Content here-->
</div>
</div>
<div data-bind="compose:'viewmodels/footer'"></div>
<a class="go-inner-pages" data-bind="click: loginRedirect" href="#">Test link to go to 'inner' pages</a>
And here's my shell.html, that is the doorway to all other pages:
<div>
<div data-bind="compose:'viewmodels/header-nav'"></div>
<div class="container">
<div data-bind="compose:'viewmodels/site-nav'"></div>
<div data-bind="router"></div>
</div>
<div data-bind="compose:'viewmodels/footer'"></div>
</div>
You can see that shell.html is slightly different in structure from login.html, in that it binds to a different header-nav.html, and also a site-nav.html. (They share the same footer.html.) So on the login page, when I click on the test link that calls the loginRedirect method, it sets the root of the app to be shell.html, which will load the default page based on the parameters passed to router.map, as defined in shell.js:
activate: function(){
//Initializing router
router.map([
{ route: '', moduleId: 'viewmodels/dashboard', title: 'Dashboard', nav: true }
])
.buildNavigationModel();
//More code
return router.activate();
But, when I click on the test link and invoke loginRedirect, the new dashboard content is loaded, but the new header-nav and site-nav data binding didn't happen. I checked the inspector, and see that the structure has indeed changed to that of shell.html (as opposed to login.html), only data binding for header-nav and site-nav didn't happen. I.e. here's the new html after invoking loginRedirect:
<div>
<div data-bind="compose:'viewmodels/header-nav'"></div><!--header nav content not bound-->
<div class="container">
<div data-bind="compose:'viewmodels/site-nav'"></div><!--site nav content not bound-->
<div data-bind="router"><div class="durandal-wrapper" data-view="views/dashboard" data-active-view="true">
<!-- Dashboard content successfully loaded -->
</div><!--end data-bind="router"-->
</div><!--end .container-->
<div data-bind="compose:'viewmodels/footer'">
<!--footer content loaded-->
</div>
And from the Console (see below), one can see that while the shell and dashboard are bound, (even footer is bound a second time), header-nav and site-nav are not.
Does anyone have any idea why this is happening and what I might be doing wrong? Thanks in advance for your attention, hope to hear from someone soon. Cheers.

For view-only bindings, you would bind this way (we do the same as you, so I'm copying and pasting our code):
compose: {view: 'shellLeftFooter.html'}"
For views bound to viewModels, you bind this way:
compose: {model: 'viewmodels/navigation/shellNavigation'}
The default viewLocator will use the following convention:
If a view is specified with no model, then the view must be identified with the .html extension, as I have done in the former example, and it will not be bound to a viewModel;
If a model is specified with no view, as I have done in the latter example, then the model file is assumed to have a .js extension, and it is also assumed that a view exists in a folder named views under the app directory, with the same name as the model, but with an .html extension.
These aren't the only two conventions, but they are the ones germane to this discussion. To put the above in different words, make sure you have both a viewmodels and a views directory that are siblings of each other under the app directory. Make sure you give the viewModel and the view identical names, differing only in their extensions (.js for the former and .html for the latter).
I'm seeing all of your .html and .js files in the code you posted, so I'm sure what your intention is in each case (view only or model-view-viewModel). Also, your compose binding is not quite to standard.

Related

EmberJS template component suddenly not rendering on page

I'm following this Ember tutorial and I've suddenly run into an issue where my rental-listing.hbs template component stops rendering. It started when I began implementing the map service.
I don't understand where this could be happening. I've copied the code from parts of the GitHub repository that I thought were relevant but to no avail.
I have a rental.hbs template that looks like so:
<div class="jumbo">
<div class="right tomster"></div>
<h2>Welcome!</h2>
<p>We hope you find exactly what you're looking for in a place to stay.</p>
{{#link-to "about" class="button"}}
About Us
{{/link-to}}
</div>
{{outlet}}
Which in turn has a template component called rental-listing.hbs:
<article class="listing">
<a
onclick={{action "toggleImageSize"}}
class="image {{if this.isWide "wide"}}"
role="button"
>
<img src={{this.rental.image}} alt="">
<small>View Larger</small>
</a>
<div class="details">
<h3>{{link-to this.rental.title "rentals.show" this.rental class=this.rental.id}}</h3>
<div class="detail owner">
<span>Owner:</span> {{this.rental.owner}}
</div>
<div class="detail type">
<span>Type:</span> {{rental-property-type this.rental.category}} - {{this.rental.category}}
</div>
<div class="detail location">
<span>Location:</span> {{this.rental.city}}
</div>
<div class="detail bedrooms">
<span>Number of bedrooms:</span> {{this.rental.bedrooms}}
</div>
</div>
<LocationMap #location={{this.rental.city}}/>
</article>
The only thing I have added to the above is the line <LocationMap #location={{this.rental.city}}/> but it still doesn't work if I remove it.
The console shows me no errors and I can actually see I am getting the three dummy objects I want from Mirage:
So I'm definitely getting the objects and from what I see I'm doing everything necessary in the templates to render it but they aren't. Should I be looking somewhere else?
Are you able to provide a link to your example? By having each piece of the ember application you mention it would be best to answer definitely. I can give a general answer with strategies for debugging the template.
The conventions behind ember.js make understanding the "whys" frustrating at first and possibly opaque. Ember's handlebars implementation governs how values are populated and accessed within templates using very specific rules. Ember treats templates (handlebars files) differently depending on whether it is for a route or component. Component's have an isolated context and must receive values by explicit passing in or dependency injection. Then, you can use such values in a component's template by accessing those properties with {{this.somePassedInValue}}.
In the super-rentals app, it appears the rental index route invokes the components responsible for displaying the individual units. I found this in app/templates/rentals/index.hbs.
<li><RentalListing #rental={{rentalUnit}} /></li>
The route template iterates over the list of filteredResults. Each of these is the rentalUnit. A good first step would be to use the {{log}} helper to print out that the value of rentalUnit to ensure it is what you expect.
Alternatively, you could try cloning https://github.com/ember-learn/super-rentals and applying the changes you want to make step by step from the master branch. By doing so, you could easily undo a single change to see what caused something to not show up as expected.
<LocationMap #location={{this.rental.city}}/>
to be written as below
<LocationMap #location={{this.rentals.city}}/>
may be typo error.
also repeat this for there place in that template.
Because the model name in the console is rentals not rental

Angular : template changing depending on javascript value

I am building an angular app, with templates changing depending on with ngscope as seen on the samples below.
<label class="btn btn-default">
<input id="cacher" value="settings" type="radio" data-ng-model="user.status">Settings
</input>
</label>
and here i'm calling my html template userPageSettings
<div id="wrapper" ng-switch="user.status">
<div ng-switch-when="settings">
<user-page-settings></user-page-settings>
</div>
</div>
Trouble is, i'm trying to get access to the templates (and the correct route) through a dropdown in the navbar, which is supposed to do exactly the same thing as my normal menu. But if i simply call the templates from my navbar they will be shown on the current page, therefore at the incorrect route.
In my navbar code i'm doing the following, which is bringing me back to the correct route, but showing me my default template (aka home, not shown here, but called through the same method and has the number 6)
<li role="menuitem">Settings</li>
The number "1" (and the others using 2,3 etc) are hashed in my javascript and put into a variable. I would like to make my templates appear depending on those numbers.
Any ideas how ??
:) Thank you !!

angular.js: routing to an editor-version of the current article

I have some articles of the same category and I'm routing like this:
app.config(function($routeProvider) {
$routeProvider
.when('/chapter/:title', {
templateUrl: 'article.html',
controller: 'article'
});
});
article.html:
<h1>{{title}}</h1>
<p>{{content}}</p>
<button>Editor</button>
But now I want to get a route from every article of this chapter to an editor version to change the content. This editor-version could look like this:
editor_article.html:
<input type="text" value="{{title}}">
<textarea>{{content}}</textarea>
So what is the best way for routing the button of an article to the editor_article.html-template and load it with the same data?
There is no "best-way" here, and it all depends on your context.
Do all end-users have equal access to this (or put another way, should anybody who can open the dev-console be allowed to access this page)?
If not, you need to solve the login problem before you load most of the code for your SPA.
If login isn't a problem, or already taken care of, why do you need separate routes?
Do you need to cache these pages separately in-browser?
Are there other considerations beyond that?
Totally legitimate question; from an artistic perspective, perhaps you're hoping for page transitions which match other transitions you have for when you do change routes, or you are relying on routeParams for some logic that we don't know about.
But chances are good that all of the above things aside, you could simply have a button and a couple of ng-if statements, with directives.
<body >
<main ng-view></main>
</body>
<!-- template -->
<section >
<button
ng-click="article.toggleMode()">{{
article.editMode ? "View": "Edit"
}}</button>
<article-view
content="article.content"
ng-if="!article.editMode"
></article-view>
<article-edit
content="article.content"
onsave="article.save(content)"
ng-if="article.editMode"
></article-edit>
</section>
Using directives to define the two templates, using controllerAs:"article" in the example above.
Even that might be complicating it.

Knockout SPA: Different Layouts(Master pages)

I'm building SPA application with knockout.js .
Basically what i do is bind as current page a ko component depending on what is route.
It looks something like
<div id="currentPage" data-bind="component: { name: currentRoute.page,
attr: currentRoute }>
</div>
This is for the current page and the whole picture whit the layout looks like:
<html>
<head></head>
<body>
<div data-bind="component: {name: "nav"}></div>
<div data-bind="component: {name: "aside"}></div>
<div id="currentPage" data-bind="component: { name: currentRoute.page,
attr: currentRoute}">
</div>
</body
</html>
The problem is that i dont have one layout.. the other one looks like this (pseudo used)
<html>
<body>
<wrapper>
<currentPage>
</wrapper>
</body
</html>
So basically the first layout is not direct parent of the currentPage module but the second is..
The variants for dynamic layout changing that i can think of are
Specify the layout components in each page.. But i dont think it is good idea cause i will be writing the same code over and over and will not be able to persist the state in the layout component because of when i change the next page the layout will be recreated( not fully but enough to loose the state )
The layout is separate from the currentPage component so only the currentComponent binding will be changed.. this is perfect for persisting state in layout but not good when i have variant where i want different layout for example which is wrapper around the componentBinding..
I'll be very happy if some one shares fresh ideas how to solve such problems.
In knockout if any part of view is to be dynamically changed you got to have bindings attached to the view. You say that the outer layout of the page is dependent on the inner page being displayed, so logically I consider it part of that page view definition. That is why I would not look for some special way to handle this case.
If I am not missing something, the reasonable thing to do in this case is to just include layout in each page. You could then handle repetitive code by using "traditional" approach using knockout templates (especially Note 5 on dynamic templates from http://knockoutjs.com/documentation/template-binding.html) and code extraction.
Completely other approach you might want to take is to put all layouts in master page and then control them by using bindings like "if" and "visible".

Handling header and footer in AngularJS

I have been trying to implement the header / footer in an Angular JS App. I was thinking of adding these as ng-include in the main index.html. However this would have worked if the header and footer are static pages. My case is slightly different... In Login page no header / footer is shown. Other pages depending on whether you are logged in or not, you have to show "Welcome user [ logout] " or "Welcome guest [ login ]".
I save the login information and isLoggedIn in a service - then use HeaderCtrl controller to copy that to $scope. The biggest problem seems to be that the whole ng-include is not refreshed on a logoff. Hence divs with ng-show hide directives will not hide/show on change. Somebody suggested using ng-switch - it also behaves the same way.
If I move the header code inside individual views then everything is fine.
A similar question is here: Refresh header page in angularjs
AngularJS - Handle repeated fragments like Header and Footer
I have asked this question earlier but the answers did not help me when I moved the logged variable from the rootScope. The problem is my HeaderCtrl is only executing on a full page refresh. Not executing on Angular page navigation etc.
Here is the code:
index.html:
<body ng-app="greenhornApp">
<div id="navbar" ng-controller="HeaderCtrl"><div ng-include src="'views/partials/header.html'"></div>
</div>
<div class="title-spacer visible-desktop"></div>
<!-- Add your site or application content here -->
<div class="wrapper" ng-view></div>
</body>
header.js:
angular.module('greenhornApp')
.controller('HeaderCtrl', ['$scope', 'LoginService', function ($scope, loginService) {
$scope.isLoggedIn = loginService.isLoggedIn();
$scope.session = loginService.getSession();
}]);
header.html
<div>
<div ng-show="isLoggedIn">
<div class="navbar navbar-inverse navbar-fixed-top">
More stuff here....
</div>
</div>
</div>
I am using ng-route and angular 1.2.0-rc3. I put a breakpoint in HeaderCtrl... it gets executed only when I do a full page refresh. What am I doing wrong here?
If your loginService.isLoggedIn() is bound to a primitive value instead of an object, your HeaderCtrl needs to use $watch to monitor the variable and get the new value as it changes.
For it to work without a $watch you need to make sure an object reference is passed from your loginService to your HeaderCtrl.
I made two quick Plunkers that hopefully replicates your situation well enough to give some guidance:
With primitive and $watch: http://plnkr.co/edit/LvtVEE?p=preview
With object reference and no $watch: http://plnkr.co/edit/2u2ZDE?p=preview
EDIT: My original reasoning was wrong. Check tasseKATT's answer for that.
Here's is another way of communicating between controllers using angularJS broacasting mechanism
Here's a jsfiddle just to show the basic idea.
var module = angular.module("demo", []);
function Header ($scope){
$scope.userIsLoggedIn = true
$scope.$on('DO_LOGOUT',function(event){
alert('Header is updating log-status')
$scope.userIsLoggedIn=false
})
}
function Dynamic($scope){
$scope.logout =function(){
$scope.$emit('LOGOUT_REQUESTED')
}
}
function Root($scope) {
$scope.$on('LOGOUT_REQUESTED',function(event){
alert('Rootscope got logout event')
$scope.$broadcast('DO_LOGOUT')
})
}

Categories