0

hey guys, i'm working on a rather weird ajax based search model. The search is not actually retrieving real searchresults from the server but rather loads the website's sitemap (with the jquery load() method) and extracts its links.

That works actually really good, but there is one little bug that might cause my browser to crash.

var searchTimer = 0;

$('.s').keyup(function(e) {

    switch (e.keyCode) {
        //case 8:  // Backspace
        case 9:  // Tab
        case 13: // Enter
            doSearch(e.keyCode);
            break;
        case 16: // Shift
        ...
        case 37: // Left
            break;
        case 38: // Up
            doSearch(e.keyCode);
            break;
        case 39: // Right
            break;
        case 40: // Down
            doSearch(e.keyCode);
            break;
        ...
        break;

        default:
        if (searchTimer != 0) {
            clearTimeout(searchTimer);
        }

        searchTimer = setTimeout(function () {
            doSearch(e.keyCode);
        }, 250);
    }

});

function doSearch(keyCode) {

    if ($('.s').val() != '') {

        searchTimer = 0;

        $sr.load('/sitemap/', function() {


        }); // end load

    } else {
        clearsearch(true);
    }
}

The only problem I have with this is that the site crashes once I type a word in my .s input field and immediately delete it within the 250ms.

Imagine this: 1.) the input is empty. 2.) i quickly type "test". 3.) the doSearch function hasn't even been fired and I hit cmd-a to select all and remove the text in the input.

complete crash of my site!

Why could that happen? It does work really smooth and fine when I just type "test" or delete the input once the doSearch() has been fired. It actually works always. Just in this rare case of typing quickly and removing the typed text within the event of doSeach() it crashes.

Any idea what could cause that?

edit/update: When I copy sitemap.html into my current procets root directory and load it doesn't crash and works fine as in your example. As soon as i change it to url: "sitemap", dataType: "html", it crashes. I call my sitemap with mydomain.com/sitemap...

the code for the sitemap looks like this:

    <?php
/**
 * Template Name: Sitemap
 */
?>
<?php if (have_posts()) : while (have_posts()) : the_post(); ?>

    <div id="ajax-base">
        <h3>Pages</h3>
            <ul>
                <?php wp_list_pages('title_li=&depth=0&exclude='); ?>
            </ul>
        <h3>Posts</h3>
            <?php $first = 0;?>
            <ul>
            <?php
            $myposts = get_posts('numberposts=-1&offset=$first');
            foreach($myposts as $post) :
            ?>
            <li><a href="<?php the_permalink(); ?>#b"><?php the_title(); ?></a></li>
            <?php endforeach; ?>
            </ul>
        <h3>Categories</h3>
            <ul>
                <?php wp_list_categories('title_li=&orderby=name'); ?>
            </ul>
        <h3>Tags</h3>
            <ul>    
                <?php
                $tags = get_tags();
                foreach ($tags as $tag){
                    $tag_link = get_tag_link($tag->term_id);
                    $html .= "<li><a href='{$tag_link}#b' title='{$tag->name} Tag' class='{$tag->slug}'>";
                    $html .= "{$tag->name}</a></li>";
                }
                echo $html;
                ?>
            </ul>
    </div> <!-- ajax-base -->

<?php endwhile; endif; ?>

sorry for this last question, but any idea why that makes a difference. When I use this dynamic /sitemap as basis for my search the browser crashes. With a static html page it works fine.

matt
  • 42,713
  • 103
  • 264
  • 397
  • What is `doSearch()` doing that having an empty input box would interfere with? – AlG Mar 23 '11 at 18:08
  • doSearch() is testing the loaded content for strings, removes parts, hides parts, shows specific parts, etc. Inside doSearch() and its load-method I handle the loaded sitemap. The sitemap is based on ul's that I test for anchor links and only show those links that are matched with the string in the searchbox. – matt Mar 23 '11 at 19:45

2 Answers2

4

I suppose the main problem of your code is that you don't abort the previous pending ajax call. What happens in the browser if it simultaneously will try to modify $sr element on two server response?

Both old XMLHttpRequest and new jqXHR has abort method which you can use.

UPDATED: As I described in the comment the jQuery.load do not much more as the jQuery.ajax call and jQuery.html to place the server response on the page. You can verify this looking in the source code of jQuery.load here (for jQuery 1.4.4) or here (for jQuery 1.5.1).

I prepared one small demo example for you which shows how you can use jQuery.ajax and jQuery.html directly instead of jQuery.load. You can download the full project here.

If one types in the input box of the demo slowly one receive the following results enter image description here

If one types more quickly (I type very slow and so use 1 sec timeout on the server): enter image description here

One can see that I abort the previous ajax request to the server if any pending ajax request exist. In case of aborting the error handler of the corresponding (previous) ajax request are called and then the abort() function return.

I hope if you follow the way you will never have the problems which you describes in your question.

To be sure that you receive the example I include the full code, which I used in my test demo, below. The JavaScript code is following

var jqXHR_Old, $myinput = $('#myinput'),
    $result = $('#result'), $protocol = $('#protocol'),
    logText = function(txt) {
        $protocol.append(txt+"<br/>"); // append or prepend
    },
    doSearch = function(code) {
        var txt = $myinput.val();
        if (txt != '') {
            // send request to the server
            if (jqXHR_Old) {
                // abort the previous request
                logText("aborting...");
                jqXHR_Old.abort();
                jqXHR_Old = null;
            }

            $result.empty();
            logText('sending request to the server with '+
                    '<span style="color:blue;">'+txt+'</span>...');
            jqXHR_Old = $.ajax({
                url: "MySimpleService.svc/GetTestHtmlFragment",
                data: {str:txt},
                dataType: "html",
                success: function (data) {
                    $result.html(data);
                    logText("received from the server: "+data);
                    jqXHR_Old = null;
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    if (textStatus !== "abort" || errorThrown !== "abort") {
                        $result.html("Error Occured!" + " | " + " | " +
                                        textStatus + " | " + errorThrown +
                                        "responseText:<br/>" + XMLHttpRequest.responseText);
                    } else {
                        logText("request aborted.");
                    }
                    jqXHR_Old = null;
                }
            });
        }
    };
$myinput.keyup(function(e) {
    switch (e.keyCode) {
        //case 8:  // Backspace
        case 9:  // Tab
        case 13: // Enter
            doSearch(e.keyCode);
            break;
        case 37: // Left
            break;
        case 38: // Up
            doSearch(e.keyCode);
            break;
        case 39: // Right
            break;
        case 40: // Down
            doSearch(e.keyCode);
            break;

        default:
            doSearch(e.keyCode);
    }
});

HTML is here

<fieldset style="float:left">
    <input type="text" id="myinput"/>
</fieldset>
<div style="clear:left">
    <fieldset style="float:left">
       <legend>Results from the server:</legend>
       <div id="result"></div>
    </fieldset>
    <div style="clear:left"/>
    <fieldset style="float:left">
       <legend>Ajax protocol:</legend>
       <div id="protocol"></div>
    </fieldset>
</div>

As the server I use very simple WCF service with the interface

using System.ServiceModel; using System.ServiceModel.Web; using System.ServiceModel.Channels;

namespace AjaxLoad {
    [ServiceContract]
    public interface ISimpleService {
        [OperationContract]
        [WebGet]
        Message GetTestHtmlFragment (string str);
    }
}

and the implementation

using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using System.ServiceModel.Channels;
using System.Text;
using System.Threading;

namespace AjaxLoad {
    [AspNetCompatibilityRequirements (RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class SimpleService : ISimpleService {
        public Message GetTestHtmlFragment (string str) {
            Thread.Sleep (1000);
            return WebOperationContext.Current.CreateTextResponse ("<span style='color:red'>" + str + "</span>",
                "text/html; charset=utf-8",
                Encoding.UTF8);
        }
    }
}

I simulate slow request processing just with Thread.Sleep with 1 sec waiting. I used SVC file free implementation and so used as web.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <system.web>
        <compilation debug="true" targetFramework="4.0" />
    </system.web>

    <system.serviceModel>
        <standardEndpoints>
            <webHttpEndpoint>
                <!-- the "" standard endpoint is used by WebServiceHost for auto creating a web endpoint. -->
                <standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true"  />
            </webHttpEndpoint>
        </standardEndpoints>
        <behaviors>
            <serviceBehaviors>
                <behavior name="">
                    <serviceMetadata httpGetEnabled="true" />
                    <serviceDebug includeExceptionDetailInFaults="false" />
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true">
            <serviceActivations>
                <add relativeAddress="MySimpleService.svc" service="AjaxLoad.SimpleService"
                     factory="System.ServiceModel.Activation.WebServiceHostFactory" />
            </serviceActivations>
        </serviceHostingEnvironment>
    </system.serviceModel>
</configuration>

As "References" of the project three dependent assemblies needed: System, System.ServiceModel, System.ServiceModel.Web.

Community
  • 1
  • 1
Oleg
  • 220,925
  • 34
  • 403
  • 798
  • thank you, finally a bit closer to a non-buggy script. How would that look like in my example? Where do I set this abort() method? And how does this abort() method work with load()? – matt Mar 23 '11 at 19:33
  • @mathiregister: If you look inside of the implementation of `jQuery.load` (see [here](https://github.com/jquery/jquery/blob/1.4.4/src/ajax.js#L13) or [here](https://github.com/jquery/jquery/blob/1.5.1/src/ajax.js#L136)) you will see that you can use `$.ajax` with `dataType: "html"` and place the server response response with respect of [$.html](http://api.jquery.com/html/). The `$.ajax` return object which has `abort` method which you need. – Oleg Mar 23 '11 at 20:00
  • I was trying yesterday all day long. I couldn't implement tha $.ajax method. It seems just to complex for me. Would you please provide a short sample how that would look like with my code above? Do I have to transform a lot of code? – matt Mar 24 '11 at 09:23
  • @mathiregister: Yes, of course! I will made the example and post it here later. – Oleg Mar 24 '11 at 10:05
  • wow, thank you - this is just way to complex for me... I'll give it a try tomorrow morning. I have way to many stuff inside of my doSearch function. this is so damn confusing. how do I implement my timeout of 250ms? I don't need to put out any errors or something. I just want to load the /sitemap/ when typing. thank you anyway. I think I'll leave that bug. +150 for you. thank you for your amazing long and detailed answer, but I'm still a coding rookie. – matt Mar 24 '11 at 21:48
  • @mathiregister: You are welcome! If you do will continue to have some strange crashes you can insert more full code in your question and I would try to help you. – Oleg Mar 24 '11 at 22:02
  • @mathiregister: I looked in your code and make some changes. Together with the replacing `load` to `ajax` I made some bug fixes in the HTML (like replacing `` to `` and so on) and in the JavaScript code. If you load html fragment you don't need make it as the full HTML page. It is enough to have any UTF-8 encoded HTML text fragment. You can find modified version here: http://www.ok-soft-gmbh.com/jQuery/search/js/scripts.js, http://www.ok-soft-gmbh.com/jQuery/search/sitemap.html and http://www.ok-soft-gmbh.com/jQuery/search/index.html – Oleg Mar 25 '11 at 12:16
  • thank you, this is just amazing! seems to work fine, however if I implement it on my real site it still crashes. BUT... i tried for 5hours now, got rid of all libraries, other scripts and tested why it would crash on my site but not in your example. Even when i implement your changes to my example it works fine. However it might be the following `url: "sitemap.html", dataType: "html",` because in my case there is not static sitemap.html but a dynamic wordpress page that is loaded. Please see my updated post...sorry for the annoying questions. I can't thank you enough. – matt Mar 25 '11 at 17:35
  • @mathiregister: I recommend you to use [Fiddler](http://www.fiddler2.com/fiddler2/) of [Firebug](http://getfirefox.net/) to trace all responses from the server. It is very interesting to the the server response which crash the web browser. I don't use PHP so I can not help you in analysing on the code. What probably is wrong in the code is parallel executing of the code from two parralel requests. – Oleg Mar 25 '11 at 18:07
  • thank you. i tested with firebug, but I can't actually interpret the responses... can you shortly have a look at that? http://cl.ly/5UoK Why does the status sometimes say "301 Moved Permanently"...shouldn't the status of the response always be 200 (because the page exists and is readable) ? thanks again – matt Mar 26 '11 at 08:25
  • @mathiregister: It is very strange, that the server gives 301 response. So two things I would recommend you: 1) make trace with respect of Fiddler. You can save full contain of the trafic and I can look inside (it's much better as to see screen-shorts) 2) Try to use HTTP "POST" instead of get. In the JavaScript code you should add `type:"POST"` parameter to the `$.ajax` and on the server you should also response on POST instead of GET. The HTTP POST can not be cached. Probably not the server at the internet provider is configuriert to go to backup server if the server too busy. – Oleg Mar 26 '11 at 09:57
  • @mathiregister: It is clear that your server work wrond. The "screenshot 2011-03-26 um 11.59.17.PNG" shows the main problem wich you have: Both responses will be mixed in one. Additionally problem is that the response will be placed in the HTML header which is wrong. I don't know how to configure the web server or PHP to work correctly. I know only that is pure server problem. Multible requestes will be wrong processed. The 301 response is an additional problem. Sorry, but I could you help in Microsoft technologies and not here. Probably you should start new question with PHP tag. – Oleg Mar 26 '11 at 11:34
  • thank you I'll start a new thread! I hope I'll figure this out! – matt Mar 26 '11 at 11:56
0

Try this:

var searchTimer; //define the scope of searchTimer and set it to null
/* ...code...*/

if (searchTimer != null) {
    clearTimeout(searchTimer);
}

The ID of the timeout is always going to start from 0 and go up as more timers are created.

mattsven
  • 22,305
  • 11
  • 68
  • 104