Durandal 2 upgrade redirect issue - single-page-application

Hello and thanks for taking a look at my issue.
I have been migrating my SPA application to use the Durandal 2.0 library, following the sage advice from my oft savior, John Papa. And now that I have completed the upgrade process, I find a strange behavior (or a lack of behavior) when I try to navigate using my menu buttons. Specifically what isn't happening is the browser doesn't redirect to the new page. The interesting thing is that the browser address bar is populated properly and if I simply click in the address bar and press enter (hard reload), I am redirected as expected.
I've looked around and this is not caused due to any security check/redirect which I have seen other discussing elsewhere. Durandal code is unmodified.
js on pages can be quite trivial:
define([], function () {
console.log("welcome loaded");
var vm = {
title: 'Welcome'
};
return vm;
});
So my guess is its something in my configuration of durandal.
main.js:
require.config({
paths: {
'text': '../Scripts/text',
'durandal': '../Scripts/durandal',
'plugins': '../Scripts/durandal/plugins',
'transitions': '../Scripts/durandal/transitions',
'knockout': '../Scripts/knockout-2.3.0',
'bootstrap': '../Scripts/bootstrap',
'jquery': '../Scripts/jquery-1.9.1'
},
shim: {
'bootstrap': {
deps: ['jquery'],
exports: 'jQuery'
}
}
});
define('jquery', function () { return jQuery; });
define('knockout', ko);
define(['durandal/system', 'durandal/app', 'durandal/viewLocator'],
function (system, app, viewLocator) {
// Enable debug message to show in the console
system.debug(true);
app.configurePlugins({
router: true,
dialog: true,
widget: true
});
app.start().then(function () {
toastr.options.positionClass = 'toast-bottom-right';
toastr.options.backgroundpositionClass = 'toast-bottom-right';
// When finding a viewmodel module, replace the viewmodel string
// with view to find it partner view.
viewLocator.useConvention();
// Adapt to touch devices
// app.adaptToDevice();
//Show the app by setting the root view model for our application.
app.setRoot('viewmodels/shell', 'entrance');
});
});
shell.js:
define(['../../Scripts/durandal/plugins/router', 'viewmodels/config', 'services/datacontext'], function (router, config, datacontext) {
function addSession(item) {
router.navigate(item.hash);
}
function boot() {
// $(".page-splash-message").text("Configuring routes...");
router.makeRelative({ moduleId: 'viewmodels' });
router.map(config.routes);
router.buildNavigationModel();
$(".page-splash-message").text("Let's make traxx..!");
return router.activate();
}
function failedInitialization(error) {
var msg = 'App initialization failed: ' + error.message;
}
return {
addSession: addSession,
adminRoutes: adminRoutes,
profileRoutes: profileRoutes,
visitorRoutes: visitorRoutes,
router: router,
activate: function () {
datacontext.primeEditData().then(boot).fail(failedInitialization);
}
};
});
routes in config.js
define(['../../Scripts/durandal/plugins/router'], function (router) {
toastr.options.timeOut = 4000;
toastr.options.positionClass = 'toast-bottom-right';
var startModule = 'Welcome';
var serviceName = 'api/Zepher';
var imageSettings = {
imageBasePath: '../content/images/photos/',
unknownPersonImageSource: 'unknown_person.jpg'
};
var routes = [
{ route: '', moduleId: 'home/welcome', title: 'Welcome', nav: false, },
{ route: 'Welcome', moduleId: 'home/welcome', title: 'Welcome', nav: false, },
{ route: 'NotFound', moduleId: 'home/notFound', title: 'Not Found', nav: false, },
{ route: 'Roadmap', moduleId: 'home/roadmap', title: 'Roadmap', nav: false, },
{ route: 'Register', moduleId: 'account/register', title: 'Register', nav: true, caption: '<i class="fa fa-user"></i> Register' },
{ route: 'RegisterAccounts', moduleId: 'account/registerAccounts', title: 'Register Accounts', nav: false, caption: '<i class="fa fa-key"></i> Register Accounts', },
];
return {
debugEnabled: ko.observable(true),
imageSettings: imageSettings,
servicetitle: serviceName,
startModule: startModule,
router: router,
routes: routes,
activate: function () {
console.log("config activate called");
router.makeRelative({moduleId: 'viewmodels'});
router.map(routes);
router.buildNavigationModel();
//sets up conventional mapping for
//unrecognized routes
router.mapUnknownRoutes('home/nontFound', 'not-found');
//activates the router
return router.activate();
// no longer needs a start module
}
};
});

found what I was missing in my upgrade, so I thought I'd share what I've learned.
Seems I forgot to update my Shell.html file.
From this:
<div>
<header>
<!--ko compose: {view: 'shared/nav', afterCompose: router.afterLogging, transition: 'entrance' } --><!--/ko-->
</header>
<section id="content" class="main">
<!--ko compose: {model: router.activeItem, afterCompose: router.afterCompose, transition: 'entrance', cacheViews: true } --><!--/ko-->
</section>
<footer>
<!--ko compose: {view: 'shared/footer'} --><!--/ko-->
</footer>
</div>
to This:
<div>
<header>
<!--ko compose: {view: 'shared/nav', afterCompose: router.afterLogging, transition: 'entrance' } --><!--/ko-->
</header>
<section id="content" class="main container-fluid page-host" data-bind="router: { transition: 'entrance', cacheViews: true }">
</section>
<footer>
<!--ko compose: {view: 'shared/footer'} --><!--/ko-->
</footer>
</div>

Related

How to generate URL for SPA Page

I've been doing research on this issue but I cannot find my answer. I am trying to create a group chat with Vue2 and F7, so I use GUID to pass to the chat page:
this.value = 'http://localhost:8088/chat-group/' + this.groupId
However, the link doesn't work if I open it in a new tab. This is the error I am getting:
Failed to load resource: the server responded with a status of 404 (Not Found)
Below is the source file:
<template>
<f7-page :name="name">
<f7-navbar :title="title" :back-link="back" sliding>
<f7-nav-right>
<f7-link href="#">Share</f7-link>
</f7-nav-right>
</f7-navbar>
<div class="content-block-title">Create New Chat Group </div>
<div class="list-block media-list">
<ul>
<li>
<div class="item-inner">
<div class="item-subtitle">Please enter your topic</div>
<div class="item-text">{{groupId}}</div>
</div>
</li>
</ul>
</div>
<div class="item-media"><a :href="chatGroupId"><qrcode-vue :value="value" :size="size" level="H"></qrcode-vue></a></div>
</f7-page>
</template>
<script>
import QrcodeVue from 'qrcode.vue'
export default {
data () {
return {
title: 'Chat Group',
name: 'Chat Group',
back: 'Back',
groupId: 'new',
chatGroupId: '',
responseHTML: '',
joined: false,
username: '',
members: ['abc', 'cba'],
newMessage: '',
messages: [{'username': 'abc', 'message': 'hello'}, {'username': 'cba', 'message': 'world'}],
status: '',
value: '',
size: 250
}
},
mounted: function () {
this.groupId = this.guid()
this.value = 'http://localhost:8088/chat-group/' + this.groupId
this.chatGroupId = '/chat-group/' + this.groupId + '/'
console.log(this.value)
},
methods: {
guid () {
function s4 () {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1)
}
return s4() + s4() + s4() + s4() + s4() + s4() + s4()
}
},
components: {
QrcodeVue
}
}
</script>
I am also using App-Framework, and this is the routes.json file:
{
"path": "/chat-chinese/",
"component": "ChatChinese.vue"
},
{
"path": "/chat-english/",
"component": "ChatEnglish.vue"
},
{
"path": "/chat-group/:groupId/",
"component": "ChatGroup.vue"
},
{
"path": "/chat-groups/",
"component": "ChatGroups.vue"
}
You need to use a web server that supports rewriting URLs to a default (e.g. index.html for most Single-Page Apps). Since you're using Firebase already, you could configure and use firebase serve from the Firebase CLI. Just say Y to the question about rewriting all URLs to index.html when running firebase init.
There are plenty of other options available as well, but the key point is that this is a server-side issue, not something that can be solved with client-side code.

polymerfire-auth error "auth/popup-closed-by-user" 404 on popup redirect

I created a Firebase project based on Polymer Starter Kit and made some modifications to fix routing, but the sign-in popup immediately closes and logs an error:
The popup has been closed by the user before finalizing the operation.
The _authenticate function (in the code below) is called to authenticate the user. I was able to properly set it up both on Facebook and Google console, so I'm not sure what the error means.
Here's my code:
<dom-module id="my-app">
<template>
!-- Firebase Setup -->
<firebase-app auth-domain="foodhop-manage.firebaseapp.com"
database-url="https://foodhop-manage.firebaseio.com"
api-key="AIzaSyA-1TEbd1EhlkPwjGBLNS74h3c5FNCHNo0"></firebase-app>
<!-- Firebase Authentication -->
<firebase-auth id="auth"
user="{{user}}"
signed-in="{{signedIn}}"
on-error="handleError"></firebase-auth>
<!-- App Routing -->
<app-location route="{{route}}"></app-location>
<app-route
route="{{route}}"
pattern="/:page"
data="{{routeData}}"
tail="{{subroute}}"></app-route>
<!-- Scroll Position Control -->
<app-scrollpos-control id="scrollpos" selected="[[page]]" reset></app-scrollpos-control>
<!-- Application -->
<app-header-layout>
<app-header condenses reveals shadow>
<app-toolbar>
<div main-title style="text-align:center;font-size:40px;padding-top:20px" class="font-beautify">FoodHop</div>
</app-toolbar>
<app-toolbar sticky>
<div class="flex"></div>
<paper-tabs selected="[[page]]" attr-for-selected="name" class="self-end">
<paper-tab link name="about">
About
</paper-tab>
<paper-tab link name="register" hidden$="[[signedIn]]">
Register
</paper-tab>
<paper-tab link name="login">
Login
</paper-tab>
</paper-tabs>
<div class="flex"></div>
</app-toolbar>
</app-header>
<div>
<iron-pages selected="[[page]]"
attr-for-selected="name"
fallback-selection="404"
role="main">
<my-about name="about"></my-about>
<my-register name="register"></my-register>
<my-login name="login"
signed-in="[[signedIn]]"
user="[[user]]"></my-login>
</iron-pages>
</div>
<!-- Go to Console -->
<!-- <paper-fab icon="input"></paper-fab> -->
</app-header-layout>
<paper-toast id="toast"></paper-toast>
</template>
<script>
Polymer({
is: 'my-app',
properties: {
page: {
type: String,
reflectToAttribute: true,
observer: '_pageChanged'
},
user: {
type: Object,
observer: '_userChanged'
},
signedIn: {
type: Boolean,
observer: '_signedInChanged'
}
},
observers: [
'_routePageChanged(routeData.page)'
],
listeners: {
'register': '_register',
'authenticate': '_authenticate',
'logout': '_logout',
'showToast': 'showToast'
},
ready: function() {
this.$.auth.signOut();
},
toast: function(message) {
this.$.toast.text = message;
this.$.toast.show(message);
},
showToast: function(e) {
this.$.toast.show({
text: e.detail.message
});
},
_authenticate: function(e) {
var provider = e.detail.provider;
this.$.auth.signInWithPopup(provider)
.then(function(response) {
console.log('successful!', response);
}).catch(function(error){
console.log('oops!', error);
});
},
_userChanged: function(user) {
// console.log(user);
},
_signedInChanged: function(signIn) {
console.log(signIn);
if (signIn) {
this.page = 'login';
this.set('route.path', '/login');
this.toast('Sweet. Thanks for logging in!');
} else {
this.toast('Y U NO sign in?');
}
},
_logout: function() {
this.$.auth.signOut();
},
_register: function() {
this.page = 'register';
this.set('route.path', '/register');
},
_routePageChanged: function(page) {
this.page = page || 'about';
},
_pageChanged: function(page) {
// Load page import on demand. Show 404 page if fails
var resolvedPageUrl = this.resolveUrl('my-' + page + '.html');
this.importHref(resolvedPageUrl, null, this._showPage404, true);
},
_showPage404: function() {
this.page = '404';
},
handleError: function(e) {
console.log(e);
}
});
</script>
</dom-module>
Add the domain in authorized domains in firebase console. e.g. add localhost or 127.0.0.1.
While error in firefox says:
The popup has been closed by the user before finalizing the operation.
Chrome gives a more descriptive message:
This domain (127.0.0.1) is not authorized to run this operation. Add it to the OAuth redirect domains list in the Firebase console -> Auth section -> Sign in method tab.
Disabling browsersync solved this for me in my local development environment

(Meteor + React) Cannot access props from within subcomponent

I am passing data from one component to another (MyApplicants) via my router (FlowRouter):
FlowRouter.route('/applicants', {
name: 'Applicants',
action: function () {
var currentUser = Meteor.user();
ReactLayout.render(App, {
content: <MyApplicants institutionID={Meteor.user().profile.institutionID} />,
nav: <Nav />,
header: <Header />
});
}
});
As you can see I'm passing institutionID to the new component via a prop in the router. I know that the institutionID is being passed because I can see it in the render of the MyApplicants component.
Here is the MyApplicants component:
MyApplicants = React.createClass({
mixins: [ReactMeteorData],
pagination: new Meteor.Pagination(Applicants, {
perPage: 25,
filters: {institution_id: this.props.institutionID },
sort: {institution_id: 1, "last_name":1, "first_name":1}
}),
getMeteorData() {
return {
currentUser: Meteor.user(),
applicants: this.pagination.getPage(),
ready: this.pagination.ready()
}
},
RenderApplicantRow(applicant, key) {
return (
<div key={key}>
<p>[{applicant.institution_id}] {applicant.last_name}, {applicant.first_name}</p>
</div>
)
},
render : function () {
return (
<div>
<section className="content">
{this.data.applicants.map(this.RenderApplicantRow)}
{console.log(this.data.applicants)}
<DefaultBootstrapPaginator
pagination={this.pagination}
limit={6}
containerClass="text-center"/>
</section>
</div>
)
}
});
(FYI, I'm using krounin:pagination.) The problem is that I cannot access this.props.institutionID inside of the pagination component. I know the value is getting passed (I can see it if I'm just testing output in the render) but can't figure out why it doesn't pass into the pagination call. And I know the pagination works because I do not get an error if I hard code in a value.
Thanks for the help.
This is a simple scope problem I think, you need to bind it to the right context
Try something like this:
pagination: function() {
var self= this;
new Meteor.Pagination(Applicants, {
perPage: 25,
filters: {institution_id: self.props.institutionID },
sort: {institution_id: 1, "last_name":1, "first_name":1}
})
}
,

Ember-data loading async and order results according with property

I'm learning about ember/ember-data and would like to fetch some data from server and order it.
My json data is something like
{
'ninjas': [
{ id: '1', name: 'Ninja 1', age: 23},
{ id: '2', name: 'Ninja 2', age: 27},
{ id: '3', name: 'Ninja 3', age: 22}
],
'clans': [
{ id: '1566', title: 'Foot Clan', ninja_ids: ['1', '2']},
{ id: '8941', title: 'Hand Clan', ninja_ids: ['3']}
]
}
The templates are
<script type="text/x-handlebars" data-template-name="clans">
<div>Clans</div>
<ul>
{{#each controller}}
<li>
{{#link-to 'clan' this}}{{title}}{{/link-to}}
</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="clan">
<div>{{title}}</div>
{{#each fetchedNinjas}}
<ul>
<li>{{fetchedNinjas}}</li>
</ul>
{{/each}}
</script>
Here is the basic App script:
var App = Ember.Application.create();
App.AplicationStore = DS.Store.extend({
revision: 12,
adapter: DS.RESTAdapter.create({})
});
App.Router.map(function() {
this.resource('ninjas');
this.resource('clans');
this.resource('clan', {path: 'clans/:clan_id'});
});
The ninja script is here:
App.Ninja = DS.Model.extend({
name: DS.attr('string'),
age: DS.attr('number'),
clans: DS.hasMany('clan')
});
App.NinjasRoute = Ember.Route.extend({
model: function (params, transition, queryParams) {
return this.store.find('ninja');
}
});
Here is the Clan Model, ClansRoute, ClanRoute
App.Clan = DS.Model.extend({
title: DS.attr('string'),
ninjas: DS.hasMany('ninja', {async: true})
});
App.ClansRoute = Ember.Route.extend({
model: function (params, transition, queryParams) {
return this.store.find('clan');
}
});
App.ClanRoute = Ember.Route.extend({
model: function (params, transition, queryParams) {
return this.store.find('clan', params.clan_id);
}
});
I think that I should get the related ninja data on ClanController and then order it, but I don't know how to proceed.
App.ClanController = Ember.ObjectController.extend({
fetchedNinjas: function () {
//should I use a promise here?
}.property('ninjas')
});

Durandal SPA on iPad goes to browser when navigating

Created ASP.Net SPA using Durandal. Added a forms login page that is hit prior to the SPA. When accessed on an iPad and saved to Home screen it opens like a native app, I can login and still stays in same window when it opens the SPA. BUT as soon as I navigate to another route it opens Safari instead.
Is there something I'm missing or any possible way I can avoid this, it would be nice if I can keep it looking like a native app.
EDIT:
As required logic from shell.js from Durandal viewmodels folder:
define(['plugins/router', 'durandal/app'], function (router, app) {
return {
router: router,
activate: function () {
router.map([
{ route: '', title:'Actions', moduleId: 'viewmodels/actions', nav: true },
{ route: 'welcome', title:'Welcome', moduleId: 'viewmodels/welcome', nav: true },
{ route: 'flickr', moduleId: 'viewmodels/flickr', nav: true },
{ route: 'clients', moduleId: 'viewmodels/clients', nav: true },
{ route: 'client/:id', moduleId: 'viewmodels/client', nav: false }
]).buildNavigationModel();
return router.activate();
},
attached: function () {
$("#logoff").show();
}
};
});
You can simply create a new Application in VS 2012+ using the downloadable Durandal SPA template from asp.net site.
Logic from shell.html:
<ul class="nav" data-bind="foreach: router.navigationModel">
<li data-bind="css: { active: isActive }">
<a data-bind="attr: { href: hash }, html: title"></a>
</li>
</ul>

Resources