Aurelia: How to bind App method in Navbar? - javascript

I've spent some time trying get the wiring for this working properly and can't. I don't know what I'm doing wrong. The best reference I've found for this issue so far is Aurelia Binding Click Trigger in Nav Bar. I tried that approach but am still getting the same error:
Uncaught Error: authenticate is not a function(…) in aurelia-binding.js:1965 (getFunction)
Here's what my setup looks like:
nav-bar.html
<template bindable="router, authenticate">
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar-collapse-main">
<span class="sr-only">Toggle Navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">
<i class="fa fa-home"></i>
<span>${router.title}</span>
</a>
</div>
<div class="collapse navbar-collapse" id="navbar-collapse-main">
<ul class="nav navbar-nav">
<li repeat.for="row of router.navigation | authFilter: authenticated" class="${row.isActive ? 'active' : ''}">
<a data-toggle="collapse" data-target="#navbar-collapse-main.in" href.bind="row.href">${row.title}</a>
</li>
</ul>
<ul if.bind="authenticated" class="nav navbar-nav navbar-right">
<li>${userName}</li>
<li>Logout</li>
</ul>
<ul if.bind="!authenticated" class="nav navbar-nav navbar-right">
<li><a id="loginLink" click.trigger="authenticate()">Login</a></li>
<li> </li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="loader" if.bind="router.isNavigating">
<i class="fa fa-spinner fa-spin fa-2x"></i>
</li>
</ul>
</div>
</nav>
</template>
app.html
<template>
<require from="./views/shared/nav-bar.html"></require>
<require from="bootstrap/css/bootstrap.css"></require>
<nav-bar router.bind="router" authenticate.call="authenticate()"></nav-bar>
<div class="page-host">
<div class="container-fluid">
<router-view></router-view>
</div>
</div>
</template>
app.ts
import {inject, computedFrom} from "aurelia-framework";
import {Router, RouterConfiguration} from 'aurelia-router'
import {AuthService, AuthenticateStep} from 'aurelia-authentication';
import {log} from "./services/log";
#inject(AuthService)
export class App {
authService: AuthService;
router: Router;
userName: string;
constructor(auth) {
this.authService = auth;
}
configureRouter(config: RouterConfiguration, router: Router) {
config.title = 'AppName';
config.addPipelineStep('authorize', AuthenticateStep);
config.map([
{ route: ['', 'welcome'], name: 'welcome', moduleId: 'views/welcome', nav: true, title: 'Welcome' },
{ route: "orgTypes", name: "orgTypes", moduleId: "views/orgTypes", nav: true, auth: true, title: "Organization Types" },
{ route: "credits", name: "credits", moduleId: "views/credits", nav: true, auth: true, title: "Application Credits" }
]);
this.router = router;
}
authenticate() {
return this.authService.authenticate('identityServer')
.then((response) => {
log.info("login successful");
});
};
#computedFrom('authService.authenticated')
get authenticated() {
return this.authService.authenticated;
}
}
What is the proper setup to get a method in the App VM to bind in a subview?
Edit 1: Following FabioLuz second comment.

What Fabio has suggested is valid and should be working. You might have other issues preventing it from functioning.
Can you check it by simplifying App.authenticate() like this?
Just to rule out possible errors of underlying layer.
authenticate() {
log.info("login successful");
}
Another guess:
Is ./services/log a static object? Assuming it is not, injection might be missing for it.
Since you are using TypeScript, autoinject might help you to avoid similar pitfalls.
import {autoinject, computedFrom} from "aurelia-framework";
import {AuthService, AuthenticateStep} from 'aurelia-authentication';
import {log} from "./services/log";
#autoinject()
export class App {
authService: AuthService;
logger: log;
constructor(auth: AuthService, logger: log) {
this.authService = auth;
this.logger = logger;
}
}
What is the proper setup to get a method in the App VM to bind in a subview?
I know 3 possible solutions to achieve that (there may be more). I've created a gist showing these in action.
https://gist.run/?id=b9e8fee11e338e08bc5da7d4df68e2db
Use the dropdown to switch between navbar implementations. :)
1. HTML Only Element + bindables
This is your current scenario. See nav-bar-function.html in the demo.
2. <compose> + inheritance
Composition can be useful for some dynamic scenarios, but try not to overuse it. [Blog post]
When no model is provided, composed element inherits parent's viewmodel context.
Normally I would not recommend using it in your case. However, considering your issues with Solution 1., you could use this option for debug purposes. If you get the same error with <compose> as well, you may have a problem with App.authenticate() function itself.
Try it out in your solution by replacing
<nav-bar router.bind="router" authenticate.call="authenticate()"></nav-bar>
with
<compose view="./nav-bar.html"></compose>
This way, nav-bar.html behaves as a part of App component. See nav-bar-compose.html in the demo.
3. Custom Element + EventAggregator
You can use pub/sub communication between components* to avoid tight-coupling. Related SO answer: [Accessing Custom Component Data/Methods], and [Docs]
*components: custom elements with viewmodel classes
I hope it will help! :)

Related

JQuery search limited to Angular component's DOM

I would like to use JQuery inside my Angular components for DOM manipulation, but I would like to limit JQuery searches to the component's markup it's used in.
I'm trying using Shadow DOM, so I have this component:
import { Component, OnInit, ViewEncapsulation } from '#angular/core';
import * as $ from 'jquery';
#Component({
selector: 'app-layout',
templateUrl: './layout.component.html',
styleUrls: ['./layout.component.less'],
encapsulation: ViewEncapsulation.Native
})
export class LayoutComponent implements OnInit {
constructor() { }
ngOnInit() {
}
toggleSidebar(): void {
$('.main-sidebar').toggleClass('active');
$('.content-wrapper').toggleClass('expanded');
}
}
Here's the HTML:
<div class="wrapper">
<header class="main-header">
Logo
<nav class="navbar">
<button type="button" id="sidebar-collapse" class="btn btn-info navbar-btn" (click)='toggleSidebar()'>
<i class="fa fa-align-left"></i>
</button>
</nav>
</header>
<aside class="main-sidebar">
<section class="sidebar">
Sidebar
</section>
</aside>
<div class="content-wrapper">
<section class="content">
Content
</section>
</div>
</div>
If I remove the encapsulation configuration, it works, but if I leave it in place, JQuery can't find the elements searched. I would like to know how can I make JQuery find those elements. Is there any other way of limiting JQuery searches apart from using Shadow DOM?
Why would you do menu toggling with jQuery?
It is not good practice to use it with Angular application. Let Angular do it.
You can do it with host binding
HTML
<nav class="navbar">
<button type="button" id="sidebar-collapse" class="btn btn-info navbar-btn"
(click)='toggleSidebar()'>
<i class="fa fa-align-left"></i>
</button>
</nav>
<aside class="main-sidebar" [ngClass]="{'menu_open': menuOpen}">
<section class="sidebar">
Sidebar
</section>
</aside>
Component
import { Component, HostBinding, OnInit } from '#angular/core';
export class LayoutComponent implements OnInit {
// set it closed by default
#HostBinding('class.menu_open') public menuOpen = false;
....
toggleSidebar() {
this.menuOpen = !this.menuOpen;
}
...
}

Binding a spinner href method in nav bar to a view-model to publish a message for subscriber

I'm trying to implement a web application with aurelia and typescript.
I started from the aurelia-skeleton-typescript-webpack project as basis.
In the nav-bar i have also inserted a spinner for choose various city location, which should call a method which in turn publishes a message so that in the app.js a subscriber should display the view of the corresponding city.
I have implemented the view nav-bar-spinner.html and the view-model nav-bar-spinner.ts, which creates the spinner in the view nav-bar.html. The nav-bar.html is then inserted in the app.html as a template.
Each spinner item has a method that calls the publishLocation('city'), wich are bindet with the view-model nav-bar-spinner.ts.
Now when i click on a spinner item i receive the error: "Error: publishNavLocation is not a function"
I thing is a binding problem. I make a custom instantiation from object nav-bar-spinner in app.ts.
How i can do this binding correct?
I would be glad for some tips.
Here the code:
app.html
<template>
<require from="nav-bar.html"></require>
<nav-bar router.bind="router"></nav-bar>
<div class="page-host">
<div class="row">
<router-view swap-order="after" class="col-md-12"></router-view>
</div>
</div>
</template>
app.ts
import {Redirect} from 'aurelia-router';
import {Router, RouterConfiguration} from 'aurelia-router';
import {EventAggregator} from 'aurelia-event-aggregator';
import {inject} from 'aurelia-framework';
import {NavBarSpinner} from './nav-bar-spinner';
#inject(EventAggregator)
export class App
{
navBarSpinner;
constructor(private ea: EventAggregator)
{
this.navBarSpinner = new NavBarSpinner('hello world')
}
router : Router;
configureRouter(config: RouterConfiguration, router: Router)
{
config.title = 'bbv AmbientMonitor';
config.map([
{ route: '', name: 'login', moduleId: './login', nav: true, title: 'Anmeldung' },
{ route: 'live-view-all', name: 'live-view-all', moduleId: './live-view-all', nav: true, title: 'Live-Ansicht' },
{ route: 'live-view-zg', name: 'live-view-zg', moduleId: './live-view-zg', nav: true, title: 'Live-Ansicht' },
.
.
.
.
.
.
{ route: 'historical-view', name: 'historical-view', moduleId: './historical-view', nav: true, title: 'Historie-Ansicht'}
]);
this.router = router;
}
attached()
{
this.ea.subscribe('nav::toggleLogin', (data) =>
{
console.log('Subscribe data is: ' + data.nav);
this.router.navigateToRoute(data.nav);
});
}
}
nav-bar.html
<template bindable="router">
<require from="./nav-bar-spinner"></require>
<!-- <require from="nav-bar-spinner.html"></require> -->
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#skeleton-navigation-navbar-collapse">
<span class="sr-only">Toggle Navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">
<i class="fa fa-home"></i>
<span>${router.title}</span>
</a>
</div>
<div class="collapse navbar-collapse" id="skeleton-navigation-navbar-collapse">
<ul class="nav navbar-nav">
<div class="pull-left">
<compose class="nav navbar-nav" view="nav-bar-spinner.html" view-model.bind="navBarSpinner"></compose>
</div>
<li repeat.for="row of router.navigation" class="${row.isActive ? 'active' : ''}">
<a data-toggle="collapse" data-target="#skeleton-navigation-navbar-collapse.in" href.bind="row.href">${row.title}</a>
</li>
</ul>
<!-- <ul class="nav navbar-nav navbar-right">
<li class="loader" if.bind="router.isNavigating">
<i class="fa fa-spinner fa-spin fa-2x"></i>
</li>
</ul> -->
</div>
</nav>
</template>
nav-bar-spinner.html
<template bindable="navBarSpinner">
<li class="dropdown">
<div as-element="compose" view-model.bind="navBarSpinner"></div>
Standort <span class="caret"></span>
<ul class="dropdown-menu">
<li>Zug</li>
<li>Zürich</li>
<li>Bern</li>
<li>Luzern</li>
<li>München</li>
</ul>
</li>
</template>
nav-bar-spinner.ts
import { EventAggregator } from 'aurelia-event-aggregator';
import { inject } from 'aurelia-framework';
import { View } from "aurelia-framework";
#inject(EventAggregator)
export class NavBarSpinner {
ea;
constructor(msg) {
this.ea = new EventAggregator();
}
publishNavLocation(navToCity) {
this.ea.publish('nav::toggleLogin', {nav: navToCity});
console.log("Method call publishLocationZug()");
}
}
Your problem lies with the following line:
view-model.bind="navBarSpinner"
Aurelia doesn't process this correctly. It is the name of the class, but you need to address it differently in an HTML attribute.
view-model.bind="nav-bar-spinner"
This tells Aurelia to look for a class named NavBarSpinner.
P.S. I also recommend you look into how Aurelia Dependency Injection works, you have quite some unnecessary (and false) code right now.
Thanks for the tips. After reading better the aurelia doc i resolved my problem.
Here my changes for typescript:
Chanded inject with autoinject
import { autoinject } from 'aurelia-framework';`
-
-
-
#autoinject( EventAggregator )
export class NavBarSpinner {
constructor(private ea: EventAggregator) {}
-
-
-
and in nav-bar.html i inserted the nav-bar-spinner.html with the binding view-model="nav-bar-spinner"
<div class="pull-left">
<compose class="nav navbar-nav" view-model="nav-bar-spinner"></compose>
</div>
and removed the other unnecessary bindings and requirements.

Why localstorage is not working firefox 57 and Vuejs2?

I want to change the language by click in the language name , i am using vuex-i18n for set the language in the front end. and i am using localStorage but the localStorage is not working in firefox , i am testing in Firefox 57 , i test in Chrome and Edge and is working there but not in firefox.(I already check in the config and dom.storage.enabled is true)
My component code is :
<template>
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" id="menu-toggle" #click ="OpenMenu(menuclicked)"><span class="glyphicon glyphicon-list" aria-hidden="true"></span></a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="" #click="setLanguage('en')" >English</a></li>
<li> Spanish</li>
<li v-for ="item in topNavbarOptions"><router-link v-on:click.native="item.clickEvent" :to="{name:item.route}"><span :class="item.icon"></span> {{ item.name}}</router-link></li>
</ul>
</div>
</div>
</nav>
</template>
<script>
import 'vue-awesome/icons';
import {HTTP} from '../common/http-common';
export default {
props:['topNavbarOptions'],
data: function () {
return {
menuclicked: false
}
},
created:function(){
this.$i18n.set(localStorage.getItem("locale"));
},
methods: {
OpenMenu: function (menuclicked) {
this.menuclicked = !menuclicked;
this.$emit('openmenu', this.menuclicked );
},
setLanguage:function(locale)
{
HTTP.get('lang/'+locale)
.then(response =>{
//Set locale from backend
localStorage.setItem("locale", locale);
})
.catch(error=>{
console.log(error);
});
}
}
}
</script>
i am usign axios in my HTTP call.There is a way to set the language without using localstorage or sessionstorage?. what i have to change in my code to make compatible with Firefox?.
Thank you.
Did you try window.localStorage? This should be context- and scope-unreliant.
try to
delete the local storage for this site in settings. URL=about:preferences#privacy
close FF
restart FF
it worked in my case - I had updated to version 57 - somehow my profile got corrupted
in Windows the FF profile is located at:
C:\Users[youraccountname]\AppData\Local\Mozilla\Firefox\Profiles\
I had to change my code to solve the problem . The problem is when the LocalStorage is inside of an axios http response (setLanguage method) It works in Chrome but in firefox LocalStorage is empty. when i move the LocalStorage outside the response it works.
<script>
import 'vue-awesome/icons';
import {HTTP} from '../common/http-common';
export default {
props:['topNavbarOptions'],
data: function () {
return {
menuclicked: false,
locale:'en'
}
},
mounted:function(){
if(localStorage.getItem("locale"))
{
this.locale = localStorage.getItem("locale");
}
this.$i18n.set(this.locale);
},
methods: {
OpenMenu: function (menuclicked) {
this.menuclicked = !menuclicked;
this.$emit('openmenu', this.menuclicked );
},
setLanguage:function(localecode)
{
//Set front end locale
localStorage.setItem("locale",localecode);
//Set backend locale
HTTP.get('lang/'+localecode)
.then(response =>{
})
.catch(error=>{
console.log(error);
});
}
}
}
</script>
Try this:
browser.storage.local.get() - to retrive data
browser.storage.local.set() - to post data

Variable is undefine - React JS

I have this global variable for config values that I require in index.js
import React from 'react';
import ReactDOM from 'react-dom';
// Config
const Dir = require('./Config/dir.jsx');
// Components
import Header from './Components/Header.jsx';
ReactDOM.render(<Header />, document.getElementById('app'));
this is the content of my dir.jsx
module.exports = {
css: 'public/css/',
js: 'public/js/',
img: 'public/img/'
}
When I accesssed the config variable in my header.jsx components using { Dir.css } it gives me an error "Dir is not defined".
Here's the header.jsx:
import React from 'react';
require('./../Stylesheets/header.scss');
class Header extends React.Component {
render() {
return (
<div>
<nav className="navbar navbar-default">
<div className="container-fluid">
<div className="navbar-header">
<button type="button" className="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span className="sr-only">Toggle navigation</span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
</button>
<a className="navbar-brand" href="#">{ Dir.img }</a>
</div>
<div className="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul className="nav navbar-nav">
<li className="active">Link <span className="sr-only">(current)</span></li>
<li>Link</li>
</ul>
<ul className="nav navbar-nav navbar-right">
<li>Link</li>
</ul>
</div>
</div>
</nav>
</div>
);
}
}
export default Header;
Here's the screenshot:
Import Dir inside the Header component as
import Dir from './Config/dir.jsx'
I presume you're using webpack to build your project
webpack won't import files unless there're used. In your case, you're just importing Dir and not using anywhere. webpack will skip this import for obvious reason.
There are 2 things you can do:
Import Dir inside the Header component
Pass the Dir as a prop to the Header component. Like this <Header dir={Dir} />. This can be accessed by this.props.dir inside the Header component.
I'll prefer the 1st way of doing.
P.S. You shouldn't use import and require together.

Meteor Uncaught TypeError: Cannot read property 'push' of undefined

I'm getting 2 errors with my meteor app. These are the errors below. I'm using accounts-ui-bootstrap-3 and I'm trying to display my header template inside layout.html.
Here are my 2 errors with some code.
Uncaught TypeError: Cannot read property 'push' of undefined
Uncaught Error: There are multiple templates named 'layout'. Each template needs a unique name.
^^Even though I only have one template named layout.
Code:
layout.html
<template name="layout">
<div class="container">
{{>header}}
<div id="main" class="row-fluid">
{{>yield}}
</div>
</div>
</template>
header.html
<template name="header">
<header class="navbar">
<div class="navbar-inner">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="{{pathFor 'postsList'}}">Test</a>
<div class="nav-collapse collapse">
<ul class="nav pull-right">
<li>{{> loginButtons}}</li>
</ul>
</div>
</div>
</header>
</template>
Why isn't it displaying the loginbuttons?
Router.js
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
waitOn: function() { return Meteor.subscribe('posts'); }
});
Router.map(function() {
this.route('postsList', {path: '/'});
this.route('postPage', {
path: '/posts/:_id',
data: function() { return Posts.findOne(this.params._id); }
});
});
Do you use mrt:accounts-ui-bootstrap-3 or ian:accounts-ui-bootstrap-3? if you use mrt:accounts-ui-bootstrap-3, it won't work because not supported anymore. Use ian:accounts-ui-bootstrap-3 instead.
You can omit the li element for the login buttons. Change your header inclusion to look like this {{> header}}, notice the little caret.
EDIT:
Instead of using a Router.map try using Router.route like this:
Router.route('/', {
name: 'postsList'
});
Router.route({'/posts/:_id',
name: 'postPage',
data: function() {
return Posts.findOne(this.params._id);
}
});

Categories