1

This is a completely updated post to explain the problem in a better way with an improved concept an code (based on the answers given here so far)

I try to realize a completely ajaxed website, but I got some problems with multiple bound events.

This is the basic HTML:

<header id="navigation">
    <ul>
        <li class="red" data-type="cars">Get Cars</li>
        <li class="red" data-type="vegetables">Get Vegetables</li>
    </ul>
</header>
<div id="anything">
    <section id="dymanic-content"></section>
</div>

The navigation is been created dynamically (as the content of #navigation can be replaced with another navigation), so the binding for the nav-elements would look like this:

$('#navigation').off('click', '.red').on('click', '.red', function() { 
    var type = $(this).attr('data-type');
    var data = { 'param': 'content', 'type': type };
    ajaxSend(data);
});

The content of the site is also being loaded dynamically. For example there are two different content:

1:

<div id="vegetables">Here are some information about vegetables: <button>Anything</button></div>

2:

<div id="cars"><img src="car.jpg"></div>

While loading the content, I will also load a specific JS-file, which has all the bindings needed, for this type of content. So the loading-script looks like this:

var ajaxSend = function(data) {
    $.ajax({ url: "script.php", type: "POST", data: data, dataType: "json" })
    .done(function( json ) {
        if (json.logged === false) { login(ajaxSend, data); }
        else {
            $.getScript( 'js/' + json.file + '.js' )
            .done(function( script, textStatus ) { 
                $('#result').html(json.antwort);
            });
        }
    });
}

As you pass the parameter for the type of results you need (i.e. vegetables or cars), the result will be shown in #result. Also the files cars.js or vegetables.js would be loaded.

So my problem is to avoid multiple event bindings. This is how I'm doing it:

cars.js:

$('#result').off('mouseover', 'img').on('mouseover', 'img', function () { 
    // do anything
});

vegetables.js:

$('#result').off('click', 'button').on('click', 'button', function () { 
    // do anything
});

Is this the proper way? I think it is just a workaround to use off(). So I would appreciate any improvements!

Furthermore I don't know if there is a problem, if the user clicks on the navigation multiple times: In that case the js-files are loaded multiple times, aren't they? So are there multiple bindings with that concept?

10
  • You can have JS file specific to each dynamic content, which would be loaded with dynamic content. Commented Apr 14, 2015 at 18:39
  • Can you give a code-example? I would like to do that with different JS-files. But the problem is the same, isn't it? I have to use delegated events, right? And do I have to unload the JS-file, when a new content has been loaded? Commented Apr 14, 2015 at 18:49
  • Try this. javascriptkit.com/javatutors/loadjavascriptcss2.shtml Good luck. Commented Apr 14, 2015 at 18:57
  • With all the changes from the original code, this should have been a new question.... Commented Apr 24, 2015 at 19:00
  • 1
    It is the same topic and the same question. I just updated it to get it more clear. I don't think that there should always be a new question for everything... Commented Apr 24, 2015 at 19:36

8 Answers 8

2
+200

When you refer to a a fully ajaxed website, I think a SPA -- Single Page Application.

The distinction may be semantics, but AJAX implies DOM manipulation, while SPA implies Templating and Navigation.

HTML templates are loaded when your page is loaded. Each template maps to particular navigation route. The major changes are NOT with event mapping, but with which Template is shown, and whether new data has been loaded.

See my example AngularJS SPA Plunkr

AngularJS routing looks like this:

   scotchApp.config(function($routeProvider) {
     $routeProvider

     // route for the home page
     .when('/', {
       templateUrl: 'pages/home.html',
       controller: 'mainController'
     })

     // route for the cars page
     .when('/cars', {
       templateUrl: 'pages/Cars.html',
       controller: 'CarsController'
     })

     // route for the vegetables page
     .when('/vegetables', {
       templateUrl: 'pages/Vegetables.html',
       controller: 'VegetablesController'
     });
   });

So each route has a corresponding HTML Template and Controller (where call back functions are defined).

For CDN purposes, templates can be passed back as JSON

     // route for the vegetables page
     .when('/vegetables', {
       template: '<div class="jumbotron text-center"><div class="row"><h3>Cars Page</h3>Available Cars: <a class="btn btn-primary" ng-click='LoadCars()'>LoadCars</a></div><div class="col-sm-4"><a class="btn btn-default" ng-click="sort='name'"> Make/Model</a></div><div class="col-sm-2"><a class="btn btn-default" ng-click="sort='year'"> Year</a></div><div class="col-sm-2"><a class="btn btn-default" ng-click="sort='price'"> Price</a></div><div class="row" ng-repeat="car in cars  | orderBy:sort"><div class="row"></div><div class="col-sm-4">{{ car.name }}</div><div class="col-sm-2">{{ car.year }}</div><div class="col-sm-2">${{ car.price }}</div></div></div>',
       controller: 'VegetablesController'
     });
  • In "templated" applications, HTML of each type is loaded once.

  • Events and controls are bound once.

  • The incremental changes are JSON being passed back and forth. Your end points are not responsible for rendering HTML. They can be restful and there is a clear Separation of Concerns.

  • You can create templated SPA applications with AngularJS, Ember, Backbone, JQuery, and more.

Sign up to request clarification or add additional context in comments.

6 Comments

That looks very interesting. But it is completely new for me. The templates has to be HTML-files? Until now I always used HTML-Code which is generated by a php-file (depending on given parameters). These HTML-Code has been transferred via AJAX to the main-page. So with i.e. angular.js you would recommend to use static html, right?
@user3142695, actually, the templates can be created as JSON. Instead of passing template: <div></div>.... I added an example to my answer. But, yes, these are NOT dynamic HTML. They are dynamic in behavior. Frameworks like AngularJS or Ember have great power to easily make your pages dynamic. Take a look at my sample. I created reports, ability to sort reports, and load more data with barely any code. Instead of loading dynamic partials (I used to do that with ASP.NET too), more of the logic goes into your client page. It's a new way of thinking, but, IMO a much better design.
The idea is that your html pages are static but are dynamic in behavior. So they conform around data or user interaction. So your endpoints only concern with data and the pages interact to user requests. I admit, it's a dramatically different approach. And it's a wildly different way of looking at the app.
@user3142695, keep in mind that you can mix JQuery and AngularJS. I have added Angular to existing project sites. Results are best with pure Angular, but the transition isn't necessary. My suspicion is that once you start using AngularJS, you will grow more and more into it. But you are not required to throw away all your current work to make use of Angular's templating and routing.
And exactly this is my biggest problem. My Query-Project works quite well and it took a lot of work for me. Right now I have the feeling, that 1) I have to learn angular.js new 2) I have to create my project completely new
|
1

cars.js:

    $('#result').off('mouseover', 'img').on('mouseover', 'img', function () { 
        // do anything
    });

vegetabels.js:

    $('#result').off('click', 'button').on('click', 'button', function () { 
        // do anything
    });

I am not sure, but, if user first click cars type on nav, then the ('mouseover', 'img') listeners deregister and then register again, right? Then when user click vegetables type on nav ('click', 'button') - deregistered (but!!! 'mouseover', 'img' - are kept!!! ), and if then user clicks some type nav which script have no ('mouseover', 'img') listener but content have img - then there illegal listener for content occurs (from pre previous action).

So, you need to clear all registered to #result listeners BEFORE start loading new content and script, maybe:

    var ajaxSend = function(data) {
        $.ajax({ url: "script.php", type: "POST", data: data, dataType: "json" })
        .done(function( json ) {
            if (json.logged === false) { login(ajaxSend, data); }
            else {
                $('#result').off();
                $.getScript( 'js/' + json.file + '.js' )
                .done(function( script, textStatus ) { 
                    $('#result').html(json.antwort);
                });
            }
        });
    }

or

cars.js:

    $('#result').off().on('mouseover', 'img', function () { 
        // do anything
    });

vegetabels.js:

    $('#result').off().on('click', 'button', function () { 
        // do anything
    });

Edit: About loading scripts multiple times. I didn't find clear answer, and I think it is browser depend and jquery implementation and it is possible that each time new script are created new script will be created even if it was created earlier, so there could be 2 disadvantages:

  1. repeated loads of same script form server, if not browser nor jquery didn't cache it
  2. flooding DOM anp browser's interpreter by scripts

BUT, depending on JQuery documentation.

Caching Responses

By default, $.getScript() sets the cache setting to false. This appends a timestamped query parameter to the request URL to ensure that the browser downloads the script each time it is requested. You can override this feature by setting the cache property globally using $.ajaxSetup()


$.ajaxSetup({ cache: true });

You may rely on JQuery cache (there is an option to cache only scripts), or implement your own, for ex.:

    var scriptCache = [];
    function loadScript(scriptName, cb){
      if (scriptCache[scriptName]) {
        jQuery.globalEval(scriptCache[scriptName]);
        cb();
      } else {
        $.getScript( scriptName )
        .done(function( script, textStatus ) { 
          scriptCache[scriptName] = script;
          //$('#result').html(json.antwort);
          cb();
        });
      }
    }

7 Comments

But then there is no need of off()in the cars.js/vegetables.js, isnt't it?
it needed in each dynamically loaded script, because you can not predict which scripts in which order will be loaded, or you just add $(#result).off() in ajaxSend like in first variant so there will be no need in .off() in any dyn script but this is not reliable (because of situation when listeners are unregistered but load of new content and script failed)
I don't understand it. With $('#result').off(); all events are removed. Then json.file.js will be loaded. Now there are no events on #result, so you don't have to use off()... Why am I wrong?
sory, i may misslead in my answer i gave two variants: 1) $("#result").off() - called in ajaxSend and no need in .off() in dynamically loaded scripts (cars/vagetables) 2) use .off() instead .off(event, element) in dyn scripts do no need to .off() in ajaxSend variant 1) is flawable for dyn scripts/contents failure, because it clears listeners first (but content to replace may left and misfunction)
Oh... understand. Sorry.
|
1

First, I suggest you to pick a framework like AngularJS, as others have proposed.

But, aside of that, you could also consider using namespaces:

cars.js:

$('#result').off('mouseover.cars', 'img').on('mouseover.cars', 'img', function () { 
    // do anything
});

vegetables.js:

$('#result').off('click.vegetables', 'button').on('click.vegetables', 'button', function () { 
    // do anything
});

It would be an improvement (and a bit less workaround), because:

(It would do the work) without disturbing other click event handlers attached to the elements.

-- .on() documentation

1 Comment

After reading many long long answers I was glad to finally see someone suggested the obvious solution (simply namespacing the events) :)
0

You could create a function that takes the name of the page to load and use a single function for loading the pages. Then have the callback function load a javascript file (with a common init function) of the same name. Like:

function loadPage( pageName ) {
  $('#dymanic-content').load( pageName +'.php', function() {
    $.getScript( pageName +'.js', function() {
      init();
    });
  });
}

Or you can pass the callback function name to the function.

function loadPage( pageName, cb ) { 
 $('#dymanic-content').load( pageName +'.php', function() {
   $.getScript( pageName +'.js', function() {
     cb();
   });
 });
}

You could do this with promises instead of call backs as well.

8 Comments

But in this JS-files, I still have to use delegated events, right? That means it is the same JS-code, correct? Do I have to unload, when a new content (and new JS-file) is loaded?
You can use dynamic-content as your parent element to add your delegated events .
Oh and the HTML will be overwritten on each new call to loadPage.
I like your idea, but unfortunately I don't understand it completely. Could you please show me how i.e. example_1.js should look like with my code shown above? It would be easier for me to understand. Thanks.
In the code above your pagename.js file would contain all of your delegated events for that specific page. So if it was the menu page, the it would load menu.js which contains a function that you call after the file has been loaded (either init or cb in code above). This function should add all the delegated events for the menu.
|
0

If you going the AJAX way of the web, consider using PJAX. It is a battle tested library for creating AJAX websites, and is in use on github.

Complete example with PJAX below:

HTML:

data-js attribute will be used to run our function, once the loading of scripts is complete. This needs to be different for each page.

data-url-js attribute contains the list of JS scripts to load.

<div id="content" data-js="vegetablesAndCars" data-urljs="['/js/library1.js','/js/vegetablesAndCars.js']">
    <ul class="navigation">
       <li><a href="to/link/1">Link 1</a></li>
       <li><a href="to/link/2">Link 2</a></li>
    </ul>
    <div id="vegetables">
    </div>
    <div id="cars">
    </div>
</div>

Template: All your pages must have as container the #content div, with the two attribute mentioned above.

JS:

App.js - This file needs to be included with every page.

/*
 * PJAX Global Defaults
 */
$.pjax.defaults.timeout = 30000;
$.pjax.defaults.container = "#content";

/*
*  Loads JS scripts for each page
*/
function loadScript(scriptName, callback) {
    var body = document.getElementsByTagName('body')[0];    
    $.each(scriptArray,function(key,scripturl){
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = scripturl;
        // fire the loading
        body.appendChild(script);
    });
}

/*
*  Execute JS script for current Page
*/
function executePageScript()
{
    //Check if we have a script to execute
    if(typeof document.getElementById('content').getAttribute('data-js') !== null)
    {
        var functionName = document.getElementById('content').getAttribute('data-js').toString();
        if(typeof window[functionName] === "undefined")
        {
            var jsUrl = document.getElementById('content').getAttribute('data-url-js').toString();
            if(typeof jsUrl !== "undefined")
            {
                jsLoader(JSON.parse(jsUrl));
            }
            else
            {
                console.log('Js Url not set');
            }
        }
        else
        {
            //If script is already loaded, just execute the script
            window[functionName]();
        }            
    }
}


$(function(){
    /*
     * PJAX events
     */
    $(document).on('pjax:success, pjax:end',function(){
        //After Successful loading
        //Execute Javascript
        executePageScript();
    }).on('pjax:beforeSend',function(){
        //Before HTML replace. You might want to show a little spinning loader to your users here.
        console.log('We are loading our stuff through pjax');
    });  
});

vegetableAndCars.js - This is your page specific js file. All your page-specific js scripts will have to follow this template.

/* 
 * Name: vegetablesAndCars Script
 * Description: Self-executing function on document load.
 */
(window.vegetablesAndCars = function() {
    $('#cars').on('click',function(){
        console.log('Vegetables and cars dont mix');
    });
    $('.navigation a').on('click',function() {
        //Loads our page through pjax, i mean, ajax.
        $.pjax({url:$(this).attr('href')});
    });
})();

More explanation:

  1. A function name has been attached to the window global js namespace, so that the function can be re-executed without reloading the scripts. As you have figured out, this function name has to be unique.

  2. The function is self executable, so that it will execute itself if the user reaches the page without the use of AJAX (i.e goes straight to the page URL).

You might be asking, what about all those bindings that i have on my HTML elements? Well, once the elements are destroyed/replaced, the code bindings to them will be garbage collected by the browser. So your memory usage won't spike off the roofs.

The above pattern for implementing an ajax based website, is currently in production at one of my client's place. So it has been very much tested for all scenarios.

2 Comments

As the navigation is the main-navigation of the website, I'm using the HTML-markup like shown in the post. So the section-element is used for the content and the header-element is used for the navigation. I would like to avoid a change of that. If I would wrap this with a #content div, I could use the body-element of the page. But is this really how it should be used?
The content div is simply an example. However, if you are using the body to reload the content, it means that the whole page will be replaced. Not a good solution, if your user chooses to switch ideas mid-way through loading and clicks another AJAX link. Performance is blazing fast, after the initial libraries have loaded. It takes 1 sec to load up the page. I have incorporated a preloader on my website so that the user feels like the application is loading its components initially, and is more forgiving, than slowing down each action afterwards to progressively load each library.
0

When you are doing $('#navigation').on('some-event', '.red',function(){}); You bind event to the #navigation element (you can see this with $('#navigation').data('events')), but not to the .red-element which is inside that's why when you load new elements with new js-logic you are getting new and old bindings.

If this is possible in your case just use straight binding like $('#navigation .red').some-event(function(){}); for all events which should be removed/reloaded together with elements.

Comments

0

For the most part, everything that you can probably imagine to do in web development, has already been done. You just need to find it and get it to work with your environment. There are a number of issues with your code but there is something else that is bothering me more - why is nobody referring to angularJS or requireJS? There are great benefits to using such frameworks and libraries, which include

  • Thousands of tutorials all over the place
  • Thousands and thousands of questions on SO
  • They (mostly) have amazing plugins which are just ready to go
  • They probably have wider functionality as compared to your implementations

And also here are the benefits of using your own code

  • You are the only one who understands it.
  • Anything?

My point here is that you should use what others have already built, which in 99% of the cases is completely FREE.

Additionally using frameworks like angular you will eventually end up having much cleaner and maintainable code.

3 Comments

You are right with the suggestion of frameworks. But mostly they have more functionality, then I am needing. Example: So if you just need just fading and slid-effect, would it be better to do that in pure JS by myself or should I use JQuery, which is free and already been done?
'Disadvantage' of a framework is the need of completely rewrite the complete project in angular.js
@user3142695 it would be better to include another library that focuses on animations, such as animate.css. Those are some minimalistic concerns that will not bring any good to you. Technology advances and a few hundred bytes are nothing. Sure you can go ahead and rant how it may reduce battery life or increase cost of mobile internet in foreign countries, but then again, is the amount you save really worth the trouble?
-1

With the .off(...).on(...) approach you guarantee that events will be clear before a new bind in case you have multiple .js files binding to the same event (ie: both cars and vegetables have a button click with different logic).

However, if this is not the case, you can use class filters to detect which element already have the event bounded:

$('#result:not(.click-bound)').addClass('click-bound').on('click', 'button', function() { 
     // your stuff in here
});

That way the selector will bind events only to the elements that aren't already decorated with the class click-bound.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.