This may be a really simple problem but I can't seem to find why this is happening. I'm trying to develop a SPA in vanilla js using webpack, so far I was able to implement routing
with hashchange event and triggering rerendering. But when I tried to add an active class to the relevant link though when the hash changes, It doesn't work. But when I log to the console, it seems that class was added successfully, but in the HTML it doesn't get updated. Why is this?
this is my hashchange listener,
window.addEventListener("hashchange", (e) => {
const hash = window.location.hash.replace("#", "");
const view = routes.find((route) => {
return route.path == hash;
});
const links = document.querySelectorAll(".nav-list--link");
app.render(view.name);
links.forEach((l) => {
const hashHref = l.getAttribute("href").replace("/#", "");
if (hash === hashHref) {
l.classList.add("active");
console.log(l, l.classList);
} else {
l.classList.remove("active");
console.log(l, l.classList);
}
});
});
And this is the console output,
This is the HTML,
I don't understand why it doesn't update in the HTML if it's shown as updated in Javascript
Related
I have an HTML component that is fetched when user clicks on a button. This component/modal is used to change a user's profile image (this is processed with PHP). The JavaScript fetch() happens with the following code:
var newProfileImageButton = document.getElementById('replace-profile-photo'),
changeProfileImageWrapper = document.getElementById('change-profile-image-wrapper'),
profileImageModalPath = "modals/change-profile-image.php";
newProfileImageButton.addEventListener('click', function(){
fetch(profileImageModalPath)
.then((response) => {
return response.text();
})
.then((component)=>{
changeProfileImageWrapper.innerHTML = component;
})
.catch((error => {console.log(error)}))
})
This fetched component includes a 'close' button should the user wish to close the modal and not update their profile image.
When I click the close button though nothing is happening. I have the script below - is the issue to do with the fact the Javascript has loaded before the component/modal is fetched? And if so how do I fix this? I guess I could just toggle display:none off and on for this component but would prefer to fetch it if possible.
Note: The button is responding to CSS hover events so I'm confident it's not an HTML / CSS markup issue.
// close button is part of the HTML component that is fetched
var closeComponent = document.getElementById('form-close-x')
if (closeComponent) {
closeComponent.addEventListener('click', function(){
// Hide the main component wrapper so component disappears
changeProfileImageWrapper.style.display = 'none';
})
}
I've also tried using the following code, which I found in a similar question, but this doesn't work either (it was suggested this was a duplicate question).
var closeComponent = document.getElementById('form-close-x')
if (closeComponent) {
closeComponent.addEventListener('click', function(e){
if (e.target.id == 'form-close-x') {
changeProfileImageWrapper.style.display = 'none';
}
})
}
Try this:
// var closeComponent = document.getElementById('form-close-x') <-- remove
// if (closeComponent) { <-- remove
document.addEventListener('click', function(e){
if (e.target.id === 'form-close-x') {
changeProfileImageWrapper.style.display = 'none';
}
})
//} <-- remove
I have been trying for a few hours now and decided to finally post here. I have been trying so many methods to get my links to stop with preventDefault, this way I can load in content async.
All my links that i have tried that are apart of the template (like in the header) work perfectly, but any of the links brought in through an async view (and generated via js) completely skip over this snippet of code. I wanted to track anything that has a className "router" and stop the default action and run the function navigate instead.
var routeclicked = document.getElementsByClassName('router');
for(let i = 0; i < routeclicked.length; i++) {
routeclicked[i].addEventListener("click", e => {
console.log("Router is: " + routeclicked[i]);
e.preventDefault();
router = routeclicked[i].pathname;
console.log("Router 2 is: " + router);
navigate();
});
}
Attach a listener to "body" or the first non-dynamic parent.
You could go for Event.target and .closest().
Also, don't put functions inside for loops, it defies the reusability of functions.
const navigate = (EL) => {
location = EL.pathname;
}
const navigateHandler = (ev) => {
const EL = ev.target.closest(".router"); // Self or closest
if (!EL) return;
ev.preventDefault();
navigate(EL);
}
document.querySelector("body").addEventListener("click", navigateHandler);
I am using sammy.js for single page application in asp.net mvc. Everything is fine, but I am facing one problem which is that I can not reload the page. For example When I am in the dashboard my URL is
http://localhost:1834/#/Home/Index?lblbreadcum=Dashboard
layout.cshtml
<script>
$(function () {
var routing = new Routing('#Url.Content("~/")', '#page', 'welcome');
routing.init();
});
</script>
routing.js
var Routing = function (appRoot, contentSelector, defaultRoute) {
function getUrlFromHash(hash) {
var url = hash.replace('#/', '');
if (url === appRoot)
url = defaultRoute;
return url;
}
return {
init: function () {
Sammy(contentSelector, function () {
this.get(/\#\/(.*)/, function (context) {
var url = getUrlFromHash(context.path);
context.load(url).swap();
});
}).run('#/');
}
};
}
I want to reload the page by clicking the dashboard menu/link. But click event not firing because link is not changing. But if I want to go another page then it is fine. Please help me out. Thanks.
I think you have to append the same partial again. You can't "update" the partial in that meaning.
As you say in your post, when you click another link and then back again it works.
That's what you'll have to do. Append the same page/partial again, by doing that you clear all variables and recreate them, by that simulating a refresh.
EDIT: Added example
Observe that I didn't copy your code straight off but I think you'll understand :)
And I don't use hash (#) in my example.
var app = Sammy(function () {
this.get('/', function (context) {
// context is equalient to data.app in the custom bind example
// currentComponent('home'); I use components in my code but you should be able to swith to your implementation
var url = getUrlFromHash(context.path);
context.load(url).swap();
});
this.bind('mycustom-trigger', function (e, data) {
this.redirect('/'); // force redirect
});
this.get('/about', function (evt) {
// currentComponent('about'); I use components in my code but you should be able to swith to your implementation
var url = getUrlFromHash(context.path);
context.load(url).swap();
});
}).run();
// I did an easy example trigger here but I think you will need a trigger on your link-element. Mayby with a conditional check wheter or not to trigger the manually binding or not
$('.navbar-collapse').click(function () {
app.trigger('mycustom-trigger', app);
});
Please read more about events and routing in sammy.js
Good luck :)
An easier and cleaner way to force the route to reload is to call the Sammy.Application refresh() method:
import { sammyApp } from '../mySammyApp';
const url = `${mySearchRoute}/${encodeURIComponent(this.state.searchText)}`;
if (window.location.hash === url) {
sammyApp.refresh();
else {
window.location.hash = url;
}
First Question here, too! Yay! Just moved this from AskUbuntu.
I am just about to finish a little private project for gaining some experience where i try to change the app layout so it works as a normal website (on Jimdo, so it was quite of a challenge first) without much JavaScript required but is fully functional on mobile view.
Since Jimdo serves naturally only the actual site, I had to implement an
if (activeTab.getAttribute('jimdo-target') != null)
location.href = activeTab.getAttribute('jimdo-target');
redirect into the __doSelectTab() function in tabs.js . (In js I took the values from the jimdo menu string to build the TABS menu with this link attribute)
Now everything works fine exept at page load the first tab is selected. I got it to set the .active and .inactive classes right easily, but it is not shifted to the left.
So my next idea is to let it initialize as always and then send a command to change to the current tab.
Do you have any idea how to manage this? I couldn't because of the this.thisandthat element I apparently don't really understand...
Most of you answering have the toolkit and the whole code, but I am listing the select function part of the tabs.js:
__doSelectTab: function(tabElement, forcedSelection) {
if ( ! tabElement)
return;
if (tabElement.getAttribute("data-role") !== 'tabitem')
return;
if (forcedSelection ||
(Array.prototype.slice.call(tabElement.classList)).indexOf('inactive') > -1) {
window.clearTimeout(t2);
activeTab = this._tabs.querySelector('[data-role="tabitem"].active');
offsetX = this.offsetLeft;
this._tabs.style['-webkit-transition-duration'] = '.3s';
this._tabs.style.webkitTransform = 'translate3d(-' + offsetX + 'px,0,0)';
this.__updateActiveTab(tabElement, activeTab);
if (activeTab.getAttribute('jimdo-target') != null)
location.href = activeTab.getAttribute('jimdo-target');
[].forEach.call(this._tabs.querySelectorAll('[data-role="tabitem"]:not(.active)'), function (e) {
e.classList.remove('inactive');
});
var targetPageId = tabElement.getAttribute('data-page');
this.activate(targetPageId);
this.__dispatchTabChangedEvent(targetPageId);
} else {
[].forEach.call(this._tabs.querySelectorAll('[data-role="tabitem"]:not(.active)'), function (el) {
el.classList.toggle('inactive');
});
var self = this;
t2 = window.setTimeout(function () {
var nonActiveTabs = self._tabs.querySelectorAll('[data-role="tabitem"]:not(.active)');
[].forEach.call(nonActiveTabs, function (el) {
el.classList.toggle('inactive');
});
}, 3000);
}
},
...and my app.js hasn't anything special:
var UI = new UbuntuUI();
document.addEventListener('deviceready', function() { console.log('device ready') }, true);
$(document).ready(function () {
recreate_jimdo_nav();
UI.init();
});
So meanwhile found a simple workaround, however I'd still like to know if there is another way. Eventually I noticed the __doSelectTab() function is the one that executes the click, so it does nothing but to show the other tab names when they are hidden first. so I added the global value
var jnavinitialized = false;
at the beginning of the tabs.js and run
var t = this;
setTimeout(function(){t.__doSelectTab(t._tabs.querySelector('[data-role="tabitem"].jnav-current'))}, 0);
setTimeout(function(){t.__doSelectTab(t._tabs.querySelector('[data-role="tabitem"].jnav-current'))}, 1);
setTimeout(function(){jnavinitialized = true;}, 10);
at the top of the __setupInitialTabVisibility() function. Then I changed the location.href command to
if (activeTab.getAttribute('jimdo-target') != null && jnavinitialized)
location.href = activeTab.getAttribute('jimdo-target');
And it works. But originally I searched for a way to change the tab on command, not to run the command for selecting twice. So if you know a better or cleaner way, you are welcome!
I have made a solution for my website which includes using ajax to present the general information on the website. In doing this, I am changing the URL every time a user loads some specific content with the window.history.pushState method. However, when I press backspace or press back, the content of the old url is not loaded (however the URL is loaded).
I have tried several solutions presented on SO without any luck.
Here is an example of one of the ajax functions:
$(document).ready(function(){
$(document).on("click",".priceDeckLink",function(){
$("#hideGraphStuff").hide();
$("#giantWrapper").show();
$("#loadDeck").fadeIn("fast");
var name = $(this).text();
$.post("pages/getPriceDeckData.php",{data : name},function(data){
var $response=$(data);
var name = $response.filter('#titleDeck').text();
var data = data.split("%%%%%%%");
$("#deckInfo").html(data[0]);
$("#textContainer").html(data[1]);
$("#realTitleDeck").html(name);
$("#loadDeck").hide();
$("#hideGraphStuff").fadeIn("fast");
loadGraph();
window.history.pushState("Price Deck", "Price Deck", "?p=priceDeck&dN="+ name);
});
});
Hope you guys can help :)
pushState alone will not make your page function with back/forward. What you'd need to do is listen to onpopstate and load the contents yourself similar to what would happen on click.
var load = function (name, skipPushState) {
$("#hideGraphStuff").hide();
// pre-load, etc ...
$.post("pages/getPriceDeckData.php",{data : name}, function(data){
// on-load, etc ...
// we don't want to push the state on popstate (e.g. 'Back'), so `skipPushState`
// can be passed to prevent it
if (!skipPushState) {
// build a state for this name
var state = {name: name, page: 'Price Deck'};
window.history.pushState(state, "Price Deck", "?p=priceDeck&dN="+ name);
}
});
}
$(document).on("click", ".priceDeckLink", function() {
var name = $(this).text();
load(name);
});
$(window).on("popstate", function () {
// if the state is the page you expect, pull the name and load it.
if (history.state && "Price Deck" === history.state.page) {
load(history.state.name, true);
}
});
Note that history.state is a somewhat less supported part of the history API. If you wanted to support all pushState browsers you'd have to have another way to pull the current state on popstate, probably by parsing the URL.
It would be trivial and probably a good idea here to cache the results of the priceCheck for the name as well and pull them from the cache on back/forward instead of making more php requests.
This works for me. Very simple.
$(window).bind("popstate", function() {
window.location = location.href
});
Have same issue and the solution not working for neither
const [loadBackBtn, setLoadBackBtn] = useState(false);
useEffect(() => {
if (loadBackBtn) {
setLoadBackBtn(false);
return;
} else {
const stateQuery = router.query;
const { asPath } = router;
window.history.pushState(stateQuery, "", asPath);
},[router.query?.page]