Intra-page link starts at URL root - anchor

This is so simple I don't get how it could possibly go wrong. I'm trying to get a simple intra-page link to behave.
(If it's relevant, this is an angular 2 app, using routing.)
Here is a typical page:
Skip to main content
<div class="page-body">
<main>
<div class="content-body" id="content-start">
<h1>Employee Search</h1>
</div>
<main>
</div>
The URL of this page (in my dev env) is
http://localhost:49974/app/employee/search
When I click (or focus then press enter) on Skip to main content it should go to
http://localhost:49974/app/employee/search#content-start
but instead goes to
http://localhost:49974/app#content-start
(and then immediately switches to
http://localhost:49974/app/#content-start
)
I can't have messed up the linking itself; this must have something to do with how the routing is working.
It looks like I have to do it this way:
<a href="app/request/timeoff#content-start">
But that doesn't seem correct.

I don't know if this is a the correct way, or the angular way, but it works:
app.component.html:
Skip to main content
app.component.ts
export class AppComponent {
pageURL: string;
ngDoCheck() {
this.pageURL = window.location.href;
var link = this.pageURL.indexOf('#content-start');
if (link > -1) {
console.log(this.pageURL.substring(0, link));
this.pageURL = this.pageURL.substring(0, link);
}
}
}
every-page.html:
<h1 class="content-body" id="content-start">Page</h1>
Not sure if ngDoCheck is the appropriate event use.
Full disclosure: app.component is a wrapper - with header and the 'skip to content' link that contains all the pages and their content.
So, the logic for the 'skip to content' link exists once, while the actual content target #content-start lives in the top of each content page.

Related

anchor link in Svelte app using page.js routing

I have an anchor tag on a page in my Svelte app. The link to the anchor works on the page itself, but I can't link to the anchor from another page. And when I enter the URL with the anchor, the page doesn't scroll down. I need to be able to give people a link and have them go to a specific part of the page.
Here's the relevant code:
<script>
function scrollIntoView({ target }) {
const el = document.querySelector(target.getAttribute("href"));
if (!el) return;
el.scrollIntoView({
behavior: "smooth",
});
}
</script>
<nav>
<a href="#here" on:click|preventDefault={scrollIntoView}>go to anchor</a>
</nav>
<main>
<section id="section-1">
... lots of lorem ipsum ...
</section>
<section>
<h2 id="here">anchor</h2>
And I have a REPL here: https://svelte.dev/repl/e651218bdb47455d9cafe8bff27c8d7b?version=3.24.0
I'm using page.js for my routing to components -- I haven't found anything specific about targeting anchor tags in the documentation.
Any help would be greatly appreciated.
You don't need JS for smooth scrolling, just add some CSS:
/* :global if in component */
:global(html) {
scroll-behavior: smooth;
}
REPL
As for scroll on page load, that is an issue of client-side rendering. For SSR the CSS alone takes care of everything but if the element that should be scrolled to is added after the page is loaded, the scroll will not happen.
One way of dealing with that would be to add an onMount, ideally high up in the component hierarchy so it triggers after everything is mounted, which manually performs the scroll:
onMount(() => {
const { hash } = document.location;
const scrollTo = hash && document.getElementById(hash.slice(1));
if (scrollTo)
scrollTo.scrollIntoView();
});
(Some of that logic is from SvelteKit which does all that automatically.)

angular material mat-sidenav async content area not rendering in correct position

I have a sidenav that works great with mock data, but when I load the data for the sidenav asynchronously from a rest service, the content part renders on the entire page area, and then when the async call returns, the sidenav renders, and ends up covering part of the content area. If I close the sidenav and open again, it looks find. However, on initial load, I can't see the left part of the content.
<mat-sidenav-container fullscreen class="main-sidenav-container">
<mat-sidenav #sidenav mode="side" opened>
<mat-nav-list>
<mat-list-item disa *ngFor="let navBarItem of observableNavBarItems|async" (click)="onSelected(navBarItem.value)">
<img mat-list-icon class="{{navBarItem.icon}}" />
<h4 mat-line>{{navBarItem.name}}</h4>
</mat-list-item>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content>
<router-outlet></router-outlet>
</mat-sidenav-content>
</mat-sidenav-container>
Any thoughts on how to fix this?
i fixed a similar Problem (using the MatDrawer) by setting the following MatDrawerContainer property according to the Angular Materials documentation: https://next.material.angular.io/components/sidenav/api
#Input()
autosize: boolean
Whether to automatically resize the container whenever the size of
any of its drawers changes.
Use at your own risk! Enabling this option can cause layout thrashing
by measuring the drawers on every change detection cycle. Can be
configured globally via the MAT_DRAWER_DEFAULT_AUTOSIZE token.
in my case:
<mat-drawer-container autosize="true" >
<mat-drawer #drawer mode="side" opened="true">
...
</mat-drawer>
<mat-drawer-content>
...
</mat-drawer-content>
</mat-drawer-container>
i had the same problem and i've found a solution that works fine in my case.
First of all, i needed my component hosting the mat-nav-list being invoked as soon as its view had been rendered.
In order to do this, i choose to decorate mat-nav-list with a directive (renderingDetector).
Here is the code:
import { Directive, AfterViewInit, Output, EventEmitter } from '#angular/core';
#Directive({
selector: '[renderingDetector]'
})
export class RenderingDetectorDirective implements AfterViewInit {
#Output() onViewRendered = new EventEmitter<void>(true);
ngAfterViewInit() {
this.onViewRendered.emit();
}
}
NOTE: as a possible solution, i also tried to move the whole mat-nav-list content into a separate component in order to control its life-cycle as well as it's been done inside the directive, but this approach headed to the application recharging the page every time i clicked on an item (so that all benefits of Single Page Application went lost).
On the other side, the hosting component keeps watching on it.
<mat-nav-list renderingDetector (onViewRendered)="viewRendered()">
In order to have the mat-sidenav-content being rendered exactly beside the menus we should properly set the margin-left property.
#ViewChild('sidenavRef') sideNavRef: MatSidenav;
#ViewChild('sidenavContentRef', {read: ElementRef}) sideNavContentRef: ElementRef;
viewRendered() {
this.sideNavContentRef.nativeElement.style.marginLeft = String(this.sideNavRef._width) + "px";
}
sidenavRef and sidenavContentRef refer to the mat-sidenav and mat-sidenav-content elements in the html file
<mat-sidenav-container>
<mat-sidenav #sidenavRef>
...
</mat-sidenav>
<mat-sidenav-content #sidenavContentRef>
...
</mat-sidenav-content>
</mat-sidenav-container>

grails current page highlight

In grails I have a menu. I'm trying to highlight the menu item for the active page:
link1
link2 (show as bold if the controller action partially matches this gsp name)
link3
I have working code but it seems like there would be a better way.
Taglib works fine:
def active = { attrs, body ->
for (page in attrs.check) {
if (params.action.startsWith(page)) {
out << "current"
break
}
}
}
This option works fine, but seems wordy:
<li>Contact Info</li>
<li>About You</li>
This blows up:
<g:link action='myProfile' class="${<xyz:active check='${['myControllerAction']}'/>}">My Profile</g:link>
I don't believe you can pass a taglib as a parameter to g:link
I also have the requirement that multiple gsps/actions would cause a link to be active because of how they are named:
aboutYouLocation
aboutYouBackground
aboutYouEducation
all make this link the active one:
About You
I can do a partial match, but I've also got some actions/gsps that begin with aboutYour (extra R) resulting in my use of the array being passed into my taglib.
There's a standard way of doing that with the Platform Core plugin. It will provide you a Navigation API:
grails-app/conf/AppNavigation.groovy
navigation = {
// Declare the "app" scope, used by default in tags
app {
contact(action: 'contactInfo')
about(action: 'aboutYouFamily')
}
}
*grails-app/view/_menu.gsp* (template that you can use in your layout or GSP's)
<nav:menu scope="app" id="navigation" />
You can also customize the html generated for your menu, check the custom item rendering.

Remove hash id from url

I have a menu header and when you click one of the menu links it directs to another page half way down (which i want) as it finds the relevant div id name. I was wondering if there is a way to clean the url back up again so it doesn't include the #id in my url? Tried window.location hash and this breaks it from scrolling and leaves the # in the url. Here is what i have:
In my menu: <li><a href="about#scroll-to" ....
And on the about page, it scrolls down to a div called #scroll-to..<div id="scroll-to"></div>
Any suggestions would be great. Thanks!
Using jquery, you can make a POST call to the target page when menu is clicked.
The POST data will contain the id of the div where you want to slide to.
On your target page, use your server language (php, asp) to output that id in a js variable and on document ready slide using jquery to that id.
Then you will have a clean url, and the page scrolling to your div.
---- edit: here comes the code!
Lets use jquery to make a POST to the target page, when a menu item is clicked. We will add a class, lets say, "mymenuitem". We will add this class to our link in the menu. So we will have
<li>Information about us</li>
(the onClick stops link from redirecting manually)
and an empty form (put it after the < body >)
<form id="slidinganchorform" method="post" action="YOURTARGETPAGE.HTML"></form>
then we will create the neccessary jquery so when the < a > tag with class "mymenuitem" is clicked, we will make a POST to the target page.
<script type="text/javascript">
jQuery(document).ready(function(){
$(".mymenuitem").click(function() {
// we will split the clicked href's value by # so we will get [0]="about" [1]="scroll-to"
var the_anchor_id_to_scroll_to = $(this).attr("href").split('#')[1];
// lets do the POST (we WILL TRIGGER a normal FORM POST while appending the correct id)
$("#slidinganchorform").append('<input type="hidden" name="anchorid" value="'+ the_anchor_id_to_scroll_to + '">');
$("#slidinganchorform").submit();
});
});
</script>
then in our YOURTARGETPAGE.HTML we will have something like (let's assume we use php)
<head>
<!-- make sure your jquery is loaded ;) -->
<?php
if($_POST['anchorid']!='')
{
?>
<script type="text/javascript">
jQuery(document).ready(function(){
// lets get the position of the anchor (must be like <a name="scroll-to" id="scroll-to">Information</a>)
var thePositiontoScrollTo = jQuery('#<?php echo $_POST['anchorid']; ?>').offset().top;
// Lets scroll
jQuery('html, body').animate({scrollTop:thePositiontoScrollTo}, 'slow');
});
</script>
<?php
}
?>
</head>
be sure the correct id must exist ;)
<a name="scroll-to" id="scroll-to">Information about us or whatever...</a>
(remove your old code because i changed some variable names and it will be difficult to debug if there are parts from the previous version. write everything from the start )
You can do this when the new page loads
history.pushState("", document.title, window.location.pathname);
However it will also remove the query string. Although, you could play with it a little bit to keep it

Trying to understand - HTML replacements and scripts having access to replaced elements on the page

I have a page with a ticket list. In it, there is a <td> that is either a grab or release link. That link inside the '' is wrapped in a '' for an ajax html replacement. Like:
<td>
<div id="ticket_grab_release_<%= ticket.id %>">
*---- either a grab or release link here ----*
<div>
</td>
So, the user clicks on 'grab': They are assigned the ticket, the worklist is updated with their info on the page via HTML replacements, and the grab link is replaced with a 'release' link.
Next to this is a 'complete' link. When the user clicks on that, a small complete form opens in a jQuery UI-Dialog window. I ran into a problem though because along with the grab/replace link changing I also had to toggle this 'complete' link with a grey non-link 'complete' or an actual 'complete' link (if ticket released - disable complete link or visa versa).
The problem is that if this 'complete' link was greyed out and I replaced that with a 'complete' link, the UI Dialog window would not open. Like (no idea what I'm saying) the link wasn't in the DOM.
I got frustrated for a bit and then tried wrapping the script in a <div> and doing an html page replacement on the whole script. I HTML replaced the greyed out 'complete' with a 'complete' link and then HTML replaced the script right after. Interestingly that worked, but I'm really curious as to why it worked. When you ajax insert a script through an HTML replacement, does that inserted script have access to the modified DOM where the original script only has access to the what was the original DOM from the page load?
<div id="html_replace_sript">
<script type="text/javascript">
$('.complete_ticket_link' ).click(function(){
var url = $(this).attr("href");
$("#form_load").load(url,
function() {
$(this).dialog({
modal:true,
draggable: true,
resizable: false,
width:'auto',
height:'auto',
title: ' Complete Ticket ',
position: [125, 50]
});
});
return false;
});
</script>
</div>
Thanks - much apprecaited!
Check out live()'s much less recource-demanding counterpart: delegate()
Attach a handler to one or more events for all elements that match the selector, now or in the future, based on a specific set of root elements.
That means that instead of having to look through the entire window for an element, it starts at the specified root, significantly reducing overhead. This is the best solution for your issue.
The answer is YES.
But if you want to bind events to elements that match the selector now and in the future, you can use .live()
So you'd do:
$('.complete_ticket_link' ).live('click' function(){
...
});
Using this, your code can be on your main page and it will work.
Hope this helps. Cheers

Resources