Scroll event doesn't trigger - aurelia

I have a problem with scroll binding. I tried to use scroll binding and event listeners without luck. Using jquery doesn't work either. Any other event I tried works as expected, but with scroll it does literally nothing. No error, no message .. What am I missing?
Edit
Ok I made a mistake and didn't put it in the app.js and app.html as mentioned in examples but one layer under so there was no scroll event to catch on the element. If I put scroll.trigger there it works.
New question:
Can scroll be triggered in children views?
Eg.: lets say you have 2 view-models on 2 routes and you want one to trigger scroll event and the other not to. Can someone point me to right direction?

Can check this ? website for detail

I found mistake causing my original problem - it is described in question.
To answer my second question I used this solution:
app.html
<div class="page-host" scroll.trigger="handleScrollEvent($event) & debounce:200">
<router-view></router-view>
</div>
app.js
export class App {
configureRouter(config, router) {
config.title = 'Title';
config.map([
{ route: ['', 'first'], name: 'first', moduleId: './first', nav: true, title: 'First route' },
{ route: ['second'], name: 'second', moduleId: './second', nav: true, title: 'Second route' }
]);
this.router = router;
}
handleScrollEvent(e) {
// if not on specified route
if(this.router.currentInstruction.config.name !== 'second') {
return;
}
// if scrolled under defined pixels
if(e.target.scrollTop > 100) {
$('body').find('#toolbar').addClass('fixed');
} else {
$('body').find('#toolbar').removeClass('fixed');
}
}
}
I don't know how clean it is but it is working. Feel free to sugest other solution.

Related

Only change one viewport in Aurelia router

Good morning, I have my route setup as shown below within Aurelia CLI.
config.map([
{
route: [''],
viewPorts: {
'side': { moduleId: 'side' },
'main': { moduleId: 'main' }
},
title: 'Test',
nav: false,
name: 'Temp'
]);
What I would like to do is based on what I select on my side view, I just want to change the moduleId for main and load that view.
I don't think there's a way to dynamically change the moduleId of a view-port. I see 2 options to solve your proble:
1 - Create another route, changing the moduleId of one of the viewports
2 - Use the layout mechanism and change its content in run time. I recommend you to read http://aurelia.io/hub.html#/doc/article/aurelia/router/latest/router-configuration/10.
I know this is not the answer you were expecting but I hope it helps.

How to dynamically build navigation menu from routes linking to parent/child views/controllers

This question is a follow-up question for my original question Linking directly to both parent+child views/controllers from the main navigation menu
The accepted answer is great, but later when I added a 2nd "childRoute" dynamically, I noticed a problem. In order to build my navigation dynamically I had to add the multiple routes with the same "route" attribute. (see app.js in the example code below). The only difference were the "title" and "settings" attributes.
configureRouter(config, router){
config.title = 'Aurelia';
config.map([
{ route: 'shared-parent', moduleId: './shared-parent', settings: { childRoute: 'child-a' }, nav: true, title: 'Shared Parent - child-a' },
{ route: 'shared-parent', moduleId: './shared-parent', settings: { childRoute: 'child-b' }, nav: true, title: 'Shared Parent - child-b' },
...
]);
this.router = router;
}
The settings attribute I used in the view for doing this:
<a if.bind="row.settings.childRoute" href.bind="row.href + '/' + row.settings.childRoute">${row.title}</a>
I know it's not pretty but it does navigate to the right child route. The problem is that it's always the last of the 2 routes with duplicate "route" attributes that is marked as active.
The reason why I added the settings: {childRoute: 'child-a/b' } instead of giving them distinct "route" attributes like route: 'shared-parent/child-a' and route: 'shared-parent/child-b' was that the url would actually then match shared-parent/child-a/child-a and shared-parent/child-b/child-b since we're first linking to the shared-parent.
This live runnable gist should clearly display the problem (child-a route never activating): https://gist.run/?id=95469a9cb3a762d79da31e0b64248036
Ps.
If you have a better idea of what to call the title of this question please feel free to edit it.
So I took a stab at your problem using the EventAggregator in the Activate lifecycle hook in the child view models.
https://gist.run/?id=bfb5df5e39ac0bb73e9e1cae2d2496e2
in the child view models, I just published an event stating the child route was updated:
activate(params, routeConfig, navigationInstruction) {
let payload = navigationInstruction.parentInstruction.config.title;
payload = payload.substring(0, payload.length - 7);
this.aggregator.publish("child route updated", payload + "child-a");
}
In the app.js file, I updated the route titles, and added an activeChild property. Next, update the activeChild property when the event is captured:
constructor(aggregator) {
this.aggregator = aggregator;
this.aggregator.subscribe("child route updated", payload => {
this.activeChildRoute = payload;
console.log(this.activeChildRoute);
});
}
Finally, I updated the class expression on you list item to update based on that active child Flag:
<li repeat.for="row of router.navigation"
class="${row.title === activeChildRoute ? 'active' : ''}">

Aurelia: How navigate between child routes

I'm trying to navigate from one child route to another, but I continually get Route not found. My primary question: how to navigate between child views?
Below is the code, and I'll have additional questions below, too.
App Mode-View
App Class:
export class App {
configureRouter(config, router) {
config.title = 'My Site';
config.map([
{ route: ['','job-view'], name: 'jobView', moduleId: './job-view', nav: true, title:'Jobs'},
{ route: ['services'], name: 'services', moduleId: './services', nav: true, title:'Services'}
]);
this.router = router;
this.router.refreshNavigation();
}
}
Q.2: Why do we need to save router here if it's always accessible from aurelia-router?
App Page:
<template>
<require from='./nav-bar'></require>
<nav-bar router.bind="router"></nav-bar>
<div class="container">
<router-view></router-view>
</div>
</template>
Ok, so now that we have our root page view and nav defined, let's define the job-view MV.
JobView Class:
export class JobView {
configureRouter(config, router) {
config.map([
{ route: ['','jobs'], name: 'jobs', moduleId: './jobs', nav: false, title:'Jobs', settings:{icon:'glyphicon glyphicon-align-justify'} },
{ route: ['job/:id'], name: 'job', moduleId: './job', nav: false, title:'Job Details'}
]);
this.router = router; //WHY SAVE THIS?
this.router.refreshNavigation();
}
}
JobView Page:
<template>
<router-view></router-view>
</template>
Now, here are the child views. My assumption that is that routing that occurs should be relative to job-view. That's what I want, ideally.
Jobs Class (a bunch of code removed for brevity):
import {Router} from 'aurelia-router';
#inject(Router)
export class Jobs {
constructor(router) {
this.router = router;
}
toJob(id) {
// this.router.navigateToRoute("job", {id:id}); // ERROR - ROUTE NOT FOUND
this.router.navigate("#/job-view/job/".concat(id)); // THIS WORKS
}
}
Q.3: I've seen both router.navigateToRoute and router.navigate referenced, but no indication when to use either or what the difference is, and the document doesn't seen to explain. Which should be used? Docs
Jobs Page:
Details for jobs.html are irrelevant, so not listing them here.
Finally, the job view:
Job Class:
Nothing relevant for job.js, so not listing code. At most I may perform navigation back to jobs, but that's handled below in the page.
Job Page:
<!-- a bunch of html //-->
<!-- HOW TO USE ROUTER IN HTML, NOT BELOW URL HREF? //-->
Print Jobs
<!-- a bunch of html //-->
Q.4: Again, I'd like routing to relative, even in the HTML page. The above won't work, so what should I use?
There was a proposed answer in a similar question, but injecting job-view into job and using job-view's saved router didn't work either.
By the way, if I manually navigate to http://localhost:3000/#/job-view/job/3 the page loads fine, so it's clear something with the router.
Note to mod:
A similar question was ask at How to access child router in Aurelia? but it wasn't answered with a solution that works.
I will try to answer you questions one by one below.
I will start from Q2
Q.2: Why do we need to save router here if it's always accessible from
aurelia-router?
So in your App Mode-View App Class you are referencing router property in your view: <nav-bar router.bind="router"></nav-bar>, that's why you need to declare the property to use it then. In the second view you are not so you don't need it :-)
The property router is also added when you need do something with the router in main.ts / main.js - the starting point of you application. This is because the router is configured for the first time there, and injection will not work in constructor, so you need to save this property to get it in configureRouter(..) method (note: this was a case before beta 1, I don't know if it's still there now).
In your code you have a call for this.router.refreshNavigation(); this will ensure that your router is updated with new information regarding current location of the routing.
Q.3: I've seen both router.navigateToRoute and router.navigate referenced, but no indication when to use either or what the difference is, and the document doesn't seen to explain. Which should be used? Docs
The method router.navigate(fragment: string, options?: any) uses an URL fragment not a route name to navigate, so e.g. router.navigate('#/app/child', {...<options - not params od the URL>...}). This method must be used to navigate absolutely between routers, and to access parent URL etc.
If you only are navigating around the current router you will always use router.navigateToRoute(route: string, params?: any, options?: any). This method is using a route name, not URL, co we just put there a name of route in the custom routing map (custom means the current child routing map, or current main routing regarding the URL location we are on the page). Here you can pass URL params in more convenient way, as you can see. You can use a params object instead of concatenating the URL with params.
Q.4: Again, I'd like routing to relative, even in the HTML page. The above won't work, so what should I use?
In Aurelia we are not using href attribute of the a tag directly for navigation. As already answered by Brandon, you have to use route-href attribute, which is probably nowhere documented just appears around on forums and portals. This is equivalent of the router.navigateToRoute(route: string, params?: any, options?: any), so you cannot use it to navigate between routers in such case you can use custom attribute or just use click.triger="navTo('#/app/child')", where the navTo() method is implemented in your View-Model and looks like this:
public navTo(routeName: string) {
// Assuming you are injecting router somewhere in the constructor
this.router.navigateToRoute(routeName);
}
And finally your topic question:
Q.1: How navigate between child routes
Probably now you know the answer, just use: router.navigate(fragment: string, options?: any) with absolute URL.
Below example custom attribute to solve this:
import {inject} from "aurelia-dependency-injection";
import {Router} from "aurelia-router";
import {customAttribute} from "aurelia-framework";
#customAttribute('nav-href')
#inject(Element, Router)
export class NavHref {
private value: string = '';
constructor(private element: Element, private router: Router) {
let $this = this;
element.addEventListener('click', () => {
if ($this.value === 'back') {
$this.router.navigateBack();
} else {
// expression
$this.router.navigate($this.value);
}
});
}
public valueChanged(newValue: string, oldValue: string) {
this.value = newValue;
}
}
First you need to import it in your HTML, I named my file nav.href.ts:
<require from="common/nav.href"></require>
Then just use it in you HTML code:
<a nav-href="#/home/any-location">My link to any location</a>
Hope this will help you, cheers :-)
The way I've configured child routers in my Aurelia apps is in this fashion:
app.js
export class App {
configureRouter(config, router) {
config.map([
{ route: ['','home'], name: 'home', moduleId: 'home', nav: true },
{ route: 'work', name: 'work', moduleId: 'work/work-section', nav: true },
]);
this.router = router;
}
}
work/work-section.js
export class WorkSection {
configureRouter(config, router) {
config.map([
{ route: '', moduleId: './work-list', nav: false },
{ route: ':slug', name: 'workDetail', moduleId: './work-detail', nav: false }
]);
this.router = router;
};
}
The corresponding "work-section.html" is simply a Router View:
<template>
<router-view></router-view>
</template>
In this use case, I have my main app.js which defines a child router, "work", which sits in a subdirectory under src.
When the route /work is activated, the child router, "work-section" takes over, activating the "work-list" route, as the path segments end there: /work
"work-list.js" retrieves items from a REST API then passes the data to the view.
From there, I'm able to use route binding to get to a "work detail" in the "work-list.html" view:
<div repeat.for="sample of item.samples">
<a route-href="route: workDetail; params.bind: { slug: sample.slug }">
${sample.title}
</a>
</div>
Hope that helps you out. I'm not 100% certain if you're asking how to do a redirect, or how to nav to a child route from the view, so please correct me if I'm wrong and I'll do my best to update my answer for you.

How to use durandal router to activate dialogs?

I would love to a #signin route that would open a dialog on top of whatever page there was before.
Let's consider this example app this the following routes:
router.map([
{route: '', moduleId: 'vm/home', title: "Home"},
{route: 'about', moduleId: 'vm/about', title: "About"},
{route: 'signin', moduleId: 'vm/signin', title: 'Sign In'}
]);
Here are example use cases:
User is on # and navigates to #signin: we should see a Sign In dialog on top of Home page
User is on #about and navigates to #signin: we should see a Sign In dialog on top of About page
User navigates to http://localhost:9000/#signin: we should see a Sign In dialog on top of Home page
User is on #signin and closes dialog: we should see a page that was behind the dialog (there's always a page behind).
The dialog and router are both plugins and have no interactions between eachother.
Also having the router display dialog would ignore how the router works - it has a div which it dumps content into. Dialogs exist outside of all of this.
However if you wanted to (I may do this aswell), you could try this.
Add dialog: true to the route map.
Override router.loadUrl method. Check if the route is a dialog route as we marked before, and activate the dialog instead.
I would make the dialog a child route, so then you can know which view to display beneath the dialog. Otherwise you could just have to show the dialog over anything and ignore routing entirely.
Edit: I don't think this would entirely work actually. loadUrl returns a boolean. You could open the dialog and return false to cancel navigation.
Edit2:
My Attempt
The loadUrl method loops through all routes, and each has a callback, so ideally we need to insert our logic into this array.
for (var i = 0; i < handlers.length; i++) {
var current = handlers[i];
if (current.routePattern.test(coreFragment)) {
current.callback(coreFragment, queryString);
return true;
}
}
This array is added to using the routers route method. Durandal calls this method when you map routes, so ideally we could add some extra parameters to the route config and let Durandal handle these. However the configureRoute function is internal to the routing module, so we will need to edit that and make sure we copy changes over when updating Durandal in the future.
I created a new list of dialog routes:
{ route: 'taxcode/add(/:params)', moduleId: 'admin/taxcode/add', title: 'Add Tax Code', hash: '#taxcode/add', nav: false, dialog: true, owner: '#taxcodes' },
{ route: 'taxcode/edit/:id', moduleId: 'admin/taxcode/edit', title: 'Edit Tax Code', hash: '#taxcode/edit', nav: false, dialog: true, owner: '#taxcodes' }
The idea of an owner, is that if there is a case where the initial route is this, we need something behind the dialog.
Now replaced the router.route call in configureRoute with this:
router.route(config.routePattern, function (fragment, queryString) {
if (config.dialog) {
if (!router.activeInstruction()) {
// No current instruction, so load one to sit in the background (and go back to)
var loadBackDrop = function (hash) {
var backDropConfig = ko.utils.arrayFirst(router.routes, function (r) {
return r.hash == hash;
});
if (!backDropConfig) {
return false;
}
history.navigate(backDropConfig.hash, { trigger: false, replace: true });
history.navigate(fragment, { trigger: false, replace: false });
queueInstruction({
fragment: backDropConfig.hash,
queryString: "",
config: backDropConfig,
params: [],
queryParams: {}
});
return true;
};
if (typeof config.owner == 'string') {
if (!loadBackDrop(config.owner)) {
delete config.owner;
}
}
if (typeof config.owner != 'string') {
if (!loadBackDrop("")) {
router.navigate("");
return; // failed
}
}
}
var navigatingAway = false;
var subscription = router.activeInstruction.subscribe(function (newValue) {
subscription.dispose();
navigatingAway = true;
system.acquire(config.moduleId).then(function (dialogInstance) {
dialog.close(dialogInstance);
});
})
// Have a route. Go back to it after dialog
var paramInfo = createParams(config.routePattern, fragment, queryString);
paramInfo.params.unshift(config.moduleId);
dialog.show.apply(dialog, paramInfo.params)
.always(function () {
if (!navigatingAway) {
router.navigateBack();
}
});
} else {
var paramInfo = createParams(config.routePattern, fragment, queryString);
queueInstruction({
fragment: fragment,
queryString: queryString,
config: config,
params: paramInfo.params,
queryParams: paramInfo.queryParams
});
}
});
Make sure you import dialog into the module.
Well maybe all of that is not needed when using a trick with the activation data of your home viewmodel.
Take a look at my Github repo I created as an answer.
The idea is that the route accepts an optional activation data, which the activate method of your Home VM may check and accordingly show the desired modal.
The benefit this way is that you don't need to touch the existing Durandal plugins or core code at all.
I'm though not sure if this fully complies with your request since the requirements didn't specify anything detailed.
UPDATE:
Ok I've updated the repo now to work with the additional requirement of generalization. Essentially now we leverage the Pub/Sub mechanism of Durandal inside the shell, or place it wherever else you want. In there we listen for the router nav-complete event. When this happens inspect the instruction set and search for a given keyword. If so then fire the modal. By using the navigation-complete event we ensure additionally that the main VM is properly and fully loaded.
For those hacks where you want to navigate to #signin, just reroute them manually to wherever you want.
Expanding on my suggestion in the comments, maybe something like this would work. Simply configure an event hook on router:route:activating or one of the other similar events and intercept the activation of /#signin. Then use this hook as a way to display the dialog. Note that this example is for illustrative purposes. I am unable to provide a working example while I'm at work. :/ I can complete it when I get home, but at least this gives you an idea.
router.on('router:route:activating').then(function (instance, instruction) {
// TODO: Inspect the instruction for the sign in route, then show the sign in
// dialog and cancel route navigation.
});

events flow of Panel in sencha touch 2

This is my application architecture.
var loginView = Ext.create('Ext.Panel',{
id: 'LoginView',
.....
});
var homeView = Ext.create('Ext.TabPanel',{
id: 'HomeView',
items: [
{
xtype: 'list',
title: 'Home',
store: Ext.create('TweetStore'),
disableSelection: true,
....
},
{
title: 'Reply',
....
},
{
title: 'DM',
....
}
]
});
var mainView = Ext.create('Ext.Panel',{
id: 'MainView',
layout: 'card',
items: [ loginView, mainView ]
});
Ext.define('TweetStore', {
extend: 'Ext.data.Store',
config: {
fields: ...
pageSize: 25,
autoLoad: true,
proxy: {
type: 'ajax',
url: '/home',
pageParam: 'page',
limitParam: 'count',
reader: {
type: 'json'
}
}
}
});
There are two panels in MainView. LoginView is a login page where user input his username and password. When the authorization succeed, the second panel HomeView shows.
My questions:
I want the data in TweetStore to be loaded after the authorization, that is, when the HomeView shows, the data begins to load. But the show event is triggered even the
panel is still hidden. What event should I catch.
When the application starts, I want to send a ajax request to check whether the user is login, if true, the LoginView hide and the HomeView shows. In which event should I check this?
Q. 1) You should listen for painted() event.
Fires whenever this Component actually becomes visible (painted) on
the screen. This is useful when you need to perform 'read' operations
on the DOM element, i.e: calculating natural sizes and positioning.
Q. 2) Send the ajax request for user authentication on initialize() event.
Fires when the component has been initialized
- 1st question:
painted event seems good but in fact, it's terribly full of bugs in current release of Sencha Touch 2, as my experience. Do NOT rely on its existence and documentation.
According to what you've described, it's clear that you want to load your store after a specific event (authorization), so how about firing a custom event after that? For example:
loginForm.fireEvent('authenticated', this) (or any extra-params you need)
then in your controller, just listen to event authenticated on loginForm view.
- 2nd question:
As you said, you want to run some processes right after your application started. So the right place to put your code is in launch() function of your app.js. Just simple.