Unable to process foreach binding, wait for ajax result - javascript

I am new to knockout and I have the following issue.
Model:
function AdListModel() {
var self = this;
self.Ads = ko.observableArray([]);
this.addSome = function() {
$.ajax({
type: "GET",
url: '/Home/GetAllAds',
data: { startPosition: 0, numberOfItems: 10},
dataType: "json",
success: function(data) {
self.Ads.push(data);
},
error: function(err) {
alert(err.status + " : " + err.statusText);
}
});
};
this.addSome();
}
// The custom binding (code below) is for dynamic data loading on scroll event (like posts in facebook)
ko.bindingHandlers.scroll = {
updating: true,
init: function(element, valueAccessor, allBindingsAccessor) {
var self = this;
self.updating = true;
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(window).off("scroll.ko.scrollHandler");
self.updating = false;
});
},
update: function(element, valueAccessor, allBindingsAccessor) {
var props = allBindingsAccessor().scrollOptions;
var offset = props.offset ? props.offset : "0";
var loadFunc = props.loadFunc;
var load = ko.utils.unwrapObservable(valueAccessor());
var self = this;
if (load) {
element.style.display = "";
$(window).on("scroll.ko.scrollHandler", function() {
if (($(document).height() - offset <= $(window).height() + $(window).scrollTop())) {
if (self.updating) {
loadFunc();
self.updating = false;
}
} else {
self.updating = true;
}
});
} else {
element.style.display = "none";
$(window).off("scroll.ko.scrollHandler");
self.updating = false;
}
}
};
ko.applyBindings(new AdListModel());
HTML:
<div class="col-lg-12" data-bind="foreach: Ads">
// some code
</div>
<div data-bind="scroll: Ads().length < 50, scrollOptions: { loadFunc: addSome, offset: 10 }">loading</div>
So, initially AJAX loads 10 records from database and it renders perfectly fine. Then if I scroll down, it is supposed to add, push to my observable array another 10 (same records). It works If I add dummy data (without database) to AJAX SUCCESS, BUT if I want to push ajax result to observable array it gives me an error "Unable to process binding".
I understand that ajax is async and it needs some time to load date and at the time of rendering there is no data, but I don't know what to do. I need to wait for ajax, but how... or it could be another issue. Thanks.

Related

how to approch this object loading problem with javascript and HTML

I am working on a web application, I am just using javascript at the moment. The problem that I am trying to solve is that I have three different objects and only one HTML page. Based on the user click event, I want the objects for the chosen category to be loaded and displayed on the same page. For example, let's say the user is at the home page, if they click on category A from the navigation bar, the page will be loaded first and then the script will load the objects to the data structure. Finally, display them to the javascript generated HTML containers. The same thing should happen with a different category after the User click Event is fired. To be more precise I want to be able to reuse the HTML page for different objects without having to create a page for each category.
I already have created the code that does all of the data loading and HTML generation for the n objects I want to load. The code works fine when I am at the object's page but if the event is fired from another page nothing seems to happen. I am guessing this has to do with page loading timing.
I have posted the complete code of the part that I am working on.
var dataController = (function() {
var JSONurls = {
bags: "../JSON/bags.json",
bc: "../JSON/briefcases.json",
belts: "../JSON/belts.json",
accs: "../JSON/accs.json",
};
ProductObj = function(name, des, colors, price, pics, type, ID) {
this.name = name;
this.description = des;
this.colors = colors;
this.price = price;
this.pics = pics;
this.type = type;
this.ID = ID;
};
var dataStruc = {
allProducts: {
bags: [],
briefcases: [],
belts: [],
accessories: [],
},
};
return {
addProd: function(obj) {
var newProd, ID;
if (dataStruc.allProducts[obj.type].length > 0) {
ID =
dataStruc.allProducts[obj.type][
dataStruc.allProducts[obj.type].length - 1
].ID + 1;
} else {
ID = 0;
}
newProd = new ProductObj(
obj.name,
obj.description,
obj.colors,
obj.price,
obj.pics,
obj.type,
obj.ID
);
dataStruc.allProducts[obj.type].push(newProd);
return newProd;
},
getDataStruct: function() {
return dataStruc;
},
getJsonUrls: function() {
return JSONurls;
},
loadJSON: function(url, cat, callback) {
var requestURL, request, JsonObj;
requestURL = url;
request = new XMLHttpRequest();
request.open("GET", requestURL);
request.responseType = "text";
request.send();
request.onload = function() {
JsonObj = JSON.parse(request.response);
dataStruc.allProducts[cat] = JsonObj[cat];
callback(cat);
};
},
};
})();
var UIcontroller = (function() {
var DomStrings = {
shopCatg: ".shop-catg",
productCont: ".product-container",
};
//public methods
return {
// function display the object based on the category based on the event target
displayObjectToPage: function(cat) {
var deafultHtml;
// 1. loop over the product category
dataController.getDataStruct().allProducts[cat].forEach(function(cur) {
deafultHtml =
'<div class="col-lg-4 col-md-6 col-sm-10">' +
'<img class="img-fluid" src="../img/' +
cur.type +
"/" +
cur.pics[0] +
'.jpg">' +
'<h6 class="text-center">' +
cur.name +
"</h6>" +
'<div class="text-center text-muted">' +
cur.price +
"</div>" +
"</div>";
document
.querySelector(DomStrings.productCont)
.insertAdjacentHTML("beforeend", deafultHtml);
});
},
getDomStrings: function() {
return DomStrings;
},
};
})();
var mainController = (function(UIctrl, dataCrtl) {
var setUpEvents = function() {
var doneLoading = false;
var DOM = UIctrl.getDomStrings();
document.querySelector(DOM.shopCatg).addEventListener("click", function() {
InitializeData(event, function(cat) {
UIcontroller.displayObjectToPage(cat);
});
});
};
InitializeData = function(event, callback) {
var category = event.target.textContent;
if (event.target.textContent === "bags") {
dataController.loadJSON(
dataController.getJsonUrls().bags,
category,
callback
);
} else if (event.target.textContent === "briefcases") {
dataController.loadJSON(dataController.getJsonUrls().bags, "briefcases");
} else if (event.target.textContent === "belts") {
dataController.loadJSON(dataController.getJsonUrls().bags, "belts");
} else {
dataController.loadJSON(dataController.getJsonUrls().bags, "accs");
}
};
displayObject = function() {};
return {
init: function() {
setUpEvents();
},
};
})(UIcontroller, dataController);
mainController.init();
I'm not sure, but I noticed this potential issue:
request.send();
request.onload = function() {
// ...
}
I believe when you call send, the request should start asynchronously. If the request comes back before onload is assigned, you might be seeing it get skipped. I haven't used XHR directly in years, though.
Normally you'd want to add the onload callback before calling send() to avoid this issue.
I also just noticed that you're missing the event in the arguments of the callback function here:
▽
document.querySelector(DOM.shopCatg).addEventListener("click", function() {
▽ event is undefined
InitializeData(event, function(cat) {
UIcontroller.displayObjectToPage(cat);
});
});

Disable AJAX Load of Content via Function

I have a custom plugin that was originally loading html content via ajax into the page by appending a hash marker and page ID to the URL.
I am very new to this level of complexity and would like to 'undo' this functionality, so the plugin can initialize without the Router function. I've been looking at this for a couple days and am pretty lost...
The entire plugin seems to be initialized by this function. Any tips or suggestions on how to turn 'off' this feature, so the code still initializes without appending the # to the URL would be greatly appreciated.
$(function() {
var connections = [],
IEversion = detectIE(),
killConnections = null,
node = null,
randomBehaviour,
rootIndex = 1,
silentRoute = null;
// Splash.
var splash = {
init: function() {
$('#splash').addClass('active');
$('.node.splash').draggable({
containment: 'parent',
drag: function() {
if(!splash.destroyed) {
$('.node.splash').addClass('dragging');
splash.destroy('drag');
splash.destroyed = true;
}
},
scroll: false,
disabled: false
});
setTimeout(function() {
if($('.arrow').is(':visible')) {
splash.destroy();
}
}, 4000);
},
destroy: function(event) {
if(event === 'drag') {
$('.arrow').hide();
} else {
$('.arrow').fadeOut(500);
}
$('#splash-wrapper p, .node.splash').fadeOut(500);
setTimeout(function() {
$('#splash').remove();
nody.init();
}, 500);
},
destroyed: false
};
// Router.
var routes = {
'/': function() {
if(!splash.destroyed) {
splash.init();
}else {
nody.unloadMenu();
}
}
};
var router = Router(routes);
router.configure({
strict: false,
before: function() {
if(silentRoute) {
silentRoute = false;
return false;
}
}
}).init('/');
});
An easy answer-
$.mobile.ajaxEnabled = false;
Using jQuery Mobile.
Detail is here.
If u want to use jQuery, then there is not a direct way of doing it.
So u should use a global variable for doing it like this way-
var is_ajax_enabled = true;
$(document).ready(function()
{
...................
...................
$("selector").click(function()
{
...................
//before AJAX call, just do a checking like it-
if(is_ajax_enabled())
{
//make your AJAX call here
$.ajax({url: "demo_test.txt", success: function(result)
{
$("#div1").html(result);
}});
}
...................
});
...................
...................
});
function disable_ajax()
{
is_ajax_enabled=false;
}
function enable_ajax()
{
is_ajax_enabled=true;
}
function is_ajax_enabled()
{
return is_ajax_enabled;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
Update-
If u want to have only one request available at a time and make a queue for the request, u can try this jQuery-Ajax-Singleton like this way-
$.ajax(options || {})

Long-click jQuery plugin for dynamic content

I've written a plugin to create long click event handler for my web application. I know it is not too advanced and that it has low functionality, but I am trying to improve it. You can see my plugin below:
$(function($) {
var holdTimer;
var timerRunning = false;
$.fn.longClick = function(handler, time) {
if (time == undefined) time = 500;
return this.on({
mouseup: function() {
clearTimeout(holdTimer);
timerRunning = false;
},
mousedown: function() {
var self = this;
timerRunning = true;
holdTimer = window.setTimeout(function() {
handler.call(self)
}, time);
}
})
};
$.fn.longClick.defaultTime = 500;
}(jQuery));
What is my problem?
I am in the situation of putting Ajax-generated content on my page, and you can easily know that .longClick() won't work anymore for those elements.
I have the following snippet:
$.ajax({
url: "/ajax/",
type: "POST",
data: {
action: "load-posts",
},
dataType: "html",
success: function(data) {
$(".profile-wrapper").append(data);
}
});
The data looks like this:
<div class="post">
<div class="comments">Comments</div>
</div>
Then I need to use the .longClick event for .comments. I found this, on Stack Overflow, and I know which my situation is, but I don't know how to modify my plugin to work like $(selector).on(event,childSelector,data,function).
How would you modify this plugin to work on dynamic content? Thank you for your patience and help.
EDIT FOR #AminJafari
The long-click event now fires, but inside the function, the .post seems to be undefined:
$(".profile-wrapper .tabs-wrapper .tab .post").longClick(function () {
var $post = $(this);
var menuTop = $post.offset().top + "px";
// ...
}
Output from the console:
Uncaught TypeError: Cannot read property 'top' of undefined
UPDATED:
$(function($) {
var holdTimer;
var timerRunning = false;
$.fn.longClick = function(handler, time) {
if (time == undefined) time = 500;
var that=$(this);
$(document).on('mouseup',that,function(){
clearTimeout(holdTimer);
timerRunning = false;
});
$(document).on('mousedown',that,function(){
var self = this;
timerRunning = true;
holdTimer = window.setTimeout(function() {
handler.call(self)
}, time);
});
};
$.fn.longClick.defaultTime = 500;
}(jQuery));

Infinite Scroll with JScrollPane in Wordpress

I've done AJAX post loaders before but I'm having quite an hard time with jScrollPane.
Two things:
where should I load the posts? the div i created (#reviewspostscont) or .jspPane that JScrollPane makes? what if i have multiple loops then?
a more practical one now, this is the code i have so far, I can't get the function that triggers the AJAX to get the isAtRight variable (undefined in console), any fix?
Thanks in advance, Matt
$(function() {
$('#reviewspostscont').each(function() {
$(this).bind(
'jsp-scroll-x',
function(event, scrollPositionX, isAtLeft, isAtRight) {
console.log('Handle jsp-scroll-x', this,
'scrollPositionX=', scrollPositionX,
'isAtLeft=', isAtLeft,
'isAtRight=', isAtRight);
}
);
$(this).jScrollPane({ horizontalDragMaxWidth: 100 });
var api = $(this).data('jsp');
var throttleTimeout;
$(window).bind('resize', function() {
if (!throttleTimeout) {
throttleTimeout = setTimeout(function() {
api.reinitialise();
throttleTimeout = null;
}, 50);
}
});
});
$('#reviewspostscont').scroll(function() {
var $this = $(this);
var scrollWidth = $this[0].scrollWidth - $this.width();
var scrollPercentage = $this.scrollLeft() / scrollWidth * 100;
if (isAtRight == true) {
loadArticle(count);
count++;
}
});
function loadArticle(pageNumber) {
$.ajax({
url: "<?php bloginfo('wpurl') ?>/wp-admin/admin-ajax.php",
type:'POST',
data: "action=infinite_scroll&page_no="+ pageNumber + '&loop_file=loop',
success: function(html) {
$("#reviewspostscont").append(html); // This will be the div where our content will be loaded
}
});
return false;
}
});

Knockoutjs custom binding to display loading gif

I'm trying to create a custom binding that will show a loading gif while content is loading.
ko.bindingHandlers.loader = {
init: function (element) {
$('<div>').addClass('loader').hide().appendTo($(element));
},
update: function (element, valueAccessor) {
var isLoading = ko.utils.unwrapObservable(valueAccessor());
var $element = $(element);
var $children = $element.children(':not(.loader)');
var $loader = $(element).find('.loader');
if(isLoading) {
$children.stop(true).css('visibility', 'hidden').attr('disabled', 'disabled');
$loader.stop().fadeIn();
} else {
$loader.stop(true).fadeOut(function () {
$children.css('visibility', 'visible').removeAttr('disabled');
});
}
}
};
I can see in the init that div.loader is being appended to the element, and can see the update function fire when isLoading is changed to true. But once the images have loaded (by loaded i mean each image returns a resolved promise on the their respective load event) I don't see the update firing once isLoading is set back to false.
viewModel
function viewModel() {
var self = this;
self.movies = ko.observableArray([]);
self.searchValue = ko.observable();
self.isLoading = ko.observable(false);
self.search = function () {
self.isLoading = true;
$.getJSON(arguments[0].action, { name: this.searchValue() }, function (data) {
self.movies(data);
$.when.apply($, promises).done(function() {
setThumbnailHeight(function () {
self.isLoading = false;
});
});
});
};
var setThumbnailHeight = function(callback) {
var $items = $('.thumbnails li');
var maxHeight = Math.max.apply(null, $items.map(function () {
return $(this).innerHeight();
}).get());
$items.css('height', maxHeight);
callback();
};
}
ko.applyBindings(new viewModel());
setThumbnailHeight is being called at the correct time (once all promises have resolved) and is working properly, in that I see it setting the height of each li to the max height and can see the callback (in this case function(){ self.isLoading = false; } being called.
my binding
<ul class="content thumbnails" data-bind="foreach: movies, loader: $root.isLoading">
<li class="movie">
...
</li>
</ul>
So just to recap, the problem is that the loading gif will be displayed when isLoading is set to true but is not hiding and showing the newly loaded content when it's set back to false.
All observables are function so you cannot assign value to it using =. Use self.isLoading(true); instead of self.isLoading = true;
self.search = function () {
self.isLoading(true);
$.getJSON(arguments[0].action, { name: this.searchValue() }, function (data) {
self.movies(data);
$.when.apply($, promises).done(function() {
setThumbnailHeight(function () {
self.isLoading(false);
});
});
});
};
function(){ self.isLoading(false); }

Categories