The error ONLY occurs under a special routing circumstance when the layout (see gist link) is called by the router following a login processed in a different controller and routed here by the global event mechanism.
Everything is fine as long as an existing session is reused and there is NO Logon processed.
main code in this gist ( error at line #113 "this.headerRegion" not defined
Code blocks coming to the gist module , from logon and from router...
loginSuccess: function (user) {
vent.trigger('user:login'); //notify all widgets we have logged in
app.router.navigate('home', { trigger: true });
}
...
return marionette.AppRouter.extend({
routes: {
'home' : 'home',
...
},
home: function() {
this._showPage('home');
},
...
_showPage: function (pageName, options) {
console.log("RouterShow " +pageName);
var that = this;
//make sure we are authenicated, if not go to login
if (!Parse.User.current())
pageName = 'login';
require(['./pages/' + pageName + '/Controller'], function (PageController) {
if (that.currentPageController) {
that.currentPageController.close();
that.currentPageController = null;
}
// line below loads the layout in the gist link
that.currentPageController = new PageController(options);
that.currentPageController.show(app.regionMain)
.fail(function () {
//display the not found page
that.navigate('/not-found', { trigger: true, replace: true });
});
});
}
...
define([
'marionette',
'./Layout',
'app'
], function (
Marionette,
Layout,
app
) {
'use strict';
return Marionette.Controller.extend({
show: function (region) {
var that = this, d = new Marionette.$.Deferred();
region.show(new Layout({ })); //this layout in gist link
return d.promise();
}
});
});
pageController.show() near the end of the above block calls the Layout region in the gist
To recreate the error that ONLY OCCURs following a logon, I do the following:
Show compoundView #1 at line #57 of the gist.
click in compound view #1 firing event at line #40 of the gist ('roleList:getuser',)
swap new views #2 into EXISTING regions used for the first views at lines #113, 114
ERROR at 113, "this.headerRegion" no longer exists in the layout!
Discussion - now IMO Layout extends Marionett.ItemView and from the source, it should always have the regions defined before calling init. The constructor checks for undef "this.headerRegion" at line #23 of the gist.
My code reimplements the superclass constructor in lines 18 - 23 of the gist and it looks like "headerRegion" and "mainRegion" attributes are always defined. But, the error is :
Uncaught TypeError: Cannot call method 'show' of undefined
Layout.js:113 Marionette.Layout.extend.getUserRelation
The region properties like 'headerRegion' in your case are only available after render(), not just initialize(). From the Marionette docs:
Once you've rendered the layout, you now have direct access to all of
the specified regions as region managers.
You must be triggering those events ('roleItem:getrole', etc.) before the layout is rendered in logic outside the gist. Instead you'll need to render first if you want to implement getUserRelation() in this way.
Related
I have recently taken on the development of web app that has been written in AngularJS. In one of the files, myApp.js, there is a provider that is defined as follows:
.provider('myAppConf', function($routeProvider){
var constants = {
'HOMEPAGE': '/alarms',
...
};
// Setter function for constants
this.setConstant = function(constant, value){
constants[constant.toUpperCase()] = value;
};
...
// Other setter functions
...
// Non- setter/ getter functions:
this.addElementOverview = function(){
...
var location = 'pages/elementbrowser';
...
return '/' + location;
}
function addCreatePageRoute(){
$routeProvider.when('/pages/create', {
page: {
editable: true,
title: {
...
},
layout: {
...
},
widgets: [
...
]
}
});
}
// More non- setter/ getter functions
this.$get = ['$q', '$timeout', 'myAppUI' function($q, $timeout, myAppUI){
...
}];
}).run(function(...){
...
});
On most of the pages on the site, there is a 'settings' button, which opens a dialog box that the user can use to change the settings for a given page. I have added a checkbox to that dialog box, which, when checked, I want to use to set the page on which it is checked as the 'home' page, overwriting whichever page was previously the 'home' page. If/ when the checkbox is deselected again, the home page should be set back to its original value (i.e. the /alarms page determined in the constants).
As things currently stand, I have managed to change the home page, so that it is updated to the page selected by the user- when the click 'Home' they are taken to the page on which the checkbox was selected. But as soon as they log out, their chosen home page is forgotten, and when they log in again, the default home page is their home page until they select another one.
I now want to set the user's default home page to whatever they choose as their custom home page, and am trying to use the 'setter function for constants' that is defined in the provider function above.
I have done this by calling:
myAppConf.setConstant(myAppConf.HOMEPAGE, $location.path());
when the 'Confirm' button is pressed on the dialog box (with the 'set as homepage' checkbox checked).
However, when I press the 'Confirm' button, I get a TypeError in my console which says:
TypeError: myAppConf.setConstant is not a function
I don't understand why I'm getting this error... setConstant() is defined as a function with:
this.setConstant = function(constant, value){...};
so why is the console stating that it's not a function? How can I fix this?
Edit
The function where I'm calling myAppConf.setConstant() is defined in Pages/ctrls.js as follows:
angular.module('myApp.pagse')
...
.controller('LayoutCtrl', function($scope, $route, $location, $timeout, Page, myAppConf, NotifyMgr, DialogMgr,myAppUI){
...
$scope.confirm = function(e){
...
if($scope.checkboxModel){
...
myAppConf.setConstant(myAppConf.HOMEPAGE, $location.path());
}else{
console.log("homepage not changed");
}
};
setConstant is myAppConfProvider method, not myAppConf. If it should be available both in config and run phases, it should be defined on both a provider and an instance:
.provider('myAppConf', function(){
...
var commonMethods = {
setConstant: function (constant, value) {
constants[constant.toUpperCase()] = value;
},
...
}
Object.assign(this, commonMethods, {
$get: function () {
return commonMethods;
}
})
})
A cleaner way to do this is to use constant:
.constant('myAppConf', {
_constants: { ... },
setConstant: function (constant, value) {
this[constant.toUpperCase()] = value;
},
...
})
Since getters and setters can be considered antipattern unless they are justified, a KISS alternative is just:
.constant('myAppConf', {
'HOMEPAGE': '/alarms',
...
})
I'm quite new using Backbone and now I have found this new issue.
I use the route "jobprofile" to create a view which fetch the data from urlRoot= "job" (doing job/id using a default id) BUT if I add the :id to the route as "jobprofile/:id" which I need to type in the browser to be able to get the job.id view, then it stops to work and the url of the model change to: ".../jobprofile/job/id" which (obviously) give me 404 error.
Hope is clear. Thanks!
CODE:
I have a router.js
routes: {
...
"jobprofile/:id": "view", //without /:id works!
},
view:function(id){
console.log("view");
this.job = new Job();
this.job.setId(id); //This is set correctly
this.jobProfileView = new JobProfileView({
model: this.job,
el: $('.tab-content')
});
},
(View)JobProfileView.js:
...
initialize: function(){
var that = this;
this.model.fetch().done(function(){
console.log("fetch done!");
that.render();
});
},
...
(Model)Job.js:
urlRoot: 'job',
initialize: function () {
},
setId: function (job_id) {
this.set('id', job_id);
},
UPDATED:
Ok. So it looks that I "fix" the problem adding this.navigate('/jobprofile'); to the method view in router.js. I guess that the /:id which causes the problem is deleted from the route (actually when you see the browser its not there anymore) but I still keep the id in the method.
In any case, this is a really bad solution because when I try to go back it creates a bucle and it goes to jobprofile/id and navigate again to jobprofile. So if anyone has an idea it would be great...
Finally I understood what the problem was...
Basically there is a difference when in the url or urlRoot are set in the model. Thus, these two options appear:
url:'/foo'. In this case it will not take the base url.
example: www.web.com/foo
url:'foo'. In this case, it will take the base url
example: www.web.com/api/foo
My case is as follow:
(Model)Job.js:
urlRoot: '/job',
initialize: function () {
},
setId: function (job_id) {
this.set('id', job_id);
},
I'm getting the following error after upgrading Ember to the latest version:
Error while processing route: portfolio Cannot read property 'connectOutlet'
The error takes place whenever I navigate for example from:
http://localhost:8080/#/portfolio
to:
http://localhost:8080/#/publications
The weird thing is that if I refresh the pages many times sometimes it works and some times it's doesn't so feels like some file is loaded too late or maybe loaded twice.
aplication.hbs
The application view renders the header, footer and main container, which contains the application {{outlet}}.
<!-- ... -->
<div class="container" id="maincontainer">
<div class="maincontainer">
{{outlet}}
</div>
</div>
<!-- ... -->
index.hbs
My index view renders a couple of subviews:
<div class="jumbotron fadeInUp animated">
<div class="row">
<div id="summary_content">
{{view view.CvSummaryView}}
</div>
</div>
</div>
routes
In all my routes I'm only adding the model() function. I'm not overriding renderTemplate() or anything else.
define([
'Ember'
],
function (Ember) {
"use strict";
return Ember.Route.extend({
model: function()
{
var result = {};
$.ajax({
async: false,
dataType: "json",
url: './website/js/models/portfolio.json',
success: function(data){
result.portfolio = data;
}
});
return result;
}
});
}
);
I tried the following with no luck:
renderTemplate: function(){
this.render({
outlet: "main",
into: "application"
});
}
Do you have any ideas about what can be the root cause of this issue?
The entire app source code can be found at https://github.com/remojansen/remojansen.github.io/tree/master/website/js
UPDATE 1
I've been reading the Ember documentation and I added {{outlet "main"}} into my application template and tried with:
renderTemplate: function() {
this.render('blog', { // the template to render
into: 'application', // the template to render into
outlet: 'main' // the name of the outlet in that template
});
}
The I've been debugging the Ember code and I reached this function:
function appendView(route, view, options) {
if (options.into) {
var parentView = route.router._lookupActiveView(options.into);
var teardownOutletView = generateOutletTeardown(parentView, options.outlet);
if (!route.teardownOutletViews) { route.teardownOutletViews = []; }
replace(route.teardownOutletViews, 0, 0, [teardownOutletView]);
parentView.connectOutlet(options.outlet, view);
} else {
var rootElement = get(route.router, 'namespace.rootElement');
// tear down view if one is already rendered
if (route.teardownTopLevelView) {
route.teardownTopLevelView();
}
route.router._connectActiveView(options.name, view);
route.teardownTopLevelView = generateTopLevelTeardown(view);
view.appendTo(rootElement);
}
}
In the function above, in the line:
var parentView = route.router._lookupActiveView(options.into);
The variable parentView is null and options.into is "application". So the line below throws an exception:
parentView.connectOutlet(options.outlet, view);
I have defined the application template and view but not an application route I don't know if that could be the problem.
Thanks!
After some time debugging I noticed that the ember router._activeViews element didn't always contain the application view:
Works
Doesn't work
I tried to analyse why was this happening and because as I said in the question:
The weird thing is that if I refresh the pages many times sometimes it
works and some times it's doesn't so feels like some file is loaded
too late or maybe loaded twice.
I was almost sure that is was related with the usage of require.js and loading application components asynchronously.
The solution was use deferReadiness() and advanceReadiness(). Here is what I did in case it can help somebody in the future...
app.js
define(['Ember'], function (Ember) {
"use strict";
window.app = Ember.Application.create({
LOG_TRANSITIONS: false, // basic logging of successful transitions
LOG_TRANSITIONS_INTERNAL: false, // detailed logging of all routing steps
LOG_VIEW_LOOKUPS: false // detailed logging of view resolution
});
// Delay the app's initialization . We will invoke advanceReadiness()
// when are ready for the app to be initialized
window.app.deferReadiness();
return window.app;
});
main.js
require([
'website/js/app',
/* routes, views... */
], function (
app,
/* routes, views... */
){
"use strict";
// Configure Routes
app.Router.map(routes);
// Set Routes
app.IndexRoute = indexRoute;
// ...
// Set Views
app.IndexView = indexView;
// ...
// We're ready to launch the app!
app.advanceReadiness();
});
While a Position model loads, I want to show a loading/spinner message in the template. I am not being able to achieve that.
{{#if loading}}
<div class="spinner"></div>
{{else}}
<div>Here goes the position</div>
{{/if}}
Given the router
App.Router.map(function() {
this.resource('article', { path: '/:id' }, function() {
this.route('position', { path: '*position_id' });
});
});
my approach in the ArticlePositionRoute is the following:
App.ArticlePositionRoute = Em.Route.extend({
actions: {
loading: function() {
var controller = this.get('controller');
if (controller) controller.set('loading', true);
return false;
}
},
setupController: function(controller, model) {
controller.set('loading', false); // BEFORE _super
this._super(controller, model);
controller.set('loading', false); // AFTER _super
},
model: function(params) {
return promiseThatTakesAWhile(); // slow fetch model
},
afterModel: function(model) {
var self = this;
return promiseThatTakesAWhile().then(function(result) {
model.set('result', result);
self.set('controller.loading', false); // THIS THROWS "Uncaught Error: Property set failed: object in path "controller" could not be found or was destroyed."
});
}
});
In afterModel, the controller is not available, throws Uncaught Error: Property set failed: object in path "controller" could not be found or was destroyed.
In setupController, when the set goes before the _super call, it blows with Error: Assertion Failed: Cannot delegate set('loading', false) to the 'content' property of object proxy <App.ArticlePositionController:ember726>: its 'content' is undefined., if I put it after the _super call, the view already calls didInsertElement and errs because it can't find the right div in the template (basically, it's too late).
What are the proper hooks to set/unset the 'loading' flag? I think I have the set loading true in the right place, but I don't know where to put set loading false.
When the Ember docs say actions: { loading: function(transition, originRoute) { // displayLoadingSpinner(); ..., if that example in the docs was fully developed, what would it look like?
You should use the built in loading route. You may think it needs to exist as a child route of the resource that is taking time to load, but that's incorrect, it needs to be a child route of the parent's resource (which will have already resolved). Or in your case the loading route needs to exist as ArticleLoadingRoute. It will be shown any time any resource/route immediately under it is taking time to load.
Here's an example where I've forced two resources to take a while to load
http://emberjs.jsbin.com/OxIDiVU/465/edit
I have a page that uses marionette layout to bind views to #content.
My landing page in my app is many views drawn together, so I planned to use another layout view instead of a composite view.
However currently with the code below I get:
TypeError: object is not a function
My controller.js calls views based on routes like this:
app.addRegions({
content: "#content"
});
define([], function() {
"use strict";
return {
landing: function() {
return require(["app/views/index"], function(View) {
return MyApp.content.show(new View()); ////LINE THROWING THE ERROR
});
}
};
});
The sub layout causing the issue
define([
"marionette",
'app/views/images/collection',
"tpl!app/templates/index.html"
],
function(Marionette, ImagesView, template) {
"use strict";
var AppLayout, layout;
AppLayout = void 0;
layout = void 0;
AppLayout = Backbone.Marionette.Layout.extend({
template: template(),
regions: {
collection1: "#images",
}
});
layout = new AppLayout();
layout.collection1.show(new ImagesView());
return layout;
})
Any help greatly appreciated
It looks like you're returning a new layout object from your index, and then trying to use it as a function in your controller.
In your index:
layout = new AppLayout();
...
return layout;
and then in your controller:
return MyApp.content.show(new View());
You probably just need to do
return MyApp.content.show(View);
UPDATE
After working on this some more, we found another issue. The ordering of rendering the views was wrong. The statement layout.collection1.show(new ImagesView()); won't actually render anything because layout itself hasn't been rendered yet. The solution to this was to move that statement into the onRender method of the AppLayout. This way, when MyApp.content.show(View) is called, it will automatically render the ImagesView at the correct time.