Upon clicking a button (which is bottom of the page), I want to go to a certain element (in my case, #navbar) which is in the top of the current page, but I don't know how to do it. I've tried the following code with no avail.
<nav class="navbar navbar-light bg-faded" id="navbar">
<a class="navbar-brand" href="#">
{{appTitle}}
</a>
<!-- rest of the nav link -->
</nav>
<!-- rest of the page content -->
<!-- bottom of the page -->
<button class="btn btn-outline-secondary" (click)="gotoTop()">Top</button>
In angular component:
import { Router } from '#angular/router';
/* rest of the import statements */
export class MyComponent {
/* rest of the component code */
gotoTop(){
this.router.navigate([], { fragment: 'navbar' });
}
}
I would really appreciate if someone helped me out with a solution and explained why my code hadn't worked.
Please note that element (navbar) is in other component.
You can do this with javascript:
gotoTop() {
let el = document.getElementById('navbar');
el.scrollTop = el.scrollHeight;
}
This will bring the DOM element with id="navbar" into view when the method is called. There's also the option of using Element.scrollIntoView. This can provide a smooth animation and looks nice, but isn't supported on older browsers.
If the element is in a different component you can reference it several different ways as seen in this question.
The easiest method for your case would likely be:
import { ElementRef } from '#angular/core'; // at the top of component.ts
constructor(myElement: ElementRef) { ... } // in your export class MyComponent block
and finally
gotoTop() {
let el = this.myElement.nativeElement.querySelector('nav');
el.scrollIntoView();
}
Working plunker.
I know, you want to scroll to a specific element in the page. But, if the element is in the top of the page, then you can use the following:
window.scrollTo(0, 0);
document.getElementById("YOUR_DIV_ID").scrollIntoView({ behavior: 'smooth' });
I think your way didn't work because of the empty router.
this.router.navigate(['/home'], { fragment: 'top' });
would work if 'home' is declared as a route and you have the id="top" element on it.
I know you would like it to be "pure Angular way", but this should work (at least):
gotoTop(){
location.hash = "#navbar";
}
Related
Say, we have a Stimulus controller that toggles a sidebar. The button that triggers the toggle action is located in different places, though, depending on the device. E.g. in the header when you are on a mobile device and in the main navigation when you are on a Desktop device.
What do you do in this situation? Is it better to initialise 2 Stimulus controllers (one in the div that belongs to the header and one that belongs to the main navigation) or to initialise just one Stimulus controller, for example in a wrapper div tag that encloses both the header as well as the main navigation?
I would put a sidebar controller on the body tag.
As long as the sidebar controller is not reused it's totally fine. It wouldn't work for a generic toggler controller.
<body data-controller="sidebar">
<header class="sm:hidden">
<button data-action="sidebar#toggle">Toggle sidebar</button>
</header>
<section data-sidebar-target="sidebar">
Some content in the sidebar
</section>
<main>
<button class="hidden sm:inline-block" data-action="sidebar#toggle">Toggle sidebar</button>
</main>
</body>
I would recommend avoiding controllers with too much scope where practical. Having a large section of the DOM with multiple controllers on it could risk some performance issues.
An alternative approach to having a sidebar & sidebar-toggle controller is to have a generic 'fire an event' controller.
We can even take inspiration from Alpine.js and its x-on directive. We could have a smaller scoped 'on' action do something type controller that let's us have a generic way to trigger any event.
HTML
Let's start with the HTML, similar to the previous answer, we have a sidebar div and then two buttons that do basically the same thing (trigger an event).
We can leverage Stimulus' approach to coordinating with DOM events to help us here but this time with a custom global event with the name 'sidebar:toggle'.
Note that the sidebar controller listens to the event with #window to ensure that it picks up any 'bubbling' events all the way up the DOM.
https://stimulus.hotwired.dev/reference/controllers#cross-controller-coordination-with-events
<body>
<header class="sm:hidden">
<button
data-controller="on"
data-action="click->on#go"
data-on-event-name-param="sidebar:toggle"
>
Toggle sidebar
</button>
</header>
<section data-controller="sidebar" data-action="sidebar-toggle#window->sidebar#layout">
Some content in the sidebar
</section>
<main>
<button
class="hidden sm:inline-block"
data-controller="on"
data-action="click->on#go"
data-on-event-name-param="sidebar:toggle"
>
Toggle sidebar
</button>
</main>
</body>
JavaScript (Controllers)
The Sidebar controller will have whatever you want but also a method for toggle that we can trigger with the action.
import { Controller } from '#hotwired/stimulus';
class Sidebar extends Controller {
static targets = [];
static values = { expanded: { default: false, type: Boolean } };
connect() {
// do the things
}
toggle() {
this.expandedValue = !this.expandedValue;
}
}
The On controller leverages Stimulus action parameters to be able to receive any dynamic value set on the DOM as it's event.
Note that the On controller intentionally makes the events bubble (this is the default, but good to be explicit) so that any other event listeners on window can listen to this.
import { Controller } from '#hotwired/stimulus';
class On extends Controller {
go({ params: { eventName } }) {
this.dispatch(eventName, { prefix: '', bubbles: true, cancelable: false });
}
}
My SPA has an Admin Dashboard with a Sidebar Navigation Menu that is visible by default when the viewport is larger than 991px. When the viewport is less than 991px, the Navbar disappears and you have a hamburger icon that you can click to Toggle(show/hide) the Sidebar Menu. Right now the menu will only hide if you click on the little hamburger menu icon a second time. What I want is to close/hide the menu if a User clicks anywhere outside of it. I'm using Bootstrap-Vue for my UI.
So I have a <b-navbar toggleable="md" type="dark" variant="info"></b-navbar>.
What I'd like to do is something like this, but I know I'm mixing Javascript and Vue js and not sure exactly how to set this up.
data() {
return {
sidebarMenu: true
}
}
<section class="app-sidebar" v-if="sidebarMenu">
<div class="main-panel" #click="hideSidebarNavMenu">
methods: {
hideSidebarNavMenu() {
this.sidebarMenu = false;
},
const navbarTogglerButton = document.querySelector(".navbar-toggler");
navbarTogglerButton.addEventListener('click', function(event) {
this.sidebarMenu = true;
})
}
This main-panel div will contain all of the data for every page, so no matter where they click this hideSidebarNavMenu function should get fired.
I need this code to work only when the viewport is less than 991px.
Again, I'm getting some syntax errors in my methods, I believe because I'm trying to write Javascript in my methods attribute.
This is the idea.
Thank you.
The problem is that methods must contains… methods and not code like const navbarTogglerButton … This is probably the error you have
attach your event to .navbar-toggler
<div class="navbar-toggler" #click="showSidebarNavMenu">
methods: {
hideSidebarNavMenu() {
this.sidebarMenu = false;
},
showSidebarNavMenu() {
this.sidebarMenu = true;
},
}
I just realized now that in the Free Admin theme that I'm using, when a user clicks on the Hamburger button to toggle the Sidebar, all it does is just add and remove a class called active on the nav element.
Element:
<nav id="sidebar" class="sidebar sidebar-offcanvas active">
And so this is how I solved it:
mounted() {
var mainPanelDiv = document.querySelector(".main-panel");
var navbar = document.querySelector("#sidebar");
mainPanelDiv.addEventListener('click', function(event) {
navbar.classList.remove('active');
});
}
I'm trying to use hook activate, didTransition, or willTransition, but none of these work, they do nothing at all.
I try to start by one route:
// app/routes/section.js
import Route from '#ember/routing/route';
export default class SectionRoute extends Route {
...
activate() { scroll(0, 0); }
}
<!-- app/templates/section.hbs -->
<h1>{{model.title}}</h1>
<p>{{model.body}}</p>
{{outlet}}
{{#each model.subsections as |s| }}
<Section #section={{s}} />
{{/each}}
This works in my js browser console:
scroll(0, 0);
This is my router
// app/router.js
...
Router.map(function() {
this.route('docs');
this.route('section', { path: '/docs/section/:slug' });
});
If I make section a child of docs, it works, as long as I don't hide parent's content, but I want to hide it.
An anchor in link-to component could help.
When I remove this piece of css, it works.
html {
scroll-behavior: smooth;
}
Your approach is working as expected. I have created an Ember Twiddle to verify that one, which you can find here. It has two routes. One which scrolls to the top when activated and one which scrolls to bottom when activated. It's scrolling as expected.
I guess you may be facing issues with the hook if transitioning between subroutes? A route is considered to stay active and the activate hook is not called
if a transition does not change the route but only the dynamic segments or query parameters used for the route or
if a transition happens between subroutes of that route.
If you want to have scroll to top on every transition or want to have more granular control, which transitions should trigger that scrolling I would recommend to use routeDidChange event of RouterService instead.
i'm newbie to emberJS and i want to enable a button when it comes to the scroll end in a modal. so i tried several ways but it didn't work
View
<div class="modal--dialog--body">
<div class="app_tour--section--terms_container" id="message-container">
<ul class="app_tour--section--ul">
<li></li>
//so many lists to scroll
<li></li>
</ul>
</div>
Controller
import Ember from 'ember';
export default Ember.Controller.extend({
didTransition() {
Ember.run.later('afterRender', () => {
let objDiv = document.getElementById("message-container");
if(objDiv.scrollTop == objDiv.scrollHeight)
console.log(objDiv.scrollTop)
}, 100);
return true;
},
actions: {
close: function() {
this.send('closeModal');
}
}
});
Welcome to getting started with Ember! Like many things, there's a pretty useful addon that will make your life simpler for this: https://github.com/alphasights/ember-scrollable
That provides a scrollable-container and exposes an action when you've reached scroll bottom.
{#ember-scrollable onScrolledToBottom=(action "close")}}
add terms and consitions here
{{/ember-scrollable}}
I used basic jQuery to get the DOM’s element I was targeting and listen to the scroll event with the help of Rechardo and it worked.
you can check it via her post on medium
https://medium.com/#futoricky/ember-js-detect-scrolling-events-on-html-element-using-component-ac6b2630021a
currently i am working in ionic framework on javascript and angular-js i just put the search box and render list of customer but suppose in first attempt i can search with 'a' it shows all item which having alpha 'a' but the problem is when i scroll down to see the search result list and at the bottom if i want to search with 'd' this time it gives result but at the top of the page but my scroll is at the bottom of the page.
So to solve the above problem i want set scroll position at the top of the page when search query is empty and display all customer so what should i do to solve this problem
thanks in adv..
You can try :
$ionicScrollDelegate.scrollTop();
http://ionicframework.com/docs/api/service/$ionicScrollDelegate/
For Ionic 2 and higher use the scrollToTop() method on the content class.
page.html
<ion-content>
Add your content here!
</ion-content>
page.ts
import { Component, ViewChild } from '#angular/core';
import { Content } from 'ionic-angular';
#Component({...})
export class MyPage{
#ViewChild(Content) content: Content;
scrollToTop() {
this.content.scrollToTop();
}
}
See http://ionicframework.com/docs/api/components/content/Content/#scrollToTop
I normally do this on my home page.
import { Component, ViewChild } from '#angular/core';
import { Content } from 'ionic-angular';
#Component({...})
export class MyPage{
#ViewChild(Content) content: Content;
scrollToTop() {
this.content.scrollToTop(400);
}
ionViewDidEnter(){
this.scrollToTop();
}
}
Or you can call scrollTop() whenever you want.
Note: 400 is duration in milliseconds, it is optional and you can use this as well
this.content.scrollToTop();
in this case scrollToTop() will set the default value of 300.
For Ionic 4 this has been renamed from Content to IonContent, along with a new package name:
Breaking: Component Imports Changes
Making the snippet look like this:
import { Component, ViewChild } from '#angular/core';
import { IonContent } from '#ionic/angular';
#Component({...})
export class MyPage{
#ViewChild(IonContent) content: IonContent;
scrollToTop() {
this.content.scrollToTop(400);
}
ionViewDidEnter(){
this.scrollToTop();
}
}
Thanks #Junaid (https://stackoverflow.com/a/48987846/156388) for the base of this answer.
I didn't edit that answer as it's still valid for Ionic 2-3.
The answer above will scroll all your views to top. If you want to have more control then you will have to use a delegate handler .
First whatever you want to scroll you will have to add the delegate handler name
<ion-content delegate-handle="dashboard">
......
</ion-content>
In your controller you will have to use this handler
$timeout(function(){$ionicScrollDelegate.$getByHandle('dashboard').scrollTop(false);});
I would recommend using a $timeout because if there is current digest cycle it will not work. Also change false to true if you want to animate the scroll. Lastly dont forget to inject $ionicScrollDelegate and $timeout in your controller
For Ionic 4.X Latest Stable Release Try this
Source Link
In Page add Angular variable id on ion-content tag
<ion-content
[scrollEvents]="true"
(ionScrollStart)="logScrollStart()"
(ionScroll)="logScrolling($event)"
(ionScrollEnd)="logScrollEnd()"
>
<ion-button (click)="ScrollToTop()">
Scroll To Top
</ion-button>
</ion-content>
In component add methods
logScrollStart(){
console.log("logScrollStart : When Scroll Starts");
}
logScrolling(){
console.log("logScrolling : When Scrolling");
}
logScrollEnd(){
console.log("logScrollEnd : When Scroll Ends");
}
ScrollToBottom(){
this.content.scrollToBottom(1500);
}
ScrollToTop(){
this.content.scrollToTop(1500);
}
ScrollToPoint(X,Y){
this.content.scrollToPoint(X,Y,1500);
}
For Ionic 4, the approaches here helped a lot. However, a second param was needed on
#ViewChild(IonContent) content: IonContent;
So, I set the static false, as suggested here. But it only worked for me when I changed:
#ViewChild(IonContent, {static: false}) content: IonContent;
to
#ViewChild(IonContent, undefined) content: IonContent;
In Ionic 6+ you need to declare the element's ID to refer to, you could use any when declaring the type, but to be specific you can set IonContent as per previous versions. This is obviously for the Angular flavour of Ionic.
So after you add the ID, for example I have called my content "content":
Now you can refer to this element in the TypeScript via ViewChild as per previous versions.
Now you can utilise the methods outlined in the docs. For example: