sukarno
sukarno

Reputation: 597

How to deal with multiple scripts in a single page web application

Am new to Web development and i started an application where i have index.php with a menu and a div called #content and i load all the different pages in to the div #content using load(); I need to load the respective java script files along with the files that are being loaded in to the div #content.

Issue: Here in the HTML's that is loaded in to the div, i have some similar class names and id's so when i click on any menu item and perform some action there and go to another menu item and perform same action i get the script of previous menu item is being executed!

Is there a better way to load scripts into the div on the menu item click and on click of another menu item remove the previous scripts and load a fresh scripts from the current menu item clicked?

Script i use to load the div #content:

$('.nav-button').on('click', function(e){
e.preventDefault();
$('#content').load('../scripts/loader.php');
$('#content').load($(this).attr('href'));
});

sample1.php page i load in to the div on menu-item1 clicked:

<div id='datagrid'>
<input></input>
<input></input>
<input></input>
</div>
<script type='text/javascript' src='scripts/menu-item1/data_script.js'></script>

sample2.php page i load in to the div on menu-item2 clicked:

<div id='datagrid'>
<input></input>
<input></input>
<input></input>
</div>
<script type='text/javascript' src='scripts/menu-item2/data_script.js'></script>

Upvotes: 4

Views: 1636

Answers (2)

Pebbl
Pebbl

Reputation: 36075

Once a script has been loaded and executed, unless you tidy up your code by yourself, you will still have remnants of that code existing - even if you delete/remove the original script tag. The following points should help:

  1. Whenever you create/import a structure in js, make sure you have coded an unimport / destroy ability.
  2. Do not rely on script tags inserted via .innerHTML or $().html() - older browsers do not respect them and this can cause unexpected results - i.e. script tags being ignored, or in the case of older Internet explorer, attempted to be loaded from the wrong place.
  3. Instead do as thedev recommends and build your script tags programmatically.
  4. Obviously #3 is tricky if you want to have each ajax'd script returning it's associated script tags.
  5. So instead of returning pure HTML with your ajax request, instead return a JSON object that contains HTML and a list of script paths that need to be loaded.
  6. If you make sure that each of your loaded scripts keeps all of it's methods exposed via a particularly named object, you can then remove this object the next time you load your next script.

What follows is illustrative code, there are more complicated (and better) ways of handling this (i.e. avoiding storing objects on the window), plus the following code could be improved in many places - it should only serve to give you an idea of what you could do, and how to think about it.

Also it should be noted, when storing objects on the global window object you should use more unique names than one, two or three ;)

markup:

<ul class="menu">
  <li><a href="one.html" data-namespace="one">One Link</a></li>
  <li><a href="two.html" data-namespace="two">Two Link</a></li>
  <li><a href="three.html" data-namespace="three">Three Link</a></li>
</ul>
<div id="content">
  ...
</div>

javascript:

/// create a wrapping scope so that our variables can be local
/// to our internal code, but not mess around with the global space.
(function(){

  /// create a remembered lastID var that can store what item was loaded
  var lastID;

  /// an addScripts function that takes a string namespace (can be anything
  /// as long as it obeys usual javascript variable naming rules), and an
  /// array of script URIs.
  var addScripts = function(namespace, scripts){
    var s,i;
    for( i=0; i<scripts.length; i++ ){
      s = $('<script />')
            /// attach our src attribute with the right script path
            .attr('src', scripts[i])
            /// add our namespace as a class to help find script tag later
            .addClass(namespace); 
      /// add to the head of the document
      $('head').append(s);
    }
  }

  /// removeScripts works based on using the namespace we passed
  /// when adding scripts as a classname to find the script tags.
  /// remember removing the tags doesn't remove the executed code.
  var removeScripts = function(namespace){
    /// in order to tidy our code we should include a function
    /// to tidy up.
    if ( window[namespace] && window[namespace].tidyup ) {
      window[namespace].tidyup();
    }
    /// remove the tag to keep the markup neat
    $('script.'+namespace).remove();
  }

  /// a typical click handler applied to the a tags
  $('.menu a').click(function(){

    /// if we have a lastID remove what was before.
    if ( lastID ) {
      removeScripts(lastID);
    }

    /// get our useful info from the link
    var target = $('#content');
    var url = $(this).attr('href');
    /// grab out our "namespace" this will be used to tie the scripts
    /// together with the collection object in the loaded javascript.
    var namespace = $(this).attr('data-namespace');

    /// trigger an ajax request that fetches our json data
    /// from the server.
    $.ajax('loader.php',{dataType:'json',data:{url:url}})
      .done(function(data){
        /// once we have that data, add the html to the page
        target.html( data.html );
        /// and then add the scripts
        addScripts( id, data.scripts || [] );
      });

    /// store the last id so we know what to remove next time
    lastID = id;

  });

})();

loader.php:

<?php

  /// create a library of what scripts belong to what page
  $scripts = array(
    'one.html' => array('scripts/js/one.js'),
    'two.html' => array('scripts/js/two.js'),
    'three.html' => array('scripts/js/three.js'),
  );

  /// because `$_GET[url]` can be affected by outside influence
  /// make sure you check it's value before using it.
  switch( ($file = basename($_GET['url'])) ){
    case 'one.html':
    case 'two.html':
    case 'three.html':
      $json = (object) null;
      if ( file_exists($file) ) {
        $json->html = file_get_contents($file);
      }
      if ( isset($scripts[$file]) ) {
        $json->scripts = $scripts[$file];
      }
      header('content-type: application/json');
      /// json_encode should handle escaping all your html correctly
      /// so that it reaches your javascript in one correct piece.
      echo json_encode($json);
    break;
  }

?>

json (returned by the above php):

{
  "html": "<div class=\"random-content\">This can be anything</div>",
  "scripts": ["scripts/js/one.js"]
}

example js include - i.e. (one.js)

/// create our collection object named with the same namespace that
/// appears in the data-namespace attribute in the markup.
window.one = {};

window.one.someMethodThatMightBeUsedByLoadedContent = function(){
  /// this function has been kept specifically as part of the 'one'
  /// object because it needs to be globally accessible by the html
  /// that has been injected into the page. by keeping it as part
  /// of a named object, removing it is rather simple. (see tidyup).
}

window.one.tidyup = function(){
  /// this is the most simplistic way of tidying up a property. Depending
  /// on the complexity of your javascript there are other things you should
  /// keep in mind. If you have any large custom object structures it is best
  /// to traverse these structures key by key and remove each element. Things
  /// to read up on would be 'Javascript memory leaks', 'javascript closures' 
  /// and 'garbage collection'. It is also best to keep in mind you can only
  /// nullify variables i.e. `var abc; abc = null;` -- you can not `delete abc`
  /// this is not a problem for properties i.e. `obj.prop = 123;` and is why
  /// it is best to use them for code you wish to tidy up later.
  delete window.one;
}

$(function(){
  /// trigger off whatever code you need onload
  /// this construction will never been kept by the
  /// javascript runtime as it is entirely anonymous 
  /// so you don't need to be worry about tidying it up.
});

The above code has been manually typed so there may be errors, but it should illustrate one way of achieving a neat loading system that tidies up correctly on each load.

Upvotes: 2

thedev
thedev

Reputation: 2906

Another way of loading scripts:

var script = document.createElement("script");
script.type = "text/javascript";
script.src = src;
script.onload = callback_function;

document.getElementsById("#scriptContainer")[0].appendChild(script);

Regarding the removal of existing scripts:

Try loading your scripts in a certain container and when you want to replace them just clear out the container content and load your new scripts.

You could also try lazy loading using requirejs. Example here

Upvotes: 2

Related Questions