Trouble creating multiple generated sticky headers using jQuery and CSS - javascript

I am trying to integrate a sticky headers technique like the one shown here... Persistent Headers.
I have tried to integrate it into my code and for the most part have been successful, however it isn't behaving correctly and I REALLY can't figure it out.
I'll try to explain in a nutshell what the page it is being used on does. I have a database with a table of students and another table of assessments. This page loops through a JSON object (recieved from the database via a PHP script) and then for each student in that first object fetches another JSON with their assessments. This all works fine. It does however create a fairly long page. Visually it looks like this...
Code rendered in Chrome
The code I have written based on that tutorial I posted above is supposed to clone headers specified by a class and then hide or show them based on some logic involving scrollTop the position of the element and the length of the element. This having the effect of the header sticking to the top of the page while the container it belongs to is still visible.
The problem is something is going wrong and although all the headers are shown in sequence they are way too early, they seem to hang about for different lengths of time, and these lengths do seem to correlate to how long the container it belongs to is.
So my code...
Firstly the function used to update the headers...
containerArray = new Array;
positionArray = new Array;
floatingHeaderArray = new Array;
function updateTableHeaders() {
$(".studentContainer").each(function(i) {
containerArray[i] = $(this);
var position = containerArray[i].position();
positionArray[i] = position.top;
var scrollTop = $("#main").scrollTop();
floatingHeaderArray[i] = $(".floatingHeader", this);
if ((scrollTop > positionArray[i]) && (scrollTop < positionArray[i] + containerArray[i].outerHeight(true))) {
floatingHeaderArray[i].css({
"visibility": "visible"
});
} else {
floatingHeaderArray[i].css({
"visibility": "hidden"
});
};
});
}
Now the code that generates the containers, headers and tabs.
$("#mainContent").fadeIn(0);
loadMessage = "Loading data for " + event.target.id;
$.getJSON('php/oneFullClass.php?techClass=' + event.target.id, function(data) {
$('#mainTitle').fadeOut(0);
$('#action').html('You are ' + actionIntent + 'ing ' + event.target.id);
$('#action').fadeIn(300);
$('#mainTitle').fadeIn(300);
$('#mainContent').append('<div id="scrollTopDisplay"></div>')
dynamicPositioning();
$.each(data, function(key, val) {
var thisPosition = positionArray[0]
$('#mainContent').append(
'<div class="studentContainer studentView" id="' + val.idStudent + '">' +
'<div class="studentName">' + val.name + ' ' + val.surname + ' - (' + val.form.substr(0, 1) + '/' + val.form.substr(1, 2) + ')</div>' +
'<div class="floatingHeader">' + val.name + ' ' + val.surname + ' - (' + val.form.substr(0, 1) + '/' + val.form.substr(1, 2) + ')</div>' +
'<div class="studentTarget"> Target: <strong>' + val.target + '</strong></div>' +
'</div>');
$(".studentContainer").hide().each(function(i) {
//$(this).slideDown(0);
$(this).delay(i * 50).slideDown(300).fadeIn(500);
})
//Get previous assessments for this student and build tabs
buildTabs('php/allPreviousAssess.php?sid=' + val.idStudent, val.idStudent);
});
});
$('#mainContent').append('<div id="expandAll" onClick="expandAll()">Expand</div>');
$('#mainContent').append('<div id="collapseAll" onClick="collapseAll()">Collapse</div>');
dynamicPositioning();
$('#expandAll').delay(300).fadeIn(300);
$('#collapseAll').delay(300).fadeIn(300);
$("#main").scroll(updateTableHeaders);
I think that's all the info you'll need but I'll post any other code that may be referenced in this code if you think it'll help figure it out.
I have a suspicion that the problem is something to do with the animated slide in effect I am using on the 'assessment cards' messing with the position values, or possible position()'s inability to get positions of hidden elements. However, as I call updateTableHeaders() with every scroll event, this shouldn't be an issue as all animation is over by the time you are given access to the layout (there is a modal shade effect that only dissapears once all AJAX requests are complete.
I hope someone can help, this one is making me unhappy! :(

Balloon, a library I wrote for easily making your headers stick, is pretty hassle-free. You simply make a Balloon object instance, specifying if you want your sticky headers to be stacked or replaced, and then inflate the headers by passing in the strings of their ids. Give it a try and let me know if it helped you:
https://github.com/vhiremath4/Balloon
If you find any issues with it, file a bug report on the repository, but I feel like it should do its job in your case.

Related

How to copy text to clipboard in Vaadin without addon

I have spent long, too long, trying to make all of the directory components work. They took something that could have been super simple and made it complex and difficult.
I finally buckled and tried to copy a javascript function into my project and it worked with a simple method. The method, like all others need to be initiated by some other component. But do that in any way you want it.
All I'll leave here is the code. And the hope that somebody else will find this and use less time than I did.
void copyText(String text){
Page page = UI.getCurrent().getPage();
page.executeJavaScript(
"var el = document.createElement('textarea');" +
"el.value = $0;" +
"el.setAttribute('readonly', '');" +
"el.style = {" +
" position: 'absolute'," +
" left: '-9999px'" +
"};" +
"document.body.appendChild(el);" +
"el.select();" +
"document.execCommand('copy');" +
"document.body.removeChild(el);",
text);
}
void copyText(String text){
Page page = UI.getCurrent().getPage();
page.executeJavaScript(
"var el = document.createElement('textarea');" +
"el.value = $0;" +
"el.setAttribute('readonly', '');" +
"el.style = {" +
" position: 'absolute'," +
" left: '-9999px'" +
"};" +
"document.body.appendChild(el);" +
"el.select();" +
"document.execCommand('copy');" +
"document.body.removeChild(el);",
text);
}
Just to further explain the code. We need to initialize the page and then execute plain javascript-code. In this code, I pass the String text as a parameter and the utilize it in the code as "$0".
The code creates a textarea, which most of the addons in the directory also does. It then sets the text-string, it hide it from the ui with some styling. Then the textarea gets added to our file, because you are only allowed to copy a value that is visible. Then the text is selected, copied, and lastly the textarea is removed. Fast and clean.
You can also add the following line:
el.setSelectionRange(0, 99999);
Add it after the el.select(); line. According to W3, it should work better on phones with this line, but I have not tested it.

How to run a specific javascript code before page load (first of all)?

I have a js code that adds margin-top to a row with a specific class name (page with id=3) . I would like this code runs before page load because now it instantly displays the row without margin-top and then add it. The row should be displayed with the margin-top already be added.
My site is on wordpress and i added the js script on head.
I have tried
window.onpaint = checkMargin();
but it did not work. Any idea?
This is my js code
<script type="text/javascript">
//sets margin-top in serv-col --- IF not mobile version
function addServMargin() {
containers = document.getElementsByClassName('serv-cont');
titles = document.getElementsByClassName('serv-col-title');
texts = document.getElementsByClassName('serv-col-text');
links = document.getElementsByClassName('serv-col-link');
col_pad = '0px';
if ( window.innerHeight > 800) { col_pad = '8.3vh'; }
for (var i = 0; i < titles.length; i++) {
title_height = titles[i].offsetHeight;
text_height = texts[i].offsetHeight;
style = window.getComputedStyle(containers[i], '');
cont_height = style.getPropertyValue('height');
cont_padd = style.getPropertyValue('padding-top');
links[i].style.marginTop = 'calc(' + cont_height + ' - ' +
cont_padd + ' - ' + col_pad + ' - ' + title_height + 'px - 1.48vh - ' +
text_height + 'px - 127px - 5vh)';
}
}
function checkMargin() {
if (document.getElementsByClassName('page-id-13')[0] && window.innerWidth > 900) { addServMargin(); }
}
window.onresize = checkMargin;
</script>
I don't think making it run first will solve anything. The first thing the code does is get the containers, titles, texts, and links... which it does by searching the DOM. It then loops through the titles array and does the adjusting as needed. If the script runs before any rendering is done, the DOM elements won't exist. It 1) won't be able to find them, and 2) can't loop through them because the array will be empty.
Actually even before that, it checks for the existence of the elements it's looking for, and the screen size. I think the only way to get it to work w/o making it look like an after thought adjustment, would be to use CSS and media sizing to set the styles in the first place.
As I know JS is executed as the script tag is reached by Browser html interpreter. So putting it in the head tag on the first position may guarantee that it strats first, but can't guarantee that it ends execution before page loads, because the page loads asynchroniously.

iframe content editable that is within a jqueryUI sortable list item

I am having an issue where when I have a rich text field that has design mode on that is within a sortable LI. The problem is that designMode changes to 'Off' if the LI is dragged to a new position. And even though I am able to target the correct iframe, it seems to ignore when I tell it to turn designMode back on, does anyone know why this is happening? Thank you for reading.
edit: jfiddle example: https://jsfiddle.net/LcLfaa8j/2/
function getRichTextField( itemId ) {
console.log('get rich text field');
return document.getElementById('rtf-iframe-' + itemId);
}
$(document).ready(function() {
getRichTextField("sort-textItem-1").contentWindow.document.designMode = "On";
getRichTextField("sort-textItem-2").contentWindow.document.designMode = "On";
$("#text-areas").sortable({
axis: 'y',
opacity: 0.8,
tollerence: 'pointer',
update: function(event, ui) {
var order = $(this).sortable( "serialize", { key: "order" } );
console.log("New Order = " + order);
console.log('the iframe that was moved designMode is now Off...');
var richText = getRichTextField($(this).data().uiSortable.currentItem.attr('id'));
console.log('TEST: the current is ' + $(this).data().uiSortable.currentItem.attr('id') + ' and designMode = ' + richText.contentWindow.document.designMode + ' doubleCheckOfRTFid = ' + $(richText).attr('id') + ' class = ' + $(richText).attr('class'));
richText.contentWindow.document.designMode = "On";
console.log('just attempted to turn designMode back on... but it gets ignored');
}
});
});
This is only a partial answer.The designMode isn't getting turned off; it's off by default, and the document where you set .designMode="on" gets deleted (and then recreated from scratch) when you drag it. You can see that if you put content into one of the documents and then drag it, the content disappears. Looks like the iframe is getting a new .contentWindow, which means it has a different child document too.
You can see this in the inspector by running
var before = getRichTextField("sort-textItem-2");
var beforecontentWindow=before.contentWindow;
(now drag the second iframe)
Now run:
var after = getRichTextField("sort-textItem-2");
var aftercontentWindow=after.contentWindow;
Now compare the references with ===:
before === after ;//returns true because iframe is the same
beforecontentWindow === aftercontentWindow ;//returns false because .contentWindow changed
Also see this post Jquery Sortable and Draggable between parent and child frame where he had trouble implementing draggable and sortable, but got it working eventually.
I don't know why being dragged would give your iframe a new contentWindow, but maybe someone else will be able to build on this.
It seems like adding a small delay with a setTimeout before turning designMode back on fixes this. Is this the only solution? Just curious.

Check if file exists and display it - order of code

Section on my website with a list of fixtures.
When I click on one it loads the fixture information in a modal window - each club might have an image so I want to check if there is an image and if so display it otherwise display a generic image.
Code is below:
$('#fixModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget) // Button that triggered the modal
var hclub = button.data('hclubid')
imgpath = "/images/clubcrests/"
homecrest = imgpath + hclub + '.jpg'
gencrest = imgpath + 'generic.jpg'
$.get(homecrest)
.done(function() {
homecrestsrc = "<img src='" + homecrest + "'>"
}).fail(function() {
homecrestsrc = "<img src='" + gencrest + "'>"
})
var modal = $(this)
modal.find('.fixmodhomec').html(homecrestsrc)
})
But first time I click on it I get the error:
homecrestsrc is not defined
$.get seems to run after the modal.find.
If I click on it again it displays but always the image it should have from the previous time the button was clicked.
How can I make sure the $.get bit runs first?
$.get is asynchronous - did you read the documentation? modal.find will have run long before the response from the AJAX call has come back.
You need to move your logic inside the callbacks.
$('#fixModal').on('show.bs.modal', function (event) {
var button = $(event.relatedTarget),
hclub = button.data('hclubid'),
modal = $(this),
imgpath = "/images/clubcrests/",
homecrest = imgpath + hclub + '.jpg',
gencrest = imgpath + 'generic.jpg',
homecrestsrc;
$.get(homecrest).done(function () {
homecrestsrc = "<img src='" + homecrest + "'>"
}).fail(function () {
homecrestsrc = "<img src='" + gencrest + "'>"
}).always(function () {
modal.find('.fixmodhomec').html(homecrestsrc);
});
})
This is the confusing part of js/ajax. This code does not wait for an ajax reaponse, but instead jumps strait on to the next command. Meaning it will continue with your last 2 lines of code, and then the response will come thru, and then it will set homecrestsrc for the first time. Whatever you expect to be done AFTER you get a reaponse, you put in done and fail. So put your last or last 2 lines of code inside the done and fail functions.
Sry typing from phone so i cant explain too much, but hope you understand, it takes time to get it bcs this is not so obvious when you start
The other answer kind of covered this... basically, if the code below the get request depends on that result, it needs to be inside the .done() block.
However, I'd also like to point out that having your image displaying depend on the whether a get request fails or not is bad practice. You should instead have your api route return true if that image exists or false if it doesn't. Then you can filter the correct picture in a .success() block.
.fail() should be reserved for debugging only.

Scroll Down on innerHTML function in Native JS

I am currently working on a project that will not allow for jQuery and I am very new to JavaScript and OO programming as a whole (Two weeks). You can see the entirety of the project in the following repo: https://github.com/Sntax/jsChat/blob/master/script.js
Basically I am creating a simple chat app as a learning project. I am injecting HTML into a div onClick or Enter/Tab using:
var jsChat = {
username: document.getElementById('username'),
comment: document.getElementById('comment'),
output: document.getElementById('output'),
};
function postData(){
jsChat.output.innerHTML += '<div class="username">' + jsChat.username.value + ':' + '</div>';
jsChat.output.innerHTML += '<div class="comment">' + jsChat.comment.value + '</div>';
clearContent();
}
Right now it will print to the div successfully but once the "comments" become too long for the div-height, they will auto scroll off the bottom of the page and out of sight. I am wanting to setup a way to force the scroll to "scroll down" to the bottom of the div so that it rather scrolls old chat comments off the top of screen much like a normal chat application.
Ninja Edit: I tried throwing the following into the postData() function to no avail:
jsChat.output.scrollTop = 99999;
You can use the scrollTo function, as follows:
window.scrollTo(DOMNodeOfMyNewMessage.offsetTop,window.scrollY);
This will scroll the latest message to the top of the screen.
However, you’ll probably want to scroll the page just by the height of the new comment. That is possible too, using the scrollBy function and the element’s offsetHeight:
window.scrollBy(DOMNodeOfMyNewMessage.offsetHeight, window.scrollY);
The final code might look somewhat like the following:
var jsChat = {
username: document.getElementById('username'),
comment: document.getElementById('comment'),
output: document.getElementById('output'),
},
latestMessage;//DOM Node of my new message
function postData(){
latestMessage = document.createElement('div');
latestMessage.innerHTML = '<div class="username">' + jsChat.username.value + ':</div>' +
'<div class="comment">' + jsChat.comment.value + '</div>';
clearContent();//Not sure what this does, check if you still need it with this code
latestMessage = jsChat.output.appendChild(latestMessage);// appendChild: see https://developer.mozilla.org/docs/Web/API/Node.appendChild
//Now we scroll the message into view
window.scrollBy(latestMessage.offsetHeight, window.scrollY);
}
After posting the comment into output:
var last_comment_index = jsChat.comment.length - 1;
jsChat.output.scrollTop = jsChat.comment[last_comment_index].offsetTop;
This will scroll to the last comment. Hope that helps.

Categories