I'm doing a multipage website and using some features of AngularJS, but I'm pretty new to it so would like some help. It uses ngview to load subpages, depending on the link clicked on my index file.
But on this index file I have a header that I would like to change depending on the current view. Sample:
<h1>
{{currentheader}}
</h1>
<ul class="nav navbar-nav">
<li ng-class="{ active: isActive('/')}">
Home
</li>
<li ng-class="{ active: isActive('/rooms')}">
Rooms
</li>
</ul>
<ng-view></ng-view>
I use a 'isActive' class on the navigation to set the active list item depending on my location.path(). Rendering the ngviews is working fine.
But i can't find a way to modify that header based on my active link. I could do that with jquery but I believe Angular has an easier sollution?
Thanks
EDIT:
found a sollution based on this
Update parent scope variable
An option that lets you put all the logic to update this header in one place, instead of dividing the logic into the controllers of your child views, is to listen for a $locationChangeSuccess within the controller of your parent view.
function updateHeader($scope,val) {
$scope.currentheader = val; // Obviously you'd make this more intelligent.
}
$scope.currentheader = "Initial Value";
$scope.$on('$locationChangeSuccess', function (event, toUrl, fromUrl) {
updateHeader($scope,toUrl);
});
Related
I have been working on a sveltekit application and recently ran into an issue with my main navigation menu. when the application is loaded or refreshed the current menu item that corresponds with the current URL is not set to active. (i.e. menu item 'Home' would have URL of '/') I had a hard time finding anything useful online demonstrating how to set this up, but I have a working solution and thought I'd document it for others. It is very simple at the moment.
I have a nav.json file where I define any menus within my application, making it easy to organize menus. I import that at the top of the file and store it in an array for use later.
import menuObj from '../nav/nav.json';
let sidebar = menuObj.menus[0].items;
onMount() is just getting the current path when the nav component is loaded, and only runs once for me.
then - using <svelte:window on:click={handlePageClick}/> we can listen to any clicks on the page.
In the handlePageClick(e) method we pass the on:click event. Using that event we can capture the current pathname of the URL. this allows us to check the pathname against our item.path which is defined in the nav.json file I've imported and extracted using the #each method in our html. On the <a> we can set the class to active if the items route matches the current path.
here is the full implementation
<script>
import menuObj from '../nav/nav.json';
import {onMount} from "svelte";
let path;
let sidebar = menuObj.menus[0].items;
onMount(() => {
path = window.location.pathname;
})
function handlePageClick(e) {
path = e.view.location.pathname;
}
</script>
<svelte:window on:click={handlePageClick}/>
<nav class="sidebar">
{#each sidebar as item, i}
<a href={item.route} id="sidebar-menu-item-{item.id}"
class={path === item.route ? "item active" : "item"}
>
<i id="sidebar-menu-item-{item.id}-i" class="{item.icon}"></i>
</a>
{/each}
</nav>
I haven't found any drawbacks to this yet. I will update this question if I find anything.
It's not really a question, but I will add an answer anyway because your method is not necessary, SvelteKit already has a built in system for this:
<script>
import { page } from '$app/stores';
</script>
<a href={item.route} class="item" class:active={$page.url.pathname == item.route}>
...
</a>
page is a store like object that among other things hold the current path, it will automatically be updated with the new path
as a bonus:
class:active is an easy shorthand, removing that ternary expression there.
I'm having a struggle. It's not one of these questions how to do it but more like how to do that better.
What im trying to achieve
Also I want to load rest into content when clicking on nav's
My question is what's "proper" or correct approach to do it without making a mess in a code.
How I done it so far
<li><a class="navelement" data-url="resources/web/loadit.html" href="#">navbar A</a></li>
And used jquery
$(document).ready(function(){
$('.navelement').click(function(e){
e.preventDefault;
$('#content').load($(this).data('url'));
});
$('.navelement:eq(0)').click();
});
I like this approach but I need to execute ajax rest call each time I change navbar.
Second approach I can think off is to hide/show in css div's. Any tip will be appreaciated.
Both of your approaches are correct but decision making points would be how frequently the data in div is changed and how much bandwidth will it cost to keep all divs loaded with data.
If data in div is frequently changed then I would suggest to use approach 2, i.e. load all divs once and hide and show the required one. This will improve the performance as ajax calls over network has time latency. Using approach 2 in this case will improve UI experience. This is also called as eager loading.
But in case if you have heavy data and user need not require all the data you have loaded then it will cause unnecessary loading of all divs which could consume considerable bandwidth and also cause long initial loading of page. In this case approach 1 is recommended. This is called as lazy loading.
What I suggest to you is , first load all your html nav in the content and hide them (adding class) , also set foreach generated div a custom class (by example I added the index of nav element ) , with this you'll prevent multiple ajax loading ( only once ).
then , after clicking on each nav just show it's content by refering to the generated class by example (nav1 ,nav2, nav 3)
Please find bellow a working snippet
$(function() {
$(".navelement").each(function(index, elm) {
if ($(elm).data("url")) {
var navElment = $("<div class='navdiv nav" + index + " hidden'>").load($(elm).data("url"));
$("#content").append(navElment);
$(elm).on("click", function() {
$(".navdiv").addClass("hidden");
$(".navdiv.nav" + index).removeClass("hidden");
})
}
})
})
.hidden {
display: none;
}
ul li {
display: inline;
padding:5px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li><a class="navelement" data-url="https://httpbin.org/get?text=navAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" href="#">navbar A</a></li>
<li><a class="navelement" data-url="https://httpbin.org/get?text=navBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" href="#">navbar B</a></li>
<li><a class="navelement" data-url="https://httpbin.org/get?text=navCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" href="#">navbar C</a></li>
</ul>
<div id="content">
</div>
I'm working on a site which will be maintained by not so tech savvy people, and I need to be able to give them the ability to add "see-more" anchors which use Jquery slide up/down to reveal their content.
My code works well for a single instance of the read more, but when there are multiple instances of this, it gets fairly screwed up.
javascript/jquery
$(".see-more").nextUntil(".see-less").wrapAll("<div class='see-more-content'></div>");
$(".see-less").hide();
var count= 1
/*
$(".see-more-content").each(function(){
var count= count+1;
$(this).data("count",count);
console.log(count);
});
*/
$(".see-more-content").slideUp(0);
$(".see-more").click(function(){
$(".see-more-content").slideToggle();
$(".see-more").hide();
$(".see-less").show();
});
$(".see-less").click(function(){
$(".see-more-content").slideToggle();
$(".see-less").hide();
$(".see-more").show();
});
HTML
<a class="see-more">See More...</a>
<ul>
<li>Advanced Elastic search Technology </li>
<li>Document Text Search</li>
<li>Embed Code Web Publishing for Images, Video & PDFs</li>
<li>Video Management with HTML5 Full</li>
<li>Previews On the Fly Conversions and Transcoding</li>
<li>Print on Demand</li>
<li>Stylized Collections (Lightboxes/Galleries)</li>
<li>Alerts and Notifications</li>
<li>Comments, Ratings and Favorites</li>
<li>WordPress and Drupal CMS Integrations</li>
<li>Dropbox Integration</li>
<li>Asset Level Performance Analytics • Site Activity Analytics Dashboard</li>
<li>Unlimited Custom User Access Levels</li>
<li>Integrated Content Contribution and Workflow</li>
<li>Personal Profile Management</li>
<li>Mobile App and Site </li>
<li>Watermarking</li>
<li>Rights Management</li>
<li>All New Feature Releases3</li>
</ul>
<a class="see-less">See Less...</a></div>
What I want to happen:
I want everything between the anchor with class see-more and anchor with class see-less, to get wrapped in a div, which when the anchor for see-more is clicked that div jquery slides down, when see-more is clicked, and slides up when see-less is clicked.
What is happening:
It works perfect when there is only one instance of see-more and see-less in a page. https://jsfiddle.net/TheWebTech/by3LsLuu/
When there are multiple instances of see-more and see-less in the html, the contents of all see-more+see-less blocks after the first instance are all moved/wrapped into the first block instances of the see-more see-less blocks get added.
https://jsfiddle.net/TheWebTech/by3LsLuu/4/
How do I prevent everything from being wrapped into the first instance of the see-more see-less block and instead have each one get wrapped separately?
Bonus but not really required: how can I make each see-more section slide up/down separately from eachother?
If you're going to keep the layout the same, you can use .prev() and .next() jQuery methods to determine which selector group you're referring too. Here's an updated fiddle with two instances:
https://jsfiddle.net/szva79d6/1/
First, I've made it so that your wrapping function applies to each selector individually, like so:
$(".see-more").each(function() {
$(this).nextUntil(".see-less")
.wrapAll("<div class='see-more-content'></div>");
});
What I've done in the two event methods is to make each event only act on the previous or next siblings, so that your events are properly delegated to each dynamically wrapped element.
$(".see-more").click(function() {
var $more = $(this),
$content = $more.next(".see-more-content"),
$less = $content.next(".see-less");
$content.slideToggle();
$more.hide();
$less.show();
});
$(".see-less").click(function() {
var $less = $(this),
$content = $less.prev(".see-more-content"),
$more = $content.prev(".see-more");
$content.slideToggle();
$less.hide();
$more.show();
});
You need to target specific to itself, try this:
$(".see-more").click(function(){
$(this).next(".see-more-content").slideToggle(); // find next content and show
$(this).hide(); // hide the see more button
$(this).nextAll('.see-less').first().show(); // show the next see less button
});
$(".see-less").click(function(){
$(this).prev(".see-more-content").slideToggle();
$(this).hide();
$(this).prevAll(".see-more").first().show();
});
Here's an updated fiddle
I'd like to create a pagination control that shows the user what page they are on. I'm having trouble using ng-class to style the page links properly.
After I get the count of the data, I setup $scope.pages to be [0,1,2..n] where n is the count / itemsPerPage. The html looks like this...
<ul class="pagination">
<li ng-click="prevPage()"><a>«</a></li>
<li ng-repeat="page in pages"
ng-class="(page==$index)? 'active' : ''"
ng-click="selectedPage(page)"><a>{{page+1}}</a></li>
<li ng-click="nextPage()"><a>»</a></li>
</ul>
But this markup results in all <li> getting the active class. I can see with debugging that $index is advancing within the li's, and I can see that page is the expected value (I can also see the style looks right when I hard-code it), so why doesn't the conditional styling work as I have it above?
Thanks in advance.
You should not be evaluating the expression against $index. If you desk check the repeat loop for every iteration, this is what you're evaluating:
page $index
0 == 0 // true
1 == 1 // true
2 == 2 // true
Instead, what you should do, is add a separate variable to the scope that tracks the current page, if you don't have one already. For illustrative purposes, let's call it currentPage. Then you can change your expression to:
ng-class="(page === currentPage) ? 'active': ''"
Additionally, in your selectedPage() function you would update currentPage to the page the user selected (i.e. $scope.currentPage = page).
I'm looking for a way to integrate something like ng-repeat with static content. That is, to send static divs and to have them bound to JS array (or rather, to have an array constructed from content and then bound to it).
I realize that I could send static content, then remove and regenerate the dynamic bits. I'd like not to write the same divs twice though.
The goal is not only to cater for search engines and people without js, but to strike a healthy balance between static websites and single page applications.
I'm not sure this is exactly what you meant, but it was interesting enough to try.
Basically what this directive does is create an item for each of its children by collecting the properties that were bound with ng-bind. And after it's done that it leaves just the first child as a template for ng-repeat.
Directive:
var app = angular.module('myApp', []);
app.directive('unrepeat', function($parse) {
return {
compile : function (element, attrs) {
/* get name of array and item from unrepeat-attribute */
var arrays = $parse(attrs.unrepeat)();
angular.forEach(arrays, function(v,i){
this[i] = [];
/* get items from divs */
angular.forEach(element.children(), function(el){
var item = {}
/* find the bound properties, and put text values on item */
$(el).find('[ng-bind^="'+v+'."]').each(function(){
var prop = $(this).attr('ng-bind').split('.');
/* ignoring for the moment complex properties like item.prop.subprop */
item[prop[1]] = $(this).text();
});
this[i].push(item);
});
});
/* remove all children except first */
$(element).children(':gt(0)').remove()
/* add array to scope in postLink, when we have a scope to add it to*/
return function postLink(scope) {
angular.forEach(arrays, function(v,i){
scope[i] = this[i];
});
}
}
};
});
Usage example:
<div ng-app="myApp" >
<div unrepeat="{list:'item'}" >
<div ng-repeat="item in list">
<span ng-bind="item.name">foo</span>
<span ng-bind="item.value">bar</span>
</div>
<div ng-repeat="item in list">
<span ng-bind="item.name">spam</span>
<span ng-bind="item.value">eggs</span>
</div>
<div ng-repeat="item in list">
<span ng-bind="item.name">cookies</span>
<span ng-bind="item.value">milk</span>
</div>
</div>
<button ng-click="list.push({name:'piep', value:'bla'})">Add</button>
</div>
Presumable those repeated divs are created in a loop by PHP or some other backend application, hence why I put ng-repeat in all of them.
http://jsfiddle.net/LvjyZ/
(Note that there is some superfluous use of $(), because I didn't load jQuery and Angular in the right order, and the .find on angular's jqLite lacks some features.)
You really have only one choice for this:
Render differently for search engines on the server, using something like the approach described here
The problem is you would need to basically rewrite all the directives to support loading their data from DOM, and then loading their templates somehow without having them show up in the DOM as well.
As an alternative, you could investigate using React instead of Angular, which (at least according to their website) could be used to render things directly on the web server without using a heavy setup like phantomjs.