jquery scroll bottom to top, prepand and ajax - javascript

I need help creating a finite scroll bottom to top. Something like facebook messages.
I have checked this question but doesn't seem to work for me the way it supposed to. The problem is when I reach the top of the div and the end of the message list, it scrolls down and I can't see the the first few messages. How do I stop this from happening
And also, when new messages are prepended, the existing messages are pushed down, causing the user to lose their "viewing" place.
Here is what I have:
$(document).ready(function(){
$('#message_board').scrollTop($('#message_board')[0].scrollHeight);
});
$('#message_board').on('scroll', function() {
var scroll = $('#message_board').scrollTop();
if (scroll == 0) {
// Store eference to first message
var firstMsg = $('.bubble-left:first, .bubble-right:first');
// Prepend new message here
var content;
$.get("ajax/scripts/msg_lst.php?p=<?php echo htmlentities(isset($_GET['p']) ? $_GET['p'] : ''); ?>&f=" + ($('.bubble-left, .bubble-right').length), function(data){
content= data;
$('#message_board').prepend(content);
});
// After adding new message(s), set scroll to position of
// what was the first message
$('#message_board').scrollTop(firstMsg.offset().top);
}
});

your problem is here
$('#message_board').scrollTop(firstMsg.offset().top);
you can try this instead
$('#message_board').scrollTop(1);
or try to comment that line of code
//$('#message_board').scrollTop(firstMsg.offset().top);
but I think you should use scrollTop after prepend()
$('#message_board').prepend(content).promise().done(function(){
$(this).scrollTop(1);
});
finally .. add a specific class to the elements you want to prepend it and scrollTop the div to last element with this class name and after prepend() use removeClass() to remove this class again
$('#message_board').on('scroll', function() {
var scroll = $('#message_board').scrollTop();
if (scroll == 0) {
$(this).prepend(content).promise().done(function(){
$(this).scrollTop($('.newAppend:last').offset().top);
$('.newAppend').removeClass('newAppend');
});
}
});
Demo here
Note: wrap all your code inside $(document).ready(function(){// all of
your code here });

Make sure to scroll after the message has been prepended.
The $.get accepts a callback which is executed once you get a response from the server.
You should move .scrollTop there as well, as you want to execute that function after you've prepended the content, inside the callback. As everything outside the callback will usually be executed before that callback.
Something like this:
$(document).ready(function(){
$('#message_board').scrollTop($('#message_board')[0].scrollHeight);
});
$('#message_board').on('scroll', function() {
var scroll = $('#message_board').scrollTop();
if (scroll == 0) {
// Store eference to first message
var firstMsg = $('.bubble-left:first, .bubble-right:first');
// Prepend new message here
var content;
$.get("ajax/scripts/msg_lst.php?p=<?php echo htmlentities(isset($_GET['p']) ? $_GET['p'] : ''); ?>&f=" + ($('.bubble-left, .bubble-right').length), function(data){
content= data;
$('#message_board').prepend(content);
// After adding new message(s), set scroll to position of
// what was the first message
$('#message_board').scrollTop(firstMsg.offset().top);
});
}
});

So, I finally figured it out. Something was stopping the $('#message_board').scrollTop(firstMsg.offset().top-curOffset); event. I tried .unbind and .off, it never work. So what I did is added that line to the success attribute of ajax.
$(document).ready(function(){
$('#message_board').scrollTop($('#message_board')[0].scrollHeight);
});
$('#message_board').on('scroll', function() {
var scroll = $('#message_board').scrollTop();
if (scroll < 1) {
// Store eference to first message
var firstMsg = $('.bubble-left:first, .bubble-right:first');
var curOffset = firstMsg.offset().top - $('#message_board').scrollTop();
// Prepend new message here
var content;
$.get("ajax/scripts/msg_lst.php?p=<?php echo htmlentities(isset($_GET['p']) ? $_GET['p'] : ''); ?>&f=" + ($('.bubble-left, .bubble-right').length), function(data){
content= data;
$('#message_board').prepend(content);
$('#message_board').scrollTop(firstMsg.offset().top-curOffset);// <<---- Had to add this event to the ajax success function
});
}
});

Related

JQuery $.post callback firing a function that never finishes

Here's the problem. I'm making a callback to the server that receives an MVC partial page. It's been working great, it calls the success function and all that. However, I'm calling a function after which iterates through specific elements:
$(".tool-fields.in div.collapse, .common-fields div.collapse").each(...)
Inside this, I'm checking for a specific attribute (custom one using data-) which is also working great; however; the iterator never finishes. No error messages are given, the program doesn't hold up. It just quits.
Here's the function with the iterator
function HideShow() {
$(".tool-fields.in div.collapse, .common-fields div.collapse").each(function () {
if (IsDataYesNoHide(this)) {
$(this).collapse("show");
}
else
$(this).collapse("hide");
});
alert("test");
}
Here's the function called in that, "IsDataYesNoHide":
function IsDataYesNoHide(element) {
var $element = $(element);
var datayesnohide = $element.attr("data-yes-no-hide");
if (datayesnohide !== undefined) {
var array = datayesnohide.split(";");
var returnAnswer = true;
for (var i in array) {
var answer = array[i].split("=")[1];
returnAnswer = returnAnswer && (answer.toLowerCase() === "true");
}
return returnAnswer;
}
else {
return false;
}
}
This is the way the attribute appears
data-yes-no-hide="pKanban_Val=true;pTwoBoxSystem_Val=true;"
EDIT: Per request, here is the jquery $.post
$.post(path + conPath + '/GrabDetails', $.param({ data: dataArr }, true), function (data) {
ToggleLoader(false); //Page load finished so the spinner should stop
if (data !== "") { //if we got anything back of if there wasn't a ghost record
$container.find(".container").first().append(data); //add the content
var $changes = $("#Changes"); //grab the changes
var $details = $("#details"); //grab the current
SplitPage($container, $details, $changes); //Just CSS changes
MoveApproveReject($changes); //Moves buttons to the left of the screen
MarkAsDifferent($changes, $details) //Adds the data- attribute and colors differences
}
else {
$(".Details .modal-content").removeClass("extra-wide"); //Normal page
$(".Details input[type=radio]").each(function () {
CheckOptionalFields(this);
});
}
HideShow(); //Hide or show fields by business logic
});
For a while, I thought the jquery collapse was breaking, but putting the simple alert('test') showed me what was happening. It just was never finishing.
Are there specific lengths of time a callback function can be called from a jquery postback? I'm loading everything in modal views which would indicate "oh maybe jquery is included twice", but I've already had that problem for other things and have made sure that it only ever includes once. As in the include is only once in the entire app and the layout is only applied to the main page.
I'm open to any possibilities.
Thanks!
~Brandon
Found the problem. I had a variable that was sometimes being set as undefined cause it to silently crash. I have no idea why there was no error message.

Prevent calling ajax on scroll when already called

I have a plugin that tells me if an element is visible in the viewport with $('#element').visible() (set to true when visible).
Now I want to create a function that I scroll down a page and load new content with ajax. I have this so far:
window.onscroll = function() {
console.log($('#ele').visible());
if ($('#ele').visible()) {
//ajax call comes here
}
};
As soon as I see the element my log shows true:
I don't have problems implementing the ajax-request now, but shouldn't I block this function to occur only once? How could I prevent that a new element that already has been loaded to load again (prevent using ajax again)?
I thought of using a boolean-variable, but my problem is that I don't know how to implement that because if I set a variable, how would the browser know it's value? Because on every move of my mousewheel it cant remember what that variable's value was?
EDIT:
I tried the code of Ismail and it never reaches the ajax call (alert won't show).
window.onscroll = function() {
var ajaxExecuted = false;
var ele = $('#load_more').visible();
console.log(ele);
return function() {
if (ajaxExecuted) return;
if (ele) {
alert("OK");
var ajaxArray;
ajaxArray = { page: 2 }
ajaxLoadContent(ajaxArray, "load_more", "ajax_load");
ajaxExecuted = true;
}
}
};
You can use this:
window.onscroll = (function() {
var ajaxExecuted = false;
return function() {
if(ajaxExecuted) return;
if ($('#ele').visible()) {
$.ajax({...}).success(function() {
//Your code here;
ajaxExecuted = true;
});
}
}
})();
One easy solution: set a boolean to true when the element first becomes visible and set it to false when it stops being visible. Only fire the request if those states mismatch (i.e. if it's visible but the boolean is false - that means it's the first time you've seen the window. You'd then set the bool afterwards so it won't fire off anymore until it disappears and reappears again).

Click on a button until it disappears in CasperJS

I have this sample of my code:
function clickOldShares() {
console.log("Waiting for all shares");
element = document.querySelector("#pagelet_scrolling_pager > div > div > a");
return element;
}
casper.thenOpen("https://www.facebook.com/shares/view?id=" + fb_objectID,function(){
console.log("Open post with object-id");
});
casper.then(function(){
element = this.evaluate(clickOldShares);
});
casper.wait(2000,function() {
console.log('ELEMENT1: ' + element);
element = this.evaluate(clickOldShares);
});
casper.wait(2000,function() {
newelement = this.evaluate(clickOldShares);
console.log('ELEMENT2: ' + newelement);
});
casper.wait(2000,function() {
newelement = this.evaluate(clickOldShares);
console.log('ELEMENT3: ' + newelement);
});
I´m not understanding how can I transform this calls to clickOldShares in a loop using CasperJS because casper.wait is asynchronous. May I have some example of how to do this, please?
The page doesn't load all data in one time. It's necessary to click on the 'Older Shares' button until the data appears. And this can happen many times, depending the amount of data. So, I need to click as often as needed before capturing data.
First thing's first, you can't use clickOldShares for anything as it is now. casper.evaluate() provides access to the DOM, but the passed in function is sandboxed and executed in the page context. All data must be explicitly passed in and out, and this has to be primitive. DOM elements are not primitive and cannot be passed out of the page context (this.evaluate(clickOldShares) will always return null). You will either have to call the click code inside of the page context.
You can wait for an element to appear with waitForSelector. You really don't need to iterate to wait for it.
var selector = "#pagelet_scrolling_pager > div > div > a";
casper.start()
.thenOpen(url)
.waitForSelector(selector, null, null, 15000); // max 15 seconds
.then(function(){
this.capture("screen1.png");
this.click(selector);
})
.then(function(){
this.capture("screen2.png");
})
.run();
The third argument for waitForSelector is the callback for when the timeout is reached, but the element is not found. The fourth argument is a custom timeout. The default timeout is set to 10 seconds.
It seems you need to click on a certain selector until it disappears. You can't use a loop for this, because the functions are asynchronous. You will have to use recursion like this:
var selector = "#pagelet_scrolling_pager > div > div > a";
var i = 0;
function step() {
if (this.exists(selector)) {
this.capture("screen"+(i++)+".png");
this.click(selector);
this.wait(2000, step);
} else {
this.capture("screen_final.png");
}
}
casper.start()
.thenOpen(url)
.then(step)
.then(function(){
// TODO: do something else
})
.run()

Off click of focused div not activating on jQuery function

Here is my HTML
$html .= " <td><div class='edit_course' data-id='{$id}' data-type='_title' contenteditable='true'>{$obj->title}</div></td>";
Here is my jQuery:
var selector = 'div[contenteditable="true"]';
// initialize the "save" function
$(selector).focus(function(e) {
content_holder = $(this);
content = content_holder.html();
var id = $(this).data('id');
var type = $(this).data('type');
alert( id + type)
// one click outside the editable area saves the content
$('body').one('click', function(e) {
// but not if the content didn't change
if ($(e.target).is(selector) || content == content_holder.html()) {
return;
}
// Edited out AJAX call
});
});
The problem is, when I click on the div, the alert below triggers. When I click outside of the div (after the edit has been made), nothing happens. Can anyone see what is happening?
First click let's user edit content in div. The first click outside, should make ajax call to save.
EDIT: From recommendation below.
Here is new code this works perfectly except it calls the DB every time, all I need is a check to do that only if data is different and I think I got it from there.
Edit2: Final Code
var original_value = '';
$(".edit_course").focus(function(e) {
original_value = $(this).html();
});
// initialize the "save" function
$(".edit_course").blur(function(e) {
var content = $(this).html();
var id = $(this).data('id');
var type = $(this).data('type');
if (content !== original_value) {
// Ajax edited out
}
});
You're not using one() correctly. What you should do is run the AJAX when focus is taken away from the div. Using blur() would probably be your best bet here.
$('body').blur(function(e) {
// your code here...
});

Javascript and JQuery conflict

I'm not very expert in using javascript and jquery but I'm working with them for a client.
I have encountered a problem using two script: the first one makes a top panel sliding, the second is in a form. This one is used in order to hide or show a particular field basing on the drop down list choice.
I've found that if I disable the first script (the panel), the second script is working fine and vice versa. I tried usign JQuery noConflict() in the head of the page but nothing happened.
Here the code of the first script (sliding panel):
$(document).ready(function () {
// Lets make the top panel toggle based on the click of the show/hide link
$("#sub-panel").click(function () {
// Toggle the bar up
$("#top-panel").slideToggle();
// Settings
var el = $("#shText");
// Lets us know whats inside the element
var state = $("#shText").html();
// Change the state
state = (state == 'Nascondi' ? '<span id="shText">Entra</span>' : '<span id="shText">Nascondi</span>');
// Finally change whats insdide the element ID
el.replaceWith(state);
}); // end sub panel click function
}); // end on DOM
Here the JS code for the form (hide/show field):
$document.addEvent('domready', function () {
$('motivo_contatto').addEvent('change', function () {
if ($('motivo_contatto').value == 'Invia CV') {
$('upload_file').style.visibility = 'visible';
} else {
$('upload_file').style.visibility = 'hidden';
}
});
$('upload_file').style.visibility = 'hidden';
});
});
Can anyone help me ? Thank you and have a nice day!
you're using 2 different ways to add things to happen to the document ready event:
$(document).ready(function(){ ... });
and
$document.addEvent('domready', function() { ... });
maybe if you just use one it works; maybe the code below will work; I put it all in the first option to run code on document ready:
I edited below code and removed all mootools code; so it might work now.
$(document).ready(function(){
// Lets make the top panel toggle based on the click of the show/hide link
$("#sub-panel").click(function(){
// Toggle the bar up
$("#top-panel").slideToggle();
// Settings
var el = $("#shText");
// Lets us know whats inside the element
var state = $("#shText").html();
// Change the state
state = (state == 'Nascondi' ? '<span id="shText">Entra</span>' : '<span id="shText">Nascondi</span>');
// Finally change whats insdide the element ID
el.replaceWith(state);
}); // end sub panel click function
document.getElementById('motivo_contatto').onchange = function() {
if(document.getElementById('motivo_contatto').value == 'Invia CV') {
document.getElementById('upload_file').style.visibility = 'visible';
} else {
document.getElementById('upload_file').style.visibility = 'hidden';
}
};
document.getElementById('upload_file').style.visibility = 'hidden';
}); // end on DOM
Mixing up two different libraries. Not a good idea.
If you want to keep on following on that pattern, wrap one of the function on a different function and call if from another.
Like:
function moo() {
$('motivo_contatto').addEvent('change', function () {
if ($('motivo_contatto').value == 'Invia CV') {
$('upload_file').style.visibility = 'visible';
} else {
$('upload_file').style.visibility = 'hidden';
}
});
$('upload_file').style.visibility = 'hidden';
});
}
Then call it from another
$(document).ready(function () {
moo(); // Call the moo function
// Lets make the top panel toggle based on the click of the show/hide link
$("#sub-panel").click(function () {
// Toggle the bar up
$("#top-panel").slideToggle();
// Settings
var el = $("#shText");
// Lets us know whats inside the element
var state = $("#shText").html();
// Change the state
state = (state == 'Nascondi' ? '<span id="shText">Entra</span>' : '<span id="shText">Nascondi</span>');
// Finally change whats insdide the element ID
el.replaceWith(state);
}); // end sub panel click function
}); // end on DOM
Check this answer, if you want to use both libraries side by side

Categories