How to update cached jquery object after adding elements via AJAX - javascript

I'm trying to write a plugin-like function in jQuery to add elements to a container with AJAX.
It looks like this:
$.fn.cacheload = function(index) {
var $this = $(this);
$.get("cache.php", {{ id: index }).done(function(data) {
// cache.php returns <div class='entry'>Content</div> ...
$(data).insertAfter($this.last());
});
}
and I would like to use it like this:
var entries = $("div.entry"),
id = 28;
entries.cacheload(id);
Think that this would load another "entry"-container and add it to the DOM.
This is works so far. But of course the variable that holds the cached jQuery object (entries) isn't updated. So if there were two divs in the beginning and you would add another with this function it would show in the DOM, but entries would still reference the original two divs only.
I know you can't use the return value of get because the AJAX-call is asynchronous. But is there any way to update the cached object so it contains the elements loaded via AJAX as well?
I know I could do it like this and re-query after inserting:
$.get("cache.php", {{ id: num }).done(function(data) {
$(data).insertAfter($this.last());
entries = $("div.entry");
});
but for this I would have to reference the variable holding the cached objects directly.
Is there any way around this so the function is self-contained?
I tried re-assigning $(this), but got an error. .add() doesn't update the cached object, it creates a new (temporary) object.
Thanks a lot!
// UPDATE:
John S gave a really good answer below. However, I ended up realizing that for me something else would actually work better.
Now the plugin function inserts a blank element (synchronously) and when the AJAX call is complete the attributes of that element are updated. That also ensures that elements are loaded in the correct order. For anyone stumbling over this, here is a JSFiddle: http://jsfiddle.net/JZsLt/2/

As you said yourself, the ajax call is asynchronous. Therefore, your plugin is asynchronous as as well. There's no way for your plugin to add the new elements to the jQuery object until the ajax call returns. Plus, as you discovered, you can't really add to the original jQuery object, you can only create a new jQuery object.
What you can do is have the plugin take a callback function as a second parameter. The callback could be passed a jQuery object that contains the original elements plus the newly inserted ones.
$.fn.cacheload = function(index, callback) {
var $this = this;
$.get('cache.php', { id: index }).done(function(html) {
var $elements = $(html);
$this.last().after($elements);
if (callback) {
callback.call($this, $this.add($elements));
}
});
return $this;
};
Then you could call:
entries.cacheload(id, function($newEntries) { doSomething($newEntries); } );
Of course, you could do this:
entries.cacheload(id, function($newEntries) { entries = $newEntries; } );
But entries will not be changed until the ajax call returns, so I don't see much value in it.
BTW: this inside a plugin refers to a jQuery object, so there's no need to call $(this).

Related

Run JQuery when data is loaded in Firebase

i have a problem that i need first to get the image links from the Firebase data base then i call a JQuery code that will organize the images in a beautiful way >> But it seems that the Jquery runs before i get the images,
Help Please ..!
JS Function
new Firebase("https://zoominp.firebaseio.com/photos/"+imageID)
.once('value', function(snap)
{
link = snap.child('imageLink').val();
link = 'images/'+link;
var id = "img";
div.innerHTML += "";
});
JQuery
jQuery("#gallery").unitegallery(
{
tiles_type:"nested",
tiles_nested_optimal_tile_width:200
});
Firebase loads (and synchronizes) the data asynchronously. So the jQuery code you have, will indeed execute before the data has come back from the server.
To fix this, move the jQuery code into the Firebase callback:
var ref = new Firebase("https://zoominp.firebaseio.com/photos/"+imageID);
ref.on('value', function(snap) {
link=snap.child('imageLink').val();
link='images/'+link;
var id="img";
div.innerHTML = div.innerHTML +"";
jQuery("#gallery").unitegallery({
tiles_type:"nested",
tiles_nested_optimal_tile_width:200
});
});
I also changed once() to on(). With that tiny change, your HTML will be updated whenever the data in the database changes. Try changing the data and you'll experience the "magic" of Firebase.
Since asynchronous loading is hard to wrap your head around when you first encounter it, I highly recommend that you read the more in-depth answers to these questions:
How do I return the response from an asynchronous call?
Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference
Handling Asynchronous Calls (Firebase) in functions
Returning value from a jQuery function
i have never worked with a Firebase, but you will need to have your actual resources ready before running the jQuery - you cannot do this in a synchronous way, as when you call your jquery unitedGallery it is called before the .once('value') event triggers.
do you call that new Firebase(.... thing more times in a loop or something? you could do something like keeping information about whether have all the images loaded in an array. something like this: let's assume, your images are stored in an array allOfYourImages. then,
define a global variable like this
var images_loaded=[];
for(var i=0; i<allOfYourImages.length; i++){ images_loaded[i]=false; }
then i assume you somehow iterate over your pictures since you are using imageID. add an incrementing variable var image_number=0; before the iterator and do image_number++ after each image iteration. like
var image_number=0;
...iteratorofyourchoiseihavenoideawhatareyouusing...{
new Firebase("https://zoominp.firebaseio.com/photos/"+imageID).once('value', function(snap){
...DOM stuff previously did ...
images_loaded[image_number]=true;
checkAllImagesLoaded();
});
image_number++;
}
notice the checkAllImagesLoaded() function. this will look whether have all your images already loaded and fire the jQuery gallery thing, like this
checkAllImagesLoaded(){
var all_loaded=true;
for(var i=0; i<allOfYourImages.length; i++){
all_loaded &= images_loaded[i]; //in case any of the items is false, it will set the all_loaded to false
}
if(all_loaded){
..your jQuery.("#gallery").unitegallery stuff..
}
}

Find and manipulate a HTML DIV element that is stored in a variable, using jQuery

I've been searching for a few hours to try and find a solution to my issue, for some reason partially similar answers on here don't seem to be working for me - so I'm creating my own question.
Basically, I'm loading pre-rendered HTML from the server using jQuery's $.get method, and I need to split the HTML returned into two sections (one that's wrapped in a div called #section-one and the other simply alongside that div, with no parent element).
See the example below:
$.get('http://jamie.st/remote_file.php', function(data){
// I want to get '#section-one' and then remove it from data, basically splitting a single returned HTML resource into two, that can be placed in two different areas of the page.
var sectionOne = $(data).find('#section-one');
// This should only return the HTML of '#section-one'
console.log(sectionOne);
// Also how can I then remove '#section-one' from the 'data' variable? The equivalent of calling the below, but from the 'data' variables string/html.
$(sectionOne).remove();
// So eventually the below would return the HTML without the '#section-one' element (and it's children)
console.log(data);
});
I've also created a jsfiddle which you can play around with if you need to, it's set up to use a real PHP file that I've hosted for demo purposes.
http://jsfiddle.net/6p0spp23/6/
If you can submit a jsfiddle link back that would be much appreciated, thanks in advance guys!
When you create a jQuery object with the remote contents $(data) becomes a collection of elements so instead of find() you want to use filter() like so:
$.get('http://jamie.st/remote_file.php', function(data){
var $data = $(data),
$sectionOne = $data.filter('#section-one'),
$rest = $data.filter(':not(#section-one)');
console.log($sectionOne);
console.log($rest);
});
Demo fiddle
I think the best way to put the received data inside a parent div. Then you can call remove or any other method to use it.
You can make parent div hidden using .hide() method if you don't want to show it.
Here I did it:
http://plnkr.co/edit/jQKXyles8sP8dliB7v0K?p=preview
// Add your javascript here
$(function() {
$.get('http://jamie.st/remote_file.php', function(data) {
$("#parent").hide();
$("#parent").html(data);
$("#section-one").remove();
console.log($("#section-one").html())
alert($("#parent").html())
});
});
When you remove a subsection from a derived jQuery object, the original string is not updated with the change so if you want the updated html content you need to generate it from the jQuery object. One way to do this is to
$.get('http://jamie.st/remote_file.php', function (data) {
var $ct = $('<div />', {
html: data
});
// I want to get '#section-one' and then remove it from data, basically splitting a single returned HTML resource into two, that can be placed in two different areas of the page.
var sectionOne = $ct.find('#section-one');
// This should only return the HTML of '#section-one'
console.log(sectionOne);
// Also how can I then remove '#section-one' from the 'data' variable? The equivilant of calling the below, but from the 'data' variables string/html.
$(sectionOne).remove();
// So eventually the below would return the HTML without the '#section-one' element (and it's children)
console.log($ct.html());
});
Demo: Fiddle

TypeError: undefined is not a function jQuery

I'm new to jQuery and I can get it to sometimes work, however, for some reason, when I try to call a function, it gives me the title error, but if I do it in developer tools, it works fine.
http://jsfiddle.net/otanan/pmzzLo3e/#&togetherjs=AezijhfBrj
It seems to work fine when retrieving the classes from the DOM, but not when I call a function such as
.click(function() {});
Here's the code:
var downloads = $(".info"),
className = "info_clicked";
for(var i in downloads)
{
downloads[i].click(function()
{
if(!this.hasClass(className))
this.addClass(className);
else
this.removeClass(className);
});
}
When you access a jQuery collection as an array, it returns the DOM elements, not jQuery objects. You should use .each() rather than for (i in downloads):
downloads.each(function() {
$(this).click(function() {
if (!$(this).hasClass(className)) {
$(this).addClass(className);
} else {
$(this).removeClass(className);
}
});
});
You could also simplify the whole thing to:
downloads.click(function() {
$(this).toggleClass(className);
});
Most jQuery methods automatically iterate over all the elements in a collection if it makes sense to do so (the notable exceptions are methods that return information from the element, like .text() or .val() -- they just use the first element). So you generally only have to iterate explicitly if you need to do different things for each element. This is one of the great conveniences of using jQuery rather than plain JS: you rarely have to write explicit iterations.
I think the issue is that you're attempting to call a jQuery function on an object that is no longer a jQuery object.
For example you're saying $(".info"). Which retrieves a single jQuery object. As soon as you index that object downloads[i] it is no longer a jQuery object, it is a plain HTML element and does not have a click function available.
What you really need to do is get the jQuery object for the indexed item:
var downloads = $(".info"),
className = "info_clicked";
for(var i = 0; i < downloads.length; i++)
{
$(downloads[i]).click(function()
{
if(!this.hasClass(className))
this.addClass(className);
else
this.removeClass(className);
});
}
try it:
$(downloads[i]).click(function(){ //...

Javascript, passing a variable into a click event?

I really can't figure out how I would do this. It's more of a concept question than a code question so I'll just post an example:
object = $('#div');
function doSomething(object) {
//iterates through a list and creates a UL with items in corresponding to that list.
$(body).append("<li id='clickme'>Hello world</li>");
}
function createModal(object) {
//creates modal dialogue.
doSomething(object);
//more stuff
}
$('#clickme').live("click", function() {
//I need access to object (not the object declared at first,
//the object passed into doSomething) here.
});
Any ideas how I would do such a thing? doSomething would create a set of LIs and have a parameter passed into it. When those LIs the function creates are clicked, they need to interact with the parameter that's passed into doSomething. Is there a way to bind them or something?
Sorry if I didn't make any sense.
You can use jquery data function to associate data to your DOM elements. You then can read those data when handling events.
An alternate way, generally not recommended but useful when you build your html in one big pass and don't have an easy access to the DOM elements, and only have strings (or keys), is to add an attribute and retrieve it later using jquery's attr function. Whenever possible I recommend you to use the data function though.
Store the reference explicitly:
function doSomething(object) {
//iterates through a list and creates a UL with items in corresponding to that list.
$(body).append(
$("<li/>", { id: 'clickme', text: 'Hello world',})
.data('object', object)
);
}
Then the event handler can retrieve the reference:
$('#clickme').live("click", function() {
var object = $(this).data('object');
// ...
});
Also .live() is deprecated:
$('body').on('click', '#clickme', function() {
is the hip new way to bind delegated event handlers.
object = $('#div');
function doSomething(object) {
$(body).append("<li id='clickme'>Hello world</li>");
$('#clickme').click(function(evt) {
// Here you have access to `object`
});
}
function createModal(object) {
//creates modal dialogue.
doSomething(object);
//more stuff
}
This might not be enough. If you are creating multiple links rather than just the single one with id clickme you might have to find a different selector to use when you attach the click-handler. But if you nest the function that way, you have access to the parameter object that was used when the click-handler was created.
Another option would be to declare and attach the handler in a location where the parameter would be in scope, through closures (not tested):
function doSomething(object) {
$(body).append("<li id='clickme'>Hello world</li>").click(function() {
//object is accessible here
});
}

jquery Syncing dom element values

I have a DOM element like this:
<div id='master-value'>Apples</div>
I have many other elements elsewhere on the page that I need to sync with the 'master-value'.
<div class='fruit-value' data-reference='master-value'>Apples</div>
<div class='some-fruit' data-reference='master-value'>Apples</div>
When I change the value of the 'master-value', I want all the synced elements to update with it.
$('#master-value').text('Pears');
Should affect:
<div class='fruit-value' data-reference='master-value'>Pears</div>
<div class='some-fruit' data-reference='master-value'>Pears</div>
What I don't want, is on every change of 'master-value' to have to search through all the elements in order to find the synced elements in order to change them. I think that's quite slow when there are many elements that needs to be searched through.
There should be some way for the child values to be pre-bound to the master value so that the selection goes quickly.
$('.fruit-value, .some-fruit').sync('#master-value');
I have some ideas, for instance: I can create an array of preselected synced objects, bind a custom event on the master value and run that event whenever I change the value. The event would go through the array to update all the child elements.
I'm sure there's a better way of doing it though...
Thanks!
You can store the selector once, like this:
var elements = $('.fruit-value, .some-fruit'); //do this once
elements.text($("#master-value").text()); //when you want to sync
The elements variable/jQuery object will keep an array of references to DOM elements so it won't be traversing to find them each time.
wouldn't it be easier to give them all the same class?
So you coud do
$('.fruit').text('Pears')
If you're looking for plugin type of functionality, try this:
When setting up, it takes an object with one property syncWith to set up the elements it should sync with.
When setting the text, it will set the text for the master and the synced elements.
Try it out: http://jsfiddle.net/GH33J/
Just a first attempt. There would be room for improvement if (for example) the master was more than one element. There should be a global reference to all the elements to synchronize and an option to tell if the masters should be synced too.
$.fn.sync = function(arg) {
// if arg plain object, we are doing an initial setup
if ($.isPlainObject(arg)) {
return this.each(function() {
$.data(this, 'syncWith', $(arg.syncWith));
});
// if arg is jQuery object, we are adding new items
} else if (arg.jquery) {
return this.each(function() {
var $set = $.data(this, 'syncWith');
$.each(arg, function() {
$set.push(this);
});
});
console.log(this.data('syncWith'));
// otherwise assume we have a string, and are syncing a new value
} else {
return this.each(function() {
$(this).text(arg);
$.data(this, 'syncWith').text(arg);
});
}
};
// Set up the sync
$('#master-value').sync({
syncWith: '.fruit-value,.some-fruit'
});
var $new = $('<div class="fruit-value">Apples</div>').appendTo('body');
// Pass a jQuery object containing newly created element(s) to add to the set
$('#master-value').sync($new);
// Activate a sync
$('#master-value').sync("pears");​
OK here we go:
This is the official data linking plugin from Microsoft. It's now being supported by the jQuery Core team, so we know it's good. :)
http://weblogs.asp.net/scottgu/archive/2010/05/07/jquery-templates-and-data-linking-and-microsoft-contributing-to-jquery.aspx
http://blog.jquery.com/2010/10/04/new-official-jquery-plugins-provide-templating-data-linking-and-globalization/

Categories