Best ajax questions in April 2012

How do I save an infinite stack of AJAX content when a user leaves the page?

9 votes

I'm making a website with infinite scrolling. That is, as the user scrolls to the bottom of the page, a new block of content is appended to the bottom. It's very similar to Facebook. Here's an example of 3 pages loaded:

 _________
|         |
|    0    |
|_________|
|         |
|    1    |
|_________|
|         |
|    2    |
|_________|

When the user clicks something on the last page, I take them to a separate detail page. But if the user clicks back to the search results page, I have no memory of their previous location and must load page 0 again.

 _________
|         |
|    0    |
|_________|

I know of some old-school methods to solve this, but they each have some serious problems:

Hash URL

I could update the URL every time a new page is loaded. For example: www.website.com/page#2. When the user goes to the detail page and back, the URL tells me the last loaded page, so I load it for him:

 _________
|         |
|    2    |
|_________|

But this is poor user experience. Only page 2 is loaded. The user cannot scroll up to see older content. I can't just load pages 0, 1, and 2 because that would overload the server, considering that the back button accounts for 50% of web traffic (Jacob Nielsen study).

Local Storage

Cookies don't have the storage capacity to store many pages of data. Local Storage should have sufficient space. One idea is to store all the loaded pages into Local Storage. When the user goes back to the search results, I load from local storage instead of hitting the server. The problem with this approach is that a large chunk of my users are on browsers that don't support local storage (IE7).

Given the constraints of your problem the only plausible solution I can think of would be to cache the heights of the previous pages and then load them if the user scrolls up. This would pad your upward scrolling and allow you to trigger a load if the user looks up. Additionally if you wanted to get a little more fancy I'd try to figure out a way to freeze the scrollbar in order to prevent the user from scrolling up past the previous unloaded page. This way they wouldn't be able to trigger multiple page loads at a time.

Best way to iterate over an array without blocking the UI

8 votes

I am needing to iterate over some large arrays and store them in backbone collections from an API call. What is the best way to do this without making the loop cause the interface to become unresponsive?

The return of the ajax request also blocks as the data returned is so large. I figure that I could split it up and use setTimeout to make it run asynchronously in smaller chunks but is there an easier way to do this.

I thought a web worker would be good but it needs to alter some data structures saved on the UI thread. I have tried using this to do the ajax call but when it returns the data to the UI thread there is still a time when the interface is unresponsive.

Thanks in advance

You break the processing of the array into chunks and do each chunk on a timer. Usually, you can afford to process more than one on each timer which is both more efficient and faster than only doing one per timer. This code gives the UI thread a chance to process any pending UI events between each chunk which will keep the UI active.

function processLargeArray(array) {
    // set this to whatever number of items you can process at once
    var chunk = 100;
    var index = 0;
    function doChunk() {
        var cnt = chunk;
        while (cnt-- && index < array.length) {
            // process array[index] here
            ++index;
        }
        if (index < array.length) {
            setTimeout(doChunk, 1);
    }

    doChunk();

}

processLargeArray(veryLargeArray);

Here's a working example of the concept - not this same function, but a different long running process that uses the same setTimeout() idea to test out a probability scenario with a lot of iterations: http://jsfiddle.net/jfriend00/9hCVq/

how to display lightbox after Video Play Finishes?

6 votes

I have a youtube video.

I want to show a lightbox when it stops playing. I need this to be done using javascript/jQuery or PHP. Ajax is also fine.

I looked for a solution but didn't find one that worked.

If you can use youtube api then, something like this should work:


<script type="text/javascript">
$(document).ready(function() {
var player;
    function onYouTubePlayerAPIReady() {
        player = new YT.Player('player', {
          height: '390',
          width: '640',
          videoId: 'YmHAAqOsBqA',
          events: {
            'onReady': onPlayerReady,
            'onStateChange': onPlayerStateChange
          }
        });
    }
    function onPlayerReady(event) {
        event.target.playVideo();
    }
    function onPlayerStateChange(event) {        
        if(event.data === 0) {          
            //completed playing
            //open lightbox
            $('#yourElementId a').lightBox();
        }
    }
});
</script>

Did you mean something like this.

Hope it helps

Saving javascript objects using jquery and passing an ID from database

5 votes

I am using jQuery to save the values of my javascript objects. I need to retreive the ID of inserted object from the database. I know how to do it, if the Save function is within the javascript object (see code below). But how can I set the ID variable, if the Save function is not in the javascript object?

Working:

Person = function() {
    var self = this;

    self.ID;
    self.Name;
    self.SurName;

    self.Save = function() {
        $.ajax({
            type: "POST",
            url: "Save",
            contentType: "application/json; charset=utf-8", 
            data: JSON.stringify({ Name: self.Name, SurnName: self.SurName }),
            dataType: "json",
            success: function (result) {
                var ID = result.d.ID; //this is the ID retreived from database
                self.ID = ID; //set the ID, it works, since I can reference to self
            }
        });
    };
}ยจ

So how would I now implement a function (outside the Person class!) like:

SavePerson = function(p) {
     $.ajax({
        type: "POST",
        url: "Save",
        contentType: "application/json; charset=utf-8", 
        data: JSON.stringify({ Name: p.Name, SurnName: p.SurName }),
        dataType: "json",
        success: function (result) {
            var ID = result.d.ID; //this is the ID retreived from database
            p.ID = ID; //set the ID, it doesn't work, becouse if I call SavePerson repetedly for different objects, a p will not be a correct person.
        }
    });
};

Just to clarify, you would like the Person object id property to be updated with the recent save? If so the following script would suffice. I have used deferred's to ensure that p.ID is only updated upon completion of the asynchronous request.

$.Person = function() {
    var self = this;
    self.ID;
    self.Name;
    self.SurName;
}

$.SavePerson = function() {
var dfd = $.Deferred();
     $.ajax({
        type: "POST",
        url: "Save",
        contentType: "application/json; charset=utf-8", 
        data: JSON.stringify({ Name: p.Name, SurnName: p.SurName }),
        dataType: "json",
        success: dfd.resolve
    });
return dfd.promise();
};

var p = new $.Person();

$.SavePerson().then(function(result){
    p.ID = result.d.ID;
});

Alternative to jQuery when just requiring DOM traversal, $.ajax & Deferred

5 votes

For most JavaScript projects that I work on, I want a simple, light-weight UI stack.

Currently I use jQuery in my projects, however when I actually take a step back and look at the code, I'm only really using it for:

Is there another library (I don't want to handle all the various cross-browser & ES3/ES5 differences myself) that can provide me these features without all the additon stuff that I personally don't need?

Dojo springs to mind, but I have little experience with that so far, and would ideally like to hear from those who have used multiple libraries on this.

In minimized form, Dojo is 136kb, jQuery is 96kb. Moving to dojo is not going in the right direction.

The issues you should consider are:

  1. Suitability of library for your purpose
  2. Size of library
  3. Likelihood that it will be precached already
  4. Your familiarity with the functionality of the library
  5. Availability on a popular, public CDN
  6. Good support on the net and great documentation
  7. Good reputation for reliability, cross browser support and regular updates

Go through each of these and unless you find another library that scores well on these, jQuery may be your best bet. It is surprisingly compact for what it offers you and it doesn't really have a lot of stuff that isn't in your list of things you want. jQuery has done a pretty good job of keeping the core library focused on its central mission and let UI stuff go into jQueryUI and most everything else into their own plug-in libraries.

If you're obsessed about optimizing the code you include to only be the things you need, then you may want to look at YUI. It was designed to be modular so that you can specify only the modules you want and then you can prebuild a chunk of code that only has those modules in it (or you can dynamically load just the modules you want). My sense is that YUI is somewhat overdesigned in this regard and it's cumbersome to use for quick projects because you have to spend the time to figure out which modules you need and generate that build each time. Once you get a bunch of modules loaded, it's not that compact either which is where you find that jQuery is surprisingly compact for what it includes.

In general, you should not worry about the things that a library includes that you are not using. Just look at the overall size and suitability of the libraries that do meet your needs. You can probably find a library that does only what you want and is bigger than jQuery and isn't widely cached so that wouldn't be a win.

There are compact libraries out there for just ajax or just deferred, but you probably want one with ajax and deferred implemented together so you can use deferred with ajax (like jQuery has done). Libraries that do extensive DOM manipulation tend to be more than just that because they are more designed to be your core library and most people have other needs besides just DOM manipulation.

In the end, I'd suggest that you shouldn't care what your library has in it that you don't need. Just evaluate it's overall suitability vs. the alternatives.

Multiple ajax calls at same time

5 votes

I have developed some websites and I always stumble a the same point: multiple ajax calls. I have a main page where all the content is loaded asynchronously. When the page is loaded, there are four INDEPENDENT calls that "draw" the page by areas (top, left, right and bottom) and while it are loaded I show to the user the typical ajax spins. So, when a request is received by the browser I execute the callback and the different areas are drawing at different time. The fact is that the answer for the server sometimes are mixed up, I mean, the answer of top is drawn in the left or vice-versa.

I've tried some solutions like creating a timestamp in each request to indicate to the browser and server that each request is different.

Also I've tried to configure some parameters of cache in the server, in case.

The only way in which works has been including the request2 in the callback of the one, etc.

Anyone knows the proper way to do it or ever has beaten this issue?? I don't want to do chained request.

Thanks

Here is an example of what I mean:

$(document).ready(function() {

$.get('/activity',Common.genSafeId(),function(data){$('#stream').html(data);$("#load_activity").addClass("empty");});
$.get('/messages',Common.genSafeId(),function(data){$('#message').html(data);$("#load_messages").addClass("empty");});
$.get('/deals',Common.genSafeId(),function(data){$('#new_deals_container').html(data);$("#load_deal").addClass("empty");});
$.get('/tasks',Common.genSafeId(),function(data){$('#task_frames').html(data);$("#load_task").addClass("empty");});});

And the html is a simple jsp with four container each one with a different id.

CLOSURES

Closures are a little mind-blowing at first. They are a feature of javaScript and several other modern computing languages.

A closure is formed by an executed instance of a function that has an inner function (typically an anonymous event handler or named method) that needs access to one or more outer variables (ie. variables that are within the outer function but outside the inner function). The mind-blowing thing is that the inner function retains access to the outer variables even though the outer function has completed and returned at the time that the inner function executes!

Moreover, variables trapped by a closure are accessible only to inner functions and not to the further-out environment that brought the closure into being. This feature allows us, for example, to create class-like structures with private as well as public members even in the absence of language keywords "Public" and "Private".

Closures are made possible by inner functions' use of outer variables suppressing javaScript's "garbage collection" which would otherwise destroy the outer function's environment at some indeterminate point after completion.

The importance of closures to good, tidy javaScript programming cannot be overstressed.

In the code below the function getData() forms, at each call, a closure trapping id1 and id2 (and url), which remain available to the anonymous ajax response handler ($.get's third argument).

$(document).ready(function() {

    function getData(url, id1, id2) {
        $.get(url, Common.genSafeId(), function(data) {
            $(id1).html(data);
            $(id2).addClass("empty");
        });
    }

    getData('/activity', '#stream', '#load_activity');
    getData('/messages', '#message', '#load_messages');
    getData('/deals', '#new_deals_container', '#load_deal');
    getData('/tasks', '#task_frames', '#load_task');

});

Thus, rather than writing four separate handlers, we exploit the language's ability to form closures and call the same function, getData(), four times. At each call, getData() forms a new closure which allows $.get's response handler (which is called asynchronously when the server responds) to address its DOM elements.

Editing and Saving user HTML with Javascript - how safe is it?

5 votes

For example I have a Javascript-powered form creation tool. You use links to add html blocks of elements (like input fields) and TinyMCE to edit the text. These are saved via an autosave function that does an AJAX call in the background on specific events.

The save function being called does the database protection, but I'm wondering if a user can manipulate the DOM to add anything he wants(like custom HTML, or an unwanted script).

How safe is this, if at all?

First thing that comes to mind is that I should probably search for, and remove any inline javascript from the received html code.

Using PHP, JQuery, Ajax.

Not safe at all. You can never trust the client. It's easy even for a novice to modify DOM on the client side (just install Firebug for Firefox, for example).

While it's fine to accept HTML from the client, make sure you validate and sanitize it properly with PHP on the server side.

Does RegisterStartupScript increase page size

4 votes

I'm using this code in a page:

<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<asp:Timer ID="timer" Interval="4000" runat="server" OnTick="timer_Tick" />

<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
    <ContentTemplate>
        <asp:Panel ID="pnlAlarm" runat="server" CssClass="pnlAlarm" ClientIDMode="Static">
            <div id="Alarm">
                <asp:Label ID="lblContent" runat="server" Text="Updating" CssClass="AlarmLogo"></asp:Label>
                    ClientIDMode="Static" />
            </div>
        </asp:Panel>
    </ContentTemplate>
    <Triggers>
        <asp:AsyncPostBackTrigger ControlID="timer" />
    </Triggers>
</asp:UpdatePanel>

and in code behind I use this simple code:

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        Session["nima"] = 1;
    }
}
protected void timer_Tick(object sender, EventArgs e)
{
    int i = int.Parse(Session["nima"].ToString());
    if (i==3)
    {
        lblContent.Text = i.ToString();
        ScriptManager.RegisterStartupScript(this, GetType(), "AlarmMessage", "$('#pnlAlarm').slideToggle();", true);
        Session["nima"] = 0;
    }
    else
    {
        i = i + 1;
        Session["nima"] = i;
    }
}

I want to know every time that I use RegisterStartupScript , $('#pnlAlarm').slideToggle(); add to my page and increase my page size?

thanlks

By definition, that method will:

register a startup script block that is included every time that an asynchronous postback occurs.

So yes, it will be included, and therefore increase your page size.

msdn ScriptManager.RegisterStartupScript Method

jQuery GET html as traversable jQuery object

4 votes

This is a super simple question that I just can't seem to find a good answer too.

$.get('/myurl.html', function(response){
     console.log(response); //works!
     console.log( $(response).find('#element').text() ); //null :(
}, 'html');

I am just trying to traverse my the html response. So far the only thing I can think of that would works is to regex to inside the body tags, and use that as a string to create my traversable jQuery object. But that just seems stupid. Anyone care to point out the right way to do this?

Maybe its my html?

<html> 
    <head> 
       <title>Center</title> 
    </head> 
    <body> 
        <!-- tons-o-stuff -->
    </body>
</html>

This also works fine but will not suit my needs:

$('#myelem').load('/myurl.html #element');

It fails because it doesn't like <html> and <body>.

Using the method described here: A JavaScript parser for DOM

$.get('/myurl.html', function(response){
     var doc = document.createElement('html');
     doc.innerHTML = response;

     console.log( $("#element", doc).text() );
}, 'html');

I think the above should work.

Retain Checkbox values in Yii gridview pagination

4 votes

I have a gridview which contains a checkbox column and also uses pagination.When i check some checkboxes in the first page and navigate to the second page and check another one in the second page,The options i checked in the first page is not retained there.Is it posssible to retain the checkbox values during pagination????

Code for Gridview is

$widget= $this->widget('zii.widgets.grid.CGridView', array(
    'dataProvider' => $model->search(),
        'cssFile' => Yii::app()->baseUrl . '/media/js/admin/css/admingridview.css',
    //'filter' => $model,
    'ajaxUpdate'=>true,
            'enablePagination' => true,
        'columns' => array(


      array(
                            'name' => 'id',
                            'header' => '#',
                            'value' => '$this->grid->dataProvider->pagination->currentPage * $this->grid->dataProvider->pagination->pageSize + ($row+1)',
                        ),
array(
                'class' => 'CCheckBoxColumn',
                'selectableRows' => '2',

                'header'=>'Selected',
            ),

            array(
                                'name' => 'fb_user_id',
                                'header' => 'FaceBook Id',
                                'value' => 'CHtml::encode($data->fb_user_id)',
                            ),

        array(
                                'name' => 'first_name',
                                'header' => 'Name',
                                'value' => 'CHtml::encode($data->first_name)',
                            ),
            array(
                                'name' => 'email_id',
                                'header' => 'Email',
                                'value' => 'CHtml::encode($data->email_id)',
                            ),


            array(
                                'name' => 'demo',
                                'type'=>'raw',
                                'header' => "Select",
                                'value' => 'CHtml::checkBox("email[]","",array("class"=>"check","value"=>$data->email_id))',
                            ),


    ),
));
?>

You could use sessions/cookies to store the checked values. I'm not very sure how to make cookies work, so i'll tell you how to do it with sessions. Specifically the user session that yii creates.

Now to use sessions we need to pass the checked (and unchecked) ids to the controller, therefore we'll modify the data being sent to the controller on every ajax update(i.e between paginations), to do this we exploit the beforeAjaxUpdate option of CGridView.

I'm also using CCheckBoxColumn instead of the following in your code(of course you can modify the solution to suit your own needs):

array(
     'name' => 'demo',
     'type'=>'raw',
     'header' => "Select",
     'value' => 'CHtml::checkBox("email[]","",array("class"=>"check","value"=>$data->email_id))',
),

GridView Changes:

<?php $this->widget('zii.widgets.grid.CGridView', array(
    // added id of grid-view for use with $.fn.yiiGridView.getChecked(containerID,columnID)
    'id'=>'first-grid',

    'dataProvider'=>$model->search(),
    'cssFile' => Yii::app()->baseUrl . '/media/js/admin/css/admingridview.css',

    // added this piece of code
    'beforeAjaxUpdate'=>'function(id,options){options.data={checkedIds:$.fn.yiiGridView.getChecked("first-grid","someChecks").toString(),
        uncheckedIds:getUncheckeds()};
        return true;}',

    'ajaxUpdate'=>true,
    'enablePagination' => true,
    'columns' => array(
            array(
                 'name' => 'id',
                 'header' => '#',
                 'value' => '$this->grid->dataProvider->pagination->currentPage * $this->grid->dataProvider->pagination->pageSize + ($row+1)',
            ),
            array(
                 'name' => 'fb_user_id',
                 'header' => 'FaceBook Id',
                 'value' => 'CHtml::encode($data->fb_user_id)',
            ),
            array(
                 'name' => 'first_name',
                 'header' => 'Name',
                 'value' => 'CHtml::encode($data->first_name)',
            ),
            array(
                 'name' => 'email_id',
                 'header' => 'Email',
                 'value' => 'CHtml::encode($data->email_id)',
            ),

            /* replaced the following with CCheckBoxColumn
              array(
                 'name' => 'demo',
                 'type'=>'raw',
                 'header' => "Select",
                 'value' =>'CHtml::checkBox("email[]","",array("class"=>"check","value"=>$data->email_id))',
              ),
            */

            array(
                 'class' => 'CCheckBoxColumn',
                 'selectableRows' => '2',
                 'header'=>'Selected',
                 'id'=>'someChecks', // need this id for use with $.fn.yiiGridView.getChecked(containerID,columnID)
                 'checked'=>'Yii::app()->user->getState($data->email_id)', // we are using the user session variable to store the checked row values, also considering here that email_ids are unique for your app, it would be best to use any field that is unique in the table
            ),
    ),
));
?>

Pay special attention to the code for beforeAjaxUpdate and CCheckBoxColumn, in beforeAjaxUpdate we are passing checkedIds as a csv string of all the ids(in this case email_ids) that have been checked and uncheckedIds as a csv string of all the unchecked ids, we get the unchecked boxes by calling a function getUncheckeds(), which follows shortly. Please take note here, that when i was testing i had used an integer id field (of my table) as the unique field, and not an email field.

The getUncheckeds() function can be registered like this anywhere in the view file for gridview:

Yii::app()->clientScript->registerScript('getUnchecked', "
       function getUncheckeds(){
            var unch = [];
            $('[name^=someChec]').not(':checked,[name$=all]').each(function(){unch.push($(this).val());});
            return unch.toString();
       }
       "
);

In the above function pay attention to the selectors and each and push function.

With that done, we need to modify the controller/action for this view.

public function actionShowGrid(){
     // some code already existing
     // additional code follows
     if(isset($_GET['checkedIds'])){
          $chkArray=explode(",", $_GET['checkedIds']);
          foreach ($chkArray as $arow){
               Yii::app()->user->setState($arow,1);
          }
     }
     if(isset($_GET['uncheckedIds'])){
          $unchkArray=explode(",", $_GET['uncheckedIds']);
          foreach ($unchkArray as $arownon){
               Yii::app()->user->setState($arownon,0);
          }
     }
     // rest of the code namely render()
}

That's it, it should work now.

jquery .get data manipulation

4 votes

I am using below code to get a page:

$.get('http://example.com/page1.html', function (data) {

});

Now lets imagine there is a #content div inside that page1.html and I need to read it's inner html as I already have #content div on page where Ajax call is occurring.

What is the right way to do this? I've tried with:

data = $(data).find('#content').html();
$("#content").empty().append(data);

But it seems that html() function is not the right one as it returns null, while contents() is returning data but I am not skillful enough to get only what I need from it.

Any help appreciated, thanks!!

Untested:

var content = $("#content", data).html(); // or var content = $("#content", $(data)).html();

$("#content").html(content);

EDIT: How about this?

$('#content').load("http://example.com/page1.html #content");

From: http://api.jquery.com/load/ - "loading page fragments".