完全にAjaxをかけたWebサイトで動的にロードされたさまざまなコンテンツのJS

これは、改良された概念とコードを使って問題をよりよく説明するために完全に更新された記事です(これまでの回答に基づく)

私は完全にAJAXされたWebサイトを実現しようとしています。しかし、私は複数のバインドされたイベントに関していくつかの問題を抱えていました。

これは基本的なHTMLです:

<div id="anything">
    
</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);
});

サイトのコンテンツも動的にロードされています。たとえば、2つの異なるコンテンツがあります。

1:

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

2:

<div id="cars"></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()を使用することは、単なる回避策だと思います。だから私はどんな改善にも感謝します!

さらに、ユーザーがナビゲーションを複数回クリックしても、問題があるかどうかわかりません。それで、その概念を持つ複数の束縛がありますか?

1
あなたはコード例をあげることができますか?私は異なるJSファイルでそれをやりたいと思います。しかし、問題は同じですね。委任されたイベントを使わなければいけませんね。新しいコンテンツが読み込まれたときに、JSファイルをアンロードする必要がありますか?
追加された 著者 user3142695,
はい、良い例を見たいと思います。それが本当に良い解決策であるならば、私はすべてを移行しようとします...
追加された 著者 user3142695,
いいえ、今のところそうしません。ファイルをアンロードすることは不可能だと思ったので。スクリプトをアンロードする方法
追加された 著者 user3142695,
それは同じトピックで同じ質問です。明確にするために更新しました。私はいつもすべてに新しい質問があるべきではないと思います...
追加された 著者 user3142695,
@ user3848987 Angular、Backbone、EmberなどのMVCフレームワークを使用する必要があります。 JQueryはこれに対して十分に堅牢ではありません。 (例えば)Angularにロードされたビューは、イベントマッピングを処理するための独自のコントローラを持ち、競合しません。例を見たいですか?
追加された 著者 Dave Alperovich,
元のコードからのすべての変更で、これは新しい質問でした。
追加された 著者 Gary Storey,
これを試して。 javascriptkit.com/javatutors/loadjavascriptcss2.shtml 頑張ってください。
追加された 著者 wonderbell,
動的コンテンツがロードされる各動的コンテンツに固有のJSファイルを作成できます。
追加された 著者 wonderbell,
古いスクリプトをアンロードしますか。原因私はあなたがあまりにも多くのページ(およびブラウザ)をロードし、それがいくつかの時点でクラッシュする可能性があると思います。私はあなたがたくさんのスクリプトをロードすると思います。
追加された 著者 Catalin Sterian,
私はすべてのために新しいスレッドを開く必要はないとも思います。タイトルを変更することなく、変更は非常に明確かつ透明になりました。ちょうどあなたの答えを変えて恩恵を受けましょう:-)
追加された 著者 user3848987,

8 答え

あなたがある[完全にAJAXのウェブサイトを参照するとき、私はある[ SPA - S のある[ P の年齢<�強い> アプリ。

区別は意味論かもしれませんが、 AJAX はDOM操作を意味し、 SPAテンプレートナビゲーション

HTMLテンプレートは、ページが読み込まれると読み込まれます。各テンプレートは特定のナビゲーションルートにマッピングされます。主な変更点はイベントマッピングではなく、表示されるテンプレートと新しいデータが読み込まれたかどうかです。

私の例を見る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">

Cars Page

Available Cars: LoadCars</div><div class="col-sm-4"> Make/Model</div><div class="col-sm-2"> Year</div><div class="col-sm-2"> Price</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が1回ロードされます。

  • イベントとコントロールは一度バインドされます。

  • 差分の変更は JSON <//em> 送受信されています。あなたのエンドポイントはHTMLをレンダリングする責任を負いません。彼らは安らかにすることができ、懸念の明確な分離があります。

  • テンプレート付きSPAアプリケーションは、 Angular JSEmberバックボーンjQuery 、その他

2
追加された
そしてこれこそが私の最大の問題です。私のQuery-Projectは非常にうまく機能し、それは私にとって多くの作業を要しました。今のところ、1)angular.jsを学ぶ必要があります2)まったく新しいプロジェクトを作成しなければならないという気がします
追加された 著者 user3142695,
とてもおもしろいですね。しかし、それは私にとって全く新しいものです。テンプレートはHTMLファイルにする必要がありますか?今まで私はいつもphpファイルによって生成されたHTMLコードを使っていました(与えられたパラメータによります)。これらのHTMLコードはAJAXを介してメインページに転送されました。つまり、angular.jsでは、静的HTMLを使用することをお勧めします。
追加された 著者 user3142695,
@ user3142695、同情します。何か新しいことはゲームチェンジャーです。期限がある場合は、それに反対です。長い目で見れば、現在のアプローチをAngular SPAに合わせることはメンテナンスの手間がかかりません。ここと今ではそれは無限の穴のように見えます。
追加された 著者 Dave Alperovich,
@ user3142695、JQueryとAngularJSを混在させることができることに留意してください。 Angularを既存のプロジェクトサイトに追加しました。結果は純粋なAngularで最良ですが、移行は必要ありません。私が疑っているのは、AngularJSを使い始めると、ますますその中に成長することでしょう。しかし、Angularのテンプレートとルーティングを利用するために現在の作業をすべて捨てる必要はありません。
追加された 著者 Dave Alperovich,
あなたのHTMLページは静的だが動的に振る舞うという考えです。そのため、それらはデータやユーザーとの対話に準拠しています。そのため、エンドポイントはデータにのみ関心を持ち、ページはユーザーの要求に反応します。私は認める、それは劇的に異なるアプローチです。そしてそれはアプリを見ているのとは全く違った見方です。
追加された 著者 Dave Alperovich,
@ user3142695、実際には、テンプレートはJSONとして作成できます。 template:<div> </div> ... を渡す代わりに。私は答えに例を追加しました。しかし、はい、これらは動的HTMLではありません。それらは動的に振舞います。 AngularJSやEmberのようなフレームワークはあなたのページを簡単に動的にすることができます。私のサンプルを見てください。私はレポートを作成し、レポートをソートし、そしてほとんどどんなコードでもより多くのデータをロードすることができました。動的パーシャルをロードする代わりに(私はASP.NETでもこれを実行していました)、ロジックの多くはクライアントページに入ります。これは新しい考え方ですが、IMOははるかに優れた設計です。
追加された 著者 Dave Alperovich,

まず、他の人が提案したように、AngularJSのようなフレームワークを選ぶことをお勧めします。

ただし、それ以外に、名前空間を使用することも検討できます。

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()ドキュメント

1
追加された
たくさんの長い長い答えを読んだ後、誰かが明らかな解決策を提案したのを見てうれしく思いました(単にイベントの名前空間付け):)
追加された 著者 Gone Coding,

cars.js:

 $( '#result')。off( 'mouseover'、 'img')。on( 'mouseover'、 'img'、function(){
        // 何でもする
    ;));
 
     

vegetabels.js:

 $( '#result')。off( 'クリック'、 'ボタン')。on( 'クリック'、 'ボタン'、function(){
        // 何でもする
    ;));
 

よくわかりませんが、ユーザーが最初にnavをクリックして車をクリックすると、( 'mouseover'、 'img')リスナーの登録が解除されてから再度登録されます。 その後、ユーザーがnav ( '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
    });

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 nまたはjquery didn't cache it
  2. flooding DOM anp browser's interpreter by scripts

ただし、JQueryの資料によります。

キャッシングレスポンス

     

デフォルトでは、 $ .getScript()はキャッシュ設定を false に設定します。これにより、リクエストURLにタイムスタンプ付きのクエリパラメータが追加され、リクエストされるたびにブラウザがスクリプトを確実にダウンロードするようになります。この機能は、$.ajaxSetup()

を使用してキャッシュプロパティをグローバルに設定することで上書きできます      
     

$ .ajaxSetup({cache:true});

You may rely on JQuery cache (there is an option to cache only scripts), またはimplement your own, fまたは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();
        });
      }
    }
1
追加された
キャッシングポイントが完全にはわかりません。 $ .ajaxSetup({cache:true}); で十分ではありませんか。コードスニペットの利点は何ですか?そして、リンクされたドキュメントの$ .cachedScriptの例はどうでしょうか。 getScriptの代わりにajaxを使っているので
追加された 著者 user3142695,
ああ…わかった。ごめんなさい。
追加された 著者 user3142695,
わかりません。 $( '#result')。off(); を使用すると、すべてのイベントが削除されます。 json.file.js がロードされます。今は #result にはイベントがありませんので、 off()を使用する必要はありません。
追加された 著者 user3142695,
しかし、その場合はcars.js/vegetables.jsに off()は必要ありません。
追加された 著者 user3142695,
「JQueryキャッシュを使用することも(スクリプトのみをキャッシュするオプションもあります)、独自のものを実装することもできます。たとえば、次のようになります。.....」もう一度Jqueryにはスクリプトキャッシュ機能があります。それでも、あなたがそれを望まないのであれば(何らかの理由で)、あなたはあなた自身のスクリプトキャッシュを使うことができ、そしてそれがどのように実現されるか簡単な例のためのコードスニペットを追加することができます@ user3142695
追加された 著者 skazska,
1)$( "#result")。off() - ajaxSendで呼び出され、動的にロードされたスクリプトでは.off()は必要ありません2)use代わりにdynスクリプトの.off(event、element)はajaxSendの1)の.off()の必要はありません。最初にリスナーをクリアするので、dynスクリプト/コンテンツの失敗に対しては問題があります。誤動作)
追加された 著者 skazska,
動的にロードされる各スクリプトに必要なのは、どのスクリプトがどの順序でロードされるのかを予測できないため、または最初の変種のようにajaxSendに$(#result).off()を追加するだけです。どのdynスクリプトでも()ですが、これは信頼できません(リスナーが未登録だが新しいコンテンツとスクリプトのロードに失敗した状況のため)
追加された 著者 skazska,

.off(...)。on(...)アプローチを使用すると、同じイベントに複数の.jsファイルがバインドされている場合に、新しいバインドの前にイベントが明確になります。 :車と野菜の両方とも、異なるロジックでボタンをクリックします。

ただし、そうでない場合は、クラスフィルタを使用して、どの要素にすでにイベントがバインドされているかを検出できます。

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

こうすることで、セレクターはクラス click-bound でまだ装飾されていない要素にのみイベントをバインドします。

1
追加された

ほとんどの場合、あなたがWeb開発でやることを想像することができることはすべてすでに行われています。あなたはそれを見つけてあなたの環境で動くようにするだけです。あなたのコードには多くの問題がありますが、それ以上に私を悩ませているものがあります - なぜ angularJS を参照していない人がいるのですかまたは requireJS そのようなフレームワークやライブラリを使用することには大きな利点があります。

  • いたるところにたくさんのチュートリアルがあります
  • SOに関する何千もの質問
  • 彼らは(たいてい)すぐに使える素晴らしいプラグインを持っています
  • それらはおそらくあなたの実装と比較してより広い機能性を持っています

そしてまたここにあなた自身のコードを使うことの利点があります

  • あなたはそれを理解している唯一の人です。
  • 何かありますか?

私のここでのポイントは、あなたが他の人がすでに作ったものを使うべきだということです。

さらに、Angularのようなフレームワークを使用すると、最終的にははるかにクリーンで保守可能なコードになります。

0
追加された
フレームワークの「デメリット」は、Angular.jsでプロジェクト全体を完全に書き換える必要があること
追加された 著者 user3142695,
あなたはフレームワークの提案に正しいです。しかし、ほとんどの場合それらはより多くの機能を持っています、そして私は必要としています。例:それで、単にフェードとスライド効果だけが必要な場合は、純粋なJSで自分自身でそれを行うのが良いでしょうか、それともJQueryを使うべきですか?
追加された 著者 user3142695,
@ user3142695 animate.cssなど、アニメーションに焦点を当てた別のライブラリを含めることをお勧めします。これらはあなたに何の利益ももたらさないだろういくつかの最小主義的な懸念です。技術は進歩し、数百バイトは何もありません。確かにあなたが先に行くことができて、それがどのように海外でバッテリー寿命を減らすか、またはモバイルインターネットのコストを増加させるかもしれないかを批評します、しかし、もう一度、あなたが本当に困る価値があるあなたは節約しますか?
追加された 著者 php_nub_qq,

あなたはロードするページの名前を取る関数を作成し、ページをロードするための単一の関数を使用することができます。それからコールバック関数に同じ名前の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();
   });
 });
}

コールバックの代わりに約束を使ってこれを行うこともできます。

0
追加された
追加された 著者 user3142695,
OK。 getScriptと関数を使うのか、それとも $ .getScript( "ajax/test.js").done(function(){}); を使うのとで違いはありますか?
追加された 著者 user3142695,
私はあなたの考えが好きですが、残念ながら私はそれを完全には理解していません。上記のコードで example_1.js がどのように表示されるかを教えてください。私にとって理解しやすいでしょう。ありがとう。
追加された 著者 user3142695,
しかし、このJSファイルでは、まだ委任イベントを使用する必要があります。それはそれが同じJSコードであることを意味します、正しいですか?新しいコンテンツ(および新しいJSファイル)がロードされたときに、アンロードする必要がありますか?
追加された 著者 user3142695,
上記のコードでは、 pagename.js ファイルに、その特定のページに対するすべての委任イベントが含まれています。メニューページの場合は、ファイルが読み込まれた後に呼び出す関数を含む menu.js が読み込まれます( init または cb上記のコードのこの関数は menu のために全ての委譲されたイベントを追加するべきです。
追加された 著者 Gary Storey,
dynamic-content を親要素として使用して、委任イベントを追加できます。
追加された 著者 Gary Storey,
あなたがリストアップした方法は約束を使うことです(私はそれを代替手段として述べました)。どちらでも動作します。ドキュメント: api.jquery.com/jQuery.when
追加された 著者 Gary Storey,
ああ、HTMLは loadPage を呼び出すたびに上書きされます。
追加された 著者 Gary Storey,

AJAXを使ってWebを運営する場合は、 PJAX の使用を検討してください。これはAJAX Webサイトを作成するためのバトルテスト済みのライブラリで、githubで使用されています。

下記のPJAXを使った完全な例:

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']">
    
    <div id="vegetables">
    </div>
    <div id="cars">
    </div>
</div>

テンプレート:すべてのページはコンテナとして #content divを持ち、上記の2つの属性を持ちます。

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

さらなる説明:

  1. ウィンドウのグローバルjs名前空間に関数名が付加されているので、スクリプトを再ロードせずに関数を再実行できます。あなたが考え出したように、この関数名はユニークでなければなりません。

  2. この関数は自己実行可能なので、ユーザーがAJAXを使用せずにページにアクセスした場合(つまり、ページのURLに直接アクセスされた場合)、自動的に実行されます。

あなたは、私のHTML要素に を持っているすべてのバインディングについて尋ねているかもしれません。さて、要素が破壊されたり置き換えられたりすると、それらへのコードバインディングはブラウザによってガベージコレクションされるでしょう。だからあなたのメモリ使用量は屋根から急増しません。

AjaxベースのWebサイトを実装するための上記のパターンは、現在私のクライアントの1つの場所で運用中です。そのため、すべてのシナリオで非常にテストされています。

0
追加された
ナビゲーションはWebサイトのメインナビゲーションなので、投稿に示すようなHTMLマークアップを使用しています。そのため、section-elementがコンテンツに使用され、header-elementがナビゲーションに使用されます。その変更を避けたいと思います。これを#content divでラップすると、ページのbody要素を使用できます。しかし、これは本当にどのように使われるべきなのでしょうか。
追加された 著者 user3142695,
パフォーマンスについて少し教えてください。
追加された 著者 user3142695,
content divは単なる例です。ただし、コンテンツをリロードするために body を使用している場合は、ページ全体が置き換えられることを意味します。ユーザーがロードの途中でアイデアを切り替えることを選択し、別のAJAXリンクをクリックした場合は、良い解決策とは言えません。初期ライブラリがロードされた後のパフォーマンスは非常に速いです。ページが表示されるまでに1秒かかります。私は自分のWebサイトにプリローダーを組み込んで、アプリケーションが最初にコンポーネントをロードしているように感じ、その後各アクションを遅くして各ライブラリを徐々にロードするよりも寛容であると感じます。
追加された 著者 Mysteryos,

$( '#navigation')。on( 'some-event'、 '.red'、function(){}); を実行しているとき eventは #navigation 要素にバインドします( $( '#navigation')。data( 'events')で確認できます)。 > .red -elementこれが、新しいjs-logicを使って新しい要素をロードするときに、新しいバインディングと古いバインディングを取得している理由です。

これが可能な場合は、一緒に削除/リロードする必要があるすべてのイベントに対して、 $( '#navigation .red')。some-event(function(){}); のようにストレートバインディングを使用してください要素を持つ。

0
追加された
JavaScript - 日本のコミュニティ
JavaScript - 日本のコミュニティ
2 参加者の

日本人コミュニティのjavascript