DOM Elements loaded from ajax and (probably) not ready in time - javascript

I'm using sorting/filtering jQuery plugin Isotope and also jQuery $.ajax() to dynamically load some new elements to the page that need to be sorted with Isotope. That library seems to set all the new sorted elements with absolute position and with fixed (left, top) position in order to perform sorting.
The problem is that when you load the first set of elements with clear cache the the element positions in that absolute grid are incorrect (they are overlapping). This is caused by Isotope initialization. My (inexperienced) guess would be that the all the new DOM elements are not fully loaded by the time Isotope starts to calculate the future positions of the elements and there's where the in-accuracy comes in. If I do the exact same ajax request again it manages to calculate the positions correctly.
EDIT #1 ajax reuqest
var $isocont = $('#page-content-result');
var isoActive = false;
$.ajax({
url: actionUrl + 'ajax',
type: 'POST',
data: searchData,
success:function (data) {
if(data.trim().length > 0) {
$('#page-content-result').hide().empty().html(data).fadeIn(600);
initIsotope();
} else {
var visible = $('#page-content-result').is(':visible');
if(visible === true)
{
$('#page-content-result').empty();
}
$('#result-notif').show();
}
}
});
var initIsotope = function() {
if(isoActive === true) {
$isocont.isotope('destroy');
console.log('iso stop');
isoActive = false;
}
if(isoActive === false) {
$isocont.isotope({
getSortData: {
name: '.iso-docname',
}
});
console.log('iso start');
isoActive = true;
}
}
Can someone explain the nature of this problem and give few hints for solution?
Thnx!

Seems that once again the "morning is smarter than the night" (it's a saying in Estonian :P).
I placed a piece of test-code after part where the new data was inserted to the DOM.
if (/complete/.test(document.readyState)) {
alert('loaded');
}
The alert kind of pauses the loading of the page and I realized that the containers where quite a lot shorter because of the not-yet-loaded images. Meaning the DOM doesn't have the information how high the containers are going to be when we initialize the Isotope and that's why the Isotope fails to calculate the correct positions.
FIX: After understanding the problem the fix is really simple just tell the DOM how high the image is going to be in CSS with min-height: 122px; and that is all. I guess if the image height varies then I believe setting an interval checker for document.readyState == 'loaded' would help.
Idea came from that blog http://callmenick.com/2014/06/04/check-everything-loaded-javascript/
So my general js file now looks something like that:
var $isocont;
$(document)(function() {
//When DOM create a var of selector.
$isocont = $('#page-content-result');
//One time aka first init of Isotope with basic options.
$isocont.isotope({
getSortData: {
name: '.iso-docname',
}
});
$.ajax({
url: actionUrl,
type: 'POST',
data: searchData,
success:function (data) {
if(data.trim().length > 0) {
//Set HTML data from ajax request
$('#page-content-result').hide().empty().html(data).fadeIn(600);
//Reload isotope items and re-arrange the items according to config.
reloadIsotope();
} else {
//DO SMTH
}
}
});
});
var reloadIsotope = function() {
$isocont.isotope('reloadItems');
//We are resetting sorting since we might not want same ordering as on last result.
$isocont.isotope({sortBy: null, sortAscending: true});
}
Thanx TimSPQR for looking into!

Related

jQuery text change from HTML range slider

I'm new at jQuery, so I expect there is an easy answer.
I have a JSON file with different text strings for different dates. I also have an html range slider that I uses the <datalist> tag to define specific dates on the slider. I have written a $.getJSON function that nests a $.each() function to pull the strings from the JSON file.
I need to write in the functionality to display different strings based on the slider position.
This is my jQuery:
var location = $('#state-dates')[0];
$(document).on('input', '#state-dates', function() {
if (location === 1911) {
$.getJSON('Arizona.json', function(inputOne){
$.each(inputOne.first, function(i, field){
$("#leg-control").html(field.legControl);
});
});
}
else if (location === 1943) {
$.getJSON('Arizona.json', function(inputTwo){
$.each(inputTwo.second, function(i, field){
$("#leg-control").html(field.legControl);
});
});
}
});
And my HTML:
<input type="range" min="1911" max="2013" class="bar" step=".1" list="date-list" value="1911" id="state-dates">
Is there a different jQuery method that I should be using to detect the change in the slider, and so display the new string? I also realize that I should probably use < or > instead of = since I want the same text to only change when it reaches a new defined position. Thank you!
EDIT
To help clarify, I'm adding in the relevant JSON and HTML.
JSON:
{
"first": [
{
"legControl": "Not recorded",
}],
"second": [
{
"leg-control": "Democratic",
}]
}
And the HTML for entering the text:
<div class="json-text">
<p class="fill-in" id="leg-control"></p>
</div>
I was able to work out a solution with a web developer friend of mine, so in case anyone else stumbles across this question, the solution I used:
//call the JSON file and store it as a variable called jdata
$.getJSON('Arizona.json', function(json) {
jdata = json;
});
var slider = $('#sliderValue')[0];
slider.oninput = function() {
var position = this.value;
var jrows = $(jdata).filter(function (i, n) {
return n.sYear <= position && n.eYear > position;
});
$("#year-start").html(jrows[0].jsonLineOne);
$("#year-end").html(jrows[0].jsonLineTwo);
};
Essentially this takes the slider input, and runs a return command to check the position. If it is within the bounds of one subsection of the JSON file, then that section is pulled. Hopefully this helps anyone else who comes across it in the future.

Node / Express - Append information to list when user hits bottom of page using Snoocore (Reddit API wrapper)

I am working on a private application of a food sub reddit, what I'm retrieving some images witha limit of 20 images per view and when the user reach the bottom of the page it loads more information and appends it, like ajax; I'm doing the request server side and using Snoocore which is an API wrapper for Reddit. Snoocore actually provides a method to fetch information of the next page. From Snoocore's documentation:
// Instead of an HTTP verb, use a Snoocore helper `.listing`
// It will return a "slice" for a listing that has various
// helpers attached to it.
reddit('/r/askreddit/hot').listing().then(function(slice) {
// This is the front page of /r/askreddit
// `slice.children` contains the contents of the page.
console.log(slice.children);
// Get a promise for the next slice in this
// listing (the next page!)
return slice.next();
}).then(function(slice) {
// This is the second page of /r/askreddit
console.log(slice.children);
});
And this is my code:
reddit('/r/food/hot').listing({limit: 10}).then(result => {
for (var x in result.children){
if (result.children[x].data.link_flair_text === '[Homemade]' || result.children[x].data.link_flair_text === '[homemade]') {
if (result.children[x].data.domain !== 'imgur.com') {
if (result.children[x].data.post_hint !== "rich:video") {
titles.push({
'title': he.decode(result.children[x].data.title),
'imgurl': he.decode(result.children[x].data.url),
'user': result.children[x].data.author,
'submission': result.children[x].data.permalink
});
}
}
}
}
console.log(result.children[4].data);
res.render('index', {
'titleArr': titles
});
});
on my Pug template I have:
extends layout
block content
div#wrap
each i in titleArr
figure.figure
img.img-fluid.img-rounded(src= i.imgurl)
figcaption.figure-caption.text-center
p.text-muted #[i #{i.title}]
p
| #[i ―by ]
a(href="http://reddit.com" + i.submission, target="_blank")
| #[i /u/#{i.user}]
listing({limit: 10}) here is important because, well, it limits the post retrieved; actually I'm retrieving only 10, by default it retrieves 25; anyway, I'm looking for a way, how i said that when the user reach bottom of the page it loads more post; do I have to do this via client side? Or can I do this via server side in some way? Tell me if I need to explain my issue more in depth.
What you are looking for is "infinite scrolling", you basically want to do another request when your user reaches the bottom of the page that has been rendered.
This is going to require you to fire an ajax request when your browser scroll reaches the bottom of the page. If you were to use jQuery, your code would look something like this:
$(document).ready(function() {
var win = $(window);
var parts = location.pathname.split("/");
// assuming a url like domain.com/reddit/10 where 10
// is the total number of posts you're showing
var pageNumber = parts[3]; //this gives you access to the `10`
// then add 10 to the current page number and
pageNumber += 10
// use that for your ajax request
var url = "http://domain.com/reddit/"+pageNumber;
// Each time the user scrolls
win.scroll(function() {
// End of the document reached?
if ($(document).height() - win.height() == win.scrollTop()) {
$('#loading').show();
$.ajax({
url: url,
dataType: 'json',
success: function(json) {
// do something with your json of your posts
// then hide the loading gif or image.
$('#loading').hide();
}
});
}
});
});
This will fire the ajax request whenever your user reaches the bottom of the page, and paginate correctly. This is only the browser side, though.
You will need to edit your server endpoint slightly as well.
For example, you need to increase your limit dynamically
//you could use the req.params method from express to accomplish this
reddit('/r/food/hot').listing({limit: req.params.limit}).then(result => {
for (var x in result.children){
if (result.children[x].data.link_flair_text === '[Homemade]' || result.children[x].data.link_flair_text === '[homemade]') {
if (result.children[x].data.domain !== 'imgur.com') {
if (result.children[x].data.post_hint !== "rich:video") {
titles.push({
'title': he.decode(result.children[x].data.title),
'imgurl': he.decode(result.children[x].data.url),
'user': result.children[x].data.author,
'submission': result.children[x].data.permalink
});
}
}
}
}
console.log(result.children[4].data);
res.render('index', {
'titleArr': titles
});
});
Accessing the req.params.limit variable should work. You will need to edit your endpoint definition as well. I'm not sure how you have your server's routing setup right now, but this would be one way you could do it:
app.get('/reddit/posts/:limit', function(req, res) {
console.log('limit is: ', req.params.limit);
})
The idea here is that you want to increase the number of items you're fetching with each jQuery request. this is a pretty quick and dirty way of doing so, and I'm making some assumptions about how you're handling the server and what you're using, but this should get you 90% of the way there. Hope this helps!

How do you refresh an HTML5 datalist using JavaScript?

I'm loading options into an HTML5 datalist element dynamically. However, the browser attempts to show the datalist before the options have loaded. This results in the list not being shown or sometimes a partial list being shown. Is there any way to refresh the list via JavaScript once the options have loaded?
HTML
<input type="text" id="ingredient" list="ingredients">
<datalist id="ingredients"></datalist>
JavaScript
$("#ingredient").on("keyup", function(event) {
var value = $(this).val();
$.ajax({
url: "/api/ingredients",
data: {search: value.length > 0 ? value + "*" : ""},
success: function(ingredients) {
$("#ingredients").empty();
for (var i in ingredients) {
$("<option/>").html(ingredients[i].name).appendTo("#ingredients");
}
// Trigger a refresh of the rendered datalist
}
});
});
Note: In Chrome and Opera, the entire list is only shown if the user clicks on the input after entering text. However, I'd like the entire list to appear as the user types. Firefox is not a problem, as it appears to refresh the list automatically when the options are updated.
UPDATE
I'm not sure this question has a satisfactory answer, as I believe this is simply a shortcoming of certain browsers. If a datalist is updated, the browser should refresh the list, but some browsers (including Chrome and Opera) simply do not do this. Hacks?
Quite a long time after question but I found a workaround for IE and Chrome (not tested on Opera and already OK for Firefox).
The solution is to focus the input at the end of success (or done) function like this :
$("#ingredient").on("keyup", function(event) {
var _this = $(this);
var value = _this.val();
$.ajax({
url: "/api/ingredients",
data: { search: value.length > 0 ? value + "*" : "" },
success: function(ingredients) {
$("#ingredients").empty();
for (var i in ingredients) {
$("<option/>").html(ingredients[i].name).appendTo("#ingredients");
}
// Trigger a refresh of the rendered datalist
// Workaround using focus()
_this.focus();
}
});
It works on Firefox, Chrome and IE 11+ (perhaps 10).
I had the same problem when updating datalist.
The new values would not show until new input event.
I tried every suggested solutions but nothing using Firefox and
updating datalist via AJAX.
However, I solved the problem (for simplicity, I'll use your example):
<input type="text" id="ingredient" list="ingredients" **autocomplete="off"**>
<datalist id="ingredients"></datalist>
$("#ingredient").on("**input**", function(event) { ....}
Autocomplete and input is the couple that solve my problems and it works with Chrome too.
You can probably eliminate the problem if you don't make AJAX request on every key stroke. You can try throttle technique using set/cleatTimeout to issue request after 500ms after the last char typed:
$("#ingredient").on("keyup", function(event) {
clearTimeout($(this).data('timeout'));
$(this).data('timeout', setTimeout($.proxy(function() {
var value = $(this).val();
$.ajax({
url: "/api/ingredients",
data: {search: value.length > 0 ? value + "*" : ""},
success: function(ingredients) {
$("#ingredients").empty();
for (var i = 0; i < ingredients.length; i++) {
$("<option/>").html(ingredients[i].name).appendTo("#ingredients");
}
}
});
}, this), 500));
});
Yoyo gave the correct solution, but here's a better way to structure your inserts into the DOM.
$("#ingredient").on("keyup", function(event) {
var _this = $(this);
var value = _this.val();
$.ajax({
url: "/api/ingredients",
data: { search: value.length > 0 ? value + "*" : "" },
success: function(ingredients) {
var options = ingredients.map(function(ingredient) {
var option = document.createElement('option');
option.value = ingredient.name;
return option;
});
$("#ingredients")
.empty()
.append(options);
// Trigger a refresh of the rendered datalist
// Workaround using focus()
_this.focus();
}
});
Less DOM manipulation
With this refinement, I'm only inserting into the DOM a single time per each successful callback. This cuts down on the browser needing to re-render, and will help improve any "blips" in the view.
Functional Programming and Less Idiomatic jQuery
Here we are using the Array.prototype.map to clean up some of the jQuery and make things a bit less idiomatic. You can see from the ECMA Chart that this function will work in all browsers you are targeting.
Not Hacky
This by no means is hacky. IE appears to be the only browser that doesn't automatically refresh the input to display the new list options. focus() is just a way to ensure the input is refocused which forces a refresh of the view.
This solution works very well in all of the browsers that my company has to support internally, IE10+ Chrome and Firefox.
Place your #ingredients element is inside #container and try this code:
$.ajax({
url: "/api/ingredients",
data: {search: value.length > 0 ? value + "*" : ""},
success: function(ingredients) {
$("#ingredients").remove();
var item = $('<datalist id="ingredients"></datalist>');
for (var i in ingredients) {
item.append("<option>"+ ingredients[i].name +"</option>");
}
item.appendTo('#container');
}
});
even better without #container and using jQuery replaceWith():
$.ajax({
url: "/api/ingredients",
data: {search: value.length > 0 ? value + "*" : ""},
success: function(ingredients) {
var item = $('<datalist id="ingredients"></datalist>');
for (var i in ingredients) {
item.append("<option>"+ ingredients[i].name +"</option>");
}
$("#ingredients").replaceWith(item);
}
});
Your issue is that the AJAX is asynchronous.
You'd actually have to have a callback for the AJAX which you call onSuccess which would then update the datalist. Of course, then you might not have great performance/still have a "skipping" behavior, where your datalist options are lagging behind.
If your list of items from the AJAX isn't too large, you should:
1. load the ENTIRE list into memory array with the first query, then...
1. use a filtering function that is applied to the array each time you have a keyUp event.
I found a solution tested only on GNOME Web (WebKit) that consist on set the 'list' attribute of the input element to empty string and, inmediately after, set it again with the id of the datalist element. Here is the example, supose that your input element is stored in a variable named input_element:
var datalist = document.getElementById(input_element.list.id);
// at this point input_element.list.id is equals to datalist.id
// ... update datalist element here
// And now the trick:
input_element.setAttribute('list','')
input_element.setAttribute('list',datalist.id)

Trying to auto expand tree grid to the 3rd level with jqGrid

We are currently having a difficult time trying to auto expand a jqGrid treegrid to the 3rd level of the tree so that all children are visible. The current data set is thousands or rows deep and we were forced to dynamically load each node when requested to be expanded, which requires reloading the grid. Expanded node ids are saved in an array as a saved tree state so that the nodes can be re-expanded when the tree is redisplayed. This goes through the process of loading each node from the database as the expansion happens. AS each node is dynamically loaded and expended gridComplete and loadComplete events are handled as expected.
We are trying to trigger the 3rd level expansion by utilizing the save tree state and the existing logic to break out the tree appropriately within the existing logic. The problem we are experiencing is that the tree cannot expand out fast enough in order to be processed, and we can never break the tree apart completely.
Here is the function to iterate through the parents to capture the appropriate ids to expand:
function ExpandGridLevel(level) {
if (ExpandGridLevels == false) {
ExpandTotalLevels = level;
ExpandLevelCurrent = 0;
ExpandGridLevels = true;
}
if (!TreeExpandState) {
TreeExpandState = new Array();
}
$(".treeclick", "#Grid").each(function () {
if ($(this).hasClass("tree-plus")) {
var id = $(this).parents("tr").attr("id");
var rowLevel = $("#MaGrid").getLocalRow(id);
if (rowLevel.level == ExpandLevelCurrent) {
TreeExpandState.push(id);
$(this).removeClass("tree-plus");
$(this).addClass("tree-minus");
}
}
});
$(this).trigger("reloadGrid");
$("#Grid").jqGrid("setGridParam", { datatype: "local" });
ExpandLevelCurrent++;
RefreshGrid();
}
Our gridComplete and loadComplete sections of code:
loadComplete: function (data) {
$("#Grid").jqGrid("setGridParam", { datatype: "local" });
if (TreeExpandState.length == 0) {
setTimeout(function () { ExpandGridLevel (ExpandTotalLevels); }, 3000);
}
}
gridComplete: function () {
if (TreeExpandState) {
var rowId = TreeExpandState.shift();
ExpandNode(rowId);
}
}
Is what we are trying to do possible with jqGrid? If so, how do we know when the tree expansion is truly complete and we can reiterate over the expended grid to begin the expansion of the next level of parents?

JQuery/Javascript works on & off

I am using JQuery 1.3.2-min in a project to handle JavaScript animations, ajax, etc. I have stored the file on the same server as the site instead of using Google. When I run the site locally on my development machine, everything works fine in FF, IE, Opera, and Safari (all the latest versions - I work from home and I only have 1 machine for personal use and development use) except for some CSS differences between them and when I go to the live site on my machine it works fine also. I have cleared my caches and hard refreshed the page, and it still works.
This is where it gets interesting however. When I send the site to my boss to test in various OS/Browser configurations, one page doesn't work correctly, some of it works, some doesn't. Also, the client (who uses IE 8) has also confirmed that it is not completely working - in fact he has told me that the page will work fine for a hour, and then just "turn off" for a while. I have never heard of this sort of thing before, and google isn't turning too much up. I have a hunch it may partly be with JQuery's .data(), but I'm not sure.
The page is basically nested unordered lists, and three basic actions happen on the list.
The top most unordered list is set to visible (all list via css are set to display: none to keep them hidden on a fresh page request); all list items divs are given a hover action of full opacity on mouseon, and faded back to 50% opacity on mouseoff; and then whenver a paragraph is clicked, the top most unordered list in that list item is displayed.
Here is my Javascript file for the page:
$(function() {
// Set first level ul visible
$('div#pageListing ul:first').css('display', 'block');
// Disable all the hyperlinks in the list
$('div#pageListing li a').click(function() {
var obj;
obj = $(this).parent(0).parent('div:first');
highlight(obj);
return false;
});
// List Item mouse hovering
$('#pageListing li').hover(
// Mouse On
function() {
if ($(this).children('div').attr('id') !== 'activePage') {
$(this).children('div').css('opacity', 1).css('filter',
'alpha(opacity=100)');
}
}, // Mouse off
function() {
if ($(this).children('div').attr('id') !== 'activePage') {
$(this).children('div').css('opacity', 0.4).css('filter',
'alpha(opacity=40)');
}
});
// Active list item highlighting
$('#pageListing li div').click(function() {
highlight($(this));
});
// Sub-list expanding/collapsing
$('#pageListing p.subpageslink').click(function() {
// Get next list
var subTree = $(this).parent('div').next('ul');
// If list is currently active, close it, else open it.
if (subTree.data('active') != true) {
subTree.data('active', true);
subTree.show(400);
} else {
subTree.data('active', false);
subTree.hide(400);
}
});
// Double clicking of list item - edit a page
$('#pageListing li div').dblclick(function() {
var classes = $(this).attr('class');
var classArray = classes.split(' ');
var pageID = classArray[1];
editPage(pageID);
});
// Handle button clicking
$('button#addPage').click(function() {
addPage();
});
$('button#editPage').click(function() {
var div = $('div#activePage');
var classes = div.attr('class');
var classArray = classes.split(' ');
var pageID = classArray[1];
editPage(pageID);
});
$('button#delPage').click(function() {
var div = $('div#activePage')
var classes = div.attr('class');
var classArray = classes.split(' ');
var pageID = classArray[1];
delPage(pageID);
});
});
// Highlighting of page when clicked
function highlight(obj) {
// Get previous hightlighted element
// and un-highlight
var oldElement = $('div#activePage');
oldElement.css('background', 'white');
oldElement.css('opacity', 0.4).css('filter', 'alpha(opacity=40)');
oldElement.removeAttr('id');
// highlight current selection
obj.attr('id', 'activePage');
obj.css('opacity', 1).css('filter', 'alpha(opacity=100)');
obj.css('background', '#9dc0f4');
// add appropiate action buttons
$('button.pageButton').css('display', 'inline');
}
function addPage() {
window.location = "index.php?rt=cms/editPage";
}
function delPage(page) {
var confirm = window.confirm("Are you sure? Any sub-pages WILL BE deleted also.");
if (confirm) {
var url = './components/cms/controller/forms/deletePage.php';
$.ajax( {
url : url,
type : 'GET',
data : 'id=' + page,
success : function(result) {
if (!result) {
document.location = "index.php?rt=cms";
} else {
window.alert('There was a problem deleting the page');
}
}
});
}
}
function editPage(page) {
var url = "index.php?rt=cms/editPage/" + page;
window.location = url;
}
Is it possible that you are linking to (some of) the script files using a src that points to a file on your local disk/HDD? If so, that would explain why it works only on your machine, as then only your machine has access to the script file.
Thank you one and all for your suggestions. The end problem was miscommunication. I work from home, and upload my projects to a SVN server, which the boss then uses to update the live server. Somehow, the correct files were not getting updated - a communication error on my part. Another possible reason was that the page, while being declared XHTML 1.0 Strict, had something like 50 validation errors (mosting incorrectly nested UL), and I cleaned that up to 5 errors. So thank you all, but again a sad example of the importance of team work communication.

Categories

Resources