Source: JSAV.js

/** @preserve 
    @file
    JSAV V1.10 24.09.16
    Copyright:  (c) Dr. Andrew C.R. Martin, UCL, 2014-2016
    This program is distributed under the Gnu Public Licence (GPLv2)
*/
/** ***********************************************************************
   Program:    JSAV  
   File:       JSAV.js
   
   Version:    V1.10
   Date:       11.02.16
   Function:   JavaScript Sequence Alignment Viewier
   
   Copyright:  (c) Dr. Andrew C.R. Martin, UCL, 2014-2016
   Author:     Dr. Andrew C.R. Martin
   Address:    Institute of Structural and Molecular Biology
               Division of Biosciences
               University College
               Gower Street
               London
               WC1E 6BT
   EMail:      andrew@bioinf.org.uk
               
**************************************************************************
   This program is distributed under the Gnu Public licence (GPLv2 or 
   above)

   Alternative licences for commercial use are available on request.
**************************************************************************
   Description:
   ============
   JSAV is a simple JavaScript protein sequence alignment viewer. It
   allows you to select a region of the alignment and sort on that
   region.
**************************************************************************
   Revision History:
   =================
   V1.0    06.06.14   Original  By: ACRM
   V1.1    10.06.14   Code cleanup
                      Changed options to a hash
                      Further code cleanup - some code was assuming
                      sequences were stored in an array called 'sequences'
                      Added 'selectable' option
           12.06.14   Added 'deletable' and 'border' options
                      Implemented sequence deletion
           13.06.14   Checks that some sequences are selected before 
                      deletion
                      Cleaned up comments/documentation
                      Cleaned up defaults in printJSAV
                      Changed some routine names
   V1.2    13.06.14   Added highlight option
                      Added submit/submitLabel options
                      Added action/actionLabel options
   V1.2.1  15.06.14   Added height option
                      Changed to use ACRM_alert()
   V1.2.2  16.06.14   Changed to use ACRM_confirm()
   V1.3    16.06.14   Added dotify/nocolour/toggleDotify/toggleNocolour
   V1.4    17.06.14   Added fasta/fastaLabel export options
                      Added consensus sequence display and colourScheme
                      Added selectColour/selectColor and
                      colourChoices/colorChoices
                      Added refresh of options on reload
   V1.5    18.06.14   Added tooltips
   V1.5.1  19.06.14   Added callback option
   V1.6    17.09.14   Changed to manipulate the DOM rather than writing
                      to the document  By: JHN
   V1.7    23.09.15   Added options.toggleDotifyLabel
                      Added options.toggleNocolourLabel and options.toggleNocolorLabel
                      Added options.deleteLabel
                      Added options.idSubmit
                      By: ACRM
   V1.8    24.09.15   Added options.scrollX and options.scrollY
   V1.9    22.12.15   Added options.labels array and label printing
   V1.10   11.02.16   Modified JSAV_wrapAction() to add whole sequence 
                      object to output array rather than just the ID and sequence
                      Added options.idSubmitAttribute so that clicking on an
                      ID can now call a URL with things other than the sequence

TODO: 
      1. Bar display of conservation from entropy
      2. Allow user to specify key sequence for sorting
      3. Clean up passing of globals

*************************************************************************/
/**
This is the only routine called by a user. It takes an array of
sequence objects and displays them as a coloured sortable table
optionally with a slider and sort button, delete button, etc

@example 
var Seqs = [];
Seqs.push({id :"id1b1.L", sequence :"SASSSVNYMYACREFGHIKLMNPTRSTVWY"});
Seqs.push({id :"id1a.L",  sequence :"SASSSTNYMYACDEFGHIKLMNPQRSTVWY"});

var options = Array();
options.width = '400px';
options.sortable = true;
options.highlight = [3,5,10,14];
options.submit = "http://www.bioinf.org.uk/cgi-bin/echo.pl";
options.submitLabel = "Submit sequences";
options.action = "myAction";
options.actionLabel = "My Action";
options.consensus = true;
options.deletable = true;
options.fasta = true;
printJSAV('mySeqDisplay', sequenceArray, options);
Where 'mySeqDisplay' is the name of a div that will be created
      sequenceArray  is an array of sequence objects
      options        is an object describing options - see below

Note that the sequence object can contain additional fields that mean nothing to
JSAV itself but may be used by 'actions' that are called from JSAV (e.g. an
accession code).


@param {object[]}  sequences                      - Array of sequence objects
@param {string}    divId                          - ID of div to print in
@param {Object}    options                        - Options that can be provided - see Properties
@property {bool}      options.sortable            - Should the sorting options be displayed
                                                    (default: false)
@property {string}    options.width               - The width of the selection slider with
                                                    units (default: '400px')
@property {string}    options.height              - The height of the selection slider with
                                                    units (default: '6pt')
@property {bool}      options.selectable          - Should selection checkboxes be displayed
                                                    for each sequence
@property {bool}      options.deletable           - Makes it possible to delete sequences
@property {string}    options.deleteLabel         - Label for delete button
@property {int[]}     options.highlight           - Array of ranges to highlight
@property {string}    options.submit              - URL for submitting selected sequences
@property {string}    options.submitLabel         - Label for submit button
@property {string}    options.idSubmit            - URL for submitting a single sequence where its
                                                    id/label has been clicked. 
                                                    See also options.idSubmitAttribute
@property {bool}      options.idSubmitClean       - Remove non-alpha characters from sequence
                                                    before submitting
@property {string}    options.idSubmitAttribute   - Specifies which attribute of the sequence
                                                    object should be passed to a URL specified
                                                    with options.idSubmit. Default is 'sequence'
@property {string}    options.action              - Function to call using selected sequences.
                                                    This is passed the seqId and array of
                                                    currently selected sequence objects
@property {string}    options.actionLabel         - Label for action button
@property {bool}      options.dotify              - Repeated amino acids in the sequence are
                                                    replaced by a dot
@property {bool}      options.nocolour            - Dotified amino acids are not coloured
                                                    (except deletions)
@property {bool}      options.toggleDotify        - Create a check box for toggling dotify
@property {string}    options.toggleDotifyLabel   - Label for dotify checkbox toggle
@property {bool}      options.toggleNocolour      - Create a check box for toggling nocolour
@property {string}    options.toggleNocolourLabel - Label for nocolour checkbox toggle
@property {bool}      options.fasta               - Create a FASTA export button 
@property {string}    options.fastaLabel          - Label for FASTA export button
@property {bool}      options.consensus           - Display consensus sequence
@property {string}    options.colourScheme        - Default colour scheme - valid options 
                                                    depend on the css, but are currently
                                                    taylor, clustal, zappo, hphob, helix, 
                                                    strand, turn, buried. Note that this must be
                                                    specified in lower case
@property {bool}      options.selectColour        - Display a pull-down to choose the colour 
                                                    scheme.
@property {string[]}  options.colourChoices       - Array of colour scheme names - only used
                                                    if the user has added to the CSS. This
                                                    can be in mixed case.
@property {bool}      options.plainTooltips       - Don't use JQuery tooltips
@property {callback}  options.callback            - Specify the name of a function to be
                                                    called whenever the display is refreshed.
                                                    This is passed the seqId
@property {string}    options.scrollX             - Specify a width for the sequence display
                                                    div and make it scrollable (e.g. "500px")
                                                    Use "100%" to make it the width of the
                                                    screen (or containing div)
@property {string}    options.scrollY             - Specify a height for the sequence display
                                                    div and make it scrollable (e.g. "500px")
@property {string[]}  options.labels              - Array of residue label strings
@property {bool}      options.autolabels          - Automatically generate label strings
                                                    (overrides options.labels)

@author 
- 29.05.14 Original  By: ACRM
- 30.05.14 Now just calls JSAV_buildSequencesHTML() and prints the results
- 05.06.14 Added divId parameter and sortable
- 06.06.14 Added width
- 10.06.14 sortable and width parameters now replaced by 'options'
           Added 'selectable' option
           Stores sequence length in global array
- 11.06.14 Added deletable
- 13.06.14 Cleaned up use of defaults
- 13.06.14 Added highlight
- 13.06.14 Added submit and action, submitLabel and actionLabel
- 15.06.14 Added height
- 16.06.14 Added dotify, nocolour, toggleDotify, toggleNocolour
- 17.06.14 Added fasta, fastaLabel
           Added consensus
           Added colourScheme/colorScheme
           Added selectColour/selectColor and colourChoices/colorChoices
- 18.06.14 Added tooltips and plainTooltips option
- 19.06.14 Added callback
- 02.09.14 Avoid using write and writeln. Rather use jQuery to insert into DOM.
           Fixes overwrite issues with using after page closure. By: JHN
- 23.09.15 Added toggleDotifyLabel 
           Added toggleNocolourLabel/toggleNocolorLabel
           Added deleteLabel
           Move FASTA before submit and action buttons
           Added idSubmit and idSubmitClean
           By: ACRM
- 24.09.15 Added scrollX and scrollY
- 22.12.15 Added labels and autolabels
- 11.02.16 Added idSubmitAttribute
*/
function printJSAV(divId, sequences, options)
{
   // Deal with options
   if(options                     == undefined) { options                     = Array();                   }
   if(options.width               == undefined) { options.width               = "400px";                   }
   if(options.height              == undefined) { options.height              = "6pt";                     }
   if(options.submitLabel         == undefined) { options.submitLabel         = "Submit Sequences";        }
   if(options.actionLabel         == undefined) { options.actionLabel         = "Process Sequences";       }
   if(options.nocolor)                          { options.nocolour            = true;                      }
   if(options.toggleNocolor)                    { options.toggleNocolour      = true;                      }
   if(options.fastaLabel          == undefined) { options.fastaLabel          = "Export Selected";         }
   if(options.colorScheme)                      { options.colourScheme        = options.colorScheme;       }
   if(options.colourScheme        == undefined) { options.colourScheme        = "taylor";                  }
   if(options.selectColor)                      { options.selectColour        = true;                      }
   if(options.colorChoices        != undefined) { options.colourChoices       = options.colorChoices;      }
   if(options.deletable)                        { options.selectable          = true;                      }
   if(options.idSubmitAttribute   == undefined) { options.idSubmitAttribute   = "sequence";                }
   if(options.toggleDotifyLabel   == undefined) { options.toggleDotifyLabel   = "Dotify";                  }
   if(options.toggleNocolourLabel == undefined) { options.toggleNocolourLabel = "Do not colour dots";      }
   if(options.toggleNocolorLabel  != undefined) { options.toggleNocolourLabel = options.toggleNocolorLabel;}
   if(options.deleteLabel         == undefined) { options.deleteLabel         = "Delete Selected";         }
   if(options.autoLabels)                       { options.labels              = JSAV_autoLabels(sequences);}

   // Initialize globals if not yet done
   JSAV_init();

   gOptions[divId]         = options;
   gSequences[divId]       = sequences;
   gSequenceLengths[divId] = sequences[0].sequence.length;

   // Enable JQuery tooltips
   if(!options.plainTooltips)
   {
      $(function() {
         $(document).tooltip();
      });
   }

   if(options.consensus)
   {
       gConsensus[divId] = JSAV_buildConsensus(sequences);
   }
   var div = '';
   if($("#" + divId).length == 0) {
       div = $('<div />').appendTo($('body'));
       div.attr('id', divId);
   }
   else{
       div = $("#" + divId);
   }
   var div_sortable = $('<div />').appendTo(div);
   div_sortable.attr('id', divId + '_sortable');
   div_sortable.attr('class', 'JSAVDisplay');

   // 24.09.15
   if(options.scrollX != null)
   {
      div_sortable.css('overflow-x', 'scroll');
      div_sortable.css('white-space', 'nowrap');
      div_sortable.css('width', options.scrollX);
   }
   
   // 24.09.15
   if(options.scrollY != null)
   {
      div_sortable.css('overflow-y', 'scroll');
      div_sortable.css('white-space', 'nowrap');
      div_sortable.css('height', options.scrollY);
   }

   var html = JSAV_buildSequencesHTML(divId, sequences, options.sortable, 
                                      options.selectable, options.highlight,
                                      options.dotify, options.nocolour, options.consensus,
                                      options.labels);
   div_sortable.append(html);


   var div_controls = $('<div />').appendTo(div);
   div_controls.attr('id', divId + '_controls');
   div_controls.attr('class', 'JSAVControls');

   if(options.sortable)
   {
      var start = 1;
      var stop  = gSequenceLengths[divId];

      JSAV_printSlider(divId, stop, options.width, options.height);

      var html = "<button type='button' class='tooltip sortbutton' title='Sort the sequences based on the range specified with the slider' onclick='JSAV_sortAndRefreshSequences(\"" + divId + "\", true, " + options.selectable + ", " + options.border + ")'>Sort</button>";
      div_controls.append(html);

   }

   // 23.09.15 Pass label into JSAV_printDelete()
   if(options.deletable)
   {
      JSAV_printDelete(divId, options.deleteLabel);
   }

   // 23.09.15 Move FASTA before submit and action buttons
   if(options.fasta)
   {
      JSAV_printFASTA(divId);
   }

   if(options.submit != undefined)
   {
      JSAV_printSubmit(divId, options.submit, options.submitLabel);
   }

   if(options.action != undefined)
   {
      JSAV_printAction(divId, options.action, options.actionLabel);
   }

   // Colour related - on a new line
   if(options.selectColour || options.toggleDotify)
   {
       div_controls.append("<br />");
   }
   if(options.selectColour)
   {
       JSAV_printColourSelector(divId, options);
   }
   if(options.toggleDotify)
   {
       JSAV_printToggleDotify(divId, options);
       if(options.toggleNocolour)
       {
           JSAV_printToggleNocolour(divId, options);
       }
   }

   if(options.border)
   {
       JSAV_modifyCSS(divId);
   }

   // Ensure buttons etc match the data
   window.onload = function(){JSAV_refreshSettings(divId);};

   if(options.callback != undefined)
   {
       window[options.callback](divId);
   }
}

// ---------------------------------------------------------------------
/**
Updates the buttons to match the settings. This is called when the
window is reloaded

@param {string} divId   - The ID of the div we are printing in

@author 
- 17.06.14 Original   By: ACRM
*/
function JSAV_refreshSettings(divId)
{
    if(gOptions[divId].selectColour)
    {
        // Colour scheme
        var tag = "#" + divId + "_selectColour";
        $(tag).val(gOptions[divId].colourScheme);
    }
    if(gOptions[divId].toggleDotify)
    {
        // Dotify option
        var tag = "#" + divId + "_toggleDotify";
        if(gOptions[divId].dotify == undefined)
        {
            $(tag).prop('checked', false);
        }
        else
        {
            $(tag).prop('checked', gOptions[divId].dotify);
        }
    }
    if(gOptions[divId].toggleNocolour)
    {
        // Dotify-nocolour option
        var tag = "#" + divId + "_toggleNocolour";
        if(gOptions[divId].nocolour == undefined)
        {
            $(tag).prop('checked', false);
        }
        else
        {
            $(tag).prop('checked', gOptions[divId].nocolour);
        }
    }
    if(gOptions[divId].selectable)
    {
        // Selectable
        var tag = "#" + divId + "_AllNone";
        $(tag).prop('checked', false);
//        JSAV_unselectAll(divId);
    }
}

// ---------------------------------------------------------------------
/** 
Prints a pulldown menu to select a colour scheme

@param {string} divId   - The ID of the div we are printing in
@param {object} options - User options

@author 
- 17.06.14 Original   By: ACRM
- 18.06.14 Added tooltip
- 02.09.14 Modifies the DOM rather than writing to document  By: JHN
*/
function JSAV_printColourSelector(divId, options)
{
   // Initialize colour choices if not provided
   if(options.colourChoices == undefined)
   {
      options.colourChoices = JSAV_initColourChoices();
   }

    var id   = divId + "_selectColour";
    var html = "<select class='tooltip' title='Select colour scheme' id = '" + id + "' onchange='JSAV_setColourScheme(\"" + divId + "\", this)'>";
    for(var i=0; i<options.colourChoices.length; i++)
    {
        var lcChoice = options.colourChoices[i].toLowerCase();
        var selected = "";
        if(options.colourScheme == lcChoice)
        {
            selected = " selected='selected'";
        }
        html += "<option value='" + lcChoice + "'" + selected + ">" + 
                options.colourChoices[i] + "</option>";
    }
    html += "</select>";
    var parrenttag = '#' + divId + '_controls';
    $(parrenttag).append(html);
}

// ---------------------------------------------------------------------
/**
Called when the colour scheme selector is changed. Sets the selected
colour scheme and refreshes the display

@param {string}  divId    - The ID of the div we are working in
@param {object}  select   - The select pull-down

@author 
- 17.06.14  Original   By: ACRM
- 22.12.15  Added passing of labels
*/
function JSAV_setColourScheme(divId, select)
{
    gOptions[divId].colourScheme = select.value;

    var options = gOptions[divId];
    if(options.sorted)
    {
        JSAV_sortAndRefreshSequences(divId, options.sortable, options.selectable, options.border)
    }
    else
    {
        JSAV_refresh(divId, gSequences[divId], options.sortable, options.selectable, options.border, gStartPos[divId]-1, gStopPos[divId]-1, options.highlight, options.dotify, options.nocolour, options.consensus, options.labels);
    }
}

// ---------------------------------------------------------------------
/**
Prints the button to allow FASTA export

@param {string} divId   - The ID of the div we are printing in

@author 
- 17.06.14 Original   By: ACRM
- 18.06.14 Added tooltip
- 02.09.14 Modifies the DOM rather than printing to the document By: JHN
*/
function JSAV_printFASTA(divId)
{
   var parrenttag = '#' + divId + '_controls';
   var label = gOptions[divId].fastaLabel;
   var html = "<button type='button' class='tooltip exportbutton' title='Export the selected sequences, or all sequences if none selected' onclick='JSAV_exportFASTA(\"" + divId + "\")'>"+label+"</button>";
   $(parrenttag).append(html);
}

// ---------------------------------------------------------------------
/**
Exports the selected sequences as FASTA

@param {string} divId   - The ID of the div we are printing in

@author 
- 17.06.14 Original   By: ACRM
*/
function JSAV_exportFASTA(divId)
{
   var sequenceText = JSAV_buildFASTA(divId);

   ACRM_dialog("FASTA Export", sequenceText, 600, true);
}

// ---------------------------------------------------------------------
/** 
Print a checkbox for toggling dotify mode

@param {string}  divId    The div that we are working in
@param {object}  options  The options

@author 
- 16.06.14 Original   By: ACRM
- 18.06.14 Added tooltip
- 02.09.14 Modifies the DOM rather than printing to the document By: JHN
- 23.09.15 Dotify label now comes from options  By: ACRM
*/
function JSAV_printToggleDotify(divId, options)
{
    var html = "";
    var checked = "";
    if(options.dotify) { checked = " checked='checked'"; };
    var id = divId + "_toggleDotify";
    var idText = " id='" + id + "'";
    var onclick = " onclick='JSAV_toggleOption(\"" + divId + "\", \"" + id + "\", \"dotify\")'";
    var tooltip = "Replace repeated residues with dots";

    html += "<span><input type='checkbox'" + idText + checked + onclick + "/><label for='"+id+"' class='tooltip' title='"+tooltip+"'>" + options.toggleDotifyLabel + "</label></span>";

    var parrenttag = '#' + divId + '_controls';
    $(parrenttag).append(html);
}

// ---------------------------------------------------------------------
/** 
Print a checkbox for toggling nocolour-dotify mode

@param {string}  divId    The div that we are working in
@param {object}  options  The options

@author 
- 16.06.14 Original   By: ACRM
- 18.06.14 Added tooltip
- 02.09.14 Modifies the DOM rather than printing to the document By: JHN
- 23.09.15 Obtains label from options.toggleNocolourLabel  By: ACRM
*/
function JSAV_printToggleNocolour(divId, options)
{
    var html = "";
    var checked = "";
    if(options.nocolour) { checked = " checked='checked'"; };
    var id = divId + "_toggleNocolour";
    var idText = " id='" + id + "'";
    var onclick = " onclick='JSAV_toggleOption(\"" + divId + "\", \"" + id + "\", \"nocolour\")'";
    var tooltip = "Do not colour repeated residues";

    html += "<span><input type='checkbox'" + idText + checked + onclick + "/><label for='"+id+"' class='tooltip' title='"+tooltip+"'>" + options.toggleNocolourLabel + "</label></span>";
    var parrenttag = '#' + divId + '_controls';
    $(parrenttag).append(html);
}

// ---------------------------------------------------------------------
/** 
Read a checkbox and toggle the associated option, refreshing the display

@param {string}  divId     The div that we are working in
@param {string}  theButton The ID of the checkbox we are looking at
@param {string}  theOption The name of the option we are toggling

@author 
- 16.06.14 Original   By: ACRM
- 17.06.14 Added consensus
- 22.12.15 Added labels
*/
function JSAV_toggleOption(divId, theButton, theOption)
{
    var tag     = "#" + theButton;
    var options = gOptions[divId];
    options[theOption] = $(tag).prop('checked');
    if(options.sorted)
    {
        JSAV_sortAndRefreshSequences(divId, options.sortable, options.selectable, options.border)
    }
    else
    {
        JSAV_refresh(divId, gSequences[divId], options.sortable, options.selectable, options.border, gStartPos[divId]-1, gStopPos[divId]-1, options.highlight, options.dotify, options.nocolour, options.consensus, options.labels);
    }
}

// ---------------------------------------------------------------------
/**
Builds HTML for table rows that highlight a region in the alignment as
being important (e.g. CDRs of antibodies). Note that ranges for highlighting
count from zero.

@param   {string}  divId      - The div we are working in
@param   {int}     seqLen     - The length of the alignment
@param   {bool}    selectable - Are there sequences selection boxes
@param   {int[]}   highlight  - Array of residue ranges to highlight
@returns {string}             - HTML

@author 
- 13.06.14   Original   By: ACRM
*/
function JSAV_buildHighlightHTML(divId, seqLen, selectable, highlight)
{
    var html = "";

    if(selectable)
    {
        html += "<tr class='highlightrow'><th></th>";
        html += "<td></td>";
    }
    else
    {
        html += "<tr class='highlightrow'><td></td>";
    }

    for(var i=0; i<seqLen; i++)
    {
        var displayClass = 'unhighlighted';
        for(var j=0; j<highlight.length; j+=2)
        {
            var start = highlight[j];
            var stop  = highlight[j+1];
            if((i >= start) && (i <= stop))
            {
                displayClass = 'highlighted';
                break;
            }
        }
        html += "<td class='" + displayClass + "' /></td>";
    }
    html += "</tr>\n";
    return(html);
}

// ---------------------------------------------------------------------
/**
Prints the delete button

@param {string}  divId   - The ID of the div to print in
@param {string}  label   - The label to print in the delete button

@author 
- 12.06.14 Original   By: ACRM
- 18.06.14 Added tooltip
- 02.09.14 Modifies the DOM rather than printing to the document By: JHN
- 23.09.15 Added label parameter and use of this label  By: ACRM
*/
function JSAV_printDelete(divId, label)
{
   var parrenttag = '#' + divId + '_controls';
   var html = "<button type='button' class='tooltip delete' title='Delete the selected sequences' onclick='JSAV_deleteSelectedSequences(\"" + divId + "\")'>" + label + "</button>";
   $(parrenttag).append(html);
}

// ---------------------------------------------------------------------
/**
Prints the submit button

@param {string}  divId   - The ID of the div to print in
@param {string}  url     - URL to which we are submitting
@param {string}  label   - Label for submit button

@author 
- 12.06.14 Original   By: ACRM
- 18.06.14 Added tooltip
- 02.09.14 Modifies the DOM rather than printing to the document By: JHN
*/
function JSAV_printSubmit(divId, url, label)
{
   var parrenttag = '#' + divId + '_controls';
   var html = "<button type='button' class='tooltip submitbutton' title='Submit the selected sequences, or all sequences if none selected' onclick='JSAV_submitSequences(\"" + divId + "\")'>" + label + "</button>";
   $(parrenttag).append(html);

   // Build a hidden sequences text box in the form to contain
   var formId = divId + "_form"; 
   var html = "<div style='display:none'><form id='" + formId + "' action='" + url + "' method='post'>";
   var textId = divId + "_submit";
   html += "<textarea id='" + textId + "' name='sequences'></textarea>";
   html += "</form></div>";
   $(parrenttag).append(html);
}

// ---------------------------------------------------------------------
/**
Prints the action button

@param {string}  divId   - The ID of the div to print in
@param {string}  action  - Function to call
@param {string}  label   - Label for action button

@author 
- 13.06.14 Original   By: ACRM
- 18.06.14 Added tooltip
*/
function JSAV_printAction(divId, action, label)
{
   var parrenttag = '#' + divId + '_controls';
   var html = "<button type='button' class='tooltip actionbutton' title='Process the selected sequences, or all sequences if none selected' onclick='JSAV_wrapAction(\"" + divId + "\", \"" + action + "\")'>" + label + "</button>";
   $(parrenttag).append(html);
}

// ---------------------------------------------------------------------
/**
Wrap the action function. When the action button is clicked, this function
is called to extract the relevant sequence data and call the user specified
function, passing the divId and an array of sequence objects

@param {string}  divId   - The ID of the div to work in
@param {string}  action  - The name of the user function

@author 
- 13.06.14  Original   By: ACRM
- 11.02.16  Modified the push so that it pushes the whole object rather than
            just the selected fields. This allows additional information fields
            to be passed around associated with a sequence but not displayed
*/
function JSAV_wrapAction(divId, action)
{
   var selectedSequences = Array();

   // See if any checkboxes are set
   var count = 0;
   var toSubmit = Array();
   // Find the selected sequences
   var tag = "#" + divId + " .selectCell input";
   $(tag).each(function(index) {
       if($(this).prop('checked'))
       {
           var id = $(this).parent().parent().attr('id');
           toSubmit[id] = 1;
           count++;
       }
   });

   var textTag = "textarea#" + divId + "_submit";
   var sequenceText = "";
   var sequences = gSequences[divId];
   for(var i=0; i<sequences.length; i++)
   {
      if((count == 0) || (count == sequences.length) || (toSubmit[sequences[i].id] == 1))
      {
         selectedSequences.push(sequences[i]);
      }
   }

   window[action](divId, selectedSequences);
}

// ---------------------------------------------------------------------
/**
Handles the user clicking the submit button. Build a FASTA version of the
sequences that have been selected, fills them into a textarea in a form
and then submits the form.

@param {string} divId  - The ID of the div we are working in

@author 
- 13.06.14  Original   By: ACRM
- 17.06.14  Split out the JSAV_buildFASTA() section
*/
function JSAV_submitSequences(divId)
{
   var sequenceText = JSAV_buildFASTA(divId);

   var textTag = "textarea#" + divId + "_submit";
   $(textTag).text(sequenceText);

   var formTag = "#" + divId + "_form";
   $(formTag).submit();
}

// ---------------------------------------------------------------------
/**
Builds a FASTA version of the sequences that are currently selected, or all
sequences if none are selected

@param   {string} divId   - The ID of the div we are printing in
@returns {string}         - FASTA sequence

@author 
- 17.06.14 Split out from JSAV_submitSequences()  By: ACRM
*/
function JSAV_buildFASTA(divId)
{
   // See if any checkboxes are set
   var count = 0;
   var toFASTA = Array();
   // Find the selected sequences
   var tag = "#" + divId + " .selectCell input";
   $(tag).each(function(index) {
       if($(this).prop('checked'))
       {
           var id = $(this).parent().parent().attr('id');
           toFASTA[id] = 1;
           count++;
       }
   });

   var sequenceText = "";
   var sequences = gSequences[divId];
   for(var i=0; i<sequences.length; i++)
   {
       if((count == 0) || (count == sequences.length) || (toFASTA[sequences[i].id] == 1))
       {
           sequenceText += ">" + sequences[i].id + "\n";
           sequenceText += sequences[i].sequence + "\n";
       }
   }

   return(sequenceText);
}

// ---------------------------------------------------------------------
/**
Deletes a set of sequences that have been clicked

@param {string}  divId   - The ID of the div to work in

@author 
- 12.06.14 Original   By: ACRM
- 15.06.14 Changed from alert() to ACRM_alert()
- 16.06.14 Changed from confirm() to ACRM_confirm()
- 17.06.14 Added consensus
- 22.12.15 Added passing of labels
*/
function JSAV_deleteSelectedSequences(divId)
{
    var count = 0;
    var toDelete = Array();
    // Find the selected sequences
    var tag = "#" + divId + " .selectCell input";
    $(tag).each(function(index) {
        if($(this).prop('checked'))
        {
            var id = $(this).parent().parent().attr('id');
            toDelete.push(id);
            count++;
        }
    });

    if(count == 0)
    {
        ACRM_alert("Error!","You must select some sequences!");
    }
    else if(count == gSequences[divId].length)
    {
        ACRM_alert("Error!","You can't delete all the sequences!");
    }
    else
    {
        var message = "Delete " + count + " selected sequences?";
        ACRM_confirm("Confirm", message, function(confirm){
            if(confirm)
            {
                // Run through the global sequence array deleting the selected objects
                for(var i=0; i<toDelete.length; i++)
                {
                    ACRM_deleteItemByLabel('id', toDelete[i], gSequences[divId]);
                }

                var options = gOptions[divId];

                // Update the consensus
                if(options.consensus)
                {
                    gConsensus[divId] = JSAV_buildConsensus(gSequences[divId]);
                }

                // Refresh the display
                JSAV_refresh(divId, gSequences[divId], options.sortable, 
                             options.selectable, options.border, 
                             gStartPos[divId]-1, gStopPos[divId]-1, options.highlight,
                             options.dotify, options.nocolour, options.consensus,
                             options.labels);
                options.sorted = false;
            }
        });
    }
}

// ---------------------------------------------------------------------
/**
Toggle selection of all sequence selection buttons

@param {string}  divId   - The ID of the div to work in

@author 
- 09.06.14 Original   By: ACRM
*/
function JSAV_selectAllOrNone(divId)
{
   var tag = "#" + divId + "_AllNone";

   if($(tag).prop('checked'))
   {
       JSAV_selectAll(divId);
   }
   else
   {
       JSAV_unselectAll(divId);
   }
}


// ---------------------------------------------------------------------
/**
Select all sequence selection buttons

@param {string}  divId   - The ID of the div to work in

@author 
- 09.06.14 Original   By: ACRM
*/
function JSAV_selectAll(divId)
{
   var tag = "#" + divId + " .selectCell input";
   $(tag).prop('checked', true);
}

// ---------------------------------------------------------------------
/**
Unselect all sequence selection buttons

@param {string}  divId   - The ID of the div to work in

@author 
- 09.06.14 Original   By: ACRM
*/
function JSAV_unselectAll(divId)
{
   var tag = "#" + divId + " .selectCell input";
   $(tag).prop('checked', false);
}

// ---------------------------------------------------------------------
/**
Change the <td> elements to have a white border

@param {string}  divId   - The ID of the div to work in

@author 
- 12.06.14 Original   By: ACRM
*/
function JSAV_modifyCSS(divId)
{
    var selector = "#" + divId + " .JSAV td";
    $(selector).css("border", "1px solid white");
}

// ---------------------------------------------------------------------
/**
Builds the HTML for printing a sequence as a table row. The row 
starts with the identifier and is followed by each amino acid in a 
separate <td> tag with a class to indicate the amino acid type 
(e.g. taylorW for a tryptophan in Willie Taylor scheme). 

@param {string}   id            The identifier
@param {string}   sequence      A string containing the sequence
@param {string}   prevSequence  A string containing the previous sequence
@param {bool}     selectable    Display a selection checkbox
@param {bool}     dotify        Dotify the sequence
@param {bool}     nocolour      Don't colour dotified residues
@param {bool}     isConsensus   This is the consensus sequence
@param {string}   colourScheme  Name of colour scheme - maps to CSS - 
                                see JSAV_initColourChoices()
@param {string}   idSubmit      URL to visit when sequence label clicked
@param {bool}     idSubmitClean Remove non-alpha characters from sequence
                                before submitting it
@returns {string} text          HTML snippet

@author 
- 30.05.14 Original  By: ACRM
- 16.06.14 Added dotify and nocolour - now takes prevSequence parameter
- 17.06.14 Added isConsensus and colourScheme
- 18.06.14 Added tooltip
- 23.09.15 Added idSubmit/idSubmitClean
- 11.02.16 Added idSubmitAttribute, now takes a sequence object rather than
           the sequence and the id
*/
function JSAV_buildASequenceHTML(sequenceObject, id, sequence, prevSequence, selectable, dotify, nocolour,
                                 isConsensus, colourScheme, idSubmit, idSubmitClean,
                                 idSubmitAttribute)
{
    var seqArray     = sequence.split("");
    var prevSeqArray = undefined;

    if(dotify && (prevSequence != undefined))
    {
        prevSeqArray = prevSequence.split("");
    }

    var tableLine = "";
    if(isConsensus)
    {
        tableLine = "<tr class='tooltip consensusCell' title='The consensus shows the most frequent amino acid. This is lower case if ≤50% of the sequences have that residue.' id='" + id + "'>";
    }
    else
    {
        tableLine = "<tr id='" + id + "'>";
    }

    if(idSubmit == null)
    {
       tableLine += "<th class='idCell'>" + id + "</th>";
    }
    else
    {
       var url         = idSubmit;
       var submitParam = sequenceObject[idSubmitAttribute];
       if(idSubmitClean)
       {
          // This would only normally be done in the default case where idSubmitAttribute is 'sequence'
          // It probably wouldn't make sense for IDs etc
          submitParam = submitParam.replace(/[^A-Za-z0-9]/g, '');
       }
       
       url += submitParam;
       tableLine += "<th class='idCell'><a href='" + url + "'>" + id + "</a></th>";
    }

    if(selectable)
    {
        if(isConsensus)
        {
            tableLine += "<th class='consensusSelectCell'></th>";
        }
        else
        {
            var name = "select_" + id;
            tableLine += "<th class='selectCell'><input type='checkbox' name='" + name + "' /></th>";
        }
    }

    var consensusClass = "";
    if(isConsensus)
    {
        consensusClass = " consensusCell";
    }

    var nResidues = seqArray.length;
    if(dotify && !isConsensus)
    {
        for(var i=0; i<nResidues; i++)
        {
            var aa     = seqArray[i];
            var prevAa = '#';

            var colourClass = colourScheme + aa.toUpperCase();
            if(nocolour)
            {
                if(aa == "-") 
                { 
                    colourClass = "aaDel"; 
                }
            }

            if(prevSeqArray != undefined)
            {
                prevAa = prevSeqArray[i];
            }
            if(aa == prevAa)
            {
                if(nocolour)
                {
                    if(aa == '-')
                    {
                        colourClass = "aaDel";
                    }
                    else
                    {
                        colourClass = "aaDot";
                    }
                }
                if(aa != '-') {aa = '.';}
            }
            tableLine += "<td class='" + colourClass + "'>" + aa + "</td>";
        }
    }
    else
    {
        for(var i=0; i<nResidues; i++)
        {
            var aa = seqArray[i];
            tableLine += "<td class='" + colourScheme + aa.toUpperCase() + consensusClass + "'>" + aa + "</td>";
        }
    }
    tableLine += "</td></tr>";

    return(tableLine);
}

// ---------------------------------------------------------------------
/**
Builds and prints the slider for selecting a maximum and minimum position for
sorting. Also calls routine to display the currently selected range -
i.e. the whole sequence length

@param {string}   divId   The name of the div used for the display
@param {int}      seqLen  The length of the sequence alignment
@param {string}   width   The width of the slider
@param {string}   height  The height of the slider (text size)

@author 
- 06.06.14 Original   By: ACRM
- 10.06.14 Removed redundant variable and changed divs to spans
- 15.06.14 Added height
*/
function JSAV_printSlider(divId, seqLen, width, height)
{

   var parrenttag = '#' + divId + '_controls';
   var id = divId + "_slider";
   var tag = "#" + id;

   var span_showrange = $("<span id='" + divId + "_showrange'></span>").appendTo(parrenttag);
   var span_slider = $("<span id='" + divId + "_slider'></span>").appendTo(parrenttag);

   $(tag).css('width',   width);
   $(tag).css('margin',  '10px');
   $(tag).css('display', 'block');

   $(tag).slider({
         range: true,
         min: 1,
         max: seqLen,
         values: [1, seqLen],
         slide: JSAV_showRange
   });

   // Initial display of the range
   JSAV_showRange(divId);
}

// ---------------------------------------------------------------------
/**
Displays the currently selected range as text and calls the routine
to higlight that range in the alignment view.

Called as JSAV_showRange(divID), or as a callback from a slider event

@param {JQEvent}   eventOrId    JQuery Event
@param {JQ-UI}     ui           JQuery UI object
--OR--
@param {text}      eventOrId    Identifier of the display div
@param {null}      ui           Must be set to null

@author 
- 06.06.14  Original   By: ACRM
- 10.06.14  Removed redundant .closest() from finding parent
- 23.09.15  Changed "Sort from: xx to: xx" to "Region: positions xx to yy"
- 24.09.15  Changed to using .html() instead of .text()
*/
function JSAV_showRange(eventOrId, ui)
{
   // Here the eventOrId is the id of the div where we are working
   if(ui == null)
   {
      // Get the values out of the slider
      var tag = "#" + eventOrId + "_slider";
      gStartPos[eventOrId] = $(tag).slider("values", 0);
      gStopPos[eventOrId]  = $(tag).slider("values", 1);

      // Display the range currently selected
      tag = "#" + eventOrId + "_showrange";
      var html = "<p>Region: positions " + gStartPos[eventOrId] + " to " + gStopPos[eventOrId] + "</p>";
      $(tag).html(html);
      JSAV_markRange(eventOrId, gSequenceLengths[eventOrId], gStartPos[eventOrId]-1, gStopPos[eventOrId]-1);
   }
   else
   {
      var id = $(this).parent().parent().attr("id");
      var tag = "#" + id + "_showrange";

      // Get the values out of the slider
      gStartPos[id] = ui.values[0];
      gStopPos[id]  = ui.values[1];

      // Display the range currently selected
      var html = "<p>Region: positions " + gStartPos[id] + " to " + gStopPos[id] + "</p>";
      $(tag).html(html);
      JSAV_markRange(id, gSequenceLengths[id], gStartPos[id]-1, gStopPos[id]-1);
   }
}

// ---------------------------------------------------------------------
/**
Simple wrapper function to obtain the currently selected range

@param   {string}   divId    Identifier for display div
@returns {int[]}             Start and stop of range

@author 
- 06.06.14  Original   By: ACRM
*/
function JSAV_getRange(divId)
{
   var start = gStartPos[divId]-1;
   var stop  = gStopPos[divId]-1;
   return([start, stop]);
}

// ---------------------------------------------------------------------
/**
Takes an array of sequence objects and builds the HTML to display
them as a coloured table

@param   {string}     divId       ID of div in which to print
@param   {object[]}   sequences   Array of sequence objects
@param   {bool}       sortable    Should the marker line be displayed
                                  for sortable displays
@param   {bool}       selectable  Should check marks be displayed
@param   {int[]}      highlight   Ranges to be highlighted
@param   {bool}       dotify      Dotify the sequence alignment
@param   {bool}       nocolour    Don't colour dotified residues
@param   {bool}       consensus   Display the consensus sequence
@param   {array}      labels      Labels to display over sequence     
@returns {string}                 HTML

@author 
- 30.05.14 Original  By: ACRM
- 06.06.14 Added call to build the marker row of selected residues
- 10.06.14 Added sortable and selectable parameters
- 13.06.14 Added highlight
- 16.06.14 Added dotify
- 17.06.14 Added consensus
- 22.12.15 Added labels
*/
function JSAV_buildSequencesHTML(divId, sequences, sortable, selectable, highlight,
                                 dotify, nocolour, consensus, labels)
{
   var html = "";
   html += "<div class='JSAV'>\n";
   html += "<table border='0'>\n";

   if(selectable)
   {
       // Create the toggle all/none selection button
       html += JSAV_buildSelectAllHTML(divId, gSequenceLengths[divId]);
   }

   if(labels != undefined)
   {
       html += JSAV_buildLabelsHTML(divId,  gSequenceLengths[divId], selectable, labels);
   }

   if(highlight != undefined)
   {
       // If we are highlighting regions in the sequence, do so
       html += JSAV_buildHighlightHTML(divId, gSequenceLengths[divId], selectable, highlight);
   }

   // Build the actual sequence entries
   for(var i=0; i<sequences.length; i++)
   {
      var prevSequence = undefined;
      if(i>0) { prevSequence = sequences[i-1].sequence; }
      html += JSAV_buildASequenceHTML(sequences[i], sequences[i].id, sequences[i].sequence, prevSequence, 
                                      selectable, dotify, nocolour, false, 
                                      gOptions[divId].colourScheme,
                                      gOptions[divId].idSubmit,
                                      gOptions[divId].idSubmitClean,
                                      gOptions[divId].idSubmitAttribute) + "\n";
   }

   if(consensus != undefined)
   {
      html += JSAV_buildASequenceHTML(null, 'Consensus', gConsensus[divId], undefined,
                                      selectable, dotify, nocolour, true,
                                      gOptions[divId].colourScheme, 
                                      null,
                                      false) + "\n";
   }

   if(highlight != undefined)
   {
       // If we are highlighting regions in the sequence, do so again at the bottom of the table
       html += JSAV_buildHighlightHTML(divId, gSequenceLengths[divId], selectable, highlight);
   }

   if(sortable)
   {
      // The marker section which shows the range selected for sorting
      html += JSAV_buildMarkerHTML(divId, gSequenceLengths[divId], selectable);
   }

   html += "</table>\n";
   html += "</div>\n";
   return(html);
}


// ---------------------------------------------------------------------
/**
Build the HTML for creating a row in the table that contains a checkbox
for selecting/deselecting all sequences

@param   {string}  divId  - ID of the div we are working in
@param   {int}     seqLen - sequence length
@returns {string}         - HTML

@author 
- 09.06.14 Original   By: ACRM
- 18.06.14 Added tooltip
*/
function JSAV_buildSelectAllHTML(divId, seqLen)
{
   var html;
   var id = divId + "_AllNone";

   html = "<tr><th class='idCell'>All/None</th><th><input class='tooltip' title='Select or deselect all sequences' id='" + id + "' type='checkbox' onclick='JSAV_selectAllOrNone(\"" + divId + "\");' /></th>";
   for(var i=0; i<seqLen; i++)
   {
      html += "<td></td>";
   }
   html += "</tr>";
   return(html);
}

// ---------------------------------------------------------------------
/**
Creates the HTML to display the marker row that indicates the selected
residues to be used for sorting

@param   {string}   divId      - Identifier of display div
@param   {int}      seqLen     - Length of sequences alignement
@param   {int}      selectable - Do we have select boxes?
@returns {string}              - HTML

@author 
- 06.06.14  Original   By: ACRM
- 10.06.14  Added 'selectable'
- 23.09.15  Added   into 'Sort Region' to stop it breaking the line
*/
function JSAV_buildMarkerHTML(divId, seqLen, selectable)
{
    var html = "";

    if(selectable)
    {
        html += "<tr class='markerrow'><th class='idCell'>Sort Region:</th>";
        html += "<th class='selectCell'></th>";
    }
    else
    {
        html += "<tr class='markerrow'><th class='idCell'>Sort Region:</th>";
    }

    for(var i=0; i<seqLen; i++)
    {
        var id = divId + "_JSAVMarker" + i;
        html += "<td id='" + id + "' class='unmarked'> </td>";
    }
    html += "</tr>\n";
    return(html);
}

// ---------------------------------------------------------------------
/**
Finds the real ends of a sequence stored in an array by skipping over
'-' characters. Returns the offsets of the actual ends. 
NOTE: Consequently to loop over the sequence you now need to do 
   for(var i=seqEnds[0]; i<=seqEnds[1]; i++)
i.e. <= rather than < the last position

@param   {char[]}   seqArray   Sequence stored in an array
@returns {int[]}               Offsets of first and last real
                               amino acid

@author 
- 04.06.14 Original   By: ACRM
*/
function JSAV_FindRealSequenceEnds(seqArray)
{
   var seqEnds = [];

   // Find the real start - just increment the counter while there is a - in the sequence
   seqEnds[0] = 0;
   while((seqArray[seqEnds[0]] == '-') || (seqArray[seqEnds[0]] == ' '))
   {
      seqEnds[0]++;
   }

   // Find the real end - just decrement the counter while there is a - in the sequence
   seqEnds[1] = seqArray.length - 1;
   while((seqArray[seqEnds[1]] == '-') || (seqArray[seqEnds[1]] == ' '))
   {
      seqEnds[1]--;
   }

   return(seqEnds);
}

// ---------------------------------------------------------------------
/**
Choose a representative from a difference matrix. The difference
matrix contains the number of differences between each pair of 
sequences. This routine simply totals up the number of differences
for each sequence compared with all the other sequences and then
chooses the one with fewest differences

@param   {int-2DArray}  differenceMatrix  - Differences between each pair
                                            of sequences
@returns {int}                            - Index of the representative
                                            sequence

@author 
- 29.05.14 Original   By: ACRM
*/
function JSAV_chooseRepresentative(differenceMatrix)
{
   var totalDiffs = [];
   var nSeqs = differenceMatrix.length;

   // For each sequence, find its total differences from all other 
   // sequences
   for(var i=0; i<nSeqs; i++)
   {
      totalDiffs[i] = 0;
      for(var j=0; j<differenceMatrix[i].length; j++)
      {
         totalDiffs[i] += differenceMatrix[i][j];
      }
   }

   // Find which sequence is most similar to all the other sequences
   var lowestDiff = 1000000;
   var representative = -1;
   for(var i=0; i<nSeqs; i++)
   {
      if(totalDiffs[i] < lowestDiff)
      {
         lowestDiff = totalDiffs[i];
         representative = i;
      }
   }

   return(representative);
}

// ---------------------------------------------------------------------
/**
Takes an array of sequence indexes, the index of a reference sequence
and the difference matrix between all sequences. It then returns an
array of sequence indexes for all the sequences most similar to the
reference sequence

@param  {int[]}       sequenceIndexes   Indexes of a set of sequences to search
@param  {int}         refSeq            Index of the sequence to compare with
@param  {int-2DArray} differenceMatrix  differences between sequences
@returns {int[]}                        Indexes of the sequences closest to
                                        the reference sequence

@author 
- 04.06.14 Original    By: ACRM
*/
function JSAV_findClosestSequences(sequenceIndexes, refSeq, differenceMatrix)
{
   var closestSequences = [];
   var smallestDifference = 1000000;

   for(var i=0; i<sequenceIndexes.length; i++)
   {
      // Find the difference between the specified reference sequence
      var thisDifference =
         differenceMatrix[sequenceIndexes[i]][refSeq]; 
      if(thisDifference < smallestDifference)
      {
         smallestDifference = thisDifference;
         closestSequences = [];
         closestSequences.push(sequenceIndexes[i]);
      }
      else if(thisDifference == smallestDifference)
      {
         closestSequences.push(sequenceIndexes[i]);
      }
   }
   
   return(closestSequences);
}


// ---------------------------------------------------------------------
/**
Sorts an array of sequence objects to group similar sequences together.
The sorting method chooses a representative sequence (one
which is most similar to all the other sequences) placing that first
in the list. To add to the sorted list, it iteratively chooses the
sequences that are most similar to the last one in the list and, of
those chooses the sequence most similar to the representative
sequence

@param   {object[]}   sequences   Array of sequence objects
@param   {int}        start       Offset of start of region to sort
@param   {int}        stop        Offset of end of region to sort
@returns {object[]}               Sorted array of sequence objects

@author 
- 29.05.14 Original   By: ACRM
- 04.06.14 Added ignoreEnds (true) to JSAV_calcDifferenceMatrix() call
         Range version
*/
function JSAV_sortSequences(sequences, start, stop)
{
   var sortedSequences = [];
   var sortedIndexes   = [];
   var nSeqs           = sequences.length;
   var ignoreEnds      = false;

   // If the start or stop is specified as -1, or we are looking at the
   // whole sequence, then ignore missing residues at the ends during 
   // the sort

   if((start < 0) || (stop < 0) ||
      ((start == 0) && (stop == (sequences[0].sequence.length - 1))))
   {
      ignoreEnds = true;
   }

   // Initialize array of sequences that have been used in the sorted 
   // output
   var used = [];
   for(var i=0; i<nSeqs; i++)
   {
      used[i] = 0;
   }

   // Choose a representative sequence for the top of the list
   var differenceMatrix = JSAV_calcDifferenceMatrix(sequences, start, stop, ignoreEnds);
   var representative   = JSAV_chooseRepresentative(differenceMatrix);
   sortedIndexes[0]     = representative;
   used[representative] = 1;
   var nSortedSeqs      = 1;

   while(nSortedSeqs < nSeqs)
   {
      // Initialize array of sequence IDs to contain all unused sequences
      var unusedSequences = [];
      for(var i=0; i<nSeqs; i++)
      {
         if(!used[i])
         {
            unusedSequences.push(i);
         }
      }
      
      // Find the sequences which are closest to the last sequence in the
      // sorted list
      var closestSequencesToLast = 
         JSAV_findClosestSequences(unusedSequences, sortedIndexes[nSortedSeqs-1], differenceMatrix);

      // Of the sequences most similar to the last one in the sorted
      // list, find the ones most similar to the representative sequence
      var closestSequencesToReference = 
         JSAV_findClosestSequences(closestSequencesToLast, sortedIndexes[0], differenceMatrix);

      // Take the first of these as the next sequence in the sorted list
      var mostSimilar = closestSequencesToReference[0];
      sortedIndexes[nSortedSeqs++] = mostSimilar;
      used[mostSimilar] = 1;
   }

   // Finally copy across the sequences in sorted order
   for(var i=0; i<nSeqs; i++)
   {
      sortedSequences[i] = sequences[sortedIndexes[i]];
   }

   return(sortedSequences);
}


// ---------------------------------------------------------------------
/**
Takes an array of sequence objects and generates a 2D difference
matrix. This contains the number of differences between each pair
of sequences

@param  {object[]}  sequences    Array of sequence objects
@param  {int}       start        Offset of start of region to sort
@param  {int}       stop         Offset of end of region to sort
@param  {BOOL}      ignoreEnds   Ignore insert characters at
                                 ends of sequences
@returns {int-2DArray}           Differences between each pair
                                 of sequences

@author 
- 29.05.14 Original   By: ACRM
- 04.06.14 Added ignoreEnds handling
         Range version
*/
function JSAV_calcDifferenceMatrix(sequences, start, stop, ignoreEnds)
{
   var nSeq = sequences.length;

   var differenceMatrix = ACRM_array(nSeq, nSeq);
   for(var i=0; i<nSeq; i++)
   {
      for(var j=0; j<nSeq; j++)
      {
         differenceMatrix[i][j] =
         JSAV_calcDifference(sequences[i].sequence, sequences[j].sequence, start, stop, ignoreEnds);
      }
   }
   return(differenceMatrix);
}


// ---------------------------------------------------------------------
/**
Takes two sequences as strings and calculates the number of 
differences between them (effectively the Hamming distance).
If ignoreEnds is specified then don't include gap characters at 
the ends of the sequences

@param   {string} seq1        Sequence string
@param   {string} seq2        Sequence string
@param   {int}    regionStart Offset of start of region to sort
@param   {int}    regionStop  Offset of end of region to sort
@param   {BOOL}   ignoreEnds  Ignore gaps at the end of the sequences
                              in calculating differences
@returns {int}                Number of differences between the sequences

@author 
- 29.05.14 Original  By: ACRM
- 04.06.14 Added ignoreEnds handling
         Created this Range version
*/
function JSAV_calcDifference(seq1, seq2, regionStart, regionStop, ignoreEnds)
{
   var seqArray1   = [];
   var seqArray2   = [];
// seq1.substring(regionStart, regionStop+1).split("");

   if((regionStart < 0) || (regionStop < 0))
   {
      seqArray1 = seq1.split("");
      seqArray2 = seq2.split("");
   }
   else
   {
      seqArray1 = seq1.substring(regionStart, regionStop+1).split("");
      seqArray2 = seq2.substring(regionStart, regionStop+1).split("");
   }

   var differences = 0;

   var seqLen = Math.max(seqArray1.length, seqArray2.length);

   var start = 0;
   var stop  = seqLen - 1;
   
   if(ignoreEnds)
   {
      // Find the offsets of the first and last real residue in the sequences
      var seq1Ends = JSAV_FindRealSequenceEnds(seqArray1);
      var seq2Ends = JSAV_FindRealSequenceEnds(seqArray2);

      start = Math.max(seq1Ends[0], seq2Ends[0]);
      stop  = Math.min(seq1Ends[1], seq2Ends[1]);
   }

   for(var i=start; i<=stop; i++)
   {
      if(seqArray1[i] != seqArray2[i])
      {
         differences++;
      }
   }
   return(differences);
}


// ---------------------------------------------------------------------
/**
Sorts a set of sequences and refreshes the display in a div with
the specified ID - the idea is that the unsorted sequences would
be displayed here and then this is tied to the action on a button
that sorts and refreshes the display.

@param {char}  divId        ID of an HTML <div>
@param {bool}  sortable     Is the display sortable
@param {bool}  selectable   Are checkboxes shown next to sequences
@param {bool}  border       Should CSS be updated to show a border
@returns {bool}             FALSE - (not sure why!)

@author 
- 29.05.14 Original   By: ACRM
- 11.06.14 sequences is now global
- 12.06.14 split out the JSAV_refresh() part
- 16.06.14 Added dotify and nocolour options to refresh call
- 17.06.14 Added consensus
- 22.12.15 Added passing of labels
*/
function JSAV_sortAndRefreshSequences(divId, sortable, selectable, border)
{
   var id = divId + "_JSAVStart";

   var range=JSAV_getRange(divId);
   var sortedSequences = JSAV_sortSequences(gSequences[divId], range[0], range[1]);

   JSAV_refresh(divId, sortedSequences, sortable, selectable, border, 
                range[0], range[1], gOptions[divId].highlight, 
                gOptions[divId].dotify, gOptions[divId].nocolour, 
                gOptions[divId].consensus, gOptions[divId].labels);

   // Record the fact that the display has been sorted
   gOptions[divId].sorted = true;

   return(false);
}


// ---------------------------------------------------------------------
/**
Refreshes the content of the divId_sortable div with the new sequence table
Also updates the marked range and the CSS if the border option is set

@param {char}     divId        ID of an HTML <div>
@param {object[]} sequences    Array of sequence objects
@param {bool}     sortable     Is the display sortable
@param {bool}     selectable   Are checkboxes shown next to sequences
@param {bool}     border       Should CSS be updated to show a border
@param {int}      start        start of selected region
@param {int}      stop         end of selected region
@param {int[]}    highlight    regions to be highlighted
@param {bool}     dotify       Dotify the sequence
@param {bool}     nocolour     Don't colour dotified residues
@param {bool}     consensus    Should we display a consensus sequence
@param {string[]} labels       Array of labels (or undefined) 

@author 
- 12.06.14  Original split out from JSAV_sortAndRefreshSequences() By: ACRM
- 16.06.14  Added dotify and nocolour
- 17.06.14  Added consensus
- 19.06.14  Added callback
- 22.12.15  Added labels
*/
function JSAV_refresh(divId, sequences, sortable, selectable, border, 
                      start, stop, highlight, dotify, nocolour, consensus, 
                      labels)
{
   var html = JSAV_buildSequencesHTML(divId, sequences, sortable, 
                                      selectable, highlight, dotify, nocolour, 
                                      consensus, labels);
   var element = document.getElementById(divId + "_sortable");
   element.innerHTML = html;
   if(border)
   {
       JSAV_modifyCSS(divId);
   }
   JSAV_markRange(divId, gSequenceLengths[divId], start, stop);

    if(gOptions[divId].callback != undefined)
    {
        window[gOptions[divId].callback](divId);
    }

}

// ---------------------------------------------------------------------
/**
Marks a range of residues in the main sequence display table.
The special marker row is used for this and we simply alter the
class to pick up the appropriate colour for the cells from CSS

@param {string}    divId   Identifier for the display div
@param {int}       seqLen  Length of the alignment
@param {int}       start   Offset of first residue to be
                           marked (0-based)
@param {int}       stop    Offset of last residue to be
                           marked (0-based)

@author 
- 06.06.14  Original   By: ACRM
- 13.06.14  Changed from highlighted to marked
*/
function JSAV_markRange(divId, seqLen, start, stop)
{
   for(var i=0; i<seqLen; i++)
   {
       var id = divId + "_JSAVMarker" + i;
       document.getElementById(id).className = 'unmarked';
   }
   if((start >= 0) && (stop >= 0))
   {
      for(var i=start; i<=stop; i++)
      {
          var id = divId + "_JSAVMarker" + i;
          document.getElementById(id).className = 'marked';
      }
   }
}

// ---------------------------------------------------------------------
/**
Initializes global arrays

@author 
- 09.06.14 Original   By: ACRM
- 12.06.14 Added more arrays
- 17.06.14 Added gConsensus array
*/
function JSAV_init()
{
   // Indexed by divId and used to store the values
   try
   {
       if(gSequences == undefined)
       {
           ;
       }
   }
   catch(err)
   {
       gSequences       = Array();
       gOptions         = Array();
       gStartPos        = Array();
       gStopPos         = Array();
       gConsensus       = Array();
       gSequenceLengths = Array();
   }
}


// ---------------------------------------------------------------------
/**
Simply returns an array of the available colouring schemes in the CSS
file

@returns {string[]}   - Colour schemes

@author 
- 17.06.14   Original   By: ACRM
*/
function JSAV_initColourChoices()
{
    return(['Taylor', 'Clustal', 'Zappo', 'HPhob', 'Helix', 'Strand',
            'Turn', 'Buried']);
}

// ---------------------------------------------------------------------
/**
Calculates a consensus sequence

@param   {object[]}  sequences  - Array of sequence objects
@returns {string}               - Consensus sequence

@author 
- 17.06.14  Original   By: ACRM
*/
function JSAV_buildConsensus(sequences)
{
    var nSeqs    = sequences.length;
    var seqLen   = sequences[0].sequence.length;
    // Initialize array
    var aaCounts = Array(seqLen);
    for(var i=0; i<seqLen; i++)
    {
        aaCounts[i] = Array();
    }

    // Step through the sequences
    for(var seq=0; seq<nSeqs; seq++)
    {
        var seqArray = sequences[seq].sequence.split('');

        // Step through the positions
        for(var pos=0; pos<seqLen; pos++)
        {
            if(aaCounts[pos][seqArray[pos]] == undefined)
            {
                aaCounts[pos][seqArray[pos]] = 1;
            }
            else
            {
                aaCounts[pos][seqArray[pos]]++;
            }
        }
    }

    // Step through the positions to determine the most common residue
    var consensus = "";
    var validAAs = "ACDEFGHIKLMNPQRSTVWY-".split('');
    for(var pos=0; pos<seqLen; pos++)
    {
        var mostCommon = undefined;
        var maxAA      = 0;
        for(var aaCount=0; aaCount<validAAs.length; aaCount++)
        {
            if((aaCounts[pos][validAAs[aaCount]] != undefined) &&
               (aaCounts[pos][validAAs[aaCount]] >  maxAA))
            {
                mostCommon = validAAs[aaCount];
                maxAA = aaCounts[pos][validAAs[aaCount]];
            }
        }
        if(maxAA <= (nSeqs / 2))
        {
            mostCommon = mostCommon.toLowerCase();
        }
        consensus += mostCommon;
    }
    return(consensus);
}


// ---------------------------------------------------------------------
/** 
General purpose routine to delete an object from an array of objects
where the object contains the specified key:value pair.

@param {string}   key   - The key (item in an object or hash key) 
                          to check
@param {string}   value - The value to check
@param {object[]} array - The array of objects to manipulate

@author 
- 12.06.14 Original   By: ACRM
*/
function ACRM_deleteItemByLabel(key, value, array)
{
    for(var i=0; i<array.length; i++)
    {
       if(array[i][key] == value)
       {
           array.splice(i,1);
           break;
       }
    }
}

// ---------------------------------------------------------------------
/**
General purpose function to create a multi-dimensional array

@param   {int/int[]}  length   Size of each dimension
@returns {array}               (multi-dimensional) Array

Usage:
ACRM_array();     // [] or new Array()
ACRM_array(2);    // new Array(2)
ACRM_array(3, 2); // [new Array(2),
                  //  new Array(2),
                  //  new Array(2)]

@author 
- 29.05.14 Taken from http://stackoverflow.com/questions/966225/
           how-can-i-create-a-two-dimensional-array-in-javascript
*/
function ACRM_array(length) 
{
    var arr = new Array(length || 0),
        i   = length;

    if (arguments.length > 1) 
    {
        var args = Array.prototype.slice.call(arguments, 1);
        while(i--) arr[length-1 - i] = ACRM_array.apply(this, args);
    }

    return arr;
}


// ---------------------------------------------------------------------
/**
General purpose confirm() dialogue using JQueryUI dialog rather than the
simple JavaScript confirm() method

@param {string}   title    - title for the confirm box
@param {string}   msg      - the message to be displayed
@param {function} callback - function to be called after the OK or cancel
                             button is called. A boolean parameter is
                             passed to the function - true for OK, false
                             for cancel

@author 
- 15.06.14 Original   By: ACRM
*/
function ACRM_confirm(title, msg, callback) 
{
    var dialogObj = $("<div style='display:none' title='" + title + "'>"+msg+"</div>");
    $('body').append(dialogObj);
    $(dialogObj).dialog({
        resizable: false,
        modal: true,
        buttons: {
            Cancel: function() {
                callback(false);
                $( this ).dialog( "close" );
                $( this ).remove();
            },
            "OK": function() {
                callback(true);
                $( this ).dialog( "close" );
                $( this ).remove();
            }
        }
    });
};

// ---------------------------------------------------------------------
/**
General purpose alert() dialogue using JQueryUI dialog rather than the
simple JavaScript alert() method

@param {string}   title    - title for the confirm box
@param {string}   msg      - the message to be displayed

@author 
- 15.06.14 Original   By: ACRM
*/
function ACRM_alert(title, msg) 
{
    var dialogObj = $("<div style='display:none' title='" + title + "'>"+msg+"</div>");
    $('body').append(dialogObj);
    $(dialogObj).dialog({
        resizable: false,
        modal: true,
        buttons: {
            "OK": function() {
                $( this ).dialog( "close" );
                $( this ).remove();
            }
        }
    });
};

// ---------------------------------------------------------------------
/**
General purpose dialogue using JQueryUI dialog

@param {string}   title    - title for the confirm box
@param {string}   msg      - the message to be displayed
@param {string}   width    - the width of the window (e.g. '600px')
@param {bool}     pre      - wrap the displayed text in <pre> tags

@author 
- 17.06.14 Original   By: ACRM
*/
function ACRM_dialog(title, msg, width, pre) 
{
    var dialogObj;
    if(pre)
    {
        dialogObj = $("<div style='display:none' title='" + title + "'><pre>"+msg+"</pre></div>");
    }
    else
    {
        dialogObj = $("<div style='display:none' title='" + title + "'>"+msg+"</div>");
    }
    $('body').append(dialogObj);
    $(dialogObj).dialog({
        resizable: false,
        width: width,
        modal: true,
        buttons: {
            "Dismiss": function() {
                $( this ).dialog( "close" );
                $( this ).remove();
            }
        }
    });
};


// ---------------------------------------------------------------------
/**
Create the HTML for a label row in the sequence display

@param   {string}   divId      - The div we are working in
@param   {int}      seqLen     - The length of the alignment
@param   {bool}     selectable - Are there sequences selection boxes
@param   {string[]} labels     - Array of labels
@returns {string}              - HTML

@author 
- 22.12.15 Original   By: ACRM
*/
function JSAV_buildLabelsHTML(divId,  seqLen, selectable, labels)
{
    var html = "";
    if(selectable)
    {
        html += "<tr class='highlightrow'><th></th>";
        html += "<td></td>";
    }
    else
    {
        html += "<tr class='highlightrow'><td></td>";
    }

    for(var i=0; i<labels.length; i++)
    {
        // Make a copy of the label and remove the chain label
        var labelText = labels[i];
        labelText.replace(/^[A-Za-z]/g, '');

        // Find the last character
        var lastChar = labelText.substring(labelText.length-1,labelText.length);

        // Open a table cell with the label as a tooltip
        html += "<td class='tooltip' title='" + labels[i] + "'>";

        // Insert the appropriate character
        if(lastChar == "0")                   // 0 - do a '|'
        {
            html += "|";
        }
        else if (lastChar.match(/[A-Za-z]/))  // Insert code - show the code
        {
            html += lastChar;
        }
        else                                  // Otherwise do a '.'
        {
            html += ".";
        }

        // And finish the table cell
        html += "</td>";
    }

    // Finish the table row
    html += "</tr>\n";

    return html;
}


// ---------------------------------------------------------------------
/**
Create an array of labels

@param   {object[]} sequences  - Array of sequence objects
@returns {string[]}            - Array of labels

@author 
- 22.12.15 Original   By: ACRM
*/
function JSAV_autoLabels(sequences)
{
    var seqLen = sequences[0].sequence.length;
    var labels = Array();
    for(var i=1; i<=seqLen; i++)
    {
        labels.push(i.toString());
    }
    return labels;
}