Table of content like JavaScript-Garden - javascript

I want to create a table of content similar to JavaScript Gardens. How do they determine which section is currently active and do you have any recommended JavaScript libraries that imlpement this behavior?
Edit: So the thing I am asking for is how to know which section currently is active on the screen while the user is scrolling so that I can highlight that section in the table of content.

You can detect when an element enters the viewport of your browser, and then highlight the corresponding menu entry.
By using Firebug in Firefox, you can see that they use the scrollTop property of the window to know what the user is looking at.
highlight: function () {
// get the scroll height
var scroll = this.page.window.scrollTop(),
articleID = this.names[this.names.length - 1].id;
// while our item are above the viewport, we enumerate
for (var i = 0, l = this.names.length; i < l; i++) {
if (this.names[i].offset > scroll) {
articleID = this.names[i - 1].id;
break;
}
}
// we've got the content to highlight, let's add classes and expand menu-entries
var sectionID = articleID.split('.')[0],
page = this.page,
nav = page.nav;
if (sectionID !== page.section) {
nav.filter('.nav_' + page.section).removeClass('active');
nav.filter('.nav_' + sectionID).addClass('active');
this.expand(sectionID);
page.section = sectionID;
}
if (articleID !== page.article) {
nav.find('a[href="#' + page.article + '"]').removeClass('active');
nav.find('a[href="#' + articleID + '"]').addClass('active');
page.article = articleID;
this.mobile(articleID);
}
}
During the initialization they find out what each part takes in height
init: function(attribute) {
this.heights = this.page.nav.find('ul').map(function(idx, ele) {
return $(this).outerHeight();
}).get();
From these two pieces of info, they can highlight the correct entry to what the user is looking at, by attaching the function to the scroll, resize, etc... events of the window.

You can do that via html and css. They use a hover style for each entry and then link to html content via named anchors. You can see that in the address bar when you click on link.
E.g.:
TOC Entry:
<code>hasOwnProperty</code>
Content Body:
<a name="object.hasownproperty"></a>
<!-- HTML Content here -->
Of course, if you want nice animation and stuff, use something like
http://www.position-absolute.com/articles/better-html-anchor-a-jquery-script-to-slide-the-scrollbar/ or http://css-plus.com/2011/03/plusanchor-jquery-plugin/
Update:
To Achieve highlighting (pseudocode):
Keep a tab of all your sections
Attach an onscroll event handler to the body
onscroll, check the scrollTop to each section's top
If match found, remove highlight class from previous TOC entry and add it to new TOC entry.
You can name your TOC anchors such a way that they match the section's id. Then you can easily retrieve corresponding TOC entry by just saying #id and add your class to it.

Related

How is the active class added to the correct nav item inside scroll my event?

So I have a single page where the active menu item gets highlighted when you scroll to its corresponding section.
My code works, but I don't understand why, specifically this part:
document.querySelector('nav a[href="' + anchorID + '"]').classList.add("active");
On window.onscroll the for loop collects all the anchors (nav a) from the menu
Then I get access to individual anchorIDs (hrefs) with:
var anchorID = anchorsArray[i].getAttribute("href");
What I don't understand, is how the .activeclass gets added to the correct anchorID based on the current section inside the viewport — when no comparison is made between the section id and the corresponding anchor href. E.g. the href & section id:
Section 2
<section id="section-2"></section>
..are never compared on scroll.
DEMO: https://jsfiddle.net/zgpzrvns/
All the JS
(function() {
var anchorsArray = document.querySelectorAll("nav a");
var sections = document.querySelectorAll("section");
var sectionsArray = [];
// Collect all sections and push to sectionsArray
for (var i = 0; i < sections.length; i++) {
var section = sections[i];
sectionsArray.push(section);
}
window.onscroll = function() {
var scrollPosition = window.pageYOffset;
for (var i = 0; i < anchorsArray.length; i++) {
// Get hrefs from each anchor
var anchorID = anchorsArray[i].getAttribute("href");
var sectionHeight = sectionsArray[i].offsetHeight;
var sectionTop = sectionsArray[i].offsetTop;
if (
scrollPosition >= sectionTop &&
scrollPosition < sectionTop + sectionHeight
) {
/**
* I don't understand how querySelector finds & adds the active
* class to the correct anchor, when no comparison is made between the
* section ID (that is inside current section in viewport) and the
* anchors ID from anchorsArray
*/
document
.querySelector('nav a[href="' + anchorID + '"]')
.classList.add("active");
} else {
document
.querySelector('nav a[href="' + anchorID + '"]')
.classList.remove("active");
}
}
};
})();
In summary: how is the active class added to correct anchor ID, when the corresponding section ID inside the viewport on scroll (when section ID is never detected inside the scroll event?)
I'm so confused about this, and I bet it's something silly I'm overlooking!
Would greatly appreciate some input! :-)
In short:
It doesn't need to compare any ids, because on scroll you loop over all anchors in your navigation. For each you check, if the section at the same index is in the viewport. If so, you add the active class.
If you switch the positions of your navigation items, you'll see that it will add the active class to the wrong item, because it just checks the index.
If you need some more explanation, tell me. Going to edit the answer then.
Edit index explanation:
You have your navigation anchors in an array, and also your sections.
var anchorsArray = document.querySelectorAll("nav a");
var sections = document.querySelectorAll("section");
You are looping your anchors array
for (var i = 0; i < anchorsArray.length; i++) {
and then you get the height and top position of your section at the same index as your anchor (variable i)
var sectionHeight = sectionsArray[i].offsetHeight;
var sectionTop = sectionsArray[i].offsetTop;
if (
scrollPosition >= sectionTop &&
scrollPosition < sectionTop + sectionHeight
) {
and then you set the active class if its true, or remove it, if its false.
So on each scroll your code does the following:
Get anchor one -> check if section one is in range -> If yes -> add active -> else remove active
Get anchor two -> check if section two is in range -> If yes -> add active -> else remove active
Get anchor three -> ... and so one

Link to another page, execute function, then jump to anchor

I have two pages. The first has a slideshow that allows me to add links for each slide. The second page has numerous expanding div's that are expandable/collapsible onclick using this show/hide function:
function showtxt(divID) {
var item = document.getElementById(divID);
if (item) {
item.className=(item.className=='hidetxt')?'showtxt':'hidetxt';
}
}
Then each expanding/collapsing div on that page has its own function to call it when clicked:
function ANCHORbutton() {
var img = document.getElementById('expANCHORbutton').src;
if (img.indexOf('plus.png')!=-1) {
document.getElementById('expANCHORbutton').src = 'minus.png';
}
else {
document.getElementById('expANCHORbutton').src = 'plus.png';
}
}
What I'd like to do, if possible, is link each slide from that slideshow to the second page, expand the corresponding div, and then jump down the page to the specified anchor.
If I didn't have everything collapsed, it'd be a simple href="http://domain.com/page2.html#ANCHOR", but I'm struggling with how to expand the appropriate section before jumping to my anchor.
Problem 1: Collapsed divs all show so no need for browser to scroll
Problem 2: Hidden divs are not being scrolled to since they are hidden.
Solution could be this at the bottom of the page (onload does not trigger on reload on some browsers)
<script>
var hash = location.hash;
if (hash) {
var divID = hash.substring(1); // remove the #
showtxt(divID);
document.getElementById(divID).scrollIntoView();
}
</script>
</body>
Update to add 100px:
var hash = location.hash;
if (hash) {
var divID = hash.substring(1); // remove the #
showtxt(divID);
var div = document.getElementById(divID);
div.scrollIntoView();
div.style.top=(parseInt(div.style.top,10)+100)+"px";
}

Function when Elements className change

I am trying to change the background image of a section based on whatever slide has a particular classname.
I have an unordered list that is genereated with a loop
<ul class="tes-image-links">
<li>http://exampleimg1.jpg</li>
<li>http://exampleimg2.jpg</li>
<li>http://exampleimg3.jpg</li>
</ul>
I also have some javascript to change the background image of the section the unorderd list is contained in. The url for the background image depends on which div has the class name cycle-slide-active
var cycleSlide = $('.cycle-slideshow').find('.cycle-slide');
for (i = 0; i < cycleSlide.length; i++) {
if (cycleSlide.eq(i).hasClass('cycle-slide-active')){
var apimglink = $('.tes-image-links').children('li').eq(i).text();
apimglink = apimglink.replace("http://localhost", "");
$('.ap-testimonial').css('background-image', 'url(' + apimglink + ')');
console.log(apimglink);
} //End If
}; //End For
The above code gives me the right background image for whatever slide is first loaded, but after the slide changes nothing else happens.
I need help Executing the above code whenever cycleSlide.className for any cycleSlide element is changed.
Thank you all in advanced.

Measuring height of an off page element

I'm Ajaxing in elements. I want to decide where to put them based on their height (think box-packing type algorithm).
When I loop through my elements as such, I always get 0 for the outerHeight:
posts.each(function(i, e) {
var elementHeight = $(e).outerHeight();
// elementHeight is always 0
});
How can I get the display height of my element?
It appears I have to add the element to the page to get the height, which makes sense.
What's the best way of doing this while being invisible to the user as simply as possible?
Append the posts to a hidden (display: none) div element and then iterate over them to get their respective outerHeight values.
HTML
<div id="postContainer"></div>
CSS
#postContainer { display: none;}
JS
var cont = $('#postContainer'), postsLength = posts.length;
posts.each(function(i, e) {
cont.append(e);
if (!--postsLength) calcOuterHeight();
});
function calcOuterHeight(){
cont.find('.posts').each(function(i, e){
var oh = $(e).outerHeight();
})
}
You have to add the elements to the page so that they go through the layout process, then they will have a size.
You can add them to the body, get the height, and then remove them. As the browser is busy running your code while the elements are in the page, the elements will never show up:
var el = $('<div>test</div>');
$(document.body).append(el);
var h = el.height();
el.remove();

How to point to or select an arbitary element with content in CKEditor?

I need to add CSS classes to basically any element that the user selects or points to in CKEditor. For example if there is a table that contains an list, my element structure and elementspath might look like this under the cursor:
body > table > tbody > tr > td > ul > li
Out of those, the user needs to be able to somehow select or point to the table, tr, td, ul or li element. After the user selected / pointed to the element they want to add a class to they would get a dialog to choose a class.
How would I detect which element the user wants to point to?
I'm open to any and all suggestions, even if that means hacking the core. For example I would think that this is doable with elementspath having a context menu where I could click something like "properties" for the element and that would bring up a dialog where the user could choose a class, but I have no idea how or even if it is possible to add a context menu to elementspath!
In Firefox this kind of works out of the box in that when a user clicks an element in elementspath I can GetSelectedElement and it returns what the user has clicked, but it doesn't work in IE/Chrome.
I kind hacked together a horrible but somewhat functional solution. In elementspath plugin.js I added
function onContextMenu(elementIndex, ev) {
editor.focus();
var element = editor._.elementsPath.list[elementIndex];
editor.execCommand('elementspathContextmenuForElement', ev, element);
}
var onContextMenuHanlder = CKEDITOR.tools.addFunction(onContextMenu);
And then where the elementspath item html is generated I added:
oncontextmenu="return CKEDITOR.tools.callFunction(', onContextMenuHanlder, ',', index, ', event );"
And then I made a plugin that creates a html "context menu"
CKEDITOR.plugins.add('elementspathcontextmenu', {
init: function (editor) {
editor.addCommand('elementspathContextmenuForElement', {
exec: function (editor, event, element) {
debug(element);
var tempX = event.pageX + 'px';
var tempY = event.pageY + 'px';
window.newdiv = document.createElement('div');
window.newdiv.setAttribute('id', "tmpContextMenuDiv");
window.newdiv.style.width = 300 + 'px';
window.newdiv.style.height = 300 + 'px';
window.newdiv.style.position = "absolute";
window.newdiv.style.left = tempX;
window.newdiv.style.top = tempY;
window.newdiv.innerHTML = '<p>Do something</p>';
document.body.appendChild(window.newdiv);
},
canUndo: false // No support for undo/redo
});
}
});
I feel a bit dirty hacking the core like that and creating a div element for the context menu in that way but it kind of works for me. This is by no means the final code but it gets the point across.

Categories

Resources