JS用于完全ajaxed网站中的不同动态加载内容

时间:2015-04-14 18:33:28

标签: javascript jquery ajax events javascript-events

这是一个完全更新的帖子,以更好的方式解释问题,改进概念和代码(基于目前为止给出的答案)

我试图实现一个完整的ajaxed网站。但是我遇到了多个绑定事件的问题。

这是基本的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>

导航是动态创建的(因为#navigation的内容可以替换为其他导航),因此nav元素的绑定将如下所示:

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

该网站的内容也是动态加载的。例如,有两种不同的内容:

1:

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

2

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

在加载内容时,我还将为这种类型的内容加载一个特定的JS文件,该文件具有所需的所有绑定。所以加载脚本看起来像这样:

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);
            });
        }
    });
}

当您传递所需结果类型的参数(即蔬菜或汽车)时,结果将显示在#result中。还将加载文件cars.js或vegetables.js。

所以我的问题是避免多个事件绑定。这就是我的做法:

cars.js:

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

vegetables.js:

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

这是正确的方法吗?我认为这只是一种使用off()的解决方法。所以我很感激任何改进!

此外,如果用户多次点击导航,我不知道是否存在问题:在这种情况下,js文件被多次加载,不是吗?那么这个概念有多个绑定吗?

8 个答案:

答案 0 :(得分:2)

当您引用完全ajaxed网站时,我认为 SPA - S ingle P 年龄 A 应用。

区别可能是语义,但 AJAX 意味着DOM操作,而 SPA 意味着模板化导航

加载页面时会加载HTML模板。每个模板都映射到特定的导航路线。主要更改不是事件映射,而是显示模板,以及是否已加载新数据。

See my example AngularJS SPA Plunkr

AngularJS路由如下所示:

   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'
     });
   });

因此每个路由都有一个相应的HTML模板和控制器(其中定义了回调函数)。

出于CDN目的,模板可以作为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'
     });
  • 在“模板化”应用程序中,每种类型的HTML都会加载一次。

  • 事件和控件绑定一次。

  • 增量更改 JSON 来回传递。您的终点不负责呈现HTML。他们可以保持安宁,并且有一个明确的关注点。

  • 您可以使用 AngularJS Ember Backbone JQuery 等等。

答案 1 :(得分:1)

首先,我建议你选择像AngularJS这样的框架,正如其他人提出的那样。

但是,除此之外,您还可以考虑使用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
});

这将是一项改进(以及更少的解决方法),因为:

  

(它可以完成工作)而不会打扰其他点击 事件处理程序   附在元素上。

     

- .on() documentation

答案 2 :(得分: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
    });

我不确定,但是,如果用户首先点击导航上的汽车类型,那么('mouseover', 'img')听众取消注册,然后再次注册,对吧? 然后,当用户点击导航('click', 'button')上的蔬菜类型 - 取消注册(但!!! 'mouseover', 'img' - 保留!!!),然后用户点击某个类型导航,哪个脚本没有('mouseover', 'img')监听器,但内容有img - 然后有非法的内容监听器(从之前的行动开始)。

因此,在开始加载新内容和脚本之前,您需要清除所有已注册的#result个侦听器,可能:

    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);
                });
            }
        });
    }

<强> cars.js:

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

<强> vegetabels.js:

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

编辑:关于多次加载脚本。我没有找到明确的答案,我认为这是浏览器依赖和jquery实现,并且每次创建新脚本时都可能会创建新脚本,即使它是之前创建的,因此可能存在两个缺点:

  1. 重复加载相同的脚本表单服务器,如果不是浏览器,jquery也没有缓存它
  2. 通过脚本充斥DOM和浏览器的解释器
  3. 但是,取决于JQuery文档。

      

    缓存响应

         

    默认情况下,$.getScript()会将缓存设置设置为false。这会将带时间戳的查询参数附加到请求URL,以确保浏览器在每次请求时都下载脚本。您可以使用$.ajaxSetup()

    全局设置缓存属性来覆盖此功能            

    $.ajaxSetup({ cache: true });

    您可以依赖JQuery cache (there is an option to cache only scripts),或实施自己的,例如:

        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();
            });
          }
        }
    

答案 3 :(得分:0)

您可以创建一个函数,该函数使用要加载的页面名称并使用单个函数来加载页面。然后让回调函数加载一个同名的javascript文件(具有公共init函数)。像:

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

或者您可以将回调函数名称传递给函数。

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

你可以用promises而不是回调来做到这一点。

答案 4 :(得分:0)

如果你采用网络的AJAX方式,请考虑使用PJAX。它是一个用于创建AJAX网站的战斗测试库,并且正在github上使用。

以下PJAX的完整示例:

<强> HTML:

一旦脚本加载完成,

data-js 属性将用于运行我们的函数。每个页面需要有所不同。

data-url-js 属性包含要加载的JS脚本列表。

<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>

模板:您的所有网页都必须包含#content div作为容器,并带有上述两个属性。

<强> JS:

App.js - 每个页面都需要包含此文件。

/*
 * 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 - 这是您的页面特定的js文件。所有特定于页面的js脚本都必须遵循此模板。

/* 
 * 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')});
    });
})();

更多解释:

  1. 函数名已附加到窗口global js名称空间,因此可以在不重新加载脚本的情况下重新执行该函数。如您所知,此函数名称必须是唯一的。

  2. 该函数是自执行的,因此如果用户在不使用AJAX的情况下到达页面(即直接进入页面URL),它将自行执行。

  3. 你可能会问,我有on我的HTML元素的所有绑定怎么样?好吧,一旦元素被销毁/替换,对它们的代码绑定将被浏览器垃圾收集。所以你的记忆力不会超过屋顶。

    以上用于实施基于ajax的网站的模式目前正在我的一个客户端进行制作。因此,它已针对所有场景进行了大量测试。

答案 5 :(得分:0)

当你做$('#navigation').on('some-event', '.red',function(){});时 您将事件绑定到#navigation元素(您可以使用$('#navigation').data('events')看到这一点),但不能绑定到.red - 元素,这就是为什么当您使用新的js-logic加载新元素时你正在获得新的和旧的绑定。

如果在您的情况下可以这样做,只需对{(1}}这样的直接绑定使用应该与元素一起删除/重新加载的所有事件。

答案 6 :(得分:0)

在大多数情况下,您可以想象在Web开发中做的所有事情都已经完成。您只需要找到它并使其与您的环境一起工作。您的代码存在许多问题,但还有其他一些问题让我感到困扰 - 为什么没有人提到angularJSrequireJS?使用这样的框架和库有很多好处,包括

  • 到处都有数以千计的教程
  • 关于SO的成千上万的问题
  • 他们(大多数)都有令人惊奇的插件,只是准备好了
  • 与您的实施相比,它们可能具有更广泛的功能

此处还有使用您自己的代码的好处

  • 你是唯一理解它的人。
  • 任何?

我的观点是,您应该使用其他人已经建立的内容,其中99%的内容完全免费。

另外,使用像angular这样的框架,最终会得到更清晰,可维护的代码。

答案 7 :(得分:-1)

使用.off(...).on(...)方法,您可以保证在新绑定之前事件将被清除,以防您有多个.js文件绑定到同一事件(即:汽车和蔬菜都有不同逻辑的按钮点击)

但是,如果不是这种情况,您可以使用类过滤器来检测已经包含事件的元素:

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

这样,选择器只会将事件绑定到尚未使用类click-bound修饰的元素。