I am using node.js, express and express-handlebars. I have a menu in the top of my layout.
In my server.js, I set all the pages with
app.locals.pages = [
{ title: 'Home', link: '/' },
{ title: 'About', link: '/about' },
{ title: 'Contact', link: '/contact' },
];
and in the layout file, I print the pages with
{{#each pages}}
<li class="nav-item">
<a class="nav-link" href="{{link}}">{{title}}</a>
</li>
{{/each}}
but I want to add a class to the menu item which is currently being active.
How can I save which of the 3 menu items is active?
I solved it without to pass params over the router request. Just simple created a js file to nav partial, pass the active path of the location to the nav item and add an active class.
(function($) {
$(document).ready(function() {
// get current URL path and assign 'active' class
var pathname = window.location.pathname;
$('.nav.navbar-nav > li > a[href="'+pathname+'"]').parent().addClass('active');
});
})(jQuery);
You can pass a body_id (or any other name) parameter from your routes to your views e.g.
router.get('/', function( req, res ){
var pageInfo = {};
pageInfo.body_id = 'homepage';
// assign more parameters relevant for this view here
res.render('home_page', pageInfo );
});
Now, in your view file, assign the body_id as the id of body i.e.
body(id= typeof body_id === "undefined" ? "body_id" : body_id)
Now, you can manage the active links in your css as
body#home_page .nav_item:nth-child(1),
body#about_page .nav_item:nth-child(2),
{
background-color: red;
/* more css styles that you want to give to your active menu item */
}
i don't test this for your code, but thing it should work, but if you use handlebars, you can try this
Ember.Handlebars.helper('isSelected', function(v1,v2, options) {
if (v1 && v2) {
return new Ember.Handlebars.SafeString("active");
}else{
return new Ember.Handlebars.SafeString("nav-item");
}
});
and on your codes
on your node js
router.get('/', function( req, res ){
var pageData = {};
pageData.page_link = 'home';
res.render('template_page', pageData );
});
on your template
{{#each page in pages}}
<li class="{{isSelected page.link page_link }}">
<a class="nav-link" href="{{page.link}}">{{page.title}}</a>
</li>
{{/each}}
again: i don't test this. but handlebars helper work fine for html elements
Related
I built tabbing functionality in my web app using Links and an Outlet with subroutes
const { currentTab } = useLoaderData();
function tabClassName(tab: string) {
if (currentTab == tab) { return "is-active"; }
return "";
}
return ([
<div className="tabs is-centered m-5 pr-5 pl-5">
<ul>
<li className={tabClassName("tab1")}><Link to="tab1">one</Link></li>
<li className={tabClassName("tab2")}><Link to="tab2">two</Link></li>
<li className={tabClassName("tab3")}><Link to="tab3">three</Link></li>
</ul>
</div>,
<Outlet></Outlet>
]);
The loader function supplies the component with the current subroute like this:
const parts = new URL(request.url).pathname.split("/");
const tab = parts.pop() || parts.pop() || ""; // get final part of path /settings/tab1 -> tab1
if (tab === "settings") {
return redirect("/settings/tab1");
}
return json<LoaderData>({ currentTab: tab });
When I click the links the correct route is loaded instantly. However, the css class is only applied after clicking the same link a second time. I found out that the loader function is only executed after the second click. How can i force remix to make a server request again? or should i use client side js for this? I cant use the NavLink component because of the way my css framework is structured.
Thanks!
I'm building a site that uses Vue for to power the majority of the UI. The main component is a list of videos that is updated whenever a certain URL pattern is matched.
The main (video-list) component looks largely like this:
let VideoList = Vue.component( 'video-list', {
data: () => ({ singlePost: '' }),
props: ['posts', 'categorySlug'],
template: `
<div>
<transition-group tag="ul">
<li v-for="(post, index) in filterPostsByCategory( posts )">
<div #click.prevent="showPost( post )">
<img :src="post.video_cover" />
/* ... */
</div>
</li>
</transition-group>
</div>`,
methods: {
orderPostsInCategory: function ( inputArray, currentCategory ) {
let outputArray = [];
for (let i = 0; i < inputArray.length; i++) {
let currentCategoryObj = inputArray[i].video_categories.find( (category) => {
return category.slug === currentCategory;
});
let positionInCategory = currentCategoryObj.category_post_order;
outputArray[positionInCategory] = inputArray[i];
}
return outputArray;
},
filterPostsByCategory: function ( posts ) {
let categorySlug = this.categorySlug,
filteredPosts = posts.filter( (post) => {
return post.video_categories.some( (category) => {
return category.slug === categorySlug;
})
});
return this.orderPostsInCategory( filteredPosts, categorySlug );
}
}
});
The filterPostsByCategory() method does its job switching between the various possible categories, and instantly updating the list, according to the routes below:
let router = new VueRouter({
mode: 'history',
linkActiveClass: 'active',
routes: [
{ path: '/', component: VideoList, props: {categorySlug: 'home-page'} },
{ path: '/category/:categorySlug', component: VideoList, props: true }
]
});
The difficulty I'm having is transitioning the list in the way that I'd like. Ideally, when new category is selected all currently visible list items would fade out and the new list items would then fade in. I've looked at the vue transitions documentation, but haven't been able to get the effect I'm after.
The issue is that some items have more than one category, and when switching between these categories, those items are never affected by whatever transition I try to apply (I assume because Vue is just trying to be efficient and update as few nodes as possible). It's also possible that two or more categories contain the exact same list items, and in these instances enter and leave methods don't seem to fire at all.
So the question is, what would be a good way to ensure that I can target all current items (regardless of whether they're still be visible after the route change) whenever the route patterns above are matched?
Have you noticed the special key attribute in the documentation?
Vue.js is really focused on performance, because of that, when you modify lists used with v-for, vue tries to update as few DOM nodes as possible. Sometimes it only updates text content of the nodes instead of removing the whole node and then append a newly created one. Using :key you tell vue that this node is specifically related to the given key/id, and you force vue to completely update the DOM when the list/array is modified and as a result the key is changed. In your case is appropriate to bind the key attribute to some info related to the post and the category filter itself, so that whenever the list is modified or the category is changed the whole list may be rerendered and thus apply the animation on all items:
<li v-for="(post, index) in filterPostsByCategory( posts )" :key="post.id + categorySlug">
<div #click.prevent="showPost( post )">
<img :src="post.video_cover" />
/* ... */
</div>
</li>
i would like to navigate my nested view in backbone app with routes. I have next code:
var StoreRouter = Backbone.Marionette.AppRouter.extend({
appRoutes: {
'item/:name/' : 'showItem',
"item/:name/species/:speciesName/" : "showSpecies"
}
});
var StoreCtrl = Marionette.Object.extend({
showItem: function(name){
console.log("showItem");
/* execute function for show Item */
},
showSpecies: function(name, speciesName){
console.log("showSpecies");
/* execute function for show Species inside Item Layout */
}
});
So, i need to show species when route is "item/:name/species/:speciesName/" but i get only triggering showSpecies fucntion, not both. What shall i do to trigger showItem and then showSpecies functions when route is "item/:name/species/:speciesName/" ?
There is nothing brand new here. Simply call your showItem function directly from showSpecies.
Additionally, you could use routes hash instead of appRoutes and then it is possible to do so:
var StoreRouter = Backbone.Marionette.AppRouter.extend({
routes: {
'item/:name/' : 'showItem',
'item/:name/species/:speciesName/' : function(){
this.showItem();
this.showSpecies();
}
}
});
I have a Bootstrap implementation where the navigation bar is an unordered list with each items set to highlight when active. The highlight itself is styled using CSS but the check for active status is made using AngularJS:
<ul class="nav navbar-nav navbar-right">
<li ng-class="{ active: isActive('/blog')}">
<a onClick="pagetop()" href="#blog">BLOG</a>
</li>
</ul>
The isActive() method is defined in an AngularJS Controller as:
function HeaderController($scope, $location){
$scope.isActive = function (viewLocation) {
return viewLocation === $location.path();
};
}
As you can see, AngularJS simply adds an .active class to the <li> item if the linked URL is active. This is the class that is styled using CSS. This works fine when the currently open page is http://localhost:8888/a-s/#/blog/ but not when it's http://localhost:8888/a-s/#/blog/sub-page/ or http://localhost:8888/a-s/#/blog/sub-page/sub-sub-page/. How can I modify the code to ensure all paths under /blog trigger the .active class logic? Is there any way one could use wild-cards in this syntax?
Now you checking whether path and the passed value are equal, instead you can check whether the passed value exists in the path
function HeaderController($scope, $location) {
$scope.isActive = function(viewLocation) {
return $location.path().indexOf(viewLocation) > -1;
};
}
This is not clean solution. Because it work on http://localhost:8888/a-s/#/blog-another/ b or http://localhost:8888/a-s/#/blog_edit/ b and so on. It is need to update like this:
function HeaderController($scope, $location) {
$scope.isActive = function(viewLocation) {
var path = $location.path();
var addViewLocation = viewLocation+"/";
var inPath = false;
if(path.indexOf(addViewLocation)==-1)
{
inPath = path.indexOf(viewLocation)+viewLocation.length==path.length;
}
else
inPath = true;
return inPath;
};
}
This is test locations:
isActive("www.location.ru/path","/path");
true
isActive("www.location.ru/path/tr","/path");
true
isActive("www.location.ru/path-r/tr","/path");
false
isActive("www.location.ru/path/","/path");
true
isActive("www.location.ru/path/we/ew","/path");
true
isActive("www.location.ru/path-another/we/ew","/path");
false
I have been looking at this for quite few hours and I don't think I am able to see the solution.
This is my router.js:
define('router', ['jquery', 'config', 'nav','store'], function ($, config, nav, store) {
var
concepTouch = Sammy('body', function () {
// This says to sammy to use the title plugin
this.use(Sammy.Title);
this.use(Sammy.Mustache);
// Sets the global title prefix
this.setTitle(config.title.prefix);
// So I can access sammy inside private methods
var sammy = this;
function establishRoutes() {
// Defines the main container for content then
var mainConainer = $(config.mainContentContainerId);
// Adds animation loading class to the main container
mainConainer.addClass(config.loadingAnimationCssClass);
// iterates through routes defined in config class then
_.forEach(config.appRoutes, function(obj, key) {
// defines each one as a route
sammy.get(obj.hashV, function(context) {
// Store the requested route as the last viewed route
store.save(config.stateKeys.lastView, context.path);
// Fetches its html template
context.render(obj.tmpltURL, { 'routeData': context.params })
// Appends that htmlo template to the main container and removes loading animation
.then(function(content) {
mainConainer.removeClass(config.loadingAnimationCssClass).html(content);
});
// Finally adds the route title to the prefix
this.title(obj.title);
});
// Overriding sammy's 404
sammy.notFound = function () {
// toast an error about the missing command
toastr.error(sammy.getLocation() + ' Does not exist yet!');
// Go to last visited anf if not
sammy.setLocation(
store.fetch(config.stateKeys.lastView) || config.getDefaultRoute()
);
};
});
}
// Calls for routes to be established
establishRoutes();
}),
// runs concep touch as a sammy App with the initial view of default route
init = function () {
// Try to get today's last visit and if not available then fallback on default
concepTouch.run(store.fetch(config.stateKeys.lastView) || config.getDefaultRoute());
// Make the correct nav item active and add Click handlers for navigation menu
nav.setStartupActiveClass(store.fetch(config.stateKeys.lastView) || sammy.getLocation())
.addActiveClassEventHandlers();
};
return {
init: init,
concepTouch: concepTouch
};
});
This when I submit the search form gets this template for me:
<div id="contacts" class="view animated fadeInLeft">
<h3>Search results for {{routeData}}</h3>
<ul data-bind="template: { name: 'searchresults-template', foreach: searchResults }"></ul>
</div>
<script type="text/html" id="searchresults-template">
<li data-bind="text: type"></li>
</script>
<script>
require(['searchresults'], function (searchresults) {
searchresults.get(to Some how Get routeData.term);
});
</script>
and I can not find the right way to make Mustache pass the data from this line of router.js context.render(obj.tmpltURL, { 'routeData': context.params }) to the {{routeData.term}} inside the template.
{{routeData}} on its own returns `SAMMY.OBJECT: {"TERM": MY SEARCH TERM}`
which I can't navigate to the property i want to from it using . notation. Furthermore even if that worked it can not be passed into Javascript which is what I really need as
searchresults.init(); is waiting for this paramter `searchresults.init(routeData.term);`
Or maybe the answer is to find a way to access sammy's context here? outside of sammy in order to get the params? something like Sammy.Application.context.params['term'] but ofcourse application has no such method so don't know!? :(
Am I going totally the wrong way about it? How Can I easily pass the query string params as accessible objects inside my template so knockout can use it.
Your help is greatly appreciated.
<div id="contacts" class="view animated fadeInLeft">
<h3>Search results for {{#routeData}}{{term}}{{/routeData}}</h3>
<ul data-bind="template: { name: 'searchresults-template', foreach: searchResults }"></ul>
</div>
<script type="text/html" id="searchresults-template">
<li data-bind="text: type"></li>
</script>
<script>
require(['searchresults'], function (searchresults) {
var searchTerm = "{{#routeData}}{{term}}{{/routeData}}";
searchresults.get(searchTerm);
});
</script>